@gi-tcg/gts-runtime 0.2.0 → 0.3.1

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,95 @@ 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
- BlockDef extends AttributeBlockDefinition
54
- > {
55
- parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT;
56
- }
57
- interface ViewModel<
58
- ModelT,
59
- BlockDef extends AttributeBlockDefinition
77
+ BlockDef extends AttributeBlockDefinition,
78
+ CtorArgs extends any[]
60
79
  > {
61
- /**
62
- * Helper for fetching symbol types
63
- * @internal
64
- */
65
- _symbols: AllSymbols;
66
- [NamedDefinition]: BlockDef;
80
+ parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>, ...args: CtorArgs): ModelT;
81
+ "~namedDefinition": BlockDef;
67
82
  }
83
+ type LazyAttributeActionOrBinder<ModelT> = (model: ModelT, positionals: () => unknown[], named: View<any>) => unknown;
68
84
  declare class ViewModel<
69
85
  ModelT,
70
- BlockDef extends AttributeBlockDefinition
71
- > implements IViewModel<ModelT, BlockDef> {
86
+ BlockDef extends AttributeBlockDefinition,
87
+ CtorArgs extends any[]
88
+ > implements IViewModel<ModelT, BlockDef, CtorArgs> {
72
89
  private;
73
- private Ctor;
74
- constructor(Ctor: new () => ModelT);
75
- _setAction(name: PropertyKey, action: AttributeAction<ModelT, any>): void;
76
- _setBinder(name: PropertyKey, binder: AttributeBinder<ModelT, any>): void;
77
- parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT;
90
+ "~namedDefinition": BlockDef;
91
+ constructor(Ctor: new (...args: CtorArgs) => ModelT);
92
+ "~setActionOrBinder"(context: "action" | "binder", name: PropertyKey, action: LazyAttributeActionOrBinder<ModelT>): void;
93
+ parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>, ...args: CtorArgs): ModelT;
78
94
  }
79
95
  declare class AttributeDefHelper<ModelT> {
80
96
  private;
81
- constructor(viewModel: ViewModel<ModelT, any>);
82
- _assignActions(defResult: Record<string, unknown>): void;
97
+ constructor(viewModel: ViewModel<ModelT, any, any>);
98
+ "~assignActions"(defResult: Partial<Record<string, unknown>>): void;
83
99
  attribute<T extends AttributeDefinition & {
84
100
  as?: undefined;
85
- }>(action: AttributeAction<ModelT, T>, binder?: AttributeBinder<ModelT, T> | ViewModel<any, ReturnType<T>["namedDefinition"]>): T;
101
+ }>(action: AttributeAction<ModelT, T>, binder?: AttributeBinder<ModelT, T> | IViewModel<any, ReturnType<T>["namedDefinition"], []>): T;
86
102
  attribute<T extends AttributeDefinition>(action: AttributeAction<ModelT, T>, binder: AttributeBinder<ModelT, T>): T;
103
+ simpleAttribute<Args extends any[]>(action: (this: ModelT, ...args: Args) => void): {
104
+ (...args: Args): AttributeReturn.Done;
105
+ };
106
+ simpleAttribute<
107
+ Args extends any[],
108
+ U
109
+ >(action: (this: ModelT, ...args: Args) => void, binder: (this: ModelT, ...args: Args) => U): {
110
+ (...args: Args): AttributeReturn.Done;
111
+ as?(): U;
112
+ };
87
113
  }
88
114
  declare function defineViewModel<
89
115
  T,
90
- const BlockDef extends Record<string, AttributeDefinition>,
116
+ const BlockDef extends Partial<Record<string | Action, AttributeDefinition>>,
117
+ CtorArgs extends any[] = [],
91
118
  InitMeta = void
92
- >(Ctor: new () => T, modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef, initMeta?: InitMeta): ViewModel<T, BlockDef & {
93
- [Meta]: InitMeta;
94
- }>;
119
+ >(Ctor: new (...args: CtorArgs) => T, modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef, initMeta?: InitMeta): IViewModel<T, BlockDef & {
120
+ "~meta": InitMeta;
121
+ }, CtorArgs>;
122
+ type SimpleViewModel<T> = IViewModel<T, { [K in keyof T]-? : {
123
+ (value: T[K]): AttributeReturn.Done;
124
+ uniqueKey(): K;
125
+ required(): {} extends Pick<T, K> ? false : true;
126
+ } } & {
127
+ "~meta": undefined;
128
+ }, []>;
129
+ declare function defineSimpleViewModel<const T extends StandardJSONSchemaV1>(schema: T): SimpleViewModel<StandardJSONSchemaV1.InferInput<T>>;
95
130
  interface AttributeDefinition {
96
131
  (...args: any[]): AttributePositionalReturnBase;
97
132
  as?(): any;
133
+ required?(): boolean;
134
+ uniqueKey?(): string;
98
135
  }
99
136
  interface AttributePositionalReturnBase {
100
137
  rewriteMeta?: any;
101
138
  namedDefinition: AttributeBlockDefinition;
102
139
  }
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
140
  type AttributeAction<
131
141
  Model,
132
142
  T extends AttributeDefinition
133
- > = (model: Model, positional: () => Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
134
- [Meta]: void;
143
+ > = (model: Model, positional: Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
144
+ "~meta": void;
135
145
  }>) => void;
136
146
  type AttributeBinder<
137
147
  Model,
138
148
  T extends AttributeDefinition
139
- > = (model: Model, positional: () => Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
140
- [Meta]: void;
149
+ > = (model: Model, positional: Parameters<T>, named: View<ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition ? ReturnType<T>["namedDefinition"] : {
150
+ "~meta": void;
141
151
  }>) => T["as"] extends () => infer U ? U : void;
142
- export { defineViewModel, createDefine, createBinding, Prelude, IViewModel, AttributeReturn, Action };
152
+ 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
- parse(view) {
47
- const model = new this.Ctor;
47
+ parse(view, ...args) {
48
+ const model = new this.#Ctor(...args);
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.1",
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,79 @@
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
- export interface IViewModel<ModelT, BlockDef extends AttributeBlockDefinition> {
24
- parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT;
24
+ export interface IViewModel<
25
+ ModelT,
26
+ BlockDef extends AttributeBlockDefinition,
27
+ CtorArgs extends any[],
28
+ > {
29
+ parse(
30
+ view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>,
31
+ ...args: CtorArgs
32
+ ): ModelT;
33
+ "~namedDefinition": BlockDef;
25
34
  }
26
35
 
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
- }
36
+ type LazyAttributeActionOrBinder<ModelT> = (
37
+ model: ModelT,
38
+ positionals: () => unknown[],
39
+ named: View<any>,
40
+ ) => unknown;
35
41
 
36
- export class ViewModel<
42
+ class ViewModel<
37
43
  ModelT,
38
44
  BlockDef extends AttributeBlockDefinition,
39
- > implements IViewModel<ModelT, BlockDef> {
40
- #registeredActions: Map<PropertyKey, AttributeAction<ModelT, any>> =
45
+ CtorArgs extends any[],
46
+ > implements IViewModel<ModelT, BlockDef, CtorArgs> {
47
+ declare "~namedDefinition": BlockDef;
48
+
49
+ #registeredActions: Map<PropertyKey, LazyAttributeActionOrBinder<ModelT>> =
41
50
  new Map();
42
- #registeredBinders: Map<PropertyKey, AttributeBinder<ModelT, any>> =
51
+ #registeredBinders: Map<PropertyKey, LazyAttributeActionOrBinder<ModelT>> =
43
52
  new Map();
44
53
 
45
- constructor(private Ctor: new () => ModelT) {}
54
+ #Ctor: new (...args: CtorArgs) => ModelT;
46
55
 
47
- _setAction(name: PropertyKey, action: AttributeAction<ModelT, any>): void {
48
- this.#registeredActions.set(name, action);
56
+ constructor(Ctor: new (...args: CtorArgs) => ModelT) {
57
+ this.#Ctor = Ctor;
49
58
  }
50
- _setBinder(name: PropertyKey, binder: AttributeBinder<ModelT, any>): void {
51
- this.#registeredBinders.set(name, binder);
59
+
60
+ "~setActionOrBinder"(
61
+ context: "action" | "binder",
62
+ name: PropertyKey,
63
+ action: LazyAttributeActionOrBinder<ModelT>,
64
+ ): void {
65
+ if (context === "action") {
66
+ this.#registeredActions.set(name, action);
67
+ } else {
68
+ this.#registeredBinders.set(name, action);
69
+ }
52
70
  }
53
71
 
54
- parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>): ModelT {
55
- const model = new this.Ctor();
72
+ parse(
73
+ view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>,
74
+ ...args: CtorArgs
75
+ ): ModelT {
76
+ const model = new this.#Ctor(...args);
56
77
  for (const attrNode of view._node.attributes) {
57
78
  let { name, positionals, named, binding } = attrNode;
58
79
  const insideBindingCtx = !!view._bindingCtx;
@@ -60,7 +81,10 @@ export class ViewModel<
60
81
  insideBindingCtx ? this.#registeredBinders : this.#registeredActions
61
82
  ).get(name);
62
83
  if (!insideBindingCtx && !fn) {
63
- throw new Error(`No action registered for attribute: ${String(name)}`);
84
+ const modelName = this.#Ctor.name;
85
+ throw new Error(
86
+ `No action registered for attribute "${String(name)}" on model "${modelName}"`,
87
+ );
64
88
  }
65
89
  fn ??= () => {};
66
90
  named ??= { attributes: [] };
@@ -74,29 +98,37 @@ export class ViewModel<
74
98
  }
75
99
 
76
100
  class AttributeDefHelper<ModelT> {
77
- #viewModel: ViewModel<ModelT, any>;
78
- constructor(viewModel: ViewModel<ModelT, any>) {
101
+ #viewModel: ViewModel<ModelT, any, any>;
102
+ constructor(viewModel: ViewModel<ModelT, any, any>) {
79
103
  this.#viewModel = viewModel;
80
104
  }
81
105
 
82
- static readonly #actionSlot: unique symbol = Symbol("actionSlot");
83
- static readonly #binderSlot: unique symbol = Symbol("binderSlot");
106
+ static readonly #lazyActionSlot: unique symbol = Symbol("actionSlot");
107
+ static readonly #lazyBinderSlot: unique symbol = Symbol("binderSlot");
84
108
 
85
- _assignActions(defResult: Record<string, unknown>): void {
86
- for (const [name, returnValue] of Object.entries(defResult)) {
109
+ "~assignActions"(defResult: Partial<Record<string, unknown>>): void {
110
+ for (const [name, value] of Object.entries(defResult)) {
87
111
  const actionDescriptor = Object.getOwnPropertyDescriptor(
88
- returnValue,
89
- AttributeDefHelper.#actionSlot,
112
+ value,
113
+ AttributeDefHelper.#lazyActionSlot,
90
114
  );
91
115
  if (actionDescriptor) {
92
- this.#viewModel._setAction(name, actionDescriptor.value);
116
+ this.#viewModel["~setActionOrBinder"](
117
+ "action",
118
+ name,
119
+ actionDescriptor.value,
120
+ );
93
121
  }
94
122
  const binderDescriptor = Object.getOwnPropertyDescriptor(
95
- returnValue,
96
- AttributeDefHelper.#binderSlot,
123
+ value,
124
+ AttributeDefHelper.#lazyBinderSlot,
97
125
  );
98
126
  if (binderDescriptor) {
99
- this.#viewModel._setBinder(name, binderDescriptor.value);
127
+ this.#viewModel["~setActionOrBinder"](
128
+ "binder",
129
+ name,
130
+ binderDescriptor.value,
131
+ );
100
132
  }
101
133
  }
102
134
  }
@@ -105,7 +137,7 @@ class AttributeDefHelper<ModelT> {
105
137
  action: AttributeAction<ModelT, T>,
106
138
  binder?:
107
139
  | AttributeBinder<ModelT, T>
108
- | ViewModel<any, ReturnType<T>["namedDefinition"]>,
140
+ | IViewModel<any, ReturnType<T>["namedDefinition"], []>,
109
141
  ): T;
110
142
  attribute<T extends AttributeDefinition>(
111
143
  action: AttributeAction<ModelT, T>,
@@ -113,48 +145,117 @@ class AttributeDefHelper<ModelT> {
113
145
  ): T;
114
146
  attribute(
115
147
  action: any,
116
- binder?: AttributeBinder<any, any> | ViewModel<any, any>,
148
+ binder?: AttributeBinder<any, any> | IViewModel<any, any, any>,
117
149
  ) {
118
- if (binder instanceof ViewModel) {
150
+ const returnValue = {};
151
+ const lazyAction: LazyAttributeActionOrBinder<ModelT> = (
152
+ model,
153
+ positionals,
154
+ named,
155
+ ) => action(model, positionals(), named);
156
+ Object.defineProperty(returnValue, AttributeDefHelper.#lazyActionSlot, {
157
+ value: lazyAction,
158
+ enumerable: true,
159
+ });
160
+ let lazyBinder: LazyAttributeActionOrBinder<ModelT>;
161
+ if (typeof binder === "function") {
162
+ lazyBinder = (model, positionals, named) =>
163
+ binder(model, positionals(), named);
164
+ } else if (binder) {
119
165
  const vm = binder;
120
- binder = (model, positionals, named) => {
166
+ lazyBinder = (model, positionals, named) => {
121
167
  return vm.parse(named);
122
168
  };
169
+ } else {
170
+ lazyBinder = () => {};
123
171
  }
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,
172
+ Object.defineProperty(returnValue, AttributeDefHelper.#lazyBinderSlot, {
173
+ value: lazyBinder,
132
174
  enumerable: true,
133
175
  });
134
176
  return returnValue;
135
177
  }
178
+
179
+ simpleAttribute<Args extends any[]>(
180
+ action: (this: ModelT, ...args: Args) => void,
181
+ ): {
182
+ (...args: Args): AttributeReturn.Done;
183
+ };
184
+ simpleAttribute<Args extends any[], U>(
185
+ action: (this: ModelT, ...args: Args) => void,
186
+ binder: (this: ModelT, ...args: Args) => U,
187
+ ): {
188
+ (...args: Args): AttributeReturn.Done;
189
+ as?(): U;
190
+ };
191
+ simpleAttribute<Args extends any[], U>(
192
+ action: (this: ModelT, ...args: Args) => void,
193
+ binder?: (this: ModelT, ...args: Args) => U,
194
+ ) {
195
+ const action2: AttributeAction<ModelT, any> = (model, positionals) =>
196
+ action.apply(model, positionals as Args);
197
+ let binder2: AttributeBinder<ModelT, any> | undefined;
198
+ if (binder) {
199
+ binder2 = (model, positionals) =>
200
+ binder.apply(model, positionals as Args);
201
+ }
202
+ return this.attribute<any>(action2, binder2);
203
+ }
136
204
  }
137
205
 
138
206
  export function defineViewModel<
139
207
  T,
140
- const BlockDef extends Record<string, AttributeDefinition>,
208
+ const BlockDef extends Partial<Record<string | Action, AttributeDefinition>>,
209
+ CtorArgs extends any[] = [],
141
210
  InitMeta = void,
142
211
  >(
143
- Ctor: new () => T,
212
+ Ctor: new (...args: CtorArgs) => T,
144
213
  modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef,
145
214
  initMeta?: InitMeta,
146
- ): ViewModel<T, BlockDef & { [Meta]: InitMeta }> {
147
- const vm = new ViewModel<T, BlockDef & { [Meta]: InitMeta }>(Ctor);
215
+ ): IViewModel<T, BlockDef & { "~meta": InitMeta }, CtorArgs> {
216
+ const vm = new ViewModel<T, BlockDef & { "~meta": InitMeta }, CtorArgs>(Ctor);
148
217
  const helper = new AttributeDefHelper(vm);
149
218
  const defResult = modelDefFn(helper);
150
- helper._assignActions(defResult);
219
+ helper["~assignActions"](defResult);
220
+ return vm;
221
+ }
222
+
223
+ export type SimpleViewModel<T> = IViewModel<
224
+ T,
225
+ {
226
+ [K in keyof T]-?: {
227
+ (value: T[K]): AttributeReturn.Done;
228
+ uniqueKey(): K;
229
+ required(): {} extends Pick<T, K> ? false : true;
230
+ };
231
+ } & { "~meta": undefined },
232
+ []
233
+ >;
234
+
235
+ export function defineSimpleViewModel<const T extends StandardJSONSchemaV1>(
236
+ schema: T,
237
+ ): SimpleViewModel<StandardJSONSchemaV1.InferInput<T>> {
238
+ const jsonSchema = schema["~standard"].jsonSchema.input({
239
+ target: "draft-2020-12",
240
+ });
241
+ const Ctor = class SimpleViewModel {};
242
+ const vm = new ViewModel<any, any, []>(Ctor);
243
+ const helper = new AttributeDefHelper(vm);
244
+ const defResult: Record<string, any> = {};
245
+ for (const key of Object.keys(jsonSchema.properties ?? {})) {
246
+ defResult[key] = helper.simpleAttribute(function (this: any, value) {
247
+ this[key] = value;
248
+ });
249
+ }
250
+ helper["~assignActions"](defResult);
151
251
  return vm;
152
252
  }
153
253
 
154
- interface AttributeDefinition {
254
+ export interface AttributeDefinition {
155
255
  (...args: any[]): AttributePositionalReturnBase;
156
256
  as?(): any;
157
- // required?(): boolean;
257
+ required?(): boolean;
258
+ uniqueKey?(): string;
158
259
  }
159
260
 
160
261
  interface AttributePositionalReturnBase {
@@ -162,50 +263,22 @@ interface AttributePositionalReturnBase {
162
263
  namedDefinition: AttributeBlockDefinition;
163
264
  }
164
265
 
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
266
  export type AttributeAction<Model, T extends AttributeDefinition> = (
194
267
  model: Model,
195
- positional: () => Parameters<T>,
268
+ positional: Parameters<T>,
196
269
  named: View<
197
270
  ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition
198
271
  ? ReturnType<T>["namedDefinition"]
199
- : { [Meta]: void }
272
+ : { "~meta": void }
200
273
  >,
201
274
  ) => void;
202
275
 
203
276
  export type AttributeBinder<Model, T extends AttributeDefinition> = (
204
277
  model: Model,
205
- positional: () => Parameters<T>,
278
+ positional: Parameters<T>,
206
279
  named: View<
207
280
  ReturnType<T>["namedDefinition"] extends AttributeBlockDefinition
208
281
  ? ReturnType<T>["namedDefinition"]
209
- : { [Meta]: void }
282
+ : { "~meta": void }
210
283
  >,
211
284
  ) => T["as"] extends () => infer U ? U : void;