septor-store 0.0.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/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # Vue 3 + TypeScript + Vite
2
+
3
+ # septor-store
4
+
5
+ Description (Full)
6
+ septor-store is a structured and scalable state management solution built on top of Pinia for Vue 3. It embraces the Plain Old Module (POM) pattern to simplify how developers—junior or senior—write, organize, and scale their application state. Whether you're just starting out or architecting large Vue applications, this tool helps you keep your stores clean, predictable, and easy to maintain.
7
+
8
+ ### Features
9
+
10
+ ### Add Quick Summary Table
11
+
12
+ | Feature | Description |
13
+ |-------------------------------------------|----------------------------------------------|
14
+ | API Deduplication | Prevents duplicate in-flight API calls |
15
+ | State Sync | Dynamically generates Pinia state per API |
16
+ | Caching | SessionStorage support |
17
+ | Pagination Helpers | Stubbed pagination extractors |
18
+ | Post + Submit Support | Easily manage POST data and track responses |
19
+ | Flexible Callbacks | Use custom callbacks on state update |
20
+ | Loading & Error States | Built-in support for loading/errors |
21
+
22
+ - Automatic API Call Queue Management Prevents multiple simultaneous calls to the same endpoint by tracking ongoing requests.
23
+ - Session Storage Caching Support Optionally caches API responses in sessionStorage for faster retrieval and offline resilience.
24
+ - Dynamic State Generation Creates and manages state slices dynamically based on API response keys, supporting scalable state organization.
25
+ - Paginated Data Handling Includes helper methods to extract page numbers from URLs and manage paginated API responses (stubbed but extensible).
26
+ - State Reload and Refresh Controls Supports forced reload of API data with configurable delay timers to prevent rapid repeat requests.
27
+ - Flexible Callback Integration Allows injection of custom callback functions to extend or modify store behavior after state updates.
28
+ - Error Handling and Logging Centralizes error capturing and logs API call issues for easier debugging and maintenance.
29
+ - Loading State Management Automatically tracks and exposes loading status for UI feedback during asynchronous operations.
30
+ - Generic Data Validation Includes utility methods to validate object/array length and type, improving robustness.
31
+ - Built-in Sleep/Delay Utility Supports asynchronous delay for throttling or debouncing API requests.
32
+ - Fast, cache-friendly state access
33
+ - Lightweight reactivity
34
+ - Data-ready views
35
+ - Seamless session recovery
36
+
37
+ ---
38
+
39
+ ### Features (comming Soon)
40
+
41
+ **Here are some possible future features to consider adding and highlighting:**
42
+
43
+ - Optimistic UI Updates to immediately reflect user changes before API confirmation.
44
+ - Automatic Retry Mechanism for failed API calls with configurable backoff.
45
+ - Global State Reset and Clear Functions for user logout or data purge scenarios.
46
+ - Integration with Vue Devtools for enhanced debugging.
47
+ - TypeScript Support fully typed throughout for safer development.
48
+ - Support for Different Storage Backends like localStorage, IndexedDB, or custom persistence.
49
+ - Reduces server load and ensures consistent state by ignoring duplicate in-flight requests.
50
+
51
+ ---
52
+
53
+ ### Note
54
+
55
+ - This package is designed with both junior and senior developers in mind, making state management and API handling effortless and reliable. By intelligently preventing unnecessary and duplicate API calls, it optimizes your app’s performance and reduces network overhead — a huge win for user experience and resource management.
56
+ It empowers your application with dynamic, flexible state generation and smart caching, so your data is always fresh yet efficiently retrieved. The built-in loading states and error handling simplify complex async workflows, letting you focus more on building features rather than wrestling with data consistency.
57
+ Whether you're building a small project or scaling a large application, septor-store offers a clean, extensible, and maintainable approach to state management that saves you time, prevents bugs, and keeps your codebase organized.
58
+
59
+ ---
60
+
61
+ ### can be ignored
62
+
63
+ ```bash
64
+ setBearerToken({token:hellothere}); set Bearer token object
65
+ getBearerToken(); get Bearer token object
66
+ *❌ These will now throw errors: *
67
+ setBearerToken(null);
68
+ setBearerToken("string");
69
+ setBearerToken(123);
70
+ setBearerToken(["array"]);
71
+
72
+ *✅ These will now :*
73
+ setBearerToken({ token: "abc123" });
74
+ // Or with additional props
75
+ setBearerToken({ token: "abc123", expiresIn: 3600 });
76
+ ```
77
+
78
+ ### Installation
79
+
80
+ ```bash
81
+ npm install septor-store
82
+
83
+ # septor-store
84
+ ```
85
+
86
+ ### Usage **EXAMPLE 1**
87
+
88
+ .env file
89
+
90
+ variable
91
+
92
+ VITE_BACKEND_URL=<http://127.0.0.1:8000/api>
93
+ always api will come like this
94
+
95
+ ## Step 1
96
+
97
+ <http://127.0.0.1:8000/api/and-your-endpoint>
98
+
99
+ ```bash
100
+ // main.ts
101
+ import { createApp } from 'vue';
102
+ import { createPinia } from 'pinia';
103
+ import App from './App.vue';
104
+ const app = createApp(App);
105
+ const pinia = createPinia();
106
+ app.use(pinia);
107
+ ```
108
+
109
+ ## Step 2
110
+
111
+ ```bash
112
+ // functions.ts
113
+ import { pomPinia,setBearerToken,getBearerToken } from "septor-store"
114
+
115
+ const usePomStore = pomPinia('myCustomStoreId')|| pomPinia();
116
+ const pomStore = usePomStore();
117
+ // Example: Call API and store data
118
+ const fetchData = async () => {
119
+ const data = await pomStore.stateGenaratorApi({
120
+ reload: true,// if false ,Once the data is colleted never call again And if true call the data in all stuation
121
+ StateStore: 'users',
122
+ reqs: { url: '/api/users', method: 'get',data:{} }, //data:{sex:male} is not required
123
+ time: 5,// if the state not empty time taken to recall again the api
124
+ });
125
+ console.log('Fetched data:', data);
126
+ };
127
+ return {fetchData};
128
+ }
129
+ ```
130
+
131
+ ## Step 3
132
+
133
+ ```bash
134
+ componets.vue
135
+ <script>
136
+ import {onMounted} from 'vue'
137
+ import { pomPinia,setBearerToken,getBearerToken } from "septor-store"
138
+
139
+ const usePomStore = pomPinia('myCustomStoreId')|| pomPinia();
140
+ import fetchData from './functions.ts'
141
+ onMounted(()=>{
142
+ fetchData()
143
+ })
144
+ </script>
145
+ <template>
146
+ <div>
147
+ <!-- all the users result bellow -->
148
+ <h1> {{ fetchData.users }}</h1>
149
+ </div>
150
+ </template>
151
+ ```
152
+
153
+ ### Usage **EXAMPLE 2**
154
+
155
+ ```bash
156
+ # mUse avoid using this while posting data
157
+ <script setup>
158
+ import { onMounted, ref } from 'vue'
159
+ import { pomPinia,setBearerToken,getBearerToken } from "septor-store"
160
+
161
+ const pomStore = pomPinia('user-post-demo')
162
+ // Create your POM store instance 😇
163
+
164
+ // Reactive reference for user list
165
+ const users = ref([])
166
+
167
+ onMounted(async () => {
168
+ await pomStore.stateGenaratorApi({
169
+ StateStore: 'users',
170
+ reqs: { url: '/api/users', method: 'get', },
171
+ # mStore: { mUse: true },
172
+ })
173
+
174
+ // Fetch the result from the state
175
+ users.value = pomStore.users?.payload || []
176
+ })
177
+ </script>
178
+
179
+ <template>
180
+ <div>
181
+ <h2 class="text-xl font-bold mb-4">👥 User List</h2>
182
+ <div v-if="pomStore.Loading" class="text-blue-500">Loading users...</div>
183
+ <ul v-else>
184
+ <li v-for="user in users" :key="user.id" class="mb-2">
185
+ {{ user.name }} - {{ user.email }}
186
+ </li>
187
+ </ul>
188
+ </div>
189
+ </template>
190
+
191
+ <style scoped>
192
+ ul {
193
+ list-style-type: none;
194
+ padding: 0;
195
+ }
196
+ </style>
197
+
198
+
199
+ ~~~
200
+ ### Usage **EXAMPLE 3** data quick access 😃
201
+
202
+ ```bash
203
+ store.stateGenaratorApi(
204
+ {
205
+ StateStore: 'users',
206
+ reqs: { url: '/api/users', method: 'get' },
207
+ // mStore: { mUse: true }, // mStore: { mUse: true } is not required
208
+ },
209
+ (existingData) => {
210
+ console.log('💡 Instant access to cached/previous data:', existingData)
211
+
212
+ if (existingData?.length) {
213
+ renderUsers(existingData)
214
+ }
215
+ }
216
+ )
217
+
218
+ ```
219
+
220
+ ### Usage **EXAMPLE 3** Posting data 🤭
221
+
222
+ ``` bash
223
+ <template>
224
+ <div>
225
+ <h2>Add User</h2>
226
+ <form @submit.prevent="handleSubmit">
227
+ <input v-model="user.name" placeholder="Name" required />
228
+ <input v-model="user.email" placeholder="Email" required />
229
+ <button :disabled="store.Loading">Submit</button>
230
+ </form>
231
+
232
+ <div v-if="store.Loading">Loading...</div>
233
+ <div v-if="result">Success: {{ result.message }}</div>
234
+ </div>
235
+ </template>
236
+
237
+ <script setup>
238
+ import { ref } from 'vue'
239
+ import { pomPinia,setBearerToken,getBearerToken } from "septor-store"
240
+
241
+ const store = pomPinia('user-post-demo')
242
+ const user = ref({ name: '', email: '' })
243
+ const result = ref(null)
244
+
245
+ const handleSubmit = async () => {
246
+ const config = {
247
+ StateStore: 'createUserResult',
248
+ reqs: {
249
+ url: '/api/users', // your POST endpoint
250
+ method: 'post',
251
+ data: user.value,
252
+ },
253
+ // mStore: { mUse: false }, can be removed
254
+ reload: true,
255
+ }
256
+
257
+ const res = await store.stateGenaratorApi(config, (oldData) => {
258
+ console.log('Previous post result if any:', oldData)
259
+ })
260
+
261
+ result.value = res
262
+ }
263
+ </script>
264
+
265
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,308 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.js
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ generateParams: () => generateParams,
33
+ getBearerToken: () => getBearerToken,
34
+ pomPinia: () => createPomStore,
35
+ setBearerToken: () => setBearerToken,
36
+ useFetch: () => useFetch
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/StoreManager/pomStateManagement.js
41
+ var import_pinia = require("pinia");
42
+
43
+ // src/utils/custom-axios.js
44
+ var import_axios = __toESM(require("axios"), 1);
45
+
46
+ // src/utils/abortHandler.js
47
+ var AbortHandler = {
48
+ abortController: new AbortController(),
49
+ abortHttpRequests() {
50
+ this.abortController.abort();
51
+ this.abortController = new AbortController();
52
+ custom_axios_default.defaults.signal = this.abortController.signal;
53
+ }
54
+ };
55
+ var abortHandler_default = AbortHandler;
56
+
57
+ // src/utils/custom-axios.js
58
+ var import_meta = {};
59
+ var baseURL = typeof import_meta !== "undefined" ? import_meta.env.VITE_BACKEND_URL ?? "" : void 0;
60
+ var customAxios = import_axios.default.create({
61
+ baseURL,
62
+ signal: abortHandler_default.abortController.signal
63
+ // Signal that is assiciated with any request to be made with this axios instance
64
+ });
65
+ var requestHandler = (request) => {
66
+ const tokenData = getBearerToken();
67
+ const token = (tokenData == null ? void 0 : tokenData.token) ?? null;
68
+ const expiresIn = (tokenData == null ? void 0 : tokenData.expiresIn) ?? null;
69
+ if (expiresIn) {
70
+ const timestamp = (tokenData == null ? void 0 : tokenData.timestamp) ?? null;
71
+ const now = Date.now();
72
+ if (expiresIn && now - timestamp > expiresIn * 1e3) {
73
+ console.warn("Token has expired.");
74
+ localStorage.removeItem("logginToken");
75
+ return null;
76
+ }
77
+ }
78
+ if (token) {
79
+ request.headers.Authorization = `Bearer ${token}`;
80
+ }
81
+ if (request.data instanceof FormData) {
82
+ const formData = request.data;
83
+ request.data = formData;
84
+ } else if (request.data) {
85
+ request.data = { ...request.data ?? {} };
86
+ }
87
+ return request;
88
+ };
89
+ customAxios.interceptors.response.use(
90
+ (response) => response,
91
+ // Resolve response as is
92
+ (error) => {
93
+ var _a;
94
+ console.log("Error.....", error);
95
+ if (((_a = error.response) == null ? void 0 : _a.status) === 401) {
96
+ }
97
+ return Promise.reject(error);
98
+ }
99
+ );
100
+ customAxios.interceptors.request.use(
101
+ requestHandler,
102
+ (error) => Promise.reject(error)
103
+ );
104
+ var custom_axios_default = customAxios;
105
+
106
+ // src/utils/useFetch.js
107
+ var useFetch = async ({ url, method, data }) => {
108
+ let _data = data ?? {};
109
+ try {
110
+ let response = null;
111
+ const uniformMethod = method.toLowerCase();
112
+ if (uniformMethod === "get") {
113
+ const params = generateParams(_data ?? {});
114
+ response = await custom_axios_default.get(`${url}${params}`);
115
+ } else {
116
+ response = await custom_axios_default.post(url, _data);
117
+ }
118
+ return (response == null ? void 0 : response.data) ?? { Empty: "Empty" };
119
+ } catch (err) {
120
+ console.error(err);
121
+ return { success: false, error: err };
122
+ }
123
+ };
124
+ var generateParams = (params = {}) => {
125
+ let data = "?";
126
+ for (let key in params) {
127
+ if (Object.hasOwnProperty.call(params, key)) {
128
+ if (params[key]) {
129
+ if (Array.isArray(params[key])) {
130
+ const elements = params[key];
131
+ for (const ele of elements) {
132
+ data += `${key}[]=${ele}&`;
133
+ }
134
+ } else {
135
+ data += `${key}=${params[key]}&`;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ return data.slice(0, -1);
141
+ };
142
+ function setBearerToken(name = { token: null }) {
143
+ if (typeof name !== "object" || name === null || Array.isArray(name))
144
+ throw new TypeError("setBearerToken expects an object {token} with a 'token' property.");
145
+ const tokenObj = { token: null, ...name, timestamp: Date.now() };
146
+ localStorage.setItem("logginToken", JSON.stringify(tokenObj));
147
+ }
148
+ function getBearerToken() {
149
+ const token = localStorage.getItem("logginToken");
150
+ try {
151
+ return token ? JSON.parse(token) : null;
152
+ } catch (e) {
153
+ console.error("Invalid token format in localStorage:", e);
154
+ return null;
155
+ }
156
+ }
157
+
158
+ // src/StoreManager/pomStateManagement.js
159
+ function createPomStore(piniaStore = "7286204094", callBack = null) {
160
+ const storeId = "POM" + piniaStore;
161
+ const piniaData = (0, import_pinia.defineStore)(storeId, {
162
+ state: () => {
163
+ const StateObjectsContainer = {
164
+ CheckQueriesInQue: {},
165
+ StateValue: {},
166
+ Loading: true,
167
+ isThereAnyDataChangeInAform: false
168
+ };
169
+ return StateObjectsContainer;
170
+ },
171
+ getters: {
172
+ stateItems: (state) => (TagetState) => {
173
+ return state[TagetState] = state[TagetState];
174
+ }
175
+ // store.getStateItem(key1) usage to ge the ge
176
+ // getStateItem: (state) => (stateKey=nu) => state[stateKey],
177
+ },
178
+ actions: {
179
+ validateObjectLength(object) {
180
+ if (Array.isArray(object)) {
181
+ return object.length;
182
+ } else if (typeof object == "object") {
183
+ const lng = Object.keys(object);
184
+ return lng.length;
185
+ }
186
+ return 0;
187
+ },
188
+ pageNumber(pageUrl) {
189
+ const inputString = pageUrl;
190
+ const regex = /\?(page)+=\d+/;
191
+ const match = regex.exec(inputString);
192
+ if (match) {
193
+ const matchedPattern = match[0];
194
+ const dataBack = matchedPattern.split("?page=").join().replace(/[^\d]/, "");
195
+ return dataBack;
196
+ } else return "1";
197
+ },
198
+ sleep(timer) {
199
+ return new Promise((r) => setTimeout(r, timer));
200
+ },
201
+ async CallApiData(StateStore, callApi, dataParams, pagnated, mStore = {}) {
202
+ if (!this.CheckQueriesInQue[callApi]) {
203
+ this.CheckQueriesInQue[callApi] = callApi;
204
+ let [res] = await Promise.all([
205
+ useFetch(dataParams)
206
+ ]);
207
+ if (res) {
208
+ this[StateStore] = res;
209
+ if (mStore == null ? void 0 : mStore.mUse) {
210
+ const checkType = typeof res === "object" ? JSON.stringify(res) : res;
211
+ sessionStorage.setItem(StateStore, JSON.stringify(checkType));
212
+ }
213
+ delete this.CheckQueriesInQue[callApi];
214
+ } else {
215
+ delete this.CheckQueriesInQue[callApi];
216
+ console.error(res);
217
+ }
218
+ this.Loading = false;
219
+ return res;
220
+ }
221
+ this.Loading = false;
222
+ return this[StateStore] ?? [];
223
+ },
224
+ async parseData(dataResponseName = "xxx", mStore = { mUse: false, reset: false }) {
225
+ try {
226
+ if (mStore == null ? void 0 : mStore.reset) {
227
+ sessionStorage.removeItem(dataResponseName);
228
+ }
229
+ if (mStore == null ? void 0 : mStore.mUse) {
230
+ const thisData = JSON.parse(
231
+ JSON.parse(sessionStorage.getItem(dataResponseName) || {})
232
+ );
233
+ return thisData;
234
+ }
235
+ } catch (error) {
236
+ }
237
+ return this[dataResponseName];
238
+ },
239
+ async stateGenaratorApi(dd = { reload: true, StateStore: "", reqs: {}, time: 5, pagnated: false, mStore: { mUse: 0 } }, fasterDataCollection = null) {
240
+ this.Loading = true;
241
+ const { reload, StateStore, reqs, time, pagnated, mStore } = dd;
242
+ const callApi = JSON.stringify(reqs);
243
+ const StateVariable = await this.parseData(StateStore, mStore);
244
+ this[StateStore] = StateVariable ?? [];
245
+ if (this.StateValue[StateStore]) {
246
+ this.StateValue[StateStore] = false;
247
+ this[StateStore] = false;
248
+ }
249
+ if (this.CheckQueriesInQue[callApi]) {
250
+ console.warn(`************************************* dont call this api again ${reqs["url"]} \u{1F6E9}\uFE0F *************************************`, reqs);
251
+ return;
252
+ }
253
+ try {
254
+ const counters = this.validateObjectLength(StateVariable);
255
+ if (typeof fasterDataCollection == "function")
256
+ fasterDataCollection(StateVariable);
257
+ if (counters > 0) {
258
+ if (reload) {
259
+ const delay = await this.sleep(1e3 * time);
260
+ return await this.CallApiData(
261
+ StateStore,
262
+ callApi,
263
+ reqs,
264
+ pagnated,
265
+ mStore
266
+ );
267
+ }
268
+ this.Loading = false;
269
+ return this[StateStore];
270
+ } else {
271
+ this.Loading = true;
272
+ }
273
+ return await this.CallApiData(
274
+ StateStore,
275
+ callApi,
276
+ reqs,
277
+ pagnated,
278
+ mStore
279
+ );
280
+ } catch (error) {
281
+ if (this.CheckQueriesInQue[callApi])
282
+ delete this.CheckQueriesInQue[callApi];
283
+ }
284
+ },
285
+ Creator() {
286
+ if (typeof callBack === "function") {
287
+ return callBack(this);
288
+ }
289
+ },
290
+ DriveManual() {
291
+ return function() {
292
+ return this;
293
+ };
294
+ },
295
+ pagenatedData() {
296
+ }
297
+ }
298
+ });
299
+ return piniaData();
300
+ }
301
+ // Annotate the CommonJS export names for ESM import in node:
302
+ 0 && (module.exports = {
303
+ generateParams,
304
+ getBearerToken,
305
+ pomPinia,
306
+ setBearerToken,
307
+ useFetch
308
+ });