@gi-tcg/gts-runtime 0.6.8 → 0.7.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
@@ -15,8 +15,8 @@ declare namespace AttributeReturn {
15
15
  "~meta": void;
16
16
  };
17
17
  };
18
- type With<VM extends IViewModel<any, any, any>, TMeta = VM["~namedDefinition"]["~meta"]> = {
19
- namedDefinition: BlockDefinitionRewriteMeta<VM["~namedDefinition"], TMeta>;
18
+ type With<VMI extends IViewModelInstance, TMeta = VMI["~viewModel"]["~namedDefinition"]["~meta"]> = {
19
+ namedDefinition: BlockDefinitionRewriteMeta<VMI["~viewModel"]["~namedDefinition"], TMeta>;
20
20
  };
21
21
  type DoneRewriteMeta<NewMeta> = {
22
22
  namedDefinition: {
@@ -24,17 +24,12 @@ declare namespace AttributeReturn {
24
24
  };
25
25
  rewriteMeta: NewMeta;
26
26
  };
27
- type WithRewriteMeta<NewMeta, VM extends IViewModel<any, any, any>, TMeta = VM["~namedDefinition"]["~meta"]> = {
28
- namedDefinition: BlockDefinitionRewriteMeta<VM["~namedDefinition"], TMeta>;
27
+ type WithRewriteMeta<NewMeta, VMI extends IViewModelInstance, TMeta = VMI["~viewModel"]["~namedDefinition"]["~meta"]> = {
28
+ namedDefinition: BlockDefinitionRewriteMeta<VMI["~viewModel"]["~namedDefinition"], TMeta>;
29
29
  rewriteMeta: NewMeta;
30
30
  };
31
31
  }
32
32
  //#endregion
33
- //#region src/symbols.d.ts
34
- type Meta = "~meta";
35
- type Action = "~action";
36
- type NamedDefinition = "~namedDefinition";
37
- //#endregion
38
33
  //#region src/view.d.ts
39
34
  type AttributeName = string | symbol;
40
35
  interface SingleAttributeNode {
@@ -66,39 +61,50 @@ declare function createBinding(rootVM: IViewModel<any, any, any>, node: SingleAt
66
61
  interface AttributeBlockDefinition {
67
62
  "~meta": any;
68
63
  }
69
- type BlockDefinitionRewriteMeta<BlockDef extends AttributeBlockDefinition, NewMeta> = Computed<Omit<BlockDef, Meta> & {
64
+ interface PartialAttributeBlockDefinition extends Partial<Record<string, AttributeDefinition>> {}
65
+ type BlockDefinitionRewriteMeta<BlockDef extends AttributeBlockDefinition, NewMeta> = Computed<Omit<BlockDef, "~meta"> & {
70
66
  "~meta": NewMeta;
71
67
  }, AttributeBlockDefinition>;
68
+ interface IViewModelInstance<T extends IViewModel<any, any, any> = IViewModel<any, any, any>> {
69
+ "~viewModel": T;
70
+ }
71
+ type DeferredIViewModel<T> = T extends IViewModel<any, any, any> ? T : never;
72
+ interface IReboundViewModel<BaseViewModel extends IViewModel<any, any, any>, NewCtorArgs extends any[]> extends DeferredIViewModel<IViewModel<BaseViewModel["~modelType"], BaseViewModel["~namedDefinition"], NewCtorArgs>> {}
73
+ interface INarrowedViewModel<BaseViewModel extends IViewModel<any, any, any>, NewMeta extends BaseViewModel["~namedDefinition"]["~meta"]> extends DeferredIViewModel<IViewModel<BaseViewModel["~modelType"], BlockDefinitionRewriteMeta<BaseViewModel["~namedDefinition"], NewMeta>, BaseViewModel["~ctorArgs"]>> {}
74
+ interface IExtendedViewModel<BaseViewModel extends IViewModel<any, any, any>, ChildModelT extends BaseViewModel["~modelType"], ChildBlockDef extends PartialAttributeBlockDefinition, ChildCtorArgs extends any[]> extends DeferredIViewModel<IViewModel<ChildModelT, Computed<Omit<BaseViewModel["~namedDefinition"], keyof ChildBlockDef> & ChildBlockDef & {
75
+ "~meta": BaseViewModel["~namedDefinition"]["~meta"];
76
+ }>, ChildCtorArgs>> {}
72
77
  interface IViewModel<ModelT, BlockDef extends AttributeBlockDefinition, CtorArgs extends any[]> {
78
+ "~modelType": ModelT;
79
+ "~ctorArgs": CtorArgs;
80
+ "~namedDefinition": BlockDef;
81
+ new (): IViewModelInstance<this>;
82
+ "~viewModel": this;
73
83
  parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>, ...args: CtorArgs): ModelT;
74
84
  /**
75
85
  * Creates a new ViewModel with the same actions and binders, but with
76
- * - a different constructor that accepts the specified arguments, and/or
77
- * - a different initial Meta.
86
+ * a different constructor that accepts the specified arguments.
78
87
  *
79
88
  * This is useful for creating binding ViewModels directly that forwards
80
89
  * the binding logic to inner ViewModels.
81
90
  */
82
- bind<NewMeta = BlockDef[Meta]>(): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, CtorArgs>;
83
- bind<NewMeta = BlockDef[Meta]>(...args: CtorArgs): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, []>;
84
- extend<ChildT extends ModelT, const ChildBlockDef extends Partial<Record<string | Action, AttributeDefinition>>, ChildCtorArgs extends any[] = []>(Ctor: new (...args: ChildCtorArgs) => ChildT, modelDefFn: (helper: AttributeDefHelper<ChildT>) => ChildBlockDef): IViewModel<ChildT, Computed<Omit<BlockDef, keyof ChildBlockDef> & ChildBlockDef & {
85
- "~meta": BlockDef[Meta];
86
- }>, ChildCtorArgs>;
87
- "~namedDefinition": BlockDef;
91
+ bind<This extends IViewModel<any, any, any>>(this: This, ...args: CtorArgs): IReboundViewModel<This, []>;
92
+ /**
93
+ * Rewrite the initial meta.
94
+ * @param newMeta
95
+ */
96
+ narrow<This extends IViewModel<any, any, any>, const NewMeta extends This["~namedDefinition"]["~meta"]>(this: This, newMeta: NewMeta): INarrowedViewModel<This, NewMeta>;
97
+ 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>;
88
98
  }
89
99
  type LazyAttributeActionOrBinder<ModelT> = (model: ModelT, positionals: () => unknown[], named: View<any>) => unknown;
90
100
  declare function getCurrentContext(): "action" | "binder" | null;
91
- declare class ViewModel<ModelT, BlockDef extends AttributeBlockDefinition, CtorArgs extends any[]> implements IViewModel<ModelT, BlockDef, CtorArgs> {
101
+ declare class ViewModelRuntime {
92
102
  #private;
93
- "~namedDefinition": BlockDef;
94
- constructor(Ctor: new (...args: CtorArgs) => ModelT);
95
- "~setActionOrBinder"(context: "action" | "binder", name: PropertyKey, action: LazyAttributeActionOrBinder<ModelT>): void;
96
- parse(view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>, ...args: CtorArgs): ModelT;
97
- bind<NewMeta = BlockDef[Meta]>(): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, CtorArgs>;
98
- bind<NewMeta = BlockDef[Meta]>(...args: CtorArgs): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, []>;
99
- extend<ChildT extends ModelT, const ChildBlockDef extends Partial<Record<string | Action, AttributeDefinition>>, ChildCtorArgs extends any[] = []>(Ctor: new (...args: ChildCtorArgs) => ChildT, modelDefFn: (helper: AttributeDefHelper<ChildT>) => ChildBlockDef): IViewModel<ChildT, Computed<Omit<BlockDef, keyof ChildBlockDef> & ChildBlockDef & {
100
- "~meta": BlockDef[Meta];
101
- }>, ChildCtorArgs>;
103
+ constructor(Ctor: new (...args: any[]) => any);
104
+ setActionOrBinder(context: "action" | "binder", name: PropertyKey, action: LazyAttributeActionOrBinder<any>): void;
105
+ parse(view: View<any>, ...args: any[]): any;
106
+ bind(...args: any[]): ViewModelRuntime;
107
+ extend(ChildCtor: new (...args: any[]) => any, modelDefFn: (helper: AttributeDefHelper<any>) => any): ViewModelRuntime;
102
108
  }
103
109
  interface SimpleAttributeOptions {
104
110
  required?: true;
@@ -111,7 +117,7 @@ type WithSimpleOptions<Base, Options extends SimpleAttributeOptions> = Base & (O
111
117
  } : {});
112
118
  declare class AttributeDefHelper<ModelT> {
113
119
  #private;
114
- constructor(viewModel: ViewModel<ModelT, any, any>);
120
+ constructor(vmr: ViewModelRuntime);
115
121
  "~assignActions"(defResult: Partial<Record<string, unknown>>): void;
116
122
  attribute<T extends AttributeDefinition & {
117
123
  as?: undefined;
@@ -136,9 +142,10 @@ declare class AttributeDefHelper<ModelT> {
136
142
  }, Options>;
137
143
  };
138
144
  }
139
- declare function defineViewModel<T, const BlockDef extends Partial<Record<string | Action, AttributeDefinition>>, CtorArgs extends any[] = [], InitMeta = void>(Ctor: new (...args: CtorArgs) => T, modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef, initMeta?: InitMeta): IViewModel<T, BlockDef & {
145
+ declare function defineViewModel<T, const BlockDef extends PartialAttributeBlockDefinition, CtorArgs extends any[] = [], InitMeta = unknown>(Ctor: new (...args: CtorArgs) => T, modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef, initMeta?: InitMeta): IViewModel<T, BlockDef & {
140
146
  "~meta": InitMeta;
141
147
  }, CtorArgs>;
148
+ declare function extendViewModel<BaseViewModel extends IViewModel<any, any, any>, ChildModelT extends BaseViewModel["~modelType"], const ChildBlockDef extends PartialAttributeBlockDefinition, ChildCtorArgs extends any[] = []>(baseViewModel: BaseViewModel, Ctor: new (...args: ChildCtorArgs) => ChildModelT, modelDefFn: (helper: AttributeDefHelper<ChildModelT>) => ChildBlockDef): IExtendedViewModel<BaseViewModel, ChildModelT, ChildBlockDef, ChildCtorArgs>;
142
149
  interface AttributeDefinition {
143
150
  (...args: any[]): AttributePositionalReturnBase;
144
151
  as?(): any;
@@ -260,18 +267,18 @@ type IfAll<T extends (boolean | undefined)[], TrueT, FalseT> = T[number] extends
260
267
  type SimpleViewModelAttribute<ValueT, Options extends SimpleViewModelOptions> = {
261
268
  (value: ValueT): AttributeReturn.Done;
262
269
  } & IfAll<[Options["recursive"], IsSingleton<ExtractObject<ValueT>>], {
263
- (): AttributeReturn.With<SimpleViewModel<ExtractObject<ValueT>, Options>>;
270
+ (): AttributeReturn.With<IViewModelInstance<ISimpleViewModel<ExtractObject<ValueT>, Options>>>;
264
271
  }, IfAll<[Options["booleanSwitch"], true extends ValueT ? true : false], {
265
272
  (): AttributeReturn.Done;
266
273
  }, {}>>;
267
- interface SimpleViewModel<T, Options extends SimpleViewModelOptions = {}> extends IViewModel<T, { [K in keyof T]-?: SimpleViewModelAttribute<T[K], Options> & {
274
+ interface ISimpleViewModel<T, Options extends SimpleViewModelOptions = {}> extends IViewModel<T, { [K in keyof T]-?: SimpleViewModelAttribute<T[K], Options> & {
268
275
  uniqueKey(): K;
269
276
  required(): {} extends Pick<T, K> ? false : true;
270
277
  } } & {
271
- "~meta": undefined;
278
+ "~meta": unknown;
272
279
  }, []> {
273
280
  Model: new () => T;
274
281
  }
275
- declare function defineSimpleViewModel<const T extends StandardJSONSchemaV1, const Options extends SimpleViewModelOptions = {}>(schema: T, options?: Options): SimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options>;
282
+ declare function defineSimpleViewModel<const T extends StandardJSONSchemaV1, const Options extends SimpleViewModelOptions = {}>(schema: T, options?: Options): ISimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options>;
276
283
  //#endregion
277
- export { type AttributeReturn as AR, type AttributeReturn, type Action, type AttributeBlockDefinition, type AttributeDefinition, type IViewModel, type Meta, type NamedAttributesNode, type NamedDefinition, type SimpleAttributeOptions, type SimpleViewModel, type SimpleViewModelOptions, type SingleAttributeNode, type View, createBinding, createDefine, defineSimpleViewModel, defineViewModel, getCurrentContext };
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 };
package/dist/index.js CHANGED
@@ -34,14 +34,14 @@ let currentContext = null;
34
34
  function getCurrentContext() {
35
35
  return currentContext;
36
36
  }
37
- var ViewModel = class ViewModel {
37
+ var ViewModelRuntime = class ViewModelRuntime {
38
38
  #registeredActions = /* @__PURE__ */ new Map();
39
39
  #registeredBinders = /* @__PURE__ */ new Map();
40
40
  #Ctor;
41
41
  constructor(Ctor) {
42
42
  this.#Ctor = Ctor;
43
43
  }
44
- "~setActionOrBinder"(context, name, action) {
44
+ setActionOrBinder(context, name, action) {
45
45
  if (context === "action") this.#registeredActions.set(name, action);
46
46
  else this.#registeredBinders.set(name, action);
47
47
  }
@@ -64,36 +64,60 @@ var ViewModel = class ViewModel {
64
64
  currentContext = null;
65
65
  return model;
66
66
  }
67
- #clone() {
68
- const newVM = new ViewModel(this.#Ctor);
69
- newVM.#registeredActions = new Map(this.#registeredActions);
70
- newVM.#registeredBinders = new Map(this.#registeredBinders);
71
- return newVM;
67
+ #clone(Ctor = this.#Ctor) {
68
+ const newVMR = new ViewModelRuntime(Ctor);
69
+ newVMR.#registeredActions = new Map(this.#registeredActions);
70
+ newVMR.#registeredBinders = new Map(this.#registeredBinders);
71
+ return newVMR;
72
72
  }
73
73
  bind(...args) {
74
- if (args.length === 0) return this;
75
- const newModel = this.#clone();
76
- newModel.#Ctor = class extends this.#Ctor {
74
+ const ParentCtor = this.#Ctor;
75
+ class ReboundCtor extends ParentCtor {
77
76
  constructor() {
78
77
  super(...args);
79
78
  }
80
- };
81
- return newModel;
79
+ }
80
+ return this.#clone(ReboundCtor);
82
81
  }
83
- extend(Ctor, modelDefFn) {
84
- const newVM = new ViewModel(Ctor);
85
- for (const [name, action] of this.#registeredActions) newVM["~setActionOrBinder"]("action", name, action);
86
- for (const [name, binder] of this.#registeredBinders) newVM["~setActionOrBinder"]("binder", name, binder);
87
- const helper = new AttributeDefHelper(newVM);
82
+ extend(ChildCtor, modelDefFn) {
83
+ const newVMR = this.#clone(ChildCtor);
84
+ const helper = new AttributeDefHelper(newVMR);
88
85
  const defResult = modelDefFn(helper);
89
86
  helper["~assignActions"](defResult);
90
- return newVM;
87
+ return newVMR;
91
88
  }
92
89
  };
90
+ const runtimeViewModelSlot = Symbol("runtimeViewModel");
91
+ function isViewModel(value) {
92
+ return typeof value === "function" && runtimeViewModelSlot in value;
93
+ }
94
+ function createViewModel(vmr) {
95
+ class ViewModelDescriptor {
96
+ static [runtimeViewModelSlot] = vmr;
97
+ constructor() {
98
+ const name = new.target.name;
99
+ throw new Error(`ViewModel cannot be constructed. Use static method instead: \`${name}.parse\`, \`${name}.extend\` etc.`);
100
+ }
101
+ static parse(view, ...args) {
102
+ return vmr.parse(view, ...args);
103
+ }
104
+ static bind(...args) {
105
+ if (args.length === 0) return this;
106
+ return createViewModel(vmr.bind(...args));
107
+ }
108
+ static narrow(_newMeta) {
109
+ return this;
110
+ }
111
+ static extend(Ctor, modelDefFn) {
112
+ return createViewModel(vmr.extend(Ctor, modelDefFn));
113
+ }
114
+ }
115
+ return ViewModelDescriptor;
116
+ }
93
117
  var AttributeDefHelper = class AttributeDefHelper {
94
- #viewModel;
95
- constructor(viewModel) {
96
- this.#viewModel = viewModel;
118
+ #vmr;
119
+ constructor(vmr) {
120
+ this.#vmr = vmr;
97
121
  }
98
122
  static #lazyActionSlot = Symbol("actionSlot");
99
123
  static #lazyBinderSlot = Symbol("binderSlot");
@@ -104,9 +128,9 @@ var AttributeDefHelper = class AttributeDefHelper {
104
128
  continue;
105
129
  }
106
130
  const actionDescriptor = Object.getOwnPropertyDescriptor(value, AttributeDefHelper.#lazyActionSlot);
107
- if (actionDescriptor) this.#viewModel["~setActionOrBinder"]("action", name, actionDescriptor.value);
131
+ if (actionDescriptor) this.#vmr.setActionOrBinder("action", name, actionDescriptor.value);
108
132
  const binderDescriptor = Object.getOwnPropertyDescriptor(value, AttributeDefHelper.#lazyBinderSlot);
109
- if (binderDescriptor) this.#viewModel["~setActionOrBinder"]("binder", name, binderDescriptor.value);
133
+ if (binderDescriptor) this.#vmr.setActionOrBinder("binder", name, binderDescriptor.value);
110
134
  }
111
135
  }
112
136
  attribute(action, binder) {
@@ -117,13 +141,13 @@ var AttributeDefHelper = class AttributeDefHelper {
117
141
  enumerable: true
118
142
  });
119
143
  let lazyBinder;
120
- if (typeof binder === "function") lazyBinder = (model, positionals, named) => binder(model, positionals(), named);
121
- else if (binder) {
144
+ if (isViewModel(binder)) {
122
145
  const vm = binder;
123
146
  lazyBinder = (model, positionals, named) => {
124
147
  return vm.parse(named);
125
148
  };
126
- } else lazyBinder = () => {};
149
+ } else if (typeof binder === "function") lazyBinder = (model, positionals, named) => binder(model, positionals(), named);
150
+ else lazyBinder = () => {};
127
151
  Object.defineProperty(returnValue, AttributeDefHelper.#lazyBinderSlot, {
128
152
  value: lazyBinder,
129
153
  enumerable: true
@@ -140,11 +164,14 @@ var AttributeDefHelper = class AttributeDefHelper {
140
164
  }
141
165
  };
142
166
  function defineViewModel(Ctor, modelDefFn, initMeta) {
143
- const vm = new ViewModel(Ctor);
144
- const helper = new AttributeDefHelper(vm);
167
+ const vmr = new ViewModelRuntime(Ctor);
168
+ const helper = new AttributeDefHelper(vmr);
145
169
  const defResult = modelDefFn(helper);
146
170
  helper["~assignActions"](defResult);
147
- return vm;
171
+ return createViewModel(vmr);
172
+ }
173
+ function extendViewModel(baseViewModel, Ctor, modelDefFn) {
174
+ return baseViewModel.extend(Ctor, modelDefFn);
148
175
  }
149
176
  //#endregion
150
177
  //#region src/simple_view_model.ts
@@ -166,8 +193,8 @@ function extractObjectSchema(propSchema) {
166
193
  }
167
194
  function createSimpleViewModelFromJsonSchema(jsonSchema, options) {
168
195
  const Ctor = class SimpleViewModel {};
169
- const vm = new ViewModel(Ctor);
170
- const helper = new AttributeDefHelper(vm);
196
+ const vmr = new ViewModelRuntime(Ctor);
197
+ const helper = new AttributeDefHelper(vmr);
171
198
  const defResult = {};
172
199
  for (const key of Object.keys(jsonSchema.properties ?? {})) {
173
200
  const propSchema = jsonSchema.properties[key];
@@ -175,13 +202,14 @@ function createSimpleViewModelFromJsonSchema(jsonSchema, options) {
175
202
  defResult[key] = registerAttribute(helper, key, propSchema, options);
176
203
  }
177
204
  helper["~assignActions"](defResult);
178
- Object.defineProperty(vm, "Model", {
205
+ const ViewModel = createViewModel(vmr);
206
+ Object.defineProperty(ViewModel, "Model", {
179
207
  value: Ctor,
180
208
  writable: false,
181
209
  enumerable: true,
182
210
  configurable: true
183
211
  });
184
- return vm;
212
+ return ViewModel;
185
213
  }
186
214
  function registerAttribute(helper, key, propSchema, options) {
187
215
  let objectSchema = null;
@@ -208,4 +236,4 @@ function defineSimpleViewModel(schema, options) {
208
236
  return createSimpleViewModelFromJsonSchema(schema["~standard"].jsonSchema.input({ target: "draft-2020-12" }), resolvedOptions);
209
237
  }
210
238
  //#endregion
211
- export { createBinding, createDefine, defineSimpleViewModel, defineViewModel, getCurrentContext };
239
+ export { createBinding, createDefine, defineSimpleViewModel, defineViewModel, extendViewModel, getCurrentContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-runtime",
3
- "version": "0.6.8",
3
+ "version": "0.7.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/piovium/gts.git"
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  AttributeBlockDefinition,
3
3
  BlockDefinitionRewriteMeta,
4
- IViewModel,
4
+ IViewModelInstance,
5
5
  } from "./view_model.ts";
6
6
 
7
7
  export type Computed<T, Constraint = any> = T extends Constraint
@@ -28,10 +28,13 @@ export namespace AttributeReturn {
28
28
  };
29
29
 
30
30
  export type With<
31
- VM extends IViewModel<any, any, any>,
32
- TMeta = VM["~namedDefinition"]["~meta"],
31
+ VMI extends IViewModelInstance,
32
+ TMeta = VMI["~viewModel"]["~namedDefinition"]["~meta"],
33
33
  > = {
34
- namedDefinition: BlockDefinitionRewriteMeta<VM["~namedDefinition"], TMeta>;
34
+ namedDefinition: BlockDefinitionRewriteMeta<
35
+ VMI["~viewModel"]["~namedDefinition"],
36
+ TMeta
37
+ >;
35
38
  };
36
39
 
37
40
  export type DoneRewriteMeta<NewMeta> = {
@@ -41,10 +44,13 @@ export namespace AttributeReturn {
41
44
 
42
45
  export type WithRewriteMeta<
43
46
  NewMeta,
44
- VM extends IViewModel<any, any, any>,
45
- TMeta = VM["~namedDefinition"]["~meta"],
47
+ VMI extends IViewModelInstance,
48
+ TMeta = VMI["~viewModel"]["~namedDefinition"]["~meta"],
46
49
  > = {
47
- namedDefinition: BlockDefinitionRewriteMeta<VM["~namedDefinition"], TMeta>;
50
+ namedDefinition: BlockDefinitionRewriteMeta<
51
+ VMI["~viewModel"]["~namedDefinition"],
52
+ TMeta
53
+ >;
48
54
  rewriteMeta: NewMeta;
49
55
  };
50
56
  }
package/src/index.ts CHANGED
@@ -1,18 +1,22 @@
1
1
  export {
2
2
  defineViewModel,
3
+ extendViewModel,
3
4
  getCurrentContext,
4
5
  type AttributeDefinition,
5
6
  type IViewModel,
7
+ type IViewModelInstance,
8
+ type IReboundViewModel,
9
+ type INarrowedViewModel,
10
+ type IExtendedViewModel,
6
11
  type SimpleAttributeOptions,
7
12
  type AttributeBlockDefinition,
8
13
  } from "./view_model.ts";
9
14
  export {
10
15
  defineSimpleViewModel,
11
- type SimpleViewModel,
16
+ type ISimpleViewModel,
12
17
  type SimpleViewModelOptions,
13
18
  } from "./simple_view_model.ts";
14
19
  export type { AttributeReturn, AR } from "./attribute_return.ts";
15
- export type { Action, Meta, NamedDefinition } from "./symbols.ts";
16
20
 
17
21
  export {
18
22
  createBinding,
@@ -1,9 +1,10 @@
1
1
  import type { StandardJSONSchemaV1 } from "@standard-schema/spec";
2
2
  import {
3
3
  AttributeDefHelper,
4
- ViewModel,
4
+ createViewModel,
5
5
  type IViewModel,
6
- type AttributeAction,
6
+ type IViewModelInstance,
7
+ ViewModelRuntime,
7
8
  } from "./view_model.ts";
8
9
  import type { AttributeReturn } from "./attribute_return.ts";
9
10
  import { Ajv2020 } from "ajv/dist/2020.js";
@@ -39,7 +40,9 @@ type SimpleViewModelAttribute<
39
40
  } & IfAll<
40
41
  [Options["recursive"], IsSingleton<ExtractObject<ValueT>>],
41
42
  {
42
- (): AttributeReturn.With<SimpleViewModel<ExtractObject<ValueT>, Options>>;
43
+ (): AttributeReturn.With<
44
+ IViewModelInstance<ISimpleViewModel<ExtractObject<ValueT>, Options>>
45
+ >;
43
46
  },
44
47
  IfAll<
45
48
  [Options["booleanSwitch"], true extends ValueT ? true : false],
@@ -48,7 +51,7 @@ type SimpleViewModelAttribute<
48
51
  >
49
52
  >;
50
53
 
51
- export interface SimpleViewModel<
54
+ export interface ISimpleViewModel<
52
55
  T,
53
56
  Options extends SimpleViewModelOptions = {},
54
57
  > extends IViewModel<
@@ -59,7 +62,7 @@ export interface SimpleViewModel<
59
62
  required(): {} extends Pick<T, K> ? false : true;
60
63
  };
61
64
  } & {
62
- "~meta": undefined;
65
+ "~meta": unknown;
63
66
  },
64
67
  []
65
68
  > {
@@ -97,10 +100,10 @@ function extractObjectSchema(
97
100
  function createSimpleViewModelFromJsonSchema(
98
101
  jsonSchema: Record<string, unknown>,
99
102
  options: SimpleViewModelOptions,
100
- ): SimpleViewModel<any, any> {
103
+ ): ISimpleViewModel<any, any> {
101
104
  const Ctor = class SimpleViewModel {};
102
- const vm = new ViewModel<any, any, []>(Ctor);
103
- const helper = new AttributeDefHelper(vm);
105
+ const vmr = new ViewModelRuntime(Ctor);
106
+ const helper = new AttributeDefHelper(vmr);
104
107
  const defResult: Record<string, any> = {};
105
108
  for (const key of Object.keys(jsonSchema.properties ?? {})) {
106
109
  const propSchema = (jsonSchema.properties as Record<string, unknown>)[key];
@@ -115,13 +118,14 @@ function createSimpleViewModelFromJsonSchema(
115
118
  );
116
119
  }
117
120
  helper["~assignActions"](defResult);
118
- Object.defineProperty(vm, "Model", {
121
+ const ViewModel = createViewModel(vmr);
122
+ Object.defineProperty(ViewModel, "Model", {
119
123
  value: Ctor,
120
124
  writable: false,
121
125
  enumerable: true,
122
126
  configurable: true,
123
127
  });
124
- return vm as any;
128
+ return ViewModel as ISimpleViewModel<any, any>;
125
129
  }
126
130
 
127
131
  function registerAttribute(
@@ -162,7 +166,7 @@ export function defineSimpleViewModel<
162
166
  >(
163
167
  schema: T,
164
168
  options?: Options,
165
- ): SimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options> {
169
+ ): ISimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options> {
166
170
  const resolvedOptions: SimpleViewModelOptions = {
167
171
  booleanSwitch: false,
168
172
  recursive: false,
@@ -174,5 +178,5 @@ export function defineSimpleViewModel<
174
178
  return createSimpleViewModelFromJsonSchema(
175
179
  jsonSchema,
176
180
  resolvedOptions,
177
- ) as SimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options>;
181
+ ) as ISimpleViewModel<StandardJSONSchemaV1.InferInput<T>, Options>;
178
182
  }
package/src/view_model.ts CHANGED
@@ -3,64 +3,115 @@ import type {
3
3
  AttributeReturn,
4
4
  Computed,
5
5
  } from "./attribute_return.ts";
6
- import type { Action, Meta } from "./symbols.ts";
7
6
  import { View } from "./view.ts";
8
7
 
9
8
  export interface AttributeBlockDefinition {
10
9
  "~meta": any;
11
10
  }
12
11
 
12
+ export interface PartialAttributeBlockDefinition extends Partial<
13
+ Record<string, AttributeDefinition>
14
+ > {}
15
+
13
16
  export type BlockDefinitionRewriteMeta<
14
17
  BlockDef extends AttributeBlockDefinition,
15
18
  NewMeta,
16
19
  > = Computed<
17
- Omit<BlockDef, Meta> & { "~meta": NewMeta },
20
+ Omit<BlockDef, "~meta"> & { "~meta": NewMeta },
18
21
  AttributeBlockDefinition
19
22
  >;
20
23
 
24
+ export interface IViewModelInstance<
25
+ T extends IViewModel<any, any, any> = IViewModel<any, any, any>,
26
+ > {
27
+ "~viewModel": T;
28
+ }
29
+
30
+ export type DeferredIViewModel<T> =
31
+ T extends IViewModel<any, any, any> ? T : never;
32
+
33
+ export interface IReboundViewModel<
34
+ BaseViewModel extends IViewModel<any, any, any>,
35
+ NewCtorArgs extends any[],
36
+ > extends DeferredIViewModel<
37
+ IViewModel<
38
+ BaseViewModel["~modelType"],
39
+ BaseViewModel["~namedDefinition"],
40
+ NewCtorArgs
41
+ >
42
+ > {}
43
+ export interface INarrowedViewModel<
44
+ BaseViewModel extends IViewModel<any, any, any>,
45
+ NewMeta extends BaseViewModel["~namedDefinition"]["~meta"],
46
+ > extends DeferredIViewModel<
47
+ IViewModel<
48
+ BaseViewModel["~modelType"],
49
+ BlockDefinitionRewriteMeta<BaseViewModel["~namedDefinition"], NewMeta>,
50
+ BaseViewModel["~ctorArgs"]
51
+ >
52
+ > {}
53
+
54
+ export interface IExtendedViewModel<
55
+ BaseViewModel extends IViewModel<any, any, any>,
56
+ ChildModelT extends BaseViewModel["~modelType"],
57
+ ChildBlockDef extends PartialAttributeBlockDefinition,
58
+ ChildCtorArgs extends any[],
59
+ > extends DeferredIViewModel<
60
+ IViewModel<
61
+ ChildModelT,
62
+ Computed<
63
+ Omit<BaseViewModel["~namedDefinition"], keyof ChildBlockDef> &
64
+ ChildBlockDef & { "~meta": BaseViewModel["~namedDefinition"]["~meta"] }
65
+ >,
66
+ ChildCtorArgs
67
+ >
68
+ > {}
69
+
21
70
  export interface IViewModel<
22
71
  ModelT,
23
72
  BlockDef extends AttributeBlockDefinition,
24
73
  CtorArgs extends any[],
25
74
  > {
75
+ "~modelType": ModelT;
76
+ "~ctorArgs": CtorArgs;
77
+ "~namedDefinition": BlockDef;
78
+ new (): IViewModelInstance<this>;
79
+ "~viewModel": this;
26
80
  parse(
27
81
  view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>,
28
82
  ...args: CtorArgs
29
83
  ): ModelT;
30
84
  /**
31
85
  * Creates a new ViewModel with the same actions and binders, but with
32
- * - a different constructor that accepts the specified arguments, and/or
33
- * - a different initial Meta.
86
+ * a different constructor that accepts the specified arguments.
34
87
  *
35
88
  * This is useful for creating binding ViewModels directly that forwards
36
89
  * the binding logic to inner ViewModels.
37
90
  */
38
- bind<NewMeta = BlockDef[Meta]>(): IViewModel<
39
- ModelT,
40
- BlockDefinitionRewriteMeta<BlockDef, NewMeta>,
41
- CtorArgs
42
- >;
43
- bind<NewMeta = BlockDef[Meta]>(
91
+ bind<This extends IViewModel<any, any, any>>(
92
+ this: This,
44
93
  ...args: CtorArgs
45
- ): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, []>;
94
+ ): IReboundViewModel<This, []>;
95
+
96
+ /**
97
+ * Rewrite the initial meta.
98
+ * @param newMeta
99
+ */
100
+ narrow<This extends IViewModel<any, any, any>, const NewMeta extends This["~namedDefinition"]["~meta"]>(
101
+ this: This,
102
+ newMeta: NewMeta,
103
+ ): INarrowedViewModel<This, NewMeta>;
104
+
46
105
  extend<
106
+ This extends IViewModel<any, any, any>,
47
107
  ChildT extends ModelT,
48
- const ChildBlockDef extends Partial<
49
- Record<string | Action, AttributeDefinition>
50
- >,
108
+ const ChildBlockDef extends PartialAttributeBlockDefinition,
51
109
  ChildCtorArgs extends any[] = [],
52
110
  >(
111
+ this: This,
53
112
  Ctor: new (...args: ChildCtorArgs) => ChildT,
54
113
  modelDefFn: (helper: AttributeDefHelper<ChildT>) => ChildBlockDef,
55
- ): IViewModel<
56
- ChildT,
57
- Computed<
58
- Omit<BlockDef, keyof ChildBlockDef> &
59
- ChildBlockDef & { "~meta": BlockDef[Meta] }
60
- >,
61
- ChildCtorArgs
62
- >;
63
- "~namedDefinition": BlockDef;
114
+ ): IExtendedViewModel<This, ChildT, ChildBlockDef, ChildCtorArgs>;
64
115
  }
65
116
 
66
117
  type LazyAttributeActionOrBinder<ModelT> = (
@@ -74,28 +125,19 @@ export function getCurrentContext(): "action" | "binder" | null {
74
125
  return currentContext;
75
126
  }
76
127
 
77
- export class ViewModel<
78
- ModelT,
79
- BlockDef extends AttributeBlockDefinition,
80
- CtorArgs extends any[],
81
- > implements IViewModel<ModelT, BlockDef, CtorArgs> {
82
- declare "~namedDefinition": BlockDef;
83
-
84
- #registeredActions: Map<PropertyKey, LazyAttributeActionOrBinder<ModelT>> =
85
- new Map();
86
- #registeredBinders: Map<PropertyKey, LazyAttributeActionOrBinder<ModelT>> =
87
- new Map();
128
+ export class ViewModelRuntime {
129
+ #registeredActions = new Map<PropertyKey, LazyAttributeActionOrBinder<any>>();
130
+ #registeredBinders = new Map<PropertyKey, LazyAttributeActionOrBinder<any>>();
131
+ #Ctor: new (...args: any[]) => any;
88
132
 
89
- #Ctor: new (...args: CtorArgs) => ModelT;
90
-
91
- constructor(Ctor: new (...args: CtorArgs) => ModelT) {
133
+ constructor(Ctor: new (...args: any[]) => any) {
92
134
  this.#Ctor = Ctor;
93
135
  }
94
136
 
95
- "~setActionOrBinder"(
137
+ setActionOrBinder(
96
138
  context: "action" | "binder",
97
139
  name: PropertyKey,
98
- action: LazyAttributeActionOrBinder<ModelT>,
140
+ action: LazyAttributeActionOrBinder<any>,
99
141
  ): void {
100
142
  if (context === "action") {
101
143
  this.#registeredActions.set(name, action);
@@ -104,10 +146,7 @@ export class ViewModel<
104
146
  }
105
147
  }
106
148
 
107
- parse(
108
- view: View<BlockDefinitionRewriteMeta<BlockDef, unknown>>,
109
- ...args: CtorArgs
110
- ): ModelT {
149
+ parse(view: View<any>, ...args: any[]): any {
111
150
  currentContext = view._bindingCtx ? "binder" : "action";
112
151
  const model = new this.#Ctor(...args);
113
152
  for (const attrNode of view._node.attributes) {
@@ -133,65 +172,87 @@ export class ViewModel<
133
172
  return model;
134
173
  }
135
174
 
136
- #clone() {
137
- const newVM = new ViewModel(this.#Ctor);
138
- newVM.#registeredActions = new Map(this.#registeredActions);
139
- newVM.#registeredBinders = new Map(this.#registeredBinders);
140
- return newVM;
175
+ #clone(Ctor = this.#Ctor): ViewModelRuntime {
176
+ const newVMR = new ViewModelRuntime(Ctor);
177
+ newVMR.#registeredActions = new Map(this.#registeredActions);
178
+ newVMR.#registeredBinders = new Map(this.#registeredBinders);
179
+ return newVMR;
141
180
  }
142
181
 
143
- bind<NewMeta = BlockDef[Meta]>(): IViewModel<
144
- ModelT,
145
- BlockDefinitionRewriteMeta<BlockDef, NewMeta>,
146
- CtorArgs
147
- >;
148
- bind<NewMeta = BlockDef[Meta]>(
149
- ...args: CtorArgs
150
- ): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, []>;
151
- bind<NewMeta = BlockDef[Meta]>(
152
- ...args: any[]
153
- ): IViewModel<ModelT, BlockDefinitionRewriteMeta<BlockDef, NewMeta>, any> {
154
- if (args.length === 0) {
155
- return this as any;
156
- }
157
- const newModel = this.#clone() as ViewModel<any, any, any>;
158
- newModel.#Ctor = class extends (this.#Ctor as new (...args: any[]) => any) {
182
+ bind(...args: any[]): ViewModelRuntime {
183
+ const ParentCtor = this.#Ctor;
184
+ class ReboundCtor extends ParentCtor {
159
185
  constructor() {
160
186
  super(...args);
161
187
  }
162
- };
163
- return newModel as IViewModel<any, any, any>;
188
+ }
189
+ return this.#clone(ReboundCtor);
164
190
  }
165
191
 
166
- extend<
167
- ChildT extends ModelT,
168
- const ChildBlockDef extends Partial<
169
- Record<string | Action, AttributeDefinition>
170
- >,
171
- ChildCtorArgs extends any[] = [],
172
- >(
173
- Ctor: new (...args: ChildCtorArgs) => ChildT,
174
- modelDefFn: (helper: AttributeDefHelper<ChildT>) => ChildBlockDef,
175
- ): IViewModel<
176
- ChildT,
177
- Computed<
178
- Omit<BlockDef, keyof ChildBlockDef> &
179
- ChildBlockDef & { "~meta": BlockDef[Meta] }
180
- >,
181
- ChildCtorArgs
182
- > {
183
- const newVM = new ViewModel<ChildT, any, ChildCtorArgs>(Ctor);
184
- for (const [name, action] of this.#registeredActions) {
185
- newVM["~setActionOrBinder"]("action", name, action as any);
186
- }
187
- for (const [name, binder] of this.#registeredBinders) {
188
- newVM["~setActionOrBinder"]("binder", name, binder as any);
189
- }
190
- const helper = new AttributeDefHelper(newVM);
192
+ extend(
193
+ ChildCtor: new (...args: any[]) => any,
194
+ modelDefFn: (helper: AttributeDefHelper<any>) => any,
195
+ ): ViewModelRuntime {
196
+ const newVMR = this.#clone(ChildCtor);
197
+ const helper = new AttributeDefHelper(newVMR);
191
198
  const defResult = modelDefFn(helper);
192
199
  helper["~assignActions"](defResult);
193
- return newVM as any;
200
+ return newVMR;
201
+ }
202
+ }
203
+
204
+ const runtimeViewModelSlot: unique symbol = Symbol("runtimeViewModel");
205
+
206
+ function isViewModel(value: unknown): value is IViewModel<any, any, any> {
207
+ return typeof value === "function" && runtimeViewModelSlot in value;
208
+ }
209
+
210
+ export function createViewModel<
211
+ ModelT,
212
+ BlockDef extends AttributeBlockDefinition,
213
+ CtorArgs extends any[],
214
+ >(vmr: ViewModelRuntime): IViewModel<ModelT, BlockDef, CtorArgs> {
215
+ class ViewModelDescriptor implements IViewModelInstance {
216
+ declare static "~modelType": ModelT;
217
+ declare static "~ctorArgs": CtorArgs;
218
+ declare static "~namedDefinition": BlockDef;
219
+
220
+ static readonly [runtimeViewModelSlot] = vmr;
221
+
222
+ constructor() {
223
+ const name = new.target.name;
224
+ throw new Error(
225
+ `ViewModel cannot be constructed. Use static method instead: \`${name}.parse\`, \`${name}.extend\` etc.`,
226
+ );
227
+ }
228
+
229
+ static parse(view: View<any>, ...args: CtorArgs): ModelT {
230
+ return vmr.parse(view, ...args);
231
+ }
232
+
233
+ static bind(...args: any[]): IViewModel<any, any, any> {
234
+ if (args.length === 0) {
235
+ return this;
236
+ }
237
+ return createViewModel(vmr.bind(...args));
238
+ }
239
+
240
+ static narrow(_newMeta: any): IViewModel<any, any, any> {
241
+ return this;
242
+ }
243
+
244
+ static extend(
245
+ Ctor: new (...args: any[]) => any,
246
+ modelDefFn: (helper: AttributeDefHelper<any>) => any,
247
+ ): IViewModel<any, any, any> {
248
+ return createViewModel(vmr.extend(Ctor, modelDefFn));
249
+ }
250
+
251
+ declare "~viewModel": typeof ViewModelDescriptor;
252
+ declare static "~viewModel": typeof ViewModelDescriptor;
194
253
  }
254
+
255
+ return ViewModelDescriptor as IViewModel<ModelT, BlockDef, CtorArgs>;
195
256
  }
196
257
 
197
258
  export interface SimpleAttributeOptions {
@@ -206,9 +267,9 @@ type WithSimpleOptions<Base, Options extends SimpleAttributeOptions> = Base &
206
267
  : {});
207
268
 
208
269
  export class AttributeDefHelper<ModelT> {
209
- #viewModel: ViewModel<ModelT, any, any>;
210
- constructor(viewModel: ViewModel<ModelT, any, any>) {
211
- this.#viewModel = viewModel;
270
+ #vmr: ViewModelRuntime;
271
+ constructor(vmr: ViewModelRuntime) {
272
+ this.#vmr = vmr;
212
273
  }
213
274
 
214
275
  static readonly #lazyActionSlot: unique symbol = Symbol("actionSlot");
@@ -228,22 +289,14 @@ export class AttributeDefHelper<ModelT> {
228
289
  AttributeDefHelper.#lazyActionSlot,
229
290
  );
230
291
  if (actionDescriptor) {
231
- this.#viewModel["~setActionOrBinder"](
232
- "action",
233
- name,
234
- actionDescriptor.value,
235
- );
292
+ this.#vmr.setActionOrBinder("action", name, actionDescriptor.value);
236
293
  }
237
294
  const binderDescriptor = Object.getOwnPropertyDescriptor(
238
295
  value,
239
296
  AttributeDefHelper.#lazyBinderSlot,
240
297
  );
241
298
  if (binderDescriptor) {
242
- this.#viewModel["~setActionOrBinder"](
243
- "binder",
244
- name,
245
- binderDescriptor.value,
246
- );
299
+ this.#vmr.setActionOrBinder("binder", name, binderDescriptor.value);
247
300
  }
248
301
  }
249
302
  }
@@ -273,14 +326,14 @@ export class AttributeDefHelper<ModelT> {
273
326
  enumerable: true,
274
327
  });
275
328
  let lazyBinder: LazyAttributeActionOrBinder<ModelT>;
276
- if (typeof binder === "function") {
277
- lazyBinder = (model, positionals, named) =>
278
- binder(model, positionals(), named);
279
- } else if (binder) {
329
+ if (isViewModel(binder)) {
280
330
  const vm = binder;
281
331
  lazyBinder = (model, positionals, named) => {
282
332
  return vm.parse(named);
283
333
  };
334
+ } else if (typeof binder === "function") {
335
+ lazyBinder = (model, positionals, named) =>
336
+ binder(model, positionals(), named);
284
337
  } else {
285
338
  lazyBinder = () => {};
286
339
  }
@@ -332,19 +385,37 @@ export class AttributeDefHelper<ModelT> {
332
385
 
333
386
  export function defineViewModel<
334
387
  T,
335
- const BlockDef extends Partial<Record<string | Action, AttributeDefinition>>,
388
+ const BlockDef extends PartialAttributeBlockDefinition,
336
389
  CtorArgs extends any[] = [],
337
- InitMeta = void,
390
+ InitMeta = unknown,
338
391
  >(
339
392
  Ctor: new (...args: CtorArgs) => T,
340
393
  modelDefFn: (helper: AttributeDefHelper<T>) => BlockDef,
341
394
  initMeta?: InitMeta,
342
395
  ): IViewModel<T, BlockDef & { "~meta": InitMeta }, CtorArgs> {
343
- const vm = new ViewModel<T, BlockDef & { "~meta": InitMeta }, CtorArgs>(Ctor);
344
- const helper = new AttributeDefHelper(vm);
396
+ const vmr = new ViewModelRuntime(Ctor);
397
+ const helper = new AttributeDefHelper<T>(vmr);
345
398
  const defResult = modelDefFn(helper);
346
399
  helper["~assignActions"](defResult);
347
- return vm;
400
+ return createViewModel(vmr);
401
+ }
402
+
403
+ export function extendViewModel<
404
+ BaseViewModel extends IViewModel<any, any, any>,
405
+ ChildModelT extends BaseViewModel["~modelType"],
406
+ const ChildBlockDef extends PartialAttributeBlockDefinition,
407
+ ChildCtorArgs extends any[] = [],
408
+ >(
409
+ baseViewModel: BaseViewModel,
410
+ Ctor: new (...args: ChildCtorArgs) => ChildModelT,
411
+ modelDefFn: (helper: AttributeDefHelper<ChildModelT>) => ChildBlockDef,
412
+ ): IExtendedViewModel<
413
+ BaseViewModel,
414
+ ChildModelT,
415
+ ChildBlockDef,
416
+ ChildCtorArgs
417
+ > {
418
+ return baseViewModel.extend(Ctor, modelDefFn);
348
419
  }
349
420
 
350
421
  export interface AttributeDefinition {
package/src/symbols.ts DELETED
@@ -1,3 +0,0 @@
1
- export type Meta = "~meta";
2
- export type Action = "~action";
3
- export type NamedDefinition = "~namedDefinition";