@gi-tcg/gts-runtime 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,16 +1,40 @@
1
- declare const Meta: unique symbol;
2
- type Meta = typeof Meta;
3
- declare const Action: unique symbol;
4
- type Action = typeof Action;
5
- type NamedDefinition = typeof NamedDefinition;
6
- declare const Prelude: unique symbol;
7
- type Prelude = typeof Prelude;
8
- type AllSymbols = {
9
- Meta: Meta;
10
- Action: Action;
11
- NamedDefinition: NamedDefinition;
12
- Prelude: Prelude;
13
- };
1
+ declare namespace AttributeReturn {
2
+ type This<TMeta> = {
3
+ "~meta": TMeta;
4
+ };
5
+ type EnableIf<
6
+ Condition,
7
+ T = void
8
+ > = Condition extends true ? T : never;
9
+ type Done = {
10
+ namedDefinition: {
11
+ "~meta": void;
12
+ };
13
+ };
14
+ type With<
15
+ VM extends IViewModel<any, any>,
16
+ TMeta = VM["~namedDefinition"]["~meta"]
17
+ > = {
18
+ namedDefinition: BlockDefinitionRewriteMeta<VM["~namedDefinition"], TMeta>;
19
+ };
20
+ type DoneRewriteMeta<NewMeta> = {
21
+ namedDefinition: {
22
+ "~meta": void;
23
+ };
24
+ rewriteMeta: NewMeta;
25
+ };
26
+ type WithRewriteMeta<
27
+ VM extends IViewModel<any, any>,
28
+ NewMeta
29
+ > = {
30
+ namedDefinition: VM["~namedDefinition"];
31
+ rewriteMeta: NewMeta;
32
+ };
33
+ }
34
+ type Meta = "~meta";
35
+ type Action = "~action";
36
+ type NamedDefinition = "~namedDefinition";
37
+ type Prelude = "~prelude";
14
38
  type AttributeName = string | symbol;
15
39
  interface SingleAttributeNode {
16
40
  name: AttributeName;
@@ -34,109 +58,92 @@ declare class BindingContext {
34
58
  addBinding(value: unknown): void;
35
59
  getBindings(): unknown[];
36
60
  }
37
- declare function createDefine(rootVM: ViewModel<any, any>, node: SingleAttributeNode): void;
38
- declare function createBinding(rootVM: ViewModel<any, any>, node: SingleAttributeNode): unknown[];
61
+ declare function createDefine(rootVM: IViewModel<any, any>, node: SingleAttributeNode): void;
62
+ declare function createBinding(rootVM: IViewModel<any, any>, node: SingleAttributeNode): unknown[];
63
+ import { StandardJSONSchemaV1 } from "@standard-schema/spec";
39
64
  interface AttributeBlockDefinition {
40
- [name: string]: AttributeDefinition;
41
- [Action]?: AttributeDefinition;
42
- [Meta]: any;
65
+ "~action"?: AttributeDefinition | undefined;
66
+ "~meta": any;
43
67
  }
44
68
  type Computed<T> = T extends infer U extends { [K in keyof T] : unknown } ? U : never;
45
69
  type BlockDefinitionRewriteMeta<
46
70
  BlockDef extends AttributeBlockDefinition,
47
71
  NewMeta
48
72
  > = Computed<Omit<BlockDef, Meta> & {
49
- [Meta]: NewMeta;
73
+ "~meta": NewMeta;
50
74
  }> extends infer R extends AttributeBlockDefinition ? R : never;
51
75
  interface IViewModel<
52
76
  ModelT,
53
77
  BlockDef extends AttributeBlockDefinition
54
78
  > {
55
79
  parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT;
80
+ "~namedDefinition": BlockDef;
56
81
  }
57
- interface ViewModel<
58
- ModelT,
59
- BlockDef extends AttributeBlockDefinition
60
- > {
61
- /**
62
- * Helper for fetching symbol types
63
- * @internal
64
- */
65
- _symbols: AllSymbols;
66
- [NamedDefinition]: BlockDef;
67
- }
82
+ type LazyAttributeActionOrBinder<ModelT> = (model: ModelT, positionals: () => unknown[], named: View<any>) => unknown;
68
83
  declare class ViewModel<
69
84
  ModelT,
70
85
  BlockDef extends AttributeBlockDefinition
71
86
  > implements IViewModel<ModelT, BlockDef> {
72
87
  private;
73
- private Ctor;
88
+ "~namedDefinition": BlockDef;
74
89
  constructor(Ctor: new () => ModelT);
75
- _setAction(name: PropertyKey, action: AttributeAction<ModelT, any>): void;
76
- _setBinder(name: PropertyKey, binder: AttributeBinder<ModelT, any>): void;
90
+ "~setActionOrBinder"(context: "action" | "binder", name: PropertyKey, action: LazyAttributeActionOrBinder<ModelT>): void;
77
91
  parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT;
78
92
  }
79
93
  declare class AttributeDefHelper<ModelT> {
80
94
  private;
81
95
  constructor(viewModel: ViewModel<ModelT, any>);
82
- _assignActions(defResult: Record<string, unknown>): void;
96
+ "~assignActions"(defResult: Partial<Record<string, unknown>>): void;
83
97
  attribute<T extends AttributeDefinition & {
84
98
  as?: undefined;
85
- }>(action: AttributeAction<ModelT, T>, binder?: AttributeBinder<ModelT, T> | ViewModel<any, ReturnType<T>["namedDefinition"]>): T;
99
+ }>(action: AttributeAction<ModelT, T>, binder?: AttributeBinder<ModelT, T> | IViewModel<any, ReturnType<T>["namedDefinition"]>): T;
86
100
  attribute<T extends AttributeDefinition>(action: AttributeAction<ModelT, T>, binder: AttributeBinder<ModelT, T>): T;
101
+ simpleAttribute<Args extends any[]>(action: (this: ModelT, ...args: Args) => void): {
102
+ (...args: Args): AttributeReturn.Done;
103
+ };
104
+ simpleAttribute<
105
+ Args extends any[],
106
+ U
107
+ >(action: (this: ModelT, ...args: Args) => void, binder: (this: ModelT, ...args: Args) => U): {
108
+ (...args: Args): AttributeReturn.Done;
109
+ as?(): U;
110
+ };
87
111
  }
88
112
  declare function defineViewModel<
89
113
  T,
90
- const BlockDef extends Record<string, AttributeDefinition>,
114
+ const BlockDef extends Partial<Record<string | Action, AttributeDefinition>>,
91
115
  InitMeta = void
92
- >(Ctor: new () => T, modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef, initMeta?: InitMeta): ViewModel<T, BlockDef & {
93
- [Meta]: InitMeta;
116
+ >(Ctor: new () => T, modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef, initMeta?: InitMeta): IViewModel<T, BlockDef & {
117
+ "~meta": InitMeta;
118
+ }>;
119
+ type SimpleViewModel<T> = IViewModel<T, { [K in keyof T]-? : {
120
+ (value: T[K]): AttributeReturn.Done;
121
+ uniqueKey(): K;
122
+ required(): {} extends Pick<T, K> ? false : true;
123
+ } } & {
124
+ "~meta": undefined;
94
125
  }>;
126
+ declare function defineSimpleViewModel<const T extends StandardJSONSchemaV1>(schema: T): SimpleViewModel<StandardJSONSchemaV1.InferInput<T>>;
95
127
  interface AttributeDefinition {
96
128
  (...args: any[]): AttributePositionalReturnBase;
97
129
  as?(): any;
130
+ required?(): boolean;
131
+ uniqueKey?(): string;
98
132
  }
99
133
  interface AttributePositionalReturnBase {
100
134
  rewriteMeta?: any;
101
135
  namedDefinition: AttributeBlockDefinition;
102
136
  }
103
- declare namespace AttributeReturn {
104
- type This<TMeta = any> = {
105
- [Meta]: TMeta;
106
- };
107
- type EnableIf<
108
- Condition,
109
- T = void
110
- > = Condition extends true ? T : never;
111
- type Done = {
112
- namedDefinition: {
113
- [Meta]: void;
114
- };
115
- };
116
- type With<
117
- VM extends ViewModel<any, any>,
118
- TMeta = VM[NamedDefinition][Meta]
119
- > = {
120
- namedDefinition: BlockDefinitionRewriteMeta<VM[NamedDefinition], TMeta>;
121
- };
122
- type WithRewriteMeta<
123
- VM extends ViewModel<any, any>,
124
- Meta
125
- > = {
126
- namedDefinition: VM[NamedDefinition];
127
- rewriteMeta: Meta;
128
- };
129
- }
130
137
  type AttributeAction<
131
138
  Model,
132
139
  T extends AttributeDefinition
133
- > = (model: Model, positional: () => Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
134
- [Meta]: void;
140
+ > = (model: Model, positional: Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
141
+ "~meta": void;
135
142
  }>) => void;
136
143
  type AttributeBinder<
137
144
  Model,
138
145
  T extends AttributeDefinition
139
- > = (model: Model, positional: () => Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
140
- [Meta]: void;
146
+ > = (model: Model, positional: Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
147
+ "~meta": void;
141
148
  }>) => T["as"] extends () => infer U ? U : void;
142
- export { defineViewModel, createDefine, createBinding, Prelude, IViewModel, AttributeReturn, Action };
149
+ export { defineViewModel, defineSimpleViewModel, createDefine, createBinding, View, SingleAttributeNode, SimpleViewModel, Prelude, NamedDefinition, NamedAttributesNode, Meta, IViewModel, AttributeReturn, AttributeDefinition, Action, AttributeReturn as AR };
package/dist/index.js CHANGED
@@ -31,26 +31,28 @@ function createBinding(rootVM, node) {
31
31
 
32
32
  // packages/runtime/src/view_model.ts
33
33
  class ViewModel {
34
- Ctor;
35
34
  #registeredActions = new Map;
36
35
  #registeredBinders = new Map;
36
+ #Ctor;
37
37
  constructor(Ctor) {
38
- this.Ctor = Ctor;
38
+ this.#Ctor = Ctor;
39
39
  }
40
- _setAction(name, action) {
41
- this.#registeredActions.set(name, action);
42
- }
43
- _setBinder(name, binder) {
44
- this.#registeredBinders.set(name, binder);
40
+ "~setActionOrBinder"(context, name, action) {
41
+ if (context === "action") {
42
+ this.#registeredActions.set(name, action);
43
+ } else {
44
+ this.#registeredBinders.set(name, action);
45
+ }
45
46
  }
46
47
  parse(view) {
47
- const model = new this.Ctor;
48
+ const model = new this.#Ctor;
48
49
  for (const attrNode of view._node.attributes) {
49
50
  let { name, positionals, named, binding } = attrNode;
50
51
  const insideBindingCtx = !!view._bindingCtx;
51
52
  let fn = (insideBindingCtx ? this.#registeredBinders : this.#registeredActions).get(name);
52
53
  if (!insideBindingCtx && !fn) {
53
- throw new Error(`No action registered for attribute: ${String(name)}`);
54
+ const modelName = this.#Ctor.name;
55
+ throw new Error(`No action registered for attribute "${String(name)}" on model "${modelName}"`);
54
56
  }
55
57
  fn ??= () => {};
56
58
  named ??= { attributes: [] };
@@ -68,56 +70,80 @@ class AttributeDefHelper {
68
70
  constructor(viewModel) {
69
71
  this.#viewModel = viewModel;
70
72
  }
71
- static #actionSlot = Symbol("actionSlot");
72
- static #binderSlot = Symbol("binderSlot");
73
- _assignActions(defResult) {
74
- for (const [name, returnValue] of Object.entries(defResult)) {
75
- const actionDescriptor = Object.getOwnPropertyDescriptor(returnValue, AttributeDefHelper.#actionSlot);
73
+ static #lazyActionSlot = Symbol("actionSlot");
74
+ static #lazyBinderSlot = Symbol("binderSlot");
75
+ "~assignActions"(defResult) {
76
+ for (const [name, value] of Object.entries(defResult)) {
77
+ const actionDescriptor = Object.getOwnPropertyDescriptor(value, AttributeDefHelper.#lazyActionSlot);
76
78
  if (actionDescriptor) {
77
- this.#viewModel._setAction(name, actionDescriptor.value);
79
+ this.#viewModel["~setActionOrBinder"]("action", name, actionDescriptor.value);
78
80
  }
79
- const binderDescriptor = Object.getOwnPropertyDescriptor(returnValue, AttributeDefHelper.#binderSlot);
81
+ const binderDescriptor = Object.getOwnPropertyDescriptor(value, AttributeDefHelper.#lazyBinderSlot);
80
82
  if (binderDescriptor) {
81
- this.#viewModel._setBinder(name, binderDescriptor.value);
83
+ this.#viewModel["~setActionOrBinder"]("binder", name, binderDescriptor.value);
82
84
  }
83
85
  }
84
86
  }
85
87
  attribute(action, binder) {
86
- if (binder instanceof ViewModel) {
88
+ const returnValue = {};
89
+ const lazyAction = (model, positionals, named) => action(model, positionals(), named);
90
+ Object.defineProperty(returnValue, AttributeDefHelper.#lazyActionSlot, {
91
+ value: lazyAction,
92
+ enumerable: true
93
+ });
94
+ let lazyBinder;
95
+ if (typeof binder === "function") {
96
+ lazyBinder = (model, positionals, named) => binder(model, positionals(), named);
97
+ } else if (binder) {
87
98
  const vm = binder;
88
- binder = (model, positionals, named) => {
99
+ lazyBinder = (model, positionals, named) => {
89
100
  return vm.parse(named);
90
101
  };
102
+ } else {
103
+ lazyBinder = () => {};
91
104
  }
92
- binder ??= () => {};
93
- const returnValue = {};
94
- Object.defineProperty(returnValue, AttributeDefHelper.#actionSlot, {
95
- value: action,
96
- enumerable: true
97
- });
98
- Object.defineProperty(returnValue, AttributeDefHelper.#binderSlot, {
99
- value: binder,
105
+ Object.defineProperty(returnValue, AttributeDefHelper.#lazyBinderSlot, {
106
+ value: lazyBinder,
100
107
  enumerable: true
101
108
  });
102
109
  return returnValue;
103
110
  }
111
+ simpleAttribute(action, binder) {
112
+ const action2 = (model, positionals) => action.apply(model, positionals);
113
+ let binder2;
114
+ if (binder) {
115
+ binder2 = (model, positionals) => binder.apply(model, positionals);
116
+ }
117
+ return this.attribute(action2, binder2);
118
+ }
104
119
  }
105
120
  function defineViewModel(Ctor, modelDefFn, initMeta) {
106
121
  const vm = new ViewModel(Ctor);
107
122
  const helper = new AttributeDefHelper(vm);
108
123
  const defResult = modelDefFn(helper);
109
- helper._assignActions(defResult);
124
+ helper["~assignActions"](defResult);
125
+ return vm;
126
+ }
127
+ function defineSimpleViewModel(schema) {
128
+ const jsonSchema = schema["~standard"].jsonSchema.input({
129
+ target: "draft-2020-12"
130
+ });
131
+ const Ctor = class SimpleViewModel {
132
+ };
133
+ const vm = new ViewModel(Ctor);
134
+ const helper = new AttributeDefHelper(vm);
135
+ const defResult = {};
136
+ for (const key of Object.keys(jsonSchema.properties ?? {})) {
137
+ defResult[key] = helper.simpleAttribute(function(value) {
138
+ this[key] = value;
139
+ });
140
+ }
141
+ helper["~assignActions"](defResult);
110
142
  return vm;
111
143
  }
112
- // packages/runtime/src/symbols.ts
113
- var Meta = Symbol("Meta");
114
- var Action = Symbol("Action");
115
- var NamedDefinition = Symbol("NamedDefinition");
116
- var Prelude = Symbol("Prelude");
117
144
  export {
118
145
  defineViewModel,
146
+ defineSimpleViewModel,
119
147
  createDefine,
120
- createBinding,
121
- Prelude,
122
- Action
148
+ createBinding
123
149
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "repository": "https://github.com/piovium/gts.git",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -16,5 +16,8 @@
16
16
  "dist"
17
17
  ],
18
18
  "dependencies": {},
19
- "devDependencies": {}
19
+ "devDependencies": {
20
+ "@standard-schema/spec": "^1.1.0",
21
+ "json-schema-typed": "^8.0.2"
22
+ }
20
23
  }
@@ -0,0 +1,34 @@
1
+ import type { BlockDefinitionRewriteMeta, IViewModel } from "./view_model";
2
+
3
+ export namespace AttributeReturn {
4
+ export type This<TMeta> = {
5
+ "~meta": TMeta;
6
+ };
7
+
8
+ export type EnableIf<Condition, T = void> = Condition extends true
9
+ ? T
10
+ : never;
11
+
12
+ export type Done = {
13
+ namedDefinition: { "~meta": void };
14
+ };
15
+
16
+ export type With<
17
+ VM extends IViewModel<any, any>,
18
+ TMeta = VM["~namedDefinition"]["~meta"],
19
+ > = {
20
+ namedDefinition: BlockDefinitionRewriteMeta<VM["~namedDefinition"], TMeta>;
21
+ };
22
+
23
+ export type DoneRewriteMeta<NewMeta> = {
24
+ namedDefinition: { "~meta": void };
25
+ rewriteMeta: NewMeta;
26
+ };
27
+
28
+ export type WithRewriteMeta<VM extends IViewModel<any, any>, NewMeta> = {
29
+ namedDefinition: VM["~namedDefinition"];
30
+ rewriteMeta: NewMeta;
31
+ };
32
+ }
33
+
34
+ export type { AttributeReturn as AR };
package/src/index.ts CHANGED
@@ -1,8 +1,17 @@
1
1
  export {
2
2
  defineViewModel,
3
+ defineSimpleViewModel,
4
+ type AttributeDefinition,
3
5
  type IViewModel,
4
- type AttributeReturn,
6
+ type SimpleViewModel,
5
7
  } from "./view_model";
6
- export { Action, Prelude } from "./symbols";
8
+ export type { AttributeReturn, AR } from "./attribute_return";
9
+ export type { Action, Prelude, Meta, NamedDefinition } from "./symbols";
7
10
 
8
- export { createBinding, createDefine } from "./view";
11
+ export {
12
+ createBinding,
13
+ createDefine,
14
+ type SingleAttributeNode,
15
+ type NamedAttributesNode,
16
+ type View,
17
+ } from "./view";
package/src/symbols.ts CHANGED
@@ -1,18 +1,4 @@
1
- export const Meta: unique symbol = Symbol("Meta");
2
- export type Meta = typeof Meta;
3
-
4
- export const Action: unique symbol = Symbol("Action");
5
- export type Action = typeof Action;
6
-
7
- export const NamedDefinition: unique symbol = Symbol("NamedDefinition");
8
- export type NamedDefinition = typeof NamedDefinition;
9
-
10
- export const Prelude: unique symbol = Symbol("Prelude");
11
- export type Prelude = typeof Prelude;
12
-
13
- export type AllSymbols = {
14
- Meta: Meta;
15
- Action: Action;
16
- NamedDefinition: NamedDefinition;
17
- Prelude: Prelude;
18
- };
1
+ export type Meta = "~meta";
2
+ export type Action = "~action";
3
+ export type NamedDefinition = "~namedDefinition";
4
+ export type Prelude = "~prelude";
package/src/view.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AttributeBlockDefinition, ViewModel } from "./view_model";
1
+ import type { AttributeBlockDefinition, IViewModel } from "./view_model";
2
2
 
3
3
  export type AttributeName = string | symbol;
4
4
 
@@ -35,7 +35,7 @@ export class BindingContext {
35
35
  }
36
36
 
37
37
  export function createDefine(
38
- rootVM: ViewModel<any, any>,
38
+ rootVM: IViewModel<any, any>,
39
39
  node: SingleAttributeNode,
40
40
  ): void {
41
41
  const view = new View<any>({ attributes: [node] });
@@ -43,7 +43,7 @@ export function createDefine(
43
43
  }
44
44
 
45
45
  export function createBinding(
46
- rootVM: ViewModel<any, any>,
46
+ rootVM: IViewModel<any, any>,
47
47
  node: SingleAttributeNode,
48
48
  ): unknown[] {
49
49
  const bindingCtx = new BindingContext();
package/src/view_model.ts CHANGED
@@ -1,58 +1,68 @@
1
- import { Action, AllSymbols, Meta, NamedDefinition } from "./symbols";
1
+ import { AttributeReturn } from "./attribute_return";
2
+ import { Action, Meta, NamedDefinition } from "./symbols";
2
3
  import { View } from "./view";
4
+ import { StandardJSONSchemaV1 } from "@standard-schema/spec";
3
5
 
4
6
  export interface AttributeBlockDefinition {
5
- [name: string]: AttributeDefinition;
6
- [Action]?: AttributeDefinition;
7
- [Meta]: any;
7
+ "~action"?: AttributeDefinition | undefined;
8
+ "~meta": any;
8
9
  }
9
10
 
10
11
  type Computed<T> = T extends infer U extends { [K in keyof T]: unknown }
11
12
  ? U
12
13
  : never;
13
14
 
14
- type BlockDefinitionRewriteMeta<
15
+ export type BlockDefinitionRewriteMeta<
15
16
  BlockDef extends AttributeBlockDefinition,
16
17
  NewMeta,
17
18
  > =
18
- Computed<Omit<BlockDef, Meta> & { [Meta]: NewMeta }> extends infer R extends
19
+ Computed<Omit<BlockDef, Meta> & { "~meta": NewMeta }> extends infer R extends
19
20
  AttributeBlockDefinition
20
21
  ? R
21
22
  : never;
22
23
 
23
24
  export interface IViewModel<ModelT, BlockDef extends AttributeBlockDefinition> {
24
25
  parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT;
26
+ "~namedDefinition": BlockDef;
25
27
  }
26
28
 
27
- export interface ViewModel<ModelT, BlockDef extends AttributeBlockDefinition> {
28
- /**
29
- * Helper for fetching symbol types
30
- * @internal
31
- */
32
- _symbols: AllSymbols;
33
- [NamedDefinition]: BlockDef;
34
- }
29
+ type LazyAttributeActionOrBinder<ModelT> = (
30
+ model: ModelT,
31
+ positionals: () => unknown[],
32
+ named: View<any>,
33
+ ) => unknown;
35
34
 
36
- export class ViewModel<
35
+ class ViewModel<
37
36
  ModelT,
38
37
  BlockDef extends AttributeBlockDefinition,
39
38
  > implements IViewModel<ModelT, BlockDef> {
40
- #registeredActions: Map<PropertyKey, AttributeAction<ModelT, any>> =
39
+ declare "~namedDefinition": BlockDef;
40
+
41
+ #registeredActions: Map<PropertyKey, LazyAttributeActionOrBinder<ModelT>> =
41
42
  new Map();
42
- #registeredBinders: Map<PropertyKey, AttributeBinder<ModelT, any>> =
43
+ #registeredBinders: Map<PropertyKey, LazyAttributeActionOrBinder<ModelT>> =
43
44
  new Map();
44
45
 
45
- constructor(private Ctor: new () => ModelT) {}
46
+ #Ctor: new () => ModelT;
46
47
 
47
- _setAction(name: PropertyKey, action: AttributeAction<ModelT, any>): void {
48
- this.#registeredActions.set(name, action);
48
+ constructor(Ctor: new () => ModelT) {
49
+ this.#Ctor = Ctor;
49
50
  }
50
- _setBinder(name: PropertyKey, binder: AttributeBinder<ModelT, any>): void {
51
- this.#registeredBinders.set(name, binder);
51
+
52
+ "~setActionOrBinder"(
53
+ context: "action" | "binder",
54
+ name: PropertyKey,
55
+ action: LazyAttributeActionOrBinder<ModelT>,
56
+ ): void {
57
+ if (context === "action") {
58
+ this.#registeredActions.set(name, action);
59
+ } else {
60
+ this.#registeredBinders.set(name, action);
61
+ }
52
62
  }
53
63
 
54
64
  parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT {
55
- const model = new this.Ctor();
65
+ const model = new this.#Ctor();
56
66
  for (const attrNode of view._node.attributes) {
57
67
  let { name, positionals, named, binding } = attrNode;
58
68
  const insideBindingCtx = !!view._bindingCtx;
@@ -60,7 +70,10 @@ export class ViewModel<
60
70
  insideBindingCtx ? this.#registeredBinders : this.#registeredActions
61
71
  ).get(name);
62
72
  if (!insideBindingCtx && !fn) {
63
- throw new Error(`No action registered for attribute: ${String(name)}`);
73
+ const modelName = this.#Ctor.name;
74
+ throw new Error(
75
+ `No action registered for attribute "${String(name)}" on model "${modelName}"`,
76
+ );
64
77
  }
65
78
  fn ??= () => {};
66
79
  named ??= { attributes: [] };
@@ -79,24 +92,32 @@ class AttributeDefHelper<ModelT> {
79
92
  this.#viewModel = viewModel;
80
93
  }
81
94
 
82
- static readonly #actionSlot: unique symbol = Symbol("actionSlot");
83
- static readonly #binderSlot: unique symbol = Symbol("binderSlot");
95
+ static readonly #lazyActionSlot: unique symbol = Symbol("actionSlot");
96
+ static readonly #lazyBinderSlot: unique symbol = Symbol("binderSlot");
84
97
 
85
- _assignActions(defResult: Record<string, unknown>): void {
86
- for (const [name, returnValue] of Object.entries(defResult)) {
98
+ "~assignActions"(defResult: Partial<Record<string, unknown>>): void {
99
+ for (const [name, value] of Object.entries(defResult)) {
87
100
  const actionDescriptor = Object.getOwnPropertyDescriptor(
88
- returnValue,
89
- AttributeDefHelper.#actionSlot,
101
+ value,
102
+ AttributeDefHelper.#lazyActionSlot,
90
103
  );
91
104
  if (actionDescriptor) {
92
- this.#viewModel._setAction(name, actionDescriptor.value);
105
+ this.#viewModel["~setActionOrBinder"](
106
+ "action",
107
+ name,
108
+ actionDescriptor.value,
109
+ );
93
110
  }
94
111
  const binderDescriptor = Object.getOwnPropertyDescriptor(
95
- returnValue,
96
- AttributeDefHelper.#binderSlot,
112
+ value,
113
+ AttributeDefHelper.#lazyBinderSlot,
97
114
  );
98
115
  if (binderDescriptor) {
99
- this.#viewModel._setBinder(name, binderDescriptor.value);
116
+ this.#viewModel["~setActionOrBinder"](
117
+ "binder",
118
+ name,
119
+ binderDescriptor.value,
120
+ );
100
121
  }
101
122
  }
102
123
  }
@@ -105,7 +126,7 @@ class AttributeDefHelper<ModelT> {
105
126
  action: AttributeAction<ModelT, T>,
106
127
  binder?:
107
128
  | AttributeBinder<ModelT, T>
108
- | ViewModel<any, ReturnType<T>["namedDefinition"]>,
129
+ | IViewModel<any, ReturnType<T>["namedDefinition"]>,
109
130
  ): T;
110
131
  attribute<T extends AttributeDefinition>(
111
132
  action: AttributeAction<ModelT, T>,
@@ -113,48 +134,115 @@ class AttributeDefHelper<ModelT> {
113
134
  ): T;
114
135
  attribute(
115
136
  action: any,
116
- binder?: AttributeBinder<any, any> | ViewModel<any, any>,
137
+ binder?: AttributeBinder<any, any> | IViewModel<any, any>,
117
138
  ) {
118
- if (binder instanceof ViewModel) {
139
+ const returnValue = {};
140
+ const lazyAction: LazyAttributeActionOrBinder<ModelT> = (
141
+ model,
142
+ positionals,
143
+ named,
144
+ ) => action(model, positionals(), named);
145
+ Object.defineProperty(returnValue, AttributeDefHelper.#lazyActionSlot, {
146
+ value: lazyAction,
147
+ enumerable: true,
148
+ });
149
+ let lazyBinder: LazyAttributeActionOrBinder<ModelT>;
150
+ if (typeof binder === "function") {
151
+ lazyBinder = (model, positionals, named) =>
152
+ binder(model, positionals(), named);
153
+ } else if (binder) {
119
154
  const vm = binder;
120
- binder = (model, positionals, named) => {
155
+ lazyBinder = (model, positionals, named) => {
121
156
  return vm.parse(named);
122
157
  };
158
+ } else {
159
+ lazyBinder = () => {};
123
160
  }
124
- binder ??= () => {};
125
- const returnValue = {};
126
- Object.defineProperty(returnValue, AttributeDefHelper.#actionSlot, {
127
- value: action,
128
- enumerable: true,
129
- });
130
- Object.defineProperty(returnValue, AttributeDefHelper.#binderSlot, {
131
- value: binder,
161
+ Object.defineProperty(returnValue, AttributeDefHelper.#lazyBinderSlot, {
162
+ value: lazyBinder,
132
163
  enumerable: true,
133
164
  });
134
165
  return returnValue;
135
166
  }
167
+
168
+ simpleAttribute<Args extends any[]>(
169
+ action: (this: ModelT, ...args: Args) => void,
170
+ ): {
171
+ (...args: Args): AttributeReturn.Done;
172
+ };
173
+ simpleAttribute<Args extends any[], U>(
174
+ action: (this: ModelT, ...args: Args) => void,
175
+ binder: (this: ModelT, ...args: Args) => U,
176
+ ): {
177
+ (...args: Args): AttributeReturn.Done;
178
+ as?(): U;
179
+ };
180
+ simpleAttribute<Args extends any[], U>(
181
+ action: (this: ModelT, ...args: Args) => void,
182
+ binder?: (this: ModelT, ...args: Args) => U,
183
+ ) {
184
+ const action2: AttributeAction<ModelT, any> = (model, positionals) =>
185
+ action.apply(model, positionals as Args);
186
+ let binder2: AttributeBinder<ModelT, any> | undefined;
187
+ if (binder) {
188
+ binder2 = (model, positionals) =>
189
+ binder.apply(model, positionals as Args);
190
+ }
191
+ return this.attribute<any>(action2, binder2);
192
+ }
136
193
  }
137
194
 
138
195
  export function defineViewModel<
139
196
  T,
140
- const BlockDef extends Record<string, AttributeDefinition>,
197
+ const BlockDef extends Partial<Record<string | Action, AttributeDefinition>>,
141
198
  InitMeta = void,
142
199
  >(
143
200
  Ctor: new () => T,
144
201
  modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef,
145
202
  initMeta?: InitMeta,
146
- ): ViewModel<T, BlockDef & { [Meta]: InitMeta }> {
147
- const vm = new ViewModel<T, BlockDef & { [Meta]: InitMeta }>(Ctor);
203
+ ): IViewModel<T, BlockDef & { "~meta": InitMeta }> {
204
+ const vm = new ViewModel<T, BlockDef & { "~meta": InitMeta }>(Ctor);
148
205
  const helper = new AttributeDefHelper(vm);
149
206
  const defResult = modelDefFn(helper);
150
- helper._assignActions(defResult);
207
+ helper["~assignActions"](defResult);
208
+ return vm;
209
+ }
210
+
211
+ export type SimpleViewModel<T> = IViewModel<
212
+ T,
213
+ {
214
+ [K in keyof T]-?: {
215
+ (value: T[K]): AttributeReturn.Done;
216
+ uniqueKey(): K;
217
+ required(): {} extends Pick<T, K> ? false : true;
218
+ };
219
+ } & { "~meta": undefined }
220
+ >;
221
+
222
+ export function defineSimpleViewModel<const T extends StandardJSONSchemaV1>(
223
+ schema: T,
224
+ ): SimpleViewModel<StandardJSONSchemaV1.InferInput<T>> {
225
+ const jsonSchema = schema["~standard"].jsonSchema.input({
226
+ target: "draft-2020-12",
227
+ });
228
+ const Ctor = class SimpleViewModel {};
229
+ const vm = new ViewModel<any, any>(Ctor);
230
+ const helper = new AttributeDefHelper(vm);
231
+ const defResult: Record<string, any> = {};
232
+ for (const key of Object.keys(jsonSchema.properties ?? {})) {
233
+ defResult[key] = helper.simpleAttribute(function (this: any, value) {
234
+ this[key] = value;
235
+ });
236
+ }
237
+ helper["~assignActions"](defResult);
151
238
  return vm;
152
239
  }
153
240
 
154
- interface AttributeDefinition {
241
+ export interface AttributeDefinition {
155
242
  (...args: any[]): AttributePositionalReturnBase;
156
243
  as?(): any;
157
- // required?(): boolean;
244
+ required?(): boolean;
245
+ uniqueKey?(): string;
158
246
  }
159
247
 
160
248
  interface AttributePositionalReturnBase {
@@ -162,50 +250,22 @@ interface AttributePositionalReturnBase {
162
250
  namedDefinition: AttributeBlockDefinition;
163
251
  }
164
252
 
165
- export namespace AttributeReturn {
166
- export type This<TMeta = any> = {
167
- [Meta]: TMeta;
168
- };
169
-
170
- export type EnableIf<Condition, T = void> = Condition extends true
171
- ? T
172
- : never;
173
-
174
- export type Done = {
175
- namedDefinition: { [Meta]: void };
176
- };
177
-
178
- export type With<
179
- VM extends ViewModel<any, any>,
180
- TMeta = VM[NamedDefinition][Meta],
181
- > = {
182
- namedDefinition: BlockDefinitionRewriteMeta<VM[NamedDefinition], TMeta>;
183
- };
184
-
185
- export type WithRewriteMeta<VM extends ViewModel<any, any>, Meta> = {
186
- namedDefinition: VM[NamedDefinition];
187
- rewriteMeta: Meta;
188
- };
189
- }
190
-
191
- export type { AttributeReturn as AR };
192
-
193
253
  export type AttributeAction<Model, T extends AttributeDefinition> = (
194
254
  model: Model,
195
- positional: () => Parameters<T>,
255
+ positional: Parameters<T>,
196
256
  named: View<
197
257
  ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition
198
258
  ? ReturnType<T>["namedDefinition"]
199
- : { [Meta]: void }
259
+ : { "~meta": void }
200
260
  >,
201
261
  ) => void;
202
262
 
203
263
  export type AttributeBinder<Model, T extends AttributeDefinition> = (
204
264
  model: Model,
205
- positional: () => Parameters<T>,
265
+ positional: Parameters<T>,
206
266
  named: View<
207
267
  ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition
208
268
  ? ReturnType<T>["namedDefinition"]
209
- : { [Meta]: void }
269
+ : { "~meta": void }
210
270
  >,
211
271
  ) => T["as"] extends () => infer U ? U : void;