@vue-modeler/model 2.2.0-beta.6 → 2.2.4

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Introduction
1
+ # vue-modeler/model
2
2
 
3
3
  ## What is @vue-modeler/model
4
4
 
@@ -10,7 +10,7 @@ A state management library based on models for [Vue.js](https://vuejs.org/). The
10
10
 
11
11
  - **No global state store**. No store means no problems. State is encapsulated within the model and is reactive. Outside the model, it's only accessible for reading and observation.
12
12
  - **Extremely simple API**: A model prototype is a standard class, actions are defined as standard methods with decorators. Create a model by calling a factory method — nothing more needed. Reactive properties and behavior are defined using the standard Vue API.
13
- - **Supports shared models**. A built-in model container ensures that a model remains in memory while being used. Unused models are automatically removed from the container.
13
+ - **Supports shared models**. Models can be shared across components. You can manage model instances using dependency injection or your own container pattern.
14
14
  - **Follows DRY principle**. Actions have their own state. There's no need to write additional code to track action states. Focus instead on designing architecture and business logic.
15
15
  - **Embraces OOP**. Model prototypes are classes, supporting inheritance, encapsulation, polymorphism, and destructors.
16
16
  - **Complies with SOLID principles**. Encourages clean coding practices like composition of models, dependency injection via constructors, separation of interfaces, single responsibility, etc.
@@ -55,14 +55,14 @@ This library promises to simplify state management in [Vue.js](https://vuejs.org
55
55
 
56
56
  ## Installation
57
57
 
58
- First, [install @vue-modeler/dc](https://github.com/vue-modeler/dc?tab=readme-ov-file#installation)
59
-
60
- Then install @vue-modeler/model:
58
+ Install @vue-modeler/model:
61
59
 
62
60
  ```bash
63
61
  npm add @vue-modeler/model
64
62
  ```
65
63
 
64
+ It is recommended to use it together with the dependency container [@vue-modeler/dc](https://github.com/vue-modeler/dc). For installation instructions and provider setup, see the [@vue-modeler/dc documentation](https://github.com/vue-modeler/dc?tab=readme-ov-file#installation).
65
+
66
66
  ## Usage Examples
67
67
 
68
68
  ### 1. Define Proto model
@@ -105,7 +105,7 @@ class User extends ProtoModel {
105
105
  ) {
106
106
  super()
107
107
 
108
- this.action(this.init).exec()
108
+ this.init()
109
109
  }
110
110
 
111
111
  // Actions (async methods with @action decorator)
@@ -148,17 +148,30 @@ export const patchUser = async (dto: PatchDto): Promise<void> => {
148
148
  }
149
149
  ```
150
150
 
151
- ### 3. Compose all together and create model provider
151
+ ### 3. Create model provider
152
152
 
153
- ```typescript
154
- import { model } from '@vue-modeler/model'
153
+ Using `@vue-modeler/dc` (recommended):
155
154
 
156
- export const useUser = model(() => new User({
157
- fetchUser,
158
- patch: patchUser
155
+ ```typescript
156
+ import { provider } from '@vue-modeler/dc'
157
+ import { User } from './user-model'
158
+ import { fetchUser, patchUser } from './api'
159
+
160
+ export const useUser = provider(() => User.model({
161
+ fetchUser,
162
+ patch: patchUser
159
163
  }))
160
164
  ```
161
165
 
166
+ Or without dependency container:
167
+
168
+ ```typescript
169
+ export const useUser = () => User.model({
170
+ fetchUser,
171
+ patch: patchUser
172
+ })
173
+ ```
174
+
162
175
  ### 4. Using Models in Vue Components
163
176
 
164
177
  ```html
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var K=Object.defineProperty;var L=(r,t,e)=>t in r?K(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var i=(r,t,e)=>L(r,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("vue");class I extends Error{constructor(t,e){super(e.cause.message),this.actionName=t,this.options=e,this.name=this.constructor.name}get cause(){return this.options.cause}throwCause(){throw this.cause}toString(){return this.options.cause.message}}class d extends Error{constructor(t,e){super(t,e),this.message=t,this.options=e,this.name=this.constructor.name}}class b extends d{constructor(t,e,o){super(`Trying to update state of ${t} from ${e} to ${o}`),this.name=this.constructor.name}}class $ extends d{constructor(t,e){super(`Unexpected AbortError for the action ${t} in state ${e}`),this.name=this.constructor.name}}const B=r=>r instanceof DOMException&&r.name==="AbortError"||typeof r=="object"&&r!==null&&"message"in r&&r.message==="canceled",s=class s{constructor(t,e,o,a){i(this,"name");i(this,"_state",s.possibleState.ready);i(this,"_value",null);i(this,"_args",null);this._model=t,this.actionFunction=e,this.ownerGetter=o,this.setStateCb=a;const n=e.name;if(!(n in this._model&&typeof this._model[n]=="function"))throw new d(`Model does not contain method ${n}`);if(typeof e[s.actionFlag]!="function")throw new d(`Method ${n} is not action`);this.name=n}static create(t,e,o,a){return u.shallowReactive(new s(t,e,o,a))}toString(){return this.name}get owner(){return this.ownerGetter()}get possibleStates(){return Object.values(s.possibleState)}get state(){return this._state}set state(t){const e=this._state;this._state=t,this.setStateCb&&this.setStateCb(this,e,t)}get abortController(){return this.isPending?this._value.abortController:null}get args(){return this._args||[]}get promise(){return this.isPending?this._value.promise:null}get error(){return this.isError?this._value:null}get abortReason(){return this.isAbort?this._value.abortController.signal.reason:null}get isPending(){return this.state===s.possibleState.pending}get isError(){return this.state===s.possibleState.error}get isReady(){return this.state===s.possibleState.ready}get isLock(){return this.state===s.possibleState.lock}get isAbort(){return this.state===s.possibleState.abort}is(...t){return!!t.find(e=>this.state===e)}exec(...t){if(this.is(s.possibleState.lock,s.possibleState.pending))throw new b(this.name,this.state,s.possibleState.pending);const e=[...t];let o=t.length&&t[t.length-1];o instanceof AbortController||(o=new AbortController,e.push(o)),this.state=s.possibleState.pending,this._args=t;const n=this.actionFunction[s.actionFlag].apply(this._model,e);if(!(n instanceof Promise))return this.state=s.possibleState.ready,Promise.resolve();const l=n.then(()=>{this.ready()}).catch(c=>{if(c instanceof d||c instanceof RangeError||c instanceof ReferenceError||c instanceof SyntaxError||c instanceof TypeError||c instanceof URIError||c instanceof EvalError)throw c;const y=B(c);if(y&&!this.is(s.possibleState.pending,s.possibleState.lock))throw new $(this.name,this.state);if(y&&this._value.abortController instanceof AbortController&&this._value.abortController.signal.reason===s.abortedByLock){this.state=s.possibleState.lock,this._value=null;return}if(y){this.state=s.possibleState.abort;return}this.setError(new I(this.name,{cause:c}))});return this._value={promise:l,abortController:o},l}abort(t){return this.isPending?(this._value.abortController.abort(t),this._value.promise):Promise.resolve()}lock(){return this.isPending?this.abort(s.abortedByLock):(this.state=s.possibleState.lock,this._value=null,Promise.resolve())}unlock(){if(!this.isLock)throw new b(this.name,this.state,s.possibleState.ready);return this.ready()}setError(t){if(!this.isPending)throw new b(this.name,this.state,s.possibleState.error);return this.state=s.possibleState.error,this._value=t,this}ready(){return this.state=s.possibleState.ready,this}resetError(){if(!this.error)throw new b(this.name,this.state,s.possibleState.ready);return this.ready()}};i(s,"actionFlag",Symbol("__action_original_method__")),i(s,"possibleState",{pending:"pending",error:"error",lock:"lock",ready:"ready",abort:"abort"}),i(s,"abortedByLock",Symbol("lock"));let h=s;const p=Symbol("scope"),w=Symbol("model"),A=Symbol("actions"),v=Symbol("actionIds"),m=Symbol("actionStates"),E=Symbol("actionsSize"),f=Symbol("watchStopHandlers"),T=Proxy;function D(r){if(!(r instanceof g))throw new Error("ProtoModel instance is required");const t=new T(u.shallowReactive(r),{get(e,o,a){const n=Reflect.get(e,o,a),l=typeof n=="function";return l&&h.actionFlag in n&&typeof n[h.actionFlag]=="function"?e.action(n):l?n.bind(e):n}});return r[w]=t,t}var k,M,C,P,R,x,F;F=p,x=w,R=A,P=v,C=m,M=E,k=f;const S=class S{constructor(){i(this,F,u.effectScope(!0));i(this,x,null);i(this,R,new WeakMap);i(this,P,new WeakMap);i(this,C,new Map);i(this,M,0);i(this,k,new Set)}static model(...t){if(this.prototype===S.prototype)throw new Error("ProtoModel is abstract class and can not be instantiated");const e=new this(...t);return S.createModel(e)}get hasPendingActions(){return!!this.getActionStatesRef(h.possibleState.pending).value}get hasActionWithError(){return!!this.getActionStatesRef(h.possibleState.error).value}watch(...t){if(t.length===0)throw new Error("watch requires at least one argument");const e=t.length===1?this[p].run(()=>u.watchEffect(t[0])):this[p].run(()=>u.watch(...t));if(!e)throw new Error("watchStopHandler is undefined");return this[f].add(e),()=>{e(),this[f].delete(e)}}computed(t,e){return this[p].run(()=>u.computed(t,e))}updateBit(t,e,o){const a=o?1:0,n=~(1<<e);return t&n|a<<e}createAction(t){const e=()=>{if(!this[w])throw new Error("Model not set");return this[w]},o=h.create(this,t,e,this.setActionState.bind(this));return this[A].set(t,o),this[v].set(o,++this[E]),this.setActionState(o),o}getActionStatesRef(t){const e=this[m].get(t)||u.ref(0);return this[m].get(t)===void 0&&this[m].set(t,e),e}action(t){if(!(h.actionFlag in t&&typeof t[h.actionFlag]=="function"))throw new d("Action decorator is not applied to the method");return this[A].get(t)||this.createAction(t)}setActionState(t){const e=this[v].get(t);if(!e)throw new Error("Action not found");for(const o of t.possibleStates)o!==t.state&&(this.getActionStatesRef(o).value=this.updateBit(this.getActionStatesRef(o).value,e,!1));this.getActionStatesRef(t.state).value=this.updateBit(this.getActionStatesRef(t.state).value,e,!0)}isModelOf(t){return this instanceof t}destructor(){this[f].forEach(t=>{t()}),this[f]=new Set,this[p].stop()}};i(S,"createModel",D);let g=S;function O(r,t){if(t.static)throw new Error("Action decorator is not supported for static methods");if(t.private)throw new Error("Action decorator is not supported for private methods");let e;try{e=t.name.toString()}catch(a){const l=`Invalid context. Can\`t get name of the method: ${a instanceof Error?a.message:"unknown error"}`;throw new Error(l)}const o={[e]:function(...a){return this.action(o[e]).exec(...a)}};return o[e][h.actionFlag]=r,o[e]}exports.Action=h;exports.ActionError=I;exports.ActionInternalError=d;exports.ActionStatusConflictError=b;exports.ActionUnexpectedAbortError=$;exports.ProtoModel=g;exports.action=O;
1
+ "use strict";var L=Object.defineProperty;var B=(r,t,e)=>t in r?L(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var i=(r,t,e)=>B(r,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("vue");class I extends Error{constructor(t,e){super(e.cause.message),this.actionName=t,this.options=e,this.name=this.constructor.name}get cause(){return this.options.cause}throwCause(){throw this.cause}toString(){return this.options.cause.message}}class d extends Error{constructor(t,e){super(t,e),this.message=t,this.options=e,this.name=this.constructor.name}}class b extends d{constructor(t,e,o){super(`Trying to update state of ${t} from ${e} to ${o}`),this.name=this.constructor.name}}class $ extends d{constructor(t,e){super(`Unexpected AbortError for the action ${t} in state ${e}`),this.name=this.constructor.name}}const T=r=>r instanceof DOMException&&r.name==="AbortError"||typeof r=="object"&&r!==null&&"message"in r&&r.message==="canceled",s=class s{constructor(t,e,o,c,h){i(this,"name");i(this,"_state",s.possibleState.ready);i(this,"_value",null);i(this,"_args",null);this._model=t,this.actionFunction=e,this.ownerGetter=o,this.setStateCb=c,this._validateArgs=h;const n=e.name;if(!(n in this._model&&typeof this._model[n]=="function"))throw new d(`Model does not contain method ${n}`);if(typeof e[s.actionFlag]!="function")throw new d(`Method ${n} is not action`);this.name=n}static create(t,e,o,c,h){return u.shallowReactive(new s(t,e,o,c,h))}toString(){return this.name}get owner(){return this.ownerGetter()}get possibleStates(){return Object.values(s.possibleState)}get state(){return this._state}set state(t){const e=this._state;this._state=t,this.setStateCb(this,e,t)}get abortController(){return this.isPending?this._value.abortController:null}get args(){return this._args||[]}get promise(){return this.isPending?this._value.promise:null}get error(){return this.isError?this._value:null}get abortReason(){return this.isAbort?this._value.abortController.signal.reason:null}get isPending(){return this.state===s.possibleState.pending}get isError(){return this.state===s.possibleState.error}get isReady(){return this.state===s.possibleState.ready}get isLock(){return this.state===s.possibleState.lock}get isAbort(){return this.state===s.possibleState.abort}is(...t){return!!t.find(e=>this.state===e)}validate(...t){return this._validateArgs(this,...t)}exec(...t){if(this.is(s.possibleState.lock,s.possibleState.pending))throw new b(this.name,this.state,s.possibleState.pending);const e=[...t];let o=t.length&&t[t.length-1];o instanceof AbortController||(o=new AbortController,e.push(o)),this.state=s.possibleState.pending,this._args=t;const h=this.actionFunction[s.actionFlag].apply(this._model,e);if(!(h instanceof Promise))return this.state=s.possibleState.ready,Promise.resolve();const n=h.then(()=>{this.ready()}).catch(a=>{if(a instanceof d||a instanceof RangeError||a instanceof ReferenceError||a instanceof SyntaxError||a instanceof TypeError||a instanceof URIError||a instanceof EvalError)throw a;const w=T(a);if(w&&!this.is(s.possibleState.pending,s.possibleState.lock))throw new $(this.name,this.state);if(w&&this._value.abortController instanceof AbortController&&this._value.abortController.signal.reason===s.abortedByLock){this.state=s.possibleState.lock,this._value=null;return}if(w){this.state=s.possibleState.abort;return}this.setError(new I(this.name,{cause:a}))});return this._value={promise:n,abortController:o},n}abort(t){return this.isPending?(this._value.abortController.abort(t),this._value.promise):Promise.resolve()}lock(){return this.isPending?this.abort(s.abortedByLock):(this.state=s.possibleState.lock,this._value=null,Promise.resolve())}unlock(){if(!this.isLock)throw new b(this.name,this.state,s.possibleState.ready);return this.ready()}setError(t){if(!this.isPending)throw new b(this.name,this.state,s.possibleState.error);return this.state=s.possibleState.error,this._value=t,this}ready(){return this.state=s.possibleState.ready,this}resetError(){if(!this.error)throw new b(this.name,this.state,s.possibleState.ready);return this.ready()}};i(s,"actionFlag",Symbol("__action_original_method__")),i(s,"possibleState",{pending:"pending",error:"error",lock:"lock",ready:"ready",abort:"abort"}),i(s,"abortedByLock",Symbol("lock"));let l=s;const p=Symbol("scope"),S=Symbol("model"),A=Symbol("actions"),y=Symbol("actionIds"),g=Symbol("actionStates"),E=Symbol("actionsSize"),f=Symbol("watchStopHandlers"),D=Proxy;var k,M,P,C,R,x,F;F=p,x=S,R=A,C=y,P=g,M=E,k=f;const m=class m{constructor(){i(this,F,u.effectScope(!0));i(this,x,null);i(this,R,new WeakMap);i(this,C,new WeakMap);i(this,P,new Map);i(this,M,0);i(this,k,new Set)}static createModel(t){if(!(t instanceof m))throw new Error("ProtoModel instance is required");const e=new D(u.shallowReactive(t),{get(o,c,h){const n=Reflect.get(o,c,h),a=typeof n=="function";return a&&l.actionFlag in n&&typeof n[l.actionFlag]=="function"?o.action(n):a?n.bind(o):n}});return t[S]=e,e}static model(...t){if(this.prototype===m.prototype)throw new Error("ProtoModel is abstract class and can not be instantiated");const e=new this(...t);return m.createModel(e)}get hasPendingActions(){return!!this.getActionStatesRef(l.possibleState.pending).value}get hasActionWithError(){return!!this.getActionStatesRef(l.possibleState.error).value}watch(...t){if(t.length===0)throw new Error("watch requires at least one argument");const e=t.length===1?this[p].run(()=>u.watchEffect(t[0])):this[p].run(()=>u.watch(...t));if(!e)throw new Error("watchStopHandler is undefined");return this[f].add(e),()=>{e(),this[f].delete(e)}}computed(t,e){return this[p].run(()=>u.computed(t,e))}updateBit(t,e,o){const c=o?1:0,h=~(1<<e);return t&h|c<<e}createAction(t){const e=()=>{if(!this[S])throw new Error("Model not set");return this[S]},o=l.create(this,t,e,this.setActionState.bind(this),this.validateArgs.bind(this));return this[A].set(t,o),this[y].set(o,++this[E]),this.setActionState(o),o}getActionStatesRef(t){const e=this[g].get(t)||u.ref(0);return this[g].get(t)===void 0&&this[g].set(t,e),e}action(t){if(!(l.actionFlag in t&&typeof t[l.actionFlag]=="function"))throw new d("Action decorator is not applied to the method");return this[A].get(t)||this.createAction(t)}setActionState(t){const e=this[y].get(t);if(!e)throw new Error("Action not found");for(const o of t.possibleStates)o!==t.state&&(this.getActionStatesRef(o).value=this.updateBit(this.getActionStatesRef(o).value,e,!1));this.getActionStatesRef(t.state).value=this.updateBit(this.getActionStatesRef(t.state).value,e,!0)}validateArgs(t,...e){const o=`validateArgs is not implemented for action ${t.name} in the model`;return[new Error(o)]}isModelOf(t){return this instanceof t}destructor(){this[f].forEach(t=>{t()}),this[f]=new Set,this[p].stop()}};let v=m;function O(r,t){if(t.static)throw new Error("Action decorator is not supported for static methods");if(t.private)throw new Error("Action decorator is not supported for private methods");let e;try{e=t.name.toString()}catch(c){const n=`Invalid context. Can\`t get name of the method: ${c instanceof Error?c.message:"unknown error"}`;throw new Error(n)}const o={[e]:function(...c){return this.action(o[e]).exec(...c)}};return o[e][l.actionFlag]=r,o[e]}exports.Action=l;exports.ActionError=I;exports.ActionInternalError=d;exports.ActionStatusConflictError=b;exports.ActionUnexpectedAbortError=$;exports.ProtoModel=v;exports.action=O;
package/dist/index.d.ts CHANGED
@@ -16,7 +16,8 @@ export declare class Action<T extends object, Args extends any[] = unknown[]> im
16
16
  protected _model: T;
17
17
  protected actionFunction: OriginalMethodWrapper<Args>;
18
18
  protected ownerGetter: () => Model<T>;
19
- protected setStateCb?: ((action: ActionLike<T, Args>, oldState: ActionStateName, newState: ActionStateName) => void) | undefined;
19
+ protected setStateCb: (action: ActionLike<T, Args>, oldState: ActionStateName, newState: ActionStateName) => void;
20
+ protected _validateArgs: (action: ActionLike<T, Args>, ...args: Args) => Error[];
20
21
  static readonly actionFlag: unique symbol;
21
22
  static readonly possibleState: {
22
23
  readonly pending: "pending";
@@ -30,8 +31,8 @@ export declare class Action<T extends object, Args extends any[] = unknown[]> im
30
31
  protected _state: ActionStateName;
31
32
  protected _value: ActionValue;
32
33
  protected _args: Args | null;
33
- constructor(_model: T, actionFunction: OriginalMethodWrapper<Args>, ownerGetter: () => Model<T>, setStateCb?: ((action: ActionLike<T, Args>, oldState: ActionStateName, newState: ActionStateName) => void) | undefined);
34
- static create<T extends ProtoModel, Args extends unknown[] = unknown[]>(model: T, actionFunction: OriginalMethodWrapper<Args>, ownerGetter: () => Model<T>, setStateCb?: (action: ActionLike<T, Args>, oldState: ActionStateName, newState: ActionStateName) => void): ActionLike<T, Args>;
34
+ constructor(_model: T, actionFunction: OriginalMethodWrapper<Args>, ownerGetter: () => Model<T>, setStateCb: (action: ActionLike<T, Args>, oldState: ActionStateName, newState: ActionStateName) => void, _validateArgs: (action: ActionLike<T, Args>, ...args: Args) => Error[]);
35
+ static create<T extends ProtoModel, Args extends unknown[] = unknown[]>(model: T, actionFunction: OriginalMethodWrapper<Args>, ownerGetter: () => Model<T>, setStateCb: (action: ActionLike<T, Args>, oldState: ActionStateName, newState: ActionStateName) => void, validateArgs: (action: ActionLike<T, Args>, ...args: Args) => Error[]): ActionLike<T, Args>;
35
36
  toString(): string;
36
37
  get owner(): Model<T>;
37
38
  get possibleStates(): ActionStateName[];
@@ -48,6 +49,7 @@ export declare class Action<T extends object, Args extends any[] = unknown[]> im
48
49
  get isLock(): boolean;
49
50
  get isAbort(): boolean;
50
51
  is(...args: ActionStateName[]): boolean;
52
+ validate(...args: Args): Error[];
51
53
  /**
52
54
  * Put into action in PENDING state
53
55
  */
@@ -122,6 +124,7 @@ export declare interface ActionLike<T extends object, Args extends any[] = unkno
122
124
  readonly isLock: boolean;
123
125
  readonly isAbort: boolean;
124
126
  is(...args: ActionStateName[]): boolean;
127
+ validate(...args: Args): Error[];
125
128
  exec(...args: Args): Promise<void>;
126
129
  abort(reason?: unknown): Promise<void>;
127
130
  lock(): Promise<void>;
@@ -153,33 +156,6 @@ export declare class ActionUnexpectedAbortError extends ActionInternalError {
153
156
 
154
157
  declare type ActionValue = ActionPendingValue | ActionError | null;
155
158
 
156
- /**
157
- * Wraps ProtoModel instance with proxy to handle get traps for actions
158
- * and to return Action instance instead of original method.
159
- *
160
- * ```ts
161
- * class TestModel extends ProtoModel {
162
- * constructor() {
163
- * super()
164
- * }
165
- *
166
- * @action async someAction(): Promise<void> {
167
- * return Promise.resolve()
168
- * }
169
- * }
170
- *
171
- * const model = createModel(new TestModel())
172
- * const action = model.someAction // will return Action instance instead of original method
173
- * model.someAction.exec() // will execute action
174
- * model.someAction.isPending // will return true if action is pending
175
- * model.someAction.error // will return error if action is errored
176
- * ```
177
- * @param protoModel - ProtoModel instance.
178
- * @returns model instance wrapped with proxy.
179
- * @see src/proto-model.ts
180
- */
181
- declare function createModel<Target extends ProtoModel>(protoModel: Target): Model<Target>;
182
-
183
159
  export declare type Model<T extends object = object> = ShallowReactive<{
184
160
  [K in keyof T]: T[K] extends ((...args: infer Args) => Promise<void>) ? ActionLike<T, Args> : K extends ProtectedMethodInModel ? never : T[K];
185
161
  }>;
@@ -209,13 +185,36 @@ export declare abstract class ProtoModel {
209
185
  protected [actionStatesKey]: Map<"pending" | "error" | "lock" | "ready" | "abort", Ref<number>>;
210
186
  protected [actionsSizeKey]: number;
211
187
  protected [watchStopHandlersKey]: Set<WatchStopHandle>;
212
- protected static createModel: typeof createModel;
188
+ /**
189
+ * Wraps ProtoModel instance with proxy to handle get traps for actions
190
+ * and to return Action instance instead of original method.
191
+ *
192
+ * ```ts
193
+ * class TestModel extends ProtoModel {
194
+ * constructor() {
195
+ * super()
196
+ * }
197
+ *
198
+ * @action async someAction(): Promise<void> {
199
+ * return Promise.resolve()
200
+ * }
201
+ * }
202
+ *
203
+ * const model = ProtoModel.createModel(new TestModel())
204
+ * const action = model.someAction // will return Action instance instead of original method
205
+ * model.someAction.exec() // will execute action
206
+ * model.someAction.isPending // will return true if action is pending
207
+ * model.someAction.error // will return error if action is errored
208
+ * ```
209
+ * @param protoModel - ProtoModel instance.
210
+ * @returns model instance wrapped with proxy.
211
+ */
212
+ static createModel<Target extends ProtoModel>(protoModel: Target): Model<Target>;
213
213
  /**
214
214
  * Creates a model instance.
215
215
  *
216
216
  * @param args - arguments for the model constructor.
217
217
  * @returns model instance.
218
- * @see src/create-model.ts
219
218
  */
220
219
  static model<T extends ProtoModel, Args extends unknown[]>(this: new (...args: Args) => T, ...args: Args): Model<T>;
221
220
  get hasPendingActions(): boolean;
@@ -332,6 +331,7 @@ export declare abstract class ProtoModel {
332
331
  * @see type Model<T>
333
332
  */
334
333
  setActionState(action: ActionLike<this>): void;
334
+ protected validateArgs(action: ActionLike<this>, ...args: unknown[]): Error[];
335
335
  isModelOf<T extends ProtoModel>(typeModel: ModelConstructor_2<T>): boolean;
336
336
  destructor(): void;
337
337
  }
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- var I = Object.defineProperty;
2
- var $ = (r, t, e) => t in r ? I(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
- var i = (r, t, e) => $(r, typeof t != "symbol" ? t + "" : t, e);
4
- import { shallowReactive as F, effectScope as K, watchEffect as L, watch as B, computed as D, ref as T } from "vue";
5
- class H extends Error {
1
+ var $ = Object.defineProperty;
2
+ var K = (r, t, e) => t in r ? $(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var i = (r, t, e) => K(r, typeof t != "symbol" ? t + "" : t, e);
4
+ import { shallowReactive as F, effectScope as L, watchEffect as B, watch as D, computed as T, ref as H } from "vue";
5
+ class O extends Error {
6
6
  constructor(t, e) {
7
7
  super(e.cause.message), this.actionName = t, this.options = e, this.name = this.constructor.name;
8
8
  }
@@ -26,18 +26,18 @@ class b extends u {
26
26
  super(`Trying to update state of ${t} from ${e} to ${o}`), this.name = this.constructor.name;
27
27
  }
28
28
  }
29
- class O extends u {
29
+ class j extends u {
30
30
  constructor(t, e) {
31
31
  super(`Unexpected AbortError for the action ${t} in state ${e}`), this.name = this.constructor.name;
32
32
  }
33
33
  }
34
- const j = (r) => r instanceof DOMException && r.name === "AbortError" || typeof r == "object" && r !== null && "message" in r && r.message === "canceled", s = class s {
35
- constructor(t, e, o, a) {
34
+ const z = (r) => r instanceof DOMException && r.name === "AbortError" || typeof r == "object" && r !== null && "message" in r && r.message === "canceled", s = class s {
35
+ constructor(t, e, o, c, h) {
36
36
  i(this, "name");
37
37
  i(this, "_state", s.possibleState.ready);
38
38
  i(this, "_value", null);
39
39
  i(this, "_args", null);
40
- this._model = t, this.actionFunction = e, this.ownerGetter = o, this.setStateCb = a;
40
+ this._model = t, this.actionFunction = e, this.ownerGetter = o, this.setStateCb = c, this._validateArgs = h;
41
41
  const n = e.name;
42
42
  if (!(n in this._model && typeof this._model[n] == "function"))
43
43
  throw new u(`Model does not contain method ${n}`);
@@ -45,8 +45,14 @@ const j = (r) => r instanceof DOMException && r.name === "AbortError" || typeof
45
45
  throw new u(`Method ${n} is not action`);
46
46
  this.name = n;
47
47
  }
48
- static create(t, e, o, a) {
49
- return F(new s(t, e, o, a));
48
+ static create(t, e, o, c, h) {
49
+ return F(new s(
50
+ t,
51
+ e,
52
+ o,
53
+ c,
54
+ h
55
+ ));
50
56
  }
51
57
  toString() {
52
58
  return this.name;
@@ -62,7 +68,7 @@ const j = (r) => r instanceof DOMException && r.name === "AbortError" || typeof
62
68
  }
63
69
  set state(t) {
64
70
  const e = this._state;
65
- this._state = t, this.setStateCb && this.setStateCb(this, e, t);
71
+ this._state = t, this.setStateCb(this, e, t);
66
72
  }
67
73
  get abortController() {
68
74
  return this.isPending ? this._value.abortController : null;
@@ -98,6 +104,9 @@ const j = (r) => r instanceof DOMException && r.name === "AbortError" || typeof
98
104
  is(...t) {
99
105
  return !!t.find((e) => this.state === e);
100
106
  }
107
+ validate(...t) {
108
+ return this._validateArgs(this, ...t);
109
+ }
101
110
  /**
102
111
  * Put into action in PENDING state
103
112
  */
@@ -111,31 +120,31 @@ const j = (r) => r instanceof DOMException && r.name === "AbortError" || typeof
111
120
  const e = [...t];
112
121
  let o = t.length && t[t.length - 1];
113
122
  o instanceof AbortController || (o = new AbortController(), e.push(o)), this.state = s.possibleState.pending, this._args = t;
114
- const n = this.actionFunction[s.actionFlag].apply(this._model, e);
115
- if (!(n instanceof Promise))
123
+ const h = this.actionFunction[s.actionFlag].apply(this._model, e);
124
+ if (!(h instanceof Promise))
116
125
  return this.state = s.possibleState.ready, Promise.resolve();
117
- const l = n.then(() => {
126
+ const n = h.then(() => {
118
127
  this.ready();
119
- }).catch((c) => {
120
- if (c instanceof u || c instanceof RangeError || c instanceof ReferenceError || c instanceof SyntaxError || c instanceof TypeError || c instanceof URIError || c instanceof EvalError)
121
- throw c;
122
- const w = j(c);
123
- if (w && !this.is(s.possibleState.pending, s.possibleState.lock))
124
- throw new O(this.name, this.state);
125
- if (w && this._value.abortController instanceof AbortController && this._value.abortController.signal.reason === s.abortedByLock) {
128
+ }).catch((a) => {
129
+ if (a instanceof u || a instanceof RangeError || a instanceof ReferenceError || a instanceof SyntaxError || a instanceof TypeError || a instanceof URIError || a instanceof EvalError)
130
+ throw a;
131
+ const S = z(a);
132
+ if (S && !this.is(s.possibleState.pending, s.possibleState.lock))
133
+ throw new j(this.name, this.state);
134
+ if (S && this._value.abortController instanceof AbortController && this._value.abortController.signal.reason === s.abortedByLock) {
126
135
  this.state = s.possibleState.lock, this._value = null;
127
136
  return;
128
137
  }
129
- if (w) {
138
+ if (S) {
130
139
  this.state = s.possibleState.abort;
131
140
  return;
132
141
  }
133
- this.setError(new H(this.name, { cause: c }));
142
+ this.setError(new O(this.name, { cause: a }));
134
143
  });
135
144
  return this._value = {
136
- promise: l,
145
+ promise: n,
137
146
  abortController: o
138
- }, l;
147
+ }, n;
139
148
  }
140
149
  // Returns same promise as exec method
141
150
  // but in reject state
@@ -184,44 +193,67 @@ i(s, "actionFlag", Symbol("__action_original_method__")), i(s, "possibleState",
184
193
  ready: "ready",
185
194
  abort: "abort"
186
195
  }), i(s, "abortedByLock", Symbol("lock"));
187
- let h = s;
188
- const d = Symbol("scope"), S = Symbol("model"), g = Symbol("actions"), y = Symbol("actionIds"), m = Symbol("actionStates"), v = Symbol("actionsSize"), p = Symbol("watchStopHandlers"), z = Proxy;
189
- function G(r) {
190
- if (!(r instanceof A))
191
- throw new Error("ProtoModel instance is required");
192
- const t = new z(
193
- F(r),
194
- {
195
- get(e, o, a) {
196
- const n = Reflect.get(e, o, a), l = typeof n == "function";
197
- return l && h.actionFlag in n && typeof n[h.actionFlag] == "function" ? e.action(n) : l ? n.bind(e) : n;
198
- }
199
- }
200
- );
201
- return r[S] = t, t;
202
- }
203
- var k, E, M, C, P, R, x;
204
- x = d, R = S, P = g, C = y, M = m, E = v, k = p;
196
+ let l = s;
197
+ const d = Symbol("scope"), m = Symbol("model"), w = Symbol("actions"), y = Symbol("actionIds"), g = Symbol("actionStates"), A = Symbol("actionsSize"), p = Symbol("watchStopHandlers"), G = Proxy;
198
+ var E, k, M, P, C, R, x;
199
+ x = d, R = m, C = w, P = y, M = g, k = A, E = p;
205
200
  const f = class f {
206
201
  constructor() {
207
202
  // each model has its own effect scope to avoid memory leaks
208
- i(this, x, K(!0));
203
+ i(this, x, L(!0));
209
204
  i(this, R, null);
210
205
  // we use WeakMap to store actions as keys to avoid memory leaks
211
- i(this, P, /* @__PURE__ */ new WeakMap());
212
206
  i(this, C, /* @__PURE__ */ new WeakMap());
207
+ i(this, P, /* @__PURE__ */ new WeakMap());
213
208
  i(this, M, /* @__PURE__ */ new Map());
214
209
  // WeakMap doesn't have a size property, so we need to store the size of the map
215
- i(this, E, 0);
210
+ i(this, k, 0);
216
211
  // watchers are stored in a set to avoid memory leaks
217
- i(this, k, /* @__PURE__ */ new Set());
212
+ i(this, E, /* @__PURE__ */ new Set());
213
+ }
214
+ /**
215
+ * Wraps ProtoModel instance with proxy to handle get traps for actions
216
+ * and to return Action instance instead of original method.
217
+ *
218
+ * ```ts
219
+ * class TestModel extends ProtoModel {
220
+ * constructor() {
221
+ * super()
222
+ * }
223
+ *
224
+ * @action async someAction(): Promise<void> {
225
+ * return Promise.resolve()
226
+ * }
227
+ * }
228
+ *
229
+ * const model = ProtoModel.createModel(new TestModel())
230
+ * const action = model.someAction // will return Action instance instead of original method
231
+ * model.someAction.exec() // will execute action
232
+ * model.someAction.isPending // will return true if action is pending
233
+ * model.someAction.error // will return error if action is errored
234
+ * ```
235
+ * @param protoModel - ProtoModel instance.
236
+ * @returns model instance wrapped with proxy.
237
+ */
238
+ static createModel(t) {
239
+ if (!(t instanceof f))
240
+ throw new Error("ProtoModel instance is required");
241
+ const e = new G(
242
+ F(t),
243
+ {
244
+ get(o, c, h) {
245
+ const n = Reflect.get(o, c, h), a = typeof n == "function";
246
+ return a && l.actionFlag in n && typeof n[l.actionFlag] == "function" ? o.action(n) : a ? n.bind(o) : n;
247
+ }
248
+ }
249
+ );
250
+ return t[m] = e, e;
218
251
  }
219
252
  /**
220
253
  * Creates a model instance.
221
254
  *
222
255
  * @param args - arguments for the model constructor.
223
256
  * @returns model instance.
224
- * @see src/create-model.ts
225
257
  */
226
258
  static model(...t) {
227
259
  if (this.prototype === f.prototype)
@@ -230,10 +262,10 @@ const f = class f {
230
262
  return f.createModel(e);
231
263
  }
232
264
  get hasPendingActions() {
233
- return !!this.getActionStatesRef(h.possibleState.pending).value;
265
+ return !!this.getActionStatesRef(l.possibleState.pending).value;
234
266
  }
235
267
  get hasActionWithError() {
236
- return !!this.getActionStatesRef(h.possibleState.error).value;
268
+ return !!this.getActionStatesRef(l.possibleState.error).value;
237
269
  }
238
270
  /**
239
271
  * Registers watcher in the model effect scope.
@@ -294,7 +326,7 @@ const f = class f {
294
326
  watch(...t) {
295
327
  if (t.length === 0)
296
328
  throw new Error("watch requires at least one argument");
297
- const e = t.length === 1 ? this[d].run(() => L(t[0])) : this[d].run(() => B(...t));
329
+ const e = t.length === 1 ? this[d].run(() => B(t[0])) : this[d].run(() => D(...t));
298
330
  if (!e)
299
331
  throw new Error("watchStopHandler is undefined");
300
332
  return this[p].add(e), () => {
@@ -302,29 +334,30 @@ const f = class f {
302
334
  };
303
335
  }
304
336
  computed(t, e) {
305
- return this[d].run(() => D(t, e));
337
+ return this[d].run(() => T(t, e));
306
338
  }
307
339
  // @see https://github.com/trekhleb/javascript-algorithms/blob/master/src/algorithms/math/bits/updateBit.js
308
340
  updateBit(t, e, o) {
309
- const a = o ? 1 : 0, n = ~(1 << e);
310
- return t & n | a << e;
341
+ const c = o ? 1 : 0, h = ~(1 << e);
342
+ return t & h | c << e;
311
343
  }
312
344
  createAction(t) {
313
345
  const e = () => {
314
- if (!this[S])
346
+ if (!this[m])
315
347
  throw new Error("Model not set");
316
- return this[S];
317
- }, o = h.create(
348
+ return this[m];
349
+ }, o = l.create(
318
350
  this,
319
351
  t,
320
352
  e,
321
- this.setActionState.bind(this)
353
+ this.setActionState.bind(this),
354
+ this.validateArgs.bind(this)
322
355
  );
323
- return this[g].set(t, o), this[y].set(o, ++this[v]), this.setActionState(o), o;
356
+ return this[w].set(t, o), this[y].set(o, ++this[A]), this.setActionState(o), o;
324
357
  }
325
358
  getActionStatesRef(t) {
326
- const e = this[m].get(t) || T(0);
327
- return this[m].get(t) === void 0 && this[m].set(t, e), e;
359
+ const e = this[g].get(t) || H(0);
360
+ return this[g].get(t) === void 0 && this[g].set(t, e), e;
328
361
  }
329
362
  /**
330
363
  * Gets Action instance by wrapped original method or create Action instance.
@@ -370,9 +403,9 @@ const f = class f {
370
403
  * @returns action
371
404
  */
372
405
  action(t) {
373
- if (!(h.actionFlag in t && typeof t[h.actionFlag] == "function"))
406
+ if (!(l.actionFlag in t && typeof t[l.actionFlag] == "function"))
374
407
  throw new u("Action decorator is not applied to the method");
375
- return this[g].get(t) || this.createAction(t);
408
+ return this[w].get(t) || this.createAction(t);
376
409
  }
377
410
  /**
378
411
  * It is public method in context ProtoModel,
@@ -388,6 +421,11 @@ const f = class f {
388
421
  o !== t.state && (this.getActionStatesRef(o).value = this.updateBit(this.getActionStatesRef(o).value, e, !1));
389
422
  this.getActionStatesRef(t.state).value = this.updateBit(this.getActionStatesRef(t.state).value, e, !0);
390
423
  }
424
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
425
+ validateArgs(t, ...e) {
426
+ const o = `validateArgs is not implemented for action ${t.name} in the model`;
427
+ return [new Error(o)];
428
+ }
391
429
  isModelOf(t) {
392
430
  return this instanceof t;
393
431
  }
@@ -397,9 +435,8 @@ const f = class f {
397
435
  }), this[p] = /* @__PURE__ */ new Set(), this[d].stop();
398
436
  }
399
437
  };
400
- i(f, "createModel", G);
401
- let A = f;
402
- function V(r, t) {
438
+ let v = f;
439
+ function N(r, t) {
403
440
  if (t.static)
404
441
  throw new Error("Action decorator is not supported for static methods");
405
442
  if (t.private)
@@ -407,26 +444,26 @@ function V(r, t) {
407
444
  let e;
408
445
  try {
409
446
  e = t.name.toString();
410
- } catch (a) {
411
- const l = `Invalid context. Can\`t get name of the method: ${a instanceof Error ? a.message : "unknown error"}`;
412
- throw new Error(l);
447
+ } catch (c) {
448
+ const n = `Invalid context. Can\`t get name of the method: ${c instanceof Error ? c.message : "unknown error"}`;
449
+ throw new Error(n);
413
450
  }
414
451
  const o = {
415
452
  // Action constructor checks that model has method with the same name.
416
453
  // We can`t define anonymous function and change name because Function.name is readonly property.
417
454
  // So we need to create stub object to save original method name
418
- [e]: function(...a) {
419
- return this.action(o[e]).exec(...a);
455
+ [e]: function(...c) {
456
+ return this.action(o[e]).exec(...c);
420
457
  }
421
458
  };
422
- return o[e][h.actionFlag] = r, o[e];
459
+ return o[e][l.actionFlag] = r, o[e];
423
460
  }
424
461
  export {
425
- h as Action,
426
- H as ActionError,
462
+ l as Action,
463
+ O as ActionError,
427
464
  u as ActionInternalError,
428
465
  b as ActionStatusConflictError,
429
- O as ActionUnexpectedAbortError,
430
- A as ProtoModel,
431
- V as action
466
+ j as ActionUnexpectedAbortError,
467
+ v as ProtoModel,
468
+ N as action
432
469
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@vue-modeler/model",
4
- "version": "2.2.0-beta.6",
4
+ "version": "2.2.4",
5
5
  "description": "A state management library based on models for Vue.js",
6
6
  "author": "abratko",
7
7
  "license": "MIT",