@vue-modeler/model 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024-present Aleksandr Vladimirovich Bratko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # Introduction
2
+
3
+ ## What is @vue-modeler/model
4
+
5
+ A state management library based on models for [VUE.js.](VUE.js.) The extremely simple API serves one purpose — creating models. It preserves types, supports OOP, DRY, and SOLID principles.
6
+
7
+ Try it out! Level up your development process.
8
+
9
+ ---
10
+
11
+ ### Key Features
12
+ - **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.
13
+ - **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.
14
+ - **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.
15
+ - **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.
16
+ - **Embraces OOP**. Model prototypes are classes, supporting inheritance, encapsulation, polymorphism, and destructors.
17
+ - **Complies with SOLID principles**. Encourages clean coding practices like composition of models, dependency injection via constructors, separation of interfaces, single responsibility, etc.
18
+ - **Maintains type safety**. All code completion hints will work both inside and outside the context of the class.
19
+
20
+ ---
21
+
22
+ ### Simple Example
23
+ %%Check how it's done here: [https://pinia.vuejs.org/introduction.html#Basic-example%%](https://pinia.vuejs.org/introduction.html#Basic-example%%)
24
+
25
+ ---
26
+
27
+ ### Goals of Creation
28
+
29
+ #### **Eliminate boilerplate code in actions and reduce the codebase.**
30
+ ##### Problems:
31
+ - Actions often come with repetitive code for tracking execution through extra variables such as `isLoading`, `isPending`. This code adds little value to business logic but inflates the codebase.
32
+ - Cancelling or blocking an action can be challenging. Developers tend to reinvent the wheel, even though these operations are common patterns.
33
+ - Exception handling introduces further complications—developers either forget about it or handle it inconsistently without clear guidelines.
34
+
35
+ ##### Solutions:
36
+ - An action is represented as an object with reactive properties reflecting its execution status: `ready`, `pending`, `lock`, `abort`, `error`.
37
+ - Block or cancel an action easily using the `lock` and `abort` methods.
38
+ - Actions catch exceptions, create errors, save them as part of the state, and provide a unified interface for handling.
39
+
40
+ Less code, fewer issues.
41
+
42
+ #### **Use Classes and Interfaces**
43
+ Both VUEX and Pinia employ custom approaches for managing stores and reusing common code. I wanted to use standard classes, interfaces, getters, and protected properties. That's why my models are built upon classes and support OOP patterns.
44
+
45
+ #### **Reuse Business Logic Across Projects**
46
+ Models are classes where dependencies are injected via constructors, so model files don't contain direct imports of external dependencies. Models can easily be extracted into separate modules and reused in other projects or with different UI libraries.
47
+
48
+ ---
49
+
50
+ ### Additional Benefits
51
+ - **Layered project structure**:
52
+ - Application layer contains model definitions.
53
+ - Infrastructure layer includes request/response factories and API calls.
54
+ - UI layer handles templates, filters, and display logic.
55
+ - Configuration layer is the dependency container where developers establish relationships between models and other layers.
56
+ - **Clear folder structure**. Each layer has its dedicated directory. Inside the application and infrastructure directories, subfolders are organized by domain areas.
57
+
58
+ ---
59
+
60
+ This library promises to simplify state management in [Vue.js](Vue.js) applications while promoting clean, scalable, and maintainable code.
@@ -0,0 +1,3 @@
1
+ export declare const action: MethodDecorator;
2
+
3
+ export { }
@@ -0,0 +1,156 @@
1
+ import { ComputedGetter } from 'vue';
2
+ import { ComputedRef } from 'vue';
3
+ import { DebuggerOptions } from 'vue';
4
+ import { EffectScope } from 'vue';
5
+ import { Ref } from 'vue';
6
+ import { ShallowReactive } from 'vue';
7
+ import { WatchStopHandle } from 'vue';
8
+
9
+ declare class Action<Args extends any[] = unknown[]> {
10
+ protected model: ProtoModel;
11
+ protected actionFunction: OriginalMethodWrapper<Args>;
12
+ static readonly actionFlag: unique symbol;
13
+ static readonly possibleState: {
14
+ readonly pending: "pending";
15
+ readonly error: "error";
16
+ readonly lock: "lock";
17
+ readonly ready: "ready";
18
+ readonly abort: "abort";
19
+ };
20
+ static abortedByLock: symbol;
21
+ readonly name: string;
22
+ protected _state: ActionStateName;
23
+ protected _value: ActionValue;
24
+ protected _args: Args | null;
25
+ constructor(model: ProtoModel, // TODO: thing about this arg, it may be potential problem
26
+ actionFunction: OriginalMethodWrapper<Args>);
27
+ toString(): string;
28
+ get possibleStates(): ActionStateName[];
29
+ get state(): ActionStateName;
30
+ protected set state(newState: ActionStateName);
31
+ get abortController(): null | AbortController;
32
+ get args(): Args | never[];
33
+ get promise(): null | Promise<void>;
34
+ get error(): null | ActionError;
35
+ get abortReason(): unknown;
36
+ get isPending(): boolean;
37
+ get isError(): boolean;
38
+ get isReady(): boolean;
39
+ get isLock(): boolean;
40
+ get isAbort(): boolean;
41
+ is(...args: ActionStateName[]): boolean;
42
+ /**
43
+ * Put into action in PENDING state
44
+ */
45
+ exec(...args: Args): Promise<void>;
46
+ abort(reason?: unknown): Promise<void>;
47
+ lock(): Promise<void>;
48
+ unlock(): this;
49
+ protected setError(error: ActionError): this;
50
+ protected ready(): this;
51
+ resetError(): this;
52
+ }
53
+
54
+ /**
55
+ * IMPORTANT: this class looks like error,
56
+ * but it is an exception in the correct terminology "errors vs exceptions".
57
+ *
58
+ * In other words, this is a class of errors that must be handled
59
+ * and displayed to the user as messages in UI layer.
60
+ */
61
+ export declare class ActionError extends Error {
62
+ protected actionName: string;
63
+ protected options: {
64
+ cause: Error;
65
+ };
66
+ constructor(actionName: string, options: {
67
+ cause: Error;
68
+ });
69
+ get cause(): Error;
70
+ throwCause(): void;
71
+ toString(): string;
72
+ }
73
+
74
+ /**
75
+ * IMPORTANT: this class is an error in the correct terminology "errors vs exceptions".
76
+ *
77
+ * In other words, this is a class of errors that must not be handled
78
+ * and should be visible in console.
79
+ *
80
+ * These are errors made in logic made during development
81
+ */
82
+ export declare class ActionInternalError extends Error {
83
+ message: string;
84
+ protected options?: {
85
+ cause: Error;
86
+ } | undefined;
87
+ constructor(message: string, options?: {
88
+ cause: Error;
89
+ } | undefined);
90
+ }
91
+
92
+ declare interface ActionPendingValue {
93
+ promise: Promise<void>;
94
+ abortController: AbortController;
95
+ }
96
+
97
+ declare type ActionPublic = Omit<Action, 'call'>;
98
+
99
+ declare type ActionStateName = keyof typeof Action.possibleState;
100
+
101
+ export declare class ActionStatusConflictError extends ActionInternalError {
102
+ constructor(actionName: string, currentState: ActionStateName, newState: ActionStateName);
103
+ }
104
+
105
+ export declare class ActionUnexpectedAbortError extends ActionInternalError {
106
+ constructor(actionName: string, currentState: ActionStateName);
107
+ }
108
+
109
+ declare type ActionValue = ActionPendingValue | ActionError | null;
110
+
111
+ declare type ModelConstructor = new (...args: any[]) => ProtoModel;
112
+
113
+ declare type OriginalMethod<Args extends unknown[] = unknown[]> = (...args: Args) => void | Promise<void>;
114
+
115
+ declare interface OriginalMethodWrapper<Args extends unknown[] = unknown[]> {
116
+ (...args: Args): void | Promise<void>;
117
+ [Action.actionFlag]: OriginalMethod;
118
+ }
119
+
120
+ declare abstract class ProtoModel {
121
+ protected _effectScope: EffectScope;
122
+ protected _actions: WeakMap<OriginalMethodWrapper<unknown[]>, ShallowReactive<ActionPublic>>;
123
+ protected _actionIds: WeakMap<ShallowReactive<ActionPublic>, number>;
124
+ protected _actionStates: Map<"error" | "abort" | "lock" | "pending" | "ready", Ref<number>>;
125
+ protected _actionsSize: number;
126
+ protected _unwatchers: Set<WatchStopHandle>;
127
+ get hasPendingActions(): boolean;
128
+ get hasActionWithError(): boolean;
129
+ protected watch(...args: unknown[]): WatchStopHandle;
130
+ protected computed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ComputedRef<T>;
131
+ protected updateBit(number: number, bitPosition: number, bitValue: boolean): number;
132
+ protected createAction(actionFunction: OriginalMethodWrapper): ShallowReactive<ActionPublic>;
133
+ protected getActionStatesRef(stateName: ActionStateName): Ref<number>;
134
+ /**
135
+ * It is public method in context ProtoModel,
136
+ * but in Model<ProtoModel> context it is protected method
137
+ *
138
+ * @see type Model<T>
139
+ */
140
+ setActionState(action: Action): void;
141
+ isModelOf(typeModel: ModelConstructor): boolean;
142
+ /**
143
+ * This is an open method, but you won't be able to call it from outside the model
144
+ * because its argument is "OriginalMethodWrapper".
145
+ * "OriginalMethodWrapper" is not accessible from the outside,
146
+ * because the model is wrapped in a proxy and
147
+ * the "get" trap will always return an "Action" instead of "OriginalMethodWrapper".
148
+ *
149
+ * @param originalMethod - method to create action for
150
+ * @returns action
151
+ */
152
+ action(originalMethod: OriginalMethodWrapper): ShallowReactive<ActionPublic>;
153
+ destructor(): void;
154
+ }
155
+
156
+ export { }
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("./proto-model-B4Pm8x5w.cjs"),u=(e,o,t)=>{if(typeof o!="string")throw new Error("Action name is not a string");if(typeof t.value!="function")throw new Error("Property value is not a function");if(!(e instanceof r.ProtoModel))throw new Error("Target is not instance of ProtoModel");const i=t.value,n={[o]:function(...s){return this.action(n[o]).exec(...s)}};n[o][r.Action.actionFlag]=i,t.value=n[o]};exports.action=u;
@@ -0,0 +1,152 @@
1
+ import { ComputedGetter } from 'vue';
2
+ import { ComputedRef } from 'vue';
3
+ import { DebuggerOptions } from 'vue';
4
+ import { EffectScope } from 'vue';
5
+ import { Provider } from '@vue-modeler/dc';
6
+ import { ProviderOptions } from '@vue-modeler/dc';
7
+ import { Ref } from 'vue';
8
+ import { ShallowReactive } from 'vue';
9
+ import { WatchStopHandle } from 'vue';
10
+
11
+ declare class Action<Args extends any[] = unknown[]> {
12
+ protected model: ProtoModel;
13
+ protected actionFunction: OriginalMethodWrapper<Args>;
14
+ static readonly actionFlag: unique symbol;
15
+ static readonly possibleState: {
16
+ readonly pending: "pending";
17
+ readonly error: "error";
18
+ readonly lock: "lock";
19
+ readonly ready: "ready";
20
+ readonly abort: "abort";
21
+ };
22
+ static abortedByLock: symbol;
23
+ readonly name: string;
24
+ protected _state: ActionStateName;
25
+ protected _value: ActionValue;
26
+ protected _args: Args | null;
27
+ constructor(model: ProtoModel, // TODO: thing about this arg, it may be potential problem
28
+ actionFunction: OriginalMethodWrapper<Args>);
29
+ toString(): string;
30
+ get possibleStates(): ActionStateName[];
31
+ get state(): ActionStateName;
32
+ protected set state(newState: ActionStateName);
33
+ get abortController(): null | AbortController;
34
+ get args(): Args | never[];
35
+ get promise(): null | Promise<void>;
36
+ get error(): null | ActionError;
37
+ get abortReason(): unknown;
38
+ get isPending(): boolean;
39
+ get isError(): boolean;
40
+ get isReady(): boolean;
41
+ get isLock(): boolean;
42
+ get isAbort(): boolean;
43
+ is(...args: ActionStateName[]): boolean;
44
+ /**
45
+ * Put into action in PENDING state
46
+ */
47
+ exec(...args: Args): Promise<void>;
48
+ abort(reason?: unknown): Promise<void>;
49
+ lock(): Promise<void>;
50
+ unlock(): this;
51
+ protected setError(error: ActionError): this;
52
+ protected ready(): this;
53
+ resetError(): this;
54
+ }
55
+
56
+ /**
57
+ * IMPORTANT: this class looks like error,
58
+ * but it is an exception in the correct terminology "errors vs exceptions".
59
+ *
60
+ * In other words, this is a class of errors that must be handled
61
+ * and displayed to the user as messages in UI layer.
62
+ */
63
+ declare class ActionError extends Error {
64
+ protected actionName: string;
65
+ protected options: {
66
+ cause: Error;
67
+ };
68
+ constructor(actionName: string, options: {
69
+ cause: Error;
70
+ });
71
+ get cause(): Error;
72
+ throwCause(): void;
73
+ toString(): string;
74
+ }
75
+
76
+ declare interface ActionPendingValue {
77
+ promise: Promise<void>;
78
+ abortController: AbortController;
79
+ }
80
+
81
+ export declare type ActionPublic = Omit<Action, 'call'>;
82
+
83
+ export declare type ActionStateName = keyof typeof Action.possibleState;
84
+
85
+ declare type ActionValue = ActionPendingValue | ActionError | null;
86
+
87
+ export declare function createModel<Target extends ProtoModel>(protoModel: Target): Model<Target>;
88
+
89
+ export declare type Model<T> = {
90
+ [K in keyof T]: T[K] extends ((...args: infer Args) => Promise<void>) ? Action<Args> : K extends ProtectedMethodInModel ? never : T[K];
91
+ };
92
+
93
+ export declare function model<T extends ProtoModel>(modelFactory: () => T, options?: ProviderOptions): Provider<Model<T>>;
94
+
95
+ export declare interface ModelAdapterProxyConstructor {
96
+ new <Target extends ProtoModel>(target: Target, handler: ProxyHandler<Target>): Model<Target>;
97
+ }
98
+
99
+ export declare type ModelConstructor<T extends {
100
+ new (...args: any[]): any;
101
+ }> = T extends {
102
+ new (...args: any[]): infer R;
103
+ } ? R extends ProtoModel ? T : never : never;
104
+
105
+ declare type ModelConstructor_2 = new (...args: any[]) => ProtoModel;
106
+
107
+ export declare type OriginalMethod<Args extends unknown[] = unknown[]> = (...args: Args) => void | Promise<void>;
108
+
109
+ export declare interface OriginalMethodWrapper<Args extends unknown[] = unknown[]> {
110
+ (...args: Args): void | Promise<void>;
111
+ [Action.actionFlag]: OriginalMethod;
112
+ }
113
+
114
+ export declare type ProtectedMethodInModel = 'action' | 'setActionState';
115
+
116
+ export declare abstract class ProtoModel {
117
+ protected _effectScope: EffectScope;
118
+ protected _actions: WeakMap<OriginalMethodWrapper<unknown[]>, ShallowReactive<ActionPublic>>;
119
+ protected _actionIds: WeakMap<ShallowReactive<ActionPublic>, number>;
120
+ protected _actionStates: Map<"error" | "abort" | "lock" | "pending" | "ready", Ref<number>>;
121
+ protected _actionsSize: number;
122
+ protected _unwatchers: Set<WatchStopHandle>;
123
+ get hasPendingActions(): boolean;
124
+ get hasActionWithError(): boolean;
125
+ protected watch(...args: unknown[]): WatchStopHandle;
126
+ protected computed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ComputedRef<T>;
127
+ protected updateBit(number: number, bitPosition: number, bitValue: boolean): number;
128
+ protected createAction(actionFunction: OriginalMethodWrapper): ShallowReactive<ActionPublic>;
129
+ protected getActionStatesRef(stateName: ActionStateName): Ref<number>;
130
+ /**
131
+ * It is public method in context ProtoModel,
132
+ * but in Model<ProtoModel> context it is protected method
133
+ *
134
+ * @see type Model<T>
135
+ */
136
+ setActionState(action: Action): void;
137
+ isModelOf(typeModel: ModelConstructor_2): boolean;
138
+ /**
139
+ * This is an open method, but you won't be able to call it from outside the model
140
+ * because its argument is "OriginalMethodWrapper".
141
+ * "OriginalMethodWrapper" is not accessible from the outside,
142
+ * because the model is wrapped in a proxy and
143
+ * the "get" trap will always return an "Action" instead of "OriginalMethodWrapper".
144
+ *
145
+ * @param originalMethod - method to create action for
146
+ * @returns action
147
+ */
148
+ action(originalMethod: OriginalMethodWrapper): ShallowReactive<ActionPublic>;
149
+ destructor(): void;
150
+ }
151
+
152
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import { P as s, A as f } from "./proto-model-BXAhWQOY.js";
2
+ const u = (r, o, t) => {
3
+ if (typeof o != "string")
4
+ throw new Error("Action name is not a string");
5
+ if (typeof t.value != "function")
6
+ throw new Error("Property value is not a function");
7
+ if (!(r instanceof s))
8
+ throw new Error("Target is not instance of ProtoModel");
9
+ const i = t.value, n = {
10
+ // Action constructor checks that model has method with the same name.
11
+ // We can`t define anonymous function and change name because Function.name is readonly property.
12
+ // So we need to create stub object to save original method name
13
+ [o]: function(...e) {
14
+ return this.action(n[o]).exec(...e);
15
+ }
16
+ };
17
+ n[o][f.actionFlag] = i, t.value = n[o];
18
+ };
19
+ export {
20
+ u as action
21
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("vue"),a=require("./proto-model-B4Pm8x5w.cjs");var S=Object.defineProperty,f=(t,e,r)=>e in t?S(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,s=(t,e,r)=>f(t,typeof e!="symbol"?e+"":e,r);function v(){var t;const e=(t=c.getCurrentInstance())==null?void 0:t.proxy;if(!e)throw new Error("Current instance is undefined");if(!e.$vueModelerDc)throw new Error("Dependency container undefined. Check plugin setup");return e.$vueModelerDc}function u(t,e={persistentInstance:!1}){const r=Symbol("provider"),i=()=>{const n=v(),o=n.get(r)||n.add(r,t);return e.persistentInstance||(o.subscribeOnParentScopeDispose(c.onScopeDispose),c.onScopeDispose(()=>{o.parentScopeCount>0||n.delete(r)})),o.instance};return i.__isProvider__=!0,Object.defineProperty(i,"asKey",{value:r,writable:!1}),i}class p{constructor(){s(this,"isServer",!0),s(this,"stateKey","__SSR_STATE__"),s(this,"serializers",new Set),s(this,"stateFromServer",{}),this.isServer=typeof window>"u",this.initStateFromServer()}initStateFromServer(){if(this.isServer||!globalThis.__INITIAL_STATE__)return;const e=typeof globalThis.__INITIAL_STATE__=="string"?JSON.parse(globalThis.__INITIAL_STATE__):globalThis.__INITIAL_STATE__;typeof e[this.stateKey]=="object"&&(this.stateFromServer=e[this.stateKey])}extractState(e){return this.stateFromServer[e]}addSerializer(e){return this.isServer&&this.serializers.add(e),e}removeSerializer(e){return this.isServer?this.serializers.delete(e):!1}serialize(){const e={};for(const r of this.serializers){const i=r();i.value!==void 0&&(e[i.extractionKey]=i.value)}return e}injectState(e){this.isServer&&(e[this.stateKey]=this.serialize())}}u(()=>new p);const _=Proxy;function d(t){if(!(t instanceof a.ProtoModel))throw new Error("ProtoModel instance is required");return new _(c.shallowReactive(t),{get(r,i,n){const o=Reflect.get(r,i,n),l=typeof o=="function";return l&&a.Action.actionFlag in o&&typeof o[a.Action.actionFlag]=="function"?r.action(o):l?o.bind(r):o}})}function h(t,e){return u(()=>d(t()),e)}exports.ProtoModel=a.ProtoModel;exports.createModel=d;exports.model=h;
package/dist/index2.js ADDED
@@ -0,0 +1,82 @@
1
+ import { onScopeDispose as c, getCurrentInstance as d, shallowReactive as f } from "vue";
2
+ import { P as S, A as l } from "./proto-model-BXAhWQOY.js";
3
+ var _ = Object.defineProperty, h = (t, e, r) => e in t ? _(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r, s = (t, e, r) => h(t, typeof e != "symbol" ? e + "" : e, r);
4
+ function p() {
5
+ var t;
6
+ const e = (t = d()) == null ? void 0 : t.proxy;
7
+ if (!e)
8
+ throw new Error("Current instance is undefined");
9
+ if (!e.$vueModelerDc)
10
+ throw new Error("Dependency container undefined. Check plugin setup");
11
+ return e.$vueModelerDc;
12
+ }
13
+ function u(t, e = { persistentInstance: !1 }) {
14
+ const r = Symbol("provider"), n = () => {
15
+ const o = p(), i = o.get(r) || o.add(r, t);
16
+ return e.persistentInstance || (i.subscribeOnParentScopeDispose(c), c(() => {
17
+ i.parentScopeCount > 0 || o.delete(r);
18
+ })), i.instance;
19
+ };
20
+ return n.__isProvider__ = !0, Object.defineProperty(
21
+ n,
22
+ "asKey",
23
+ {
24
+ value: r,
25
+ writable: !1
26
+ }
27
+ ), n;
28
+ }
29
+ class v {
30
+ constructor() {
31
+ s(this, "isServer", !0), s(this, "stateKey", "__SSR_STATE__"), s(this, "serializers", /* @__PURE__ */ new Set()), s(this, "stateFromServer", {}), this.isServer = typeof window > "u", this.initStateFromServer();
32
+ }
33
+ initStateFromServer() {
34
+ if (this.isServer || !globalThis.__INITIAL_STATE__)
35
+ return;
36
+ const e = typeof globalThis.__INITIAL_STATE__ == "string" ? JSON.parse(globalThis.__INITIAL_STATE__) : globalThis.__INITIAL_STATE__;
37
+ typeof e[this.stateKey] == "object" && (this.stateFromServer = e[this.stateKey]);
38
+ }
39
+ extractState(e) {
40
+ return this.stateFromServer[e];
41
+ }
42
+ addSerializer(e) {
43
+ return this.isServer && this.serializers.add(e), e;
44
+ }
45
+ removeSerializer(e) {
46
+ return this.isServer ? this.serializers.delete(e) : !1;
47
+ }
48
+ serialize() {
49
+ const e = {};
50
+ for (const r of this.serializers) {
51
+ const n = r();
52
+ n.value !== void 0 && (e[n.extractionKey] = n.value);
53
+ }
54
+ return e;
55
+ }
56
+ injectState(e) {
57
+ this.isServer && (e[this.stateKey] = this.serialize());
58
+ }
59
+ }
60
+ u(() => new v());
61
+ const y = Proxy;
62
+ function I(t) {
63
+ if (!(t instanceof S))
64
+ throw new Error("ProtoModel instance is required");
65
+ return new y(
66
+ f(t),
67
+ {
68
+ get(r, n, o) {
69
+ const i = Reflect.get(r, n, o), a = typeof i == "function";
70
+ return a && l.actionFlag in i && typeof i[l.actionFlag] == "function" ? r.action(i) : a ? i.bind(r) : i;
71
+ }
72
+ }
73
+ );
74
+ }
75
+ function g(t, e) {
76
+ return u(() => I(t()), e);
77
+ }
78
+ export {
79
+ S as ProtoModel,
80
+ I as createModel,
81
+ g as model
82
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class n extends Error{constructor(r,t){super(t.cause.message),this.actionName=r,this.options=t,this.name=this.constructor.name}get cause(){return this.options.cause}throwCause(){throw this.cause}toString(){return this.options.cause.message}}class s extends Error{constructor(r,t){super(r,t),this.message=r,this.options=t,this.name=this.constructor.name}}class c extends s{constructor(r,t,e){super(`Trying to update state of ${r} from ${t} to ${e}`),this.name=this.constructor.name}}class i extends s{constructor(r,t){super(`Unexpected AbortError for the action ${r} in state ${t}`),this.name=this.constructor.name}}exports.ActionError=n;exports.ActionInternalError=s;exports.ActionStatusConflictError=c;exports.ActionUnexpectedAbortError=i;
package/dist/index3.js ADDED
@@ -0,0 +1,35 @@
1
+ class n extends Error {
2
+ constructor(r, t) {
3
+ super(t.cause.message), this.actionName = r, this.options = t, this.name = this.constructor.name;
4
+ }
5
+ get cause() {
6
+ return this.options.cause;
7
+ }
8
+ throwCause() {
9
+ throw this.cause;
10
+ }
11
+ toString() {
12
+ return this.options.cause.message;
13
+ }
14
+ }
15
+ class e extends Error {
16
+ constructor(r, t) {
17
+ super(r, t), this.message = r, this.options = t, this.name = this.constructor.name;
18
+ }
19
+ }
20
+ class c extends e {
21
+ constructor(r, t, o) {
22
+ super(`Trying to update state of ${r} from ${t} to ${o}`), this.name = this.constructor.name;
23
+ }
24
+ }
25
+ class a extends e {
26
+ constructor(r, t) {
27
+ super(`Unexpected AbortError for the action ${r} in state ${t}`), this.name = this.constructor.name;
28
+ }
29
+ }
30
+ export {
31
+ n as ActionError,
32
+ e as ActionInternalError,
33
+ c as ActionStatusConflictError,
34
+ a as ActionUnexpectedAbortError
35
+ };
@@ -0,0 +1 @@
1
+ "use strict";var p=Object.defineProperty;var S=(i,t,e)=>t in i?p(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var r=(i,t,e)=>S(i,typeof t!="symbol"?t+"":t,e);const l=require("vue"),a=require("./index3.cjs"),b=i=>i instanceof DOMException&&i.name==="AbortError"||typeof i=="object"&&i!==null&&"message"in i&&i.message==="canceled",s=class s{constructor(t,e){r(this,"name");r(this,"_state",s.possibleState.ready);r(this,"_value",null);r(this,"_args",null);this.model=t,this.actionFunction=e;const o=e.name;if(!(o in t&&typeof t[o]=="function"))throw new a.ActionInternalError(`Model does not contain method ${o}`);if(typeof e[s.actionFlag]!="function")throw new a.ActionInternalError(`Method ${o} is not action`);this.name=o}toString(){return this.name}get possibleStates(){return Object.values(s.possibleState)}get state(){return this._state}set state(t){this._state=t,this.model.setActionState(this)}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 a.ActionStatusConflictError(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(h);const f=h.then(()=>{this.ready()}).catch(n=>{if(n instanceof a.ActionInternalError||n instanceof RangeError||n instanceof ReferenceError||n instanceof SyntaxError||n instanceof TypeError||n instanceof URIError||n instanceof EvalError)throw n;const d=b(n);if(d&&!this.is(s.possibleState.pending,s.possibleState.lock))throw new a.ActionUnexpectedAbortError(this.name,this.state);if(d&&this._value.abortController instanceof AbortController&&this._value.abortController.signal.reason===s.abortedByLock){this.state=s.possibleState.lock,this._value=null;return}if(d){this.state=s.possibleState.abort;return}this.setError(new a.ActionError(this.name,{cause:n}))});return this._value={promise:f,abortController:o},f}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 a.ActionStatusConflictError(this.name,this.state,s.possibleState.ready);return this.ready()}setError(t){if(!this.isPending)throw new a.ActionStatusConflictError(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 a.ActionStatusConflictError(this.name,this.state,s.possibleState.ready);return this.ready()}};r(s,"actionFlag",Symbol("__action_original_method__")),r(s,"possibleState",{pending:"pending",error:"error",lock:"lock",ready:"ready",abort:"abort"}),r(s,"abortedByLock",Symbol("lock"));let c=s;class g{constructor(){r(this,"_effectScope",l.effectScope(!0));r(this,"_actions",new WeakMap);r(this,"_actionIds",new WeakMap);r(this,"_actionStates",new Map);r(this,"_actionsSize",0);r(this,"_unwatchers",new Set)}get hasPendingActions(){return!!this.getActionStatesRef(c.possibleState.pending).value}get hasActionWithError(){return!!this.getActionStatesRef(c.possibleState.error).value}watch(...t){const e=this._effectScope.run(()=>l.watch(...t));if(!e)throw new Error("watchStopHandler is undefined");return this._unwatchers.add(e),e}computed(t,e){return this._effectScope.run(()=>l.computed(t,e))}updateBit(t,e,o){const u=o?1:0,h=~(1<<e);return t&h|u<<e}createAction(t){const e=l.shallowReactive(new c(this,t));return this._actions.set(t,e),this._actionIds.set(e,++this._actionsSize),this.setActionState(e),e}getActionStatesRef(t){const e=this._actionStates.get(t)||l.ref(0);return this._actionStates.get(t)===void 0&&this._actionStates.set(t,e),e}setActionState(t){const e=this._actionIds.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}action(t){return this._actions.get(t)||this.createAction(t)}destructor(){this._unwatchers.forEach(t=>{t()}),this._unwatchers=new Set,this._effectScope.stop()}}exports.Action=c;exports.ProtoModel=g;
@@ -0,0 +1,233 @@
1
+ var p = Object.defineProperty;
2
+ var b = (r, t, e) => t in r ? p(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var i = (r, t, e) => b(r, typeof t != "symbol" ? t + "" : t, e);
4
+ import { effectScope as S, watch as g, computed as w, shallowReactive as m, ref as A } from "vue";
5
+ import { ActionInternalError as d, ActionStatusConflictError as c, ActionUnexpectedAbortError as v, ActionError as y } from "./index3.js";
6
+ const k = (r) => r instanceof DOMException && r.name === "AbortError" || typeof r == "object" && r !== null && "message" in r && r.message === "canceled", s = class s {
7
+ constructor(t, e) {
8
+ i(this, "name");
9
+ i(this, "_state", s.possibleState.ready);
10
+ i(this, "_value", null);
11
+ i(this, "_args", null);
12
+ this.model = t, this.actionFunction = e;
13
+ const o = e.name;
14
+ if (!(o in t && typeof t[o] == "function"))
15
+ throw new d(`Model does not contain method ${o}`);
16
+ if (typeof e[s.actionFlag] != "function")
17
+ throw new d(`Method ${o} is not action`);
18
+ this.name = o;
19
+ }
20
+ toString() {
21
+ return this.name;
22
+ }
23
+ get possibleStates() {
24
+ return Object.values(s.possibleState);
25
+ }
26
+ get state() {
27
+ return this._state;
28
+ }
29
+ set state(t) {
30
+ this._state = t, this.model.setActionState(this);
31
+ }
32
+ get abortController() {
33
+ return this.isPending ? this._value.abortController : null;
34
+ }
35
+ // TODO: add tests
36
+ get args() {
37
+ return this._args || [];
38
+ }
39
+ get promise() {
40
+ return this.isPending ? this._value.promise : null;
41
+ }
42
+ get error() {
43
+ return this.isError ? this._value : null;
44
+ }
45
+ get abortReason() {
46
+ return this.isAbort ? this._value.abortController.signal.reason : null;
47
+ }
48
+ get isPending() {
49
+ return this.state === s.possibleState.pending;
50
+ }
51
+ get isError() {
52
+ return this.state === s.possibleState.error;
53
+ }
54
+ get isReady() {
55
+ return this.state === s.possibleState.ready;
56
+ }
57
+ get isLock() {
58
+ return this.state === s.possibleState.lock;
59
+ }
60
+ get isAbort() {
61
+ return this.state === s.possibleState.abort;
62
+ }
63
+ is(...t) {
64
+ return !!t.find((e) => this.state === e);
65
+ }
66
+ /**
67
+ * Put into action in PENDING state
68
+ */
69
+ exec(...t) {
70
+ if (this.is(s.possibleState.lock, s.possibleState.pending))
71
+ throw new c(
72
+ this.name,
73
+ this.state,
74
+ s.possibleState.pending
75
+ );
76
+ const e = [...t];
77
+ let o = t.length && t[t.length - 1];
78
+ o instanceof AbortController || (o = new AbortController(), e.push(o)), this.state = s.possibleState.pending, this._args = t;
79
+ const a = this.actionFunction[s.actionFlag].apply(this.model, e);
80
+ if (!(a instanceof Promise))
81
+ return this.state = s.possibleState.ready, Promise.resolve(a);
82
+ const f = a.then(() => {
83
+ this.ready();
84
+ }).catch((n) => {
85
+ if (n instanceof d || n instanceof RangeError || n instanceof ReferenceError || n instanceof SyntaxError || n instanceof TypeError || n instanceof URIError || n instanceof EvalError)
86
+ throw n;
87
+ const u = k(n);
88
+ if (u && !this.is(s.possibleState.pending, s.possibleState.lock))
89
+ throw new v(this.name, this.state);
90
+ if (u && this._value.abortController instanceof AbortController && this._value.abortController.signal.reason === s.abortedByLock) {
91
+ this.state = s.possibleState.lock, this._value = null;
92
+ return;
93
+ }
94
+ if (u) {
95
+ this.state = s.possibleState.abort;
96
+ return;
97
+ }
98
+ this.setError(new y(this.name, { cause: n }));
99
+ });
100
+ return this._value = {
101
+ promise: f,
102
+ abortController: o
103
+ }, f;
104
+ }
105
+ // Returns same promise as exec method
106
+ // but in reject state
107
+ // So, any code awaiting promise from exec will be rejected
108
+ abort(t) {
109
+ return this.isPending ? (this._value.abortController.abort(t), this._value.promise) : Promise.resolve();
110
+ }
111
+ lock() {
112
+ return this.isPending ? this.abort(s.abortedByLock) : (this.state = s.possibleState.lock, this._value = null, Promise.resolve());
113
+ }
114
+ unlock() {
115
+ if (!this.isLock)
116
+ throw new c(
117
+ this.name,
118
+ this.state,
119
+ s.possibleState.ready
120
+ );
121
+ return this.ready();
122
+ }
123
+ setError(t) {
124
+ if (!this.isPending)
125
+ throw new c(
126
+ this.name,
127
+ this.state,
128
+ s.possibleState.error
129
+ );
130
+ return this.state = s.possibleState.error, this._value = t, this;
131
+ }
132
+ ready() {
133
+ return this.state = s.possibleState.ready, this;
134
+ }
135
+ resetError() {
136
+ if (!this.error)
137
+ throw new c(
138
+ this.name,
139
+ this.state,
140
+ s.possibleState.ready
141
+ );
142
+ return this.ready();
143
+ }
144
+ };
145
+ i(s, "actionFlag", Symbol("__action_original_method__")), i(s, "possibleState", {
146
+ pending: "pending",
147
+ error: "error",
148
+ lock: "lock",
149
+ ready: "ready",
150
+ abort: "abort"
151
+ }), i(s, "abortedByLock", Symbol("lock"));
152
+ let h = s;
153
+ class E {
154
+ constructor() {
155
+ // each model has its own effect scope to avoid memory leaks
156
+ i(this, "_effectScope", S(!0));
157
+ // we use WeakMap to store actions as keys to avoid memory leaks
158
+ i(this, "_actions", /* @__PURE__ */ new WeakMap());
159
+ i(this, "_actionIds", /* @__PURE__ */ new WeakMap());
160
+ i(this, "_actionStates", /* @__PURE__ */ new Map());
161
+ // WeakMap doesn't have a size property, so we need to store the size of the map
162
+ i(this, "_actionsSize", 0);
163
+ // watchers are stored in a set to avoid memory leaks
164
+ i(this, "_unwatchers", /* @__PURE__ */ new Set());
165
+ }
166
+ get hasPendingActions() {
167
+ return !!this.getActionStatesRef(h.possibleState.pending).value;
168
+ }
169
+ get hasActionWithError() {
170
+ return !!this.getActionStatesRef(h.possibleState.error).value;
171
+ }
172
+ watch(...t) {
173
+ const e = this._effectScope.run(() => g(...t));
174
+ if (!e)
175
+ throw new Error("watchStopHandler is undefined");
176
+ return this._unwatchers.add(e), e;
177
+ }
178
+ computed(t, e) {
179
+ return this._effectScope.run(() => w(t, e));
180
+ }
181
+ // @see https://github.com/trekhleb/javascript-algorithms/blob/master/src/algorithms/math/bits/updateBit.js
182
+ updateBit(t, e, o) {
183
+ const l = o ? 1 : 0, a = ~(1 << e);
184
+ return t & a | l << e;
185
+ }
186
+ createAction(t) {
187
+ const e = m(new h(this, t));
188
+ return this._actions.set(t, e), this._actionIds.set(e, ++this._actionsSize), this.setActionState(e), e;
189
+ }
190
+ getActionStatesRef(t) {
191
+ const e = this._actionStates.get(t) || A(0);
192
+ return this._actionStates.get(t) === void 0 && this._actionStates.set(t, e), e;
193
+ }
194
+ /**
195
+ * It is public method in context ProtoModel,
196
+ * but in Model<ProtoModel> context it is protected method
197
+ *
198
+ * @see type Model<T>
199
+ */
200
+ setActionState(t) {
201
+ const e = this._actionIds.get(t);
202
+ if (!e)
203
+ throw new Error("Action not found");
204
+ for (const o of t.possibleStates)
205
+ o !== t.state && (this.getActionStatesRef(o).value = this.updateBit(this.getActionStatesRef(o).value, e, !1));
206
+ this.getActionStatesRef(t.state).value = this.updateBit(this.getActionStatesRef(t.state).value, e, !0);
207
+ }
208
+ isModelOf(t) {
209
+ return this instanceof t;
210
+ }
211
+ /**
212
+ * This is an open method, but you won't be able to call it from outside the model
213
+ * because its argument is "OriginalMethodWrapper".
214
+ * "OriginalMethodWrapper" is not accessible from the outside,
215
+ * because the model is wrapped in a proxy and
216
+ * the "get" trap will always return an "Action" instead of "OriginalMethodWrapper".
217
+ *
218
+ * @param originalMethod - method to create action for
219
+ * @returns action
220
+ */
221
+ action(t) {
222
+ return this._actions.get(t) || this.createAction(t);
223
+ }
224
+ destructor() {
225
+ this._unwatchers.forEach((t) => {
226
+ t();
227
+ }), this._unwatchers = /* @__PURE__ */ new Set(), this._effectScope.stop();
228
+ }
229
+ }
230
+ export {
231
+ h as A,
232
+ E as P
233
+ };
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@vue-modeler/model",
4
+ "version": "1.0.0",
5
+ "description": "A state management library based on models for Vue.js",
6
+ "author": "abratko",
7
+ "license": "MIT",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "main": "./dist/index.umd.cjs",
12
+ "module": "./dist/index.js",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.umd.cjs"
17
+ },
18
+ "./decorator": {
19
+ "import": "./dist/decorator.js",
20
+ "require": "./dist/decorator.umd.cjs"
21
+ },
22
+ "./error": {
23
+ "import": "./dist/error.js",
24
+ "require": "./dist/error.umd.cjs"
25
+ }
26
+ },
27
+ "types": "./dist/index.d.ts",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/js": "^9.11.1",
33
+ "@semantic-release/changelog": "^6.0.3",
34
+ "@semantic-release/git": "^10.0.1",
35
+ "@types/eslint__js": "^8.42.3",
36
+ "@types/node": "^22.7.2",
37
+ "@vitest/coverage-v8": "2.1.3",
38
+ "@vue/test-utils": "^1.3.6",
39
+ "eslint": "^9.11.1",
40
+ "jsdom": "^25.0.1",
41
+ "semantic-release": "^24.2.0",
42
+ "typescript": "^5.6.2",
43
+ "typescript-eslint": "^8.7.0",
44
+ "vite": "^5.4.8",
45
+ "vite-plugin-dts": "^4.2.2",
46
+ "vitest": "^2.1.3"
47
+ },
48
+ "peerDependencies": {
49
+ "@vue-modeler/dc": "^2.1.0-beta",
50
+ "vue": "^2.7"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc && vite build",
54
+ "test": "vitest",
55
+ "coverage": "vitest run --coverage",
56
+ "semantic-release": "semantic-release"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git+https://github.com/vue-modeler/modeler.git"
61
+ },
62
+ "bugs": {
63
+ "url": "https://github.com/vue-modeler/modeler/issues"
64
+ },
65
+ "directories": {
66
+ "test": "tests"
67
+ },
68
+ "keywords": [
69
+ "vue",
70
+ "model",
71
+ "state",
72
+ "manager"
73
+ ],
74
+ "homepage": "https://github.com/vue-modeler/modeler#readme",
75
+ "dependencies": {
76
+ "eslint-scope": "^8.2.0",
77
+ "eslint-visitor-keys": "^4.2.0",
78
+ "prettier": "^2.8.8",
79
+ "vue": "^2.7.16"
80
+ }
81
+ }