qortex-core 0.1.3 → 0.1.5

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
@@ -4,6 +4,7 @@
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/qortex-core.svg)](https://badge.fury.io/js/qortex-core)
6
6
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/qortex-core)](https://bundlephobia.com/package/qortex-core)
7
+ [![Bundle Size](https://img.shields.io/badge/gzipped-2.1KB-brightgreen)](https://bundlephobia.com/package/qortex-core)
7
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
8
9
 
9
10
  ## ✨ What makes this special?
@@ -92,6 +93,7 @@ function useAuth() {
92
93
 
93
94
  queryManager.subscribeQuery(["auth", "user"], (state) => {
94
95
  user.value = state.data;
96
+ console.log("Auth state:", state.isSuccess, state.isLoading);
95
97
  });
96
98
 
97
99
  return { user, isAuthenticated };
@@ -159,12 +161,23 @@ const { data, isLoading, error, refetch } = useQuery(["todos"]);
159
161
  const todos = useQueryData(["todos"]);
160
162
  ```
161
163
 
162
- ### `queryManager.subscribeQuery(key, callback)`
164
+ ### `queryManager.subscribeQuery(key, callback, options?)`
165
+
166
+ Subscribe to query state changes with flexible callback signatures.
163
167
 
164
168
  ```ts
169
+ // Callback receives the current state
165
170
  const unsubscribe = queryManager.subscribeQuery(["todos"], (state) => {
166
171
  console.log("State changed:", state);
172
+ console.log("Data:", state.data);
173
+ console.log("Loading:", state.isLoading);
174
+ console.log("Success:", state.isSuccess);
167
175
  });
176
+
177
+ // With fetcher for automatic type inference
178
+ const unsubscribe = queryManager.subscribeQuery(["todos"], (state) => {
179
+ console.log("Todos:", state.data); // Automatically typed
180
+ }, { fetcher: fetchTodos });
168
181
  ```
169
182
 
170
183
  ### `queryManager.setDefaultConfig(config)`
package/index.d.ts CHANGED
@@ -3,22 +3,29 @@
3
3
  * Using readonly to prevent accidental mutations
4
4
  */
5
5
  type QueryKey = string | readonly (string | number)[];
6
- /** Valid query key values - only strings and numbers are allowed */
7
- type QueryKeyValue = string | number;
8
6
  /** Function that fetches data, can be async or sync */
9
7
  type Fetcher<T = any> = () => Promise<T> | T;
10
8
  /** Function that compares two values for equality */
11
9
  type EqualityFn<T = any> = (a: T | undefined, b: T | undefined) => boolean;
12
10
  /**
13
- * Infers the resolved return type of a fetcher function
14
- * Falls back to any for user-friendly experience
11
+ * Infers the return type of a fetcher function
12
+ *
13
+ * This utility type extracts the return type from a fetcher function,
14
+ * handling both synchronous and asynchronous fetchers.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const fetchUser = async (id: string): Promise<User> => { ... };
19
+ * type UserType = InferFetcherResult<typeof fetchUser>; // Promise<User>
20
+ *
21
+ * const fetchConfig = (): Config => { ... };
22
+ * type ConfigType = InferFetcherResult<typeof fetchConfig>; // Config
23
+ * ```
24
+ *
25
+ * @template F - The fetcher function type
26
+ * @returns The inferred return type of the fetcher, or `any` if inference fails
15
27
  */
16
- type InferFetcherResult<F> = F extends (...args: any[]) => Promise<infer R> ? R : F extends (...args: any[]) => infer R ? R : any;
17
- /**
18
- * Infers the return type of a fetcher, handling both sync and async cases
19
- * Falls back to any for user-friendly experience
20
- */
21
- type InferFetcherReturnType<T> = T extends Fetcher<infer R> ? R : any;
28
+ type InferFetcherResult<F> = F extends Fetcher<infer R> ? R : any;
22
29
  /**
23
30
  * Query status types for better type safety
24
31
  */
@@ -79,6 +86,28 @@ declare class QueryManager {
79
86
  private lastReturnedState;
80
87
  private defaultConfig;
81
88
  private throttleTime;
89
+ /**
90
+ * ⚠️ DANGER: Clear all cached data and subscriptions
91
+ *
92
+ * This method completely wipes all internal state including:
93
+ * - All cached query data
94
+ * - All active subscriptions
95
+ * - All state references
96
+ *
97
+ * @warning This should ONLY be used in testing environments or when you need to completely reset the query manager state. Using this in production will cause all active queries to lose their data and subscriptions to break.
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * // ✅ Safe usage in tests
102
+ * beforeEach(() => {
103
+ * queryManager.dangerClearCache();
104
+ * });
105
+ *
106
+ * // ❌ Dangerous usage in production
107
+ * // queryManager.dangerClearCache(); // Don't do this!
108
+ * ```
109
+ */
110
+ dangerClearCache(): void;
82
111
  /**
83
112
  * Set default configuration for all queries
84
113
  */
@@ -120,12 +149,18 @@ declare class QueryManager {
120
149
  * Handles mount logic to potentially start fetching
121
150
  */
122
151
  getQueryData<T = any>(key: QueryKey, opts?: QueryOptions<T>): T | undefined;
152
+ getQueryData<F extends Fetcher>(key: QueryKey, opts: QueryOptions<InferFetcherResult<F>> & {
153
+ fetcher: F;
154
+ }): InferFetcherResult<F> | undefined;
123
155
  /**
124
156
  * Gets comprehensive query state including computed flags
125
157
  * Handles placeholder data and error states appropriately
126
158
  * Handles mount logic to potentially start fetching
127
159
  */
128
160
  getQueryState<T = unknown>(key: QueryKey, opts?: QueryOptions<T>): QueryState<T>;
161
+ getQueryState<F extends Fetcher>(key: QueryKey, opts: QueryOptions<InferFetcherResult<F>> & {
162
+ fetcher: F;
163
+ }): QueryState<InferFetcherResult<F>>;
129
164
  /**
130
165
  * Marks a query as invalidated, triggering refetch
131
166
  */
@@ -134,7 +169,11 @@ declare class QueryManager {
134
169
  * Subscribes to query state changes with automatic subscription management
135
170
  * Handles mount logic to potentially start fetching
136
171
  */
137
- subscribeQuery<T = any>(key: QueryKey, cb: () => void, opts?: QueryOptions<T>): () => void;
172
+ subscribeQuery(key: QueryKey, cb: (state: QueryState<any>) => void): () => void;
173
+ subscribeQuery<F extends Fetcher>(key: QueryKey, cb: (state: QueryState<InferFetcherResult<F>>) => void, opts: QueryOptions<InferFetcherResult<F>> & {
174
+ fetcher: F;
175
+ }): () => void;
176
+ subscribeQuery<T = any>(key: QueryKey, cb: (state: QueryState<T>) => void, opts?: QueryOptions<T>): () => void;
138
177
  /**
139
178
  * Core mount logic that determines when to fetch
140
179
  * Implements robust throttling and race condition prevention
@@ -149,4 +188,4 @@ declare const queryManager: QueryManager;
149
188
  */
150
189
  declare function serializeKey(key: QueryKey): string;
151
190
 
152
- export { DefaultConfig, EqualityFn, Fetcher, InferFetcherResult, InferFetcherReturnType, QueryKey, QueryKeyValue, QueryManager, QueryOptions, QueryState, QueryStatus, queryManager, serializeKey };
191
+ export { DefaultConfig, EqualityFn, Fetcher, InferFetcherResult, QueryKey, QueryManager, QueryOptions, QueryState, QueryStatus, queryManager, serializeKey };
package/index.js CHANGED
@@ -71,9 +71,54 @@ function createDefaultState(opts, refetch) {
71
71
  usePlaceholderOnError: opts?.usePlaceholderOnError ?? false,
72
72
  refetchOnSubscribe: opts?.refetchOnSubscribe ?? "stale",
73
73
  enabled: opts?.enabled === false ? false : true,
74
- refetch: refetch || (() => Promise.resolve(void 0))
74
+ refetch: refetch || (() => Promise.resolve(void 0)),
75
+ isSuccess: false,
76
+ isError: false
75
77
  };
76
78
  }
79
+ function createPublicState(state) {
80
+ const now = Date.now();
81
+ const isStale = state.updatedAt == null || now - (state.updatedAt || 0) > state.staleTime || state.isInvalidated;
82
+ let returnedData = state.data;
83
+ let isPlaceholderData = false;
84
+ switch (state.status) {
85
+ case "error":
86
+ if (state.usePlaceholderOnError && state.placeholderData !== void 0) {
87
+ returnedData = state.placeholderData;
88
+ isPlaceholderData = true;
89
+ }
90
+ break;
91
+ case "fetching":
92
+ if (!state.data && state.placeholderData) {
93
+ returnedData = state.placeholderData;
94
+ isPlaceholderData = true;
95
+ }
96
+ break;
97
+ case "success":
98
+ case "idle":
99
+ returnedData = state.data ?? state.placeholderData;
100
+ isPlaceholderData = state.data ? false : Boolean(state.placeholderData);
101
+ break;
102
+ }
103
+ return {
104
+ data: returnedData,
105
+ error: state.error,
106
+ status: state.status,
107
+ updatedAt: state.updatedAt,
108
+ isStale,
109
+ isPlaceholderData,
110
+ isLoading: state.status === "fetching" && !state.updatedAt,
111
+ isFetching: state.status === "fetching",
112
+ isError: state.isError,
113
+ isSuccess: state.isSuccess,
114
+ refetch: state.refetch
115
+ };
116
+ }
117
+ function warnNoFetcherOrData(key) {
118
+ console.warn(
119
+ `[qortex] No fetcher or data for key "${serializeKey(key)}". Register a fetcher or set initial data.`
120
+ );
121
+ }
77
122
 
78
123
  // src/queryManager.ts
79
124
  var QueryManager = class {
@@ -84,6 +129,32 @@ var QueryManager = class {
84
129
  this.defaultConfig = {};
85
130
  this.throttleTime = THROTTLE_TIME;
86
131
  }
132
+ /**
133
+ * ⚠️ DANGER: Clear all cached data and subscriptions
134
+ *
135
+ * This method completely wipes all internal state including:
136
+ * - All cached query data
137
+ * - All active subscriptions
138
+ * - All state references
139
+ *
140
+ * @warning This should ONLY be used in testing environments or when you need to completely reset the query manager state. Using this in production will cause all active queries to lose their data and subscriptions to break.
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * // ✅ Safe usage in tests
145
+ * beforeEach(() => {
146
+ * queryManager.dangerClearCache();
147
+ * });
148
+ *
149
+ * // ❌ Dangerous usage in production
150
+ * // queryManager.dangerClearCache(); // Don't do this!
151
+ * ```
152
+ */
153
+ dangerClearCache() {
154
+ this.cache.clear();
155
+ this.subs.clear();
156
+ this.lastReturnedState.clear();
157
+ }
87
158
  /**
88
159
  * Set default configuration for all queries
89
160
  */
@@ -115,12 +186,14 @@ var QueryManager = class {
115
186
  * Notifies all subscribers of a query state change
116
187
  */
117
188
  emit(key, state) {
118
- this.cache.set(serializeKey(key), state);
119
- const set = this.subs.get(serializeKey(key));
189
+ const stateKey = serializeKey(key);
190
+ this.cache.set(stateKey, state);
191
+ const set = this.subs.get(stateKey);
120
192
  if (!set)
121
193
  return;
194
+ const publicState = createPublicState(state);
122
195
  for (const cb of Array.from(set))
123
- cb();
196
+ cb(publicState);
124
197
  }
125
198
  registerFetcher(key, opts) {
126
199
  this.ensureState(key, opts);
@@ -137,7 +210,9 @@ var QueryManager = class {
137
210
  return state.fetchPromise;
138
211
  const fetcher = state.fetcher;
139
212
  if (!fetcher) {
140
- console.error("No fetcher found for key", key);
213
+ if (state.updatedAt === void 0) {
214
+ warnNoFetcherOrData(key);
215
+ }
141
216
  return Promise.resolve(state.data);
142
217
  }
143
218
  ;
@@ -151,9 +226,13 @@ var QueryManager = class {
151
226
  state.data = result2;
152
227
  state.status = "success";
153
228
  state.updatedAt = Date.now();
229
+ state.isError = false;
230
+ state.isSuccess = true;
154
231
  }).catch((error) => {
155
232
  state.error = error;
156
233
  state.status = "error";
234
+ state.isError = true;
235
+ state.isSuccess = false;
157
236
  }).finally(() => {
158
237
  state.fetchPromise = void 0;
159
238
  this.emit(key, state);
@@ -174,63 +253,19 @@ var QueryManager = class {
174
253
  state.error = void 0;
175
254
  state.status = "success";
176
255
  state.isInvalidated = false;
256
+ state.isError = false;
257
+ state.isSuccess = true;
177
258
  this.emit(key, state);
178
259
  }
179
- /**
180
- * Gets query data
181
- * Handles mount logic to potentially start fetching
182
- */
183
260
  getQueryData(key, opts) {
184
261
  const state = this.ensureState(key, opts);
185
262
  this.handleMountLogic(key, state);
186
263
  return state.data ?? state.placeholderData;
187
264
  }
188
- /**
189
- * Gets comprehensive query state including computed flags
190
- * Handles placeholder data and error states appropriately
191
- * Handles mount logic to potentially start fetching
192
- */
193
265
  getQueryState(key, opts) {
194
266
  let state = this.ensureState(key, opts);
195
- const now = Date.now();
196
- const isStale = state.updatedAt == null || now - (state.updatedAt || 0) > state.staleTime || state.isInvalidated;
197
- let returnedData = state.data;
198
- let isPlaceholderData = false;
199
- const status = state.status;
200
- switch (status) {
201
- case "error":
202
- if (state.usePlaceholderOnError && state.placeholderData !== void 0) {
203
- returnedData = state.placeholderData;
204
- isPlaceholderData = true;
205
- }
206
- break;
207
- case "fetching":
208
- if (!state.data && state.placeholderData) {
209
- returnedData = state.placeholderData;
210
- isPlaceholderData = true;
211
- }
212
- break;
213
- case "success":
214
- case "idle":
215
- returnedData = state.data ?? state.placeholderData;
216
- isPlaceholderData = state.data ? false : Boolean(state.placeholderData);
217
- break;
218
- }
219
267
  this.handleMountLogic(key, state);
220
- const currentState = {
221
- data: returnedData,
222
- error: state.error,
223
- status: state.status,
224
- updatedAt: state.updatedAt,
225
- isStale,
226
- isPlaceholderData,
227
- isLoading: state.status === "fetching" && !state.updatedAt,
228
- // true only for first fetch
229
- isFetching: state.status === "fetching",
230
- isError: state.status === "error",
231
- isSuccess: state.status === "success",
232
- refetch: state.refetch
233
- };
268
+ const currentState = createPublicState(state);
234
269
  const stateKey = serializeKey(key);
235
270
  const lastState = this.lastReturnedState?.get(stateKey);
236
271
  if (!lastState || !shallowEqual(lastState, currentState)) {
@@ -250,10 +285,6 @@ var QueryManager = class {
250
285
  this.emit(key, state);
251
286
  this.fetchQuery(key);
252
287
  }
253
- /**
254
- * Subscribes to query state changes with automatic subscription management
255
- * Handles mount logic to potentially start fetching
256
- */
257
288
  subscribeQuery(key, cb, opts) {
258
289
  const sk = serializeKey(key);
259
290
  const state = this.ensureState(key, opts);
package/index.mjs CHANGED
@@ -43,9 +43,54 @@ function createDefaultState(opts, refetch) {
43
43
  usePlaceholderOnError: opts?.usePlaceholderOnError ?? false,
44
44
  refetchOnSubscribe: opts?.refetchOnSubscribe ?? "stale",
45
45
  enabled: opts?.enabled === false ? false : true,
46
- refetch: refetch || (() => Promise.resolve(void 0))
46
+ refetch: refetch || (() => Promise.resolve(void 0)),
47
+ isSuccess: false,
48
+ isError: false
47
49
  };
48
50
  }
51
+ function createPublicState(state) {
52
+ const now = Date.now();
53
+ const isStale = state.updatedAt == null || now - (state.updatedAt || 0) > state.staleTime || state.isInvalidated;
54
+ let returnedData = state.data;
55
+ let isPlaceholderData = false;
56
+ switch (state.status) {
57
+ case "error":
58
+ if (state.usePlaceholderOnError && state.placeholderData !== void 0) {
59
+ returnedData = state.placeholderData;
60
+ isPlaceholderData = true;
61
+ }
62
+ break;
63
+ case "fetching":
64
+ if (!state.data && state.placeholderData) {
65
+ returnedData = state.placeholderData;
66
+ isPlaceholderData = true;
67
+ }
68
+ break;
69
+ case "success":
70
+ case "idle":
71
+ returnedData = state.data ?? state.placeholderData;
72
+ isPlaceholderData = state.data ? false : Boolean(state.placeholderData);
73
+ break;
74
+ }
75
+ return {
76
+ data: returnedData,
77
+ error: state.error,
78
+ status: state.status,
79
+ updatedAt: state.updatedAt,
80
+ isStale,
81
+ isPlaceholderData,
82
+ isLoading: state.status === "fetching" && !state.updatedAt,
83
+ isFetching: state.status === "fetching",
84
+ isError: state.isError,
85
+ isSuccess: state.isSuccess,
86
+ refetch: state.refetch
87
+ };
88
+ }
89
+ function warnNoFetcherOrData(key) {
90
+ console.warn(
91
+ `[qortex] No fetcher or data for key "${serializeKey(key)}". Register a fetcher or set initial data.`
92
+ );
93
+ }
49
94
 
50
95
  // src/queryManager.ts
51
96
  var QueryManager = class {
@@ -56,6 +101,32 @@ var QueryManager = class {
56
101
  this.defaultConfig = {};
57
102
  this.throttleTime = THROTTLE_TIME;
58
103
  }
104
+ /**
105
+ * ⚠️ DANGER: Clear all cached data and subscriptions
106
+ *
107
+ * This method completely wipes all internal state including:
108
+ * - All cached query data
109
+ * - All active subscriptions
110
+ * - All state references
111
+ *
112
+ * @warning This should ONLY be used in testing environments or when you need to completely reset the query manager state. Using this in production will cause all active queries to lose their data and subscriptions to break.
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // ✅ Safe usage in tests
117
+ * beforeEach(() => {
118
+ * queryManager.dangerClearCache();
119
+ * });
120
+ *
121
+ * // ❌ Dangerous usage in production
122
+ * // queryManager.dangerClearCache(); // Don't do this!
123
+ * ```
124
+ */
125
+ dangerClearCache() {
126
+ this.cache.clear();
127
+ this.subs.clear();
128
+ this.lastReturnedState.clear();
129
+ }
59
130
  /**
60
131
  * Set default configuration for all queries
61
132
  */
@@ -87,12 +158,14 @@ var QueryManager = class {
87
158
  * Notifies all subscribers of a query state change
88
159
  */
89
160
  emit(key, state) {
90
- this.cache.set(serializeKey(key), state);
91
- const set = this.subs.get(serializeKey(key));
161
+ const stateKey = serializeKey(key);
162
+ this.cache.set(stateKey, state);
163
+ const set = this.subs.get(stateKey);
92
164
  if (!set)
93
165
  return;
166
+ const publicState = createPublicState(state);
94
167
  for (const cb of Array.from(set))
95
- cb();
168
+ cb(publicState);
96
169
  }
97
170
  registerFetcher(key, opts) {
98
171
  this.ensureState(key, opts);
@@ -109,7 +182,9 @@ var QueryManager = class {
109
182
  return state.fetchPromise;
110
183
  const fetcher = state.fetcher;
111
184
  if (!fetcher) {
112
- console.error("No fetcher found for key", key);
185
+ if (state.updatedAt === void 0) {
186
+ warnNoFetcherOrData(key);
187
+ }
113
188
  return Promise.resolve(state.data);
114
189
  }
115
190
  ;
@@ -123,9 +198,13 @@ var QueryManager = class {
123
198
  state.data = result2;
124
199
  state.status = "success";
125
200
  state.updatedAt = Date.now();
201
+ state.isError = false;
202
+ state.isSuccess = true;
126
203
  }).catch((error) => {
127
204
  state.error = error;
128
205
  state.status = "error";
206
+ state.isError = true;
207
+ state.isSuccess = false;
129
208
  }).finally(() => {
130
209
  state.fetchPromise = void 0;
131
210
  this.emit(key, state);
@@ -146,63 +225,19 @@ var QueryManager = class {
146
225
  state.error = void 0;
147
226
  state.status = "success";
148
227
  state.isInvalidated = false;
228
+ state.isError = false;
229
+ state.isSuccess = true;
149
230
  this.emit(key, state);
150
231
  }
151
- /**
152
- * Gets query data
153
- * Handles mount logic to potentially start fetching
154
- */
155
232
  getQueryData(key, opts) {
156
233
  const state = this.ensureState(key, opts);
157
234
  this.handleMountLogic(key, state);
158
235
  return state.data ?? state.placeholderData;
159
236
  }
160
- /**
161
- * Gets comprehensive query state including computed flags
162
- * Handles placeholder data and error states appropriately
163
- * Handles mount logic to potentially start fetching
164
- */
165
237
  getQueryState(key, opts) {
166
238
  let state = this.ensureState(key, opts);
167
- const now = Date.now();
168
- const isStale = state.updatedAt == null || now - (state.updatedAt || 0) > state.staleTime || state.isInvalidated;
169
- let returnedData = state.data;
170
- let isPlaceholderData = false;
171
- const status = state.status;
172
- switch (status) {
173
- case "error":
174
- if (state.usePlaceholderOnError && state.placeholderData !== void 0) {
175
- returnedData = state.placeholderData;
176
- isPlaceholderData = true;
177
- }
178
- break;
179
- case "fetching":
180
- if (!state.data && state.placeholderData) {
181
- returnedData = state.placeholderData;
182
- isPlaceholderData = true;
183
- }
184
- break;
185
- case "success":
186
- case "idle":
187
- returnedData = state.data ?? state.placeholderData;
188
- isPlaceholderData = state.data ? false : Boolean(state.placeholderData);
189
- break;
190
- }
191
239
  this.handleMountLogic(key, state);
192
- const currentState = {
193
- data: returnedData,
194
- error: state.error,
195
- status: state.status,
196
- updatedAt: state.updatedAt,
197
- isStale,
198
- isPlaceholderData,
199
- isLoading: state.status === "fetching" && !state.updatedAt,
200
- // true only for first fetch
201
- isFetching: state.status === "fetching",
202
- isError: state.status === "error",
203
- isSuccess: state.status === "success",
204
- refetch: state.refetch
205
- };
240
+ const currentState = createPublicState(state);
206
241
  const stateKey = serializeKey(key);
207
242
  const lastState = this.lastReturnedState?.get(stateKey);
208
243
  if (!lastState || !shallowEqual(lastState, currentState)) {
@@ -222,10 +257,6 @@ var QueryManager = class {
222
257
  this.emit(key, state);
223
258
  this.fetchQuery(key);
224
259
  }
225
- /**
226
- * Subscribes to query state changes with automatic subscription management
227
- * Handles mount logic to potentially start fetching
228
- */
229
260
  subscribeQuery(key, cb, opts) {
230
261
  const sk = serializeKey(key);
231
262
  const state = this.ensureState(key, opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qortex-core",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Framework-agnostic query cache & fetch registry (MFE friendly).",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",