qortex-core 0.1.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.
Files changed (4) hide show
  1. package/index.d.ts +152 -0
  2. package/index.js +296 -0
  3. package/index.mjs +267 -0
  4. package/package.json +45 -0
package/index.d.ts ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Query key can be a string or readonly array of strings/numbers
3
+ * Using readonly to prevent accidental mutations
4
+ */
5
+ type QueryKey = string | readonly (string | number)[];
6
+ /** Valid query key values - only strings and numbers are allowed */
7
+ type QueryKeyValue = string | number;
8
+ /** Function that fetches data, can be async or sync */
9
+ type Fetcher<T = any> = () => Promise<T> | T;
10
+ /** Function that compares two values for equality */
11
+ type EqualityFn<T = any> = (a: T | undefined, b: T | undefined) => boolean;
12
+ /**
13
+ * Infers the resolved return type of a fetcher function
14
+ * Falls back to any for user-friendly experience
15
+ */
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;
22
+ /**
23
+ * Query status types for better type safety
24
+ */
25
+ type QueryStatus = "idle" | "fetching" | "success" | "error";
26
+ /**
27
+ * Comprehensive options for all query operations
28
+ * Improved with better type constraints
29
+ */
30
+ type QueryOptions<T = any> = {
31
+ enabled?: boolean;
32
+ refetchOnSubscribe?: "always" | "stale" | false;
33
+ fetcher?: Fetcher<T>;
34
+ equalityFn?: EqualityFn<T>;
35
+ staleTime?: number;
36
+ signal?: AbortSignal;
37
+ placeholderData?: T;
38
+ usePreviousDataOnError?: boolean;
39
+ usePlaceholderOnError?: boolean;
40
+ };
41
+ /**
42
+ * Default configuration options that can be set globally
43
+ * Includes throttleTime which is not part of regular QueryOptions
44
+ */
45
+ type DefaultConfig = {
46
+ enabled?: boolean;
47
+ refetchOnSubscribe?: "always" | "stale" | false;
48
+ staleTime?: number;
49
+ usePreviousDataOnError?: boolean;
50
+ usePlaceholderOnError?: boolean;
51
+ equalityFn?: EqualityFn<any>;
52
+ throttleTime?: number;
53
+ };
54
+ /**
55
+ * Public query state returned by getQueryState
56
+ * Improved with stricter error typing and better generic constraints
57
+ */
58
+ type QueryState<T = any, E = Error> = {
59
+ data?: T;
60
+ error?: E;
61
+ status: QueryStatus;
62
+ updatedAt?: number;
63
+ isStale: boolean;
64
+ isPlaceholderData: boolean;
65
+ isLoading: boolean;
66
+ isFetching: boolean;
67
+ isError: boolean;
68
+ isSuccess: boolean;
69
+ refetch: () => Promise<T>;
70
+ };
71
+
72
+ /**
73
+ * Core query manager that handles caching, fetching, and state management
74
+ * Implements robust throttling and race condition prevention
75
+ */
76
+ declare class QueryManager {
77
+ private cache;
78
+ private subs;
79
+ private lastReturnedState;
80
+ private defaultConfig;
81
+ private throttleTime;
82
+ /**
83
+ * Set default configuration for all queries
84
+ */
85
+ setDefaultConfig({ throttleTime, ...config }: DefaultConfig): void;
86
+ /**
87
+ * Ensures a query state exists in cache, creating it if necessary
88
+ * User-friendly with any fallback for better developer experience
89
+ */
90
+ private ensureState;
91
+ /**
92
+ * Notifies all subscribers of a query state change
93
+ */
94
+ private emit;
95
+ /**
96
+ * Registers a fetcher function for a query key
97
+ * Automatically fetches if enabled is not false
98
+ * Enhanced with automatic type inference from fetcher
99
+ */
100
+ registerFetcher<T = any>(key: QueryKey, opts: QueryOptions<T>): void;
101
+ registerFetcher<F extends Fetcher>(key: QueryKey, opts: QueryOptions<InferFetcherResult<F>> & {
102
+ fetcher: F;
103
+ }): void;
104
+ /**
105
+ * Executes a fetch operation with proper error handling and state management
106
+ * Prevents duplicate fetches
107
+ * Enhanced with automatic type inference from fetcher
108
+ */
109
+ fetchQuery<T = any>(key: QueryKey, opts?: QueryOptions<T>): Promise<T>;
110
+ fetchQuery<F extends Fetcher>(key: QueryKey, opts: QueryOptions<InferFetcherResult<F>> & {
111
+ fetcher: F;
112
+ }): Promise<InferFetcherResult<F>>;
113
+ /**
114
+ * Manually sets query data without triggering a fetch
115
+ * Marks query as successful
116
+ */
117
+ setQueryData<T = any>(key: QueryKey, data: T): void;
118
+ /**
119
+ * Gets query data
120
+ * Handles mount logic to potentially start fetching
121
+ */
122
+ getQueryData<T = any>(key: QueryKey, opts?: QueryOptions<T>): T | undefined;
123
+ /**
124
+ * Gets comprehensive query state including computed flags
125
+ * Handles placeholder data and error states appropriately
126
+ * Handles mount logic to potentially start fetching
127
+ */
128
+ getQueryState<T = unknown>(key: QueryKey, opts?: QueryOptions<T>): QueryState<T>;
129
+ /**
130
+ * Marks a query as invalidated, triggering refetch
131
+ */
132
+ invalidateQuery(key: QueryKey): void;
133
+ /**
134
+ * Subscribes to query state changes with automatic subscription management
135
+ * Handles mount logic to potentially start fetching
136
+ */
137
+ subscribeQuery<T = any>(key: QueryKey, cb: () => void, opts?: QueryOptions<T>): () => void;
138
+ /**
139
+ * Core mount logic that determines when to fetch
140
+ * Implements robust throttling and race condition prevention
141
+ */
142
+ private handleMountLogic;
143
+ }
144
+ declare const queryManager: QueryManager;
145
+
146
+ /**
147
+ * Normalizes query keys to a consistent string format for internal storage
148
+ * Arrays are joined with commas, primitives are converted to strings
149
+ */
150
+ declare function serializeKey(key: QueryKey): string;
151
+
152
+ export { DefaultConfig, EqualityFn, Fetcher, InferFetcherResult, InferFetcherReturnType, QueryKey, QueryKeyValue, QueryManager, QueryOptions, QueryState, QueryStatus, queryManager, serializeKey };
package/index.js ADDED
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ QueryManager: () => QueryManager,
24
+ queryManager: () => queryManager,
25
+ serializeKey: () => serializeKey
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/constants.ts
30
+ var DEFAULT_STALE_TIME = 0;
31
+ var THROTTLE_TIME = 50;
32
+
33
+ // src/utils.ts
34
+ function serializeKey(key) {
35
+ return Array.isArray(key) ? key.join(",") : String(key);
36
+ }
37
+ function shallowEqual(a, b) {
38
+ if (a === b)
39
+ return true;
40
+ if (a == null || b == null)
41
+ return a === b;
42
+ if (typeof a !== "object" || typeof b !== "object")
43
+ return false;
44
+ try {
45
+ const aAny = a;
46
+ const bAny = b;
47
+ const aKeys = Object.keys(aAny);
48
+ const bKeys = Object.keys(bAny);
49
+ if (aKeys.length !== bKeys.length)
50
+ return false;
51
+ for (let i = 0; i < aKeys.length; i++) {
52
+ const k = aKeys[i];
53
+ if (aAny[k] !== bAny[k])
54
+ return false;
55
+ }
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ function createDefaultState(opts, refetch) {
62
+ return {
63
+ status: "idle",
64
+ updatedAt: void 0,
65
+ staleTime: opts?.staleTime ?? DEFAULT_STALE_TIME,
66
+ isInvalidated: false,
67
+ fetcher: opts?.fetcher,
68
+ equalityFn: opts?.equalityFn ?? shallowEqual,
69
+ placeholderData: opts?.placeholderData,
70
+ usePreviousDataOnError: opts?.usePreviousDataOnError ?? false,
71
+ usePlaceholderOnError: opts?.usePlaceholderOnError ?? false,
72
+ refetchOnSubscribe: opts?.refetchOnSubscribe ?? "stale",
73
+ enabled: opts?.enabled === false ? false : true,
74
+ refetch: refetch || (() => Promise.resolve(void 0))
75
+ };
76
+ }
77
+
78
+ // src/queryManager.ts
79
+ var QueryManager = class {
80
+ constructor() {
81
+ this.cache = /* @__PURE__ */ new Map();
82
+ this.subs = /* @__PURE__ */ new Map();
83
+ this.lastReturnedState = /* @__PURE__ */ new Map();
84
+ this.defaultConfig = {};
85
+ this.throttleTime = THROTTLE_TIME;
86
+ }
87
+ /**
88
+ * Set default configuration for all queries
89
+ */
90
+ setDefaultConfig({ throttleTime, ...config }) {
91
+ this.defaultConfig = { ...this.defaultConfig, ...config };
92
+ if (throttleTime !== void 0) {
93
+ this.throttleTime = throttleTime;
94
+ }
95
+ }
96
+ /**
97
+ * Ensures a query state exists in cache, creating it if necessary
98
+ * User-friendly with any fallback for better developer experience
99
+ */
100
+ ensureState(key, opts = {}) {
101
+ const sk = serializeKey(key);
102
+ const state = this.cache.get(sk);
103
+ const mergedOpts = { ...this.defaultConfig, ...opts };
104
+ if (state) {
105
+ Object.assign(state, mergedOpts);
106
+ state.enabled = mergedOpts.enabled === false ? false : true;
107
+ this.cache.set(sk, state);
108
+ } else {
109
+ const newState = createDefaultState(mergedOpts, () => this.fetchQuery(key));
110
+ this.cache.set(sk, newState);
111
+ }
112
+ return this.cache.get(sk);
113
+ }
114
+ /**
115
+ * Notifies all subscribers of a query state change
116
+ */
117
+ emit(key, state) {
118
+ this.cache.set(serializeKey(key), state);
119
+ const set = this.subs.get(serializeKey(key));
120
+ if (!set)
121
+ return;
122
+ for (const cb of Array.from(set))
123
+ cb();
124
+ }
125
+ registerFetcher(key, opts) {
126
+ this.ensureState(key, opts);
127
+ if (opts.enabled !== false) {
128
+ try {
129
+ void this.fetchQuery(key);
130
+ } catch {
131
+ }
132
+ }
133
+ }
134
+ fetchQuery(key, opts) {
135
+ const state = this.ensureState(key, opts);
136
+ if (state.fetchPromise)
137
+ return state.fetchPromise;
138
+ const fetcher = state.fetcher;
139
+ if (!fetcher) {
140
+ console.error("No fetcher found for key", key);
141
+ return Promise.resolve(state.data);
142
+ }
143
+ ;
144
+ state.status = "fetching";
145
+ state.lastFetchTime = Date.now();
146
+ this.emit(key, state);
147
+ const result = fetcher();
148
+ const promise = Promise.resolve(result);
149
+ state.fetchPromise = promise;
150
+ promise.then((result2) => {
151
+ state.data = result2;
152
+ state.status = "success";
153
+ state.updatedAt = Date.now();
154
+ }).catch((error) => {
155
+ state.error = error;
156
+ state.status = "error";
157
+ }).finally(() => {
158
+ state.fetchPromise = void 0;
159
+ this.emit(key, state);
160
+ });
161
+ return promise;
162
+ }
163
+ /**
164
+ * Manually sets query data without triggering a fetch
165
+ * Marks query as successful
166
+ */
167
+ setQueryData(key, data) {
168
+ const state = this.ensureState(key);
169
+ const old = state.data;
170
+ if (state.equalityFn(old, data))
171
+ return;
172
+ state.data = data;
173
+ state.updatedAt = Date.now();
174
+ state.error = void 0;
175
+ state.status = "success";
176
+ state.isInvalidated = false;
177
+ this.emit(key, state);
178
+ }
179
+ /**
180
+ * Gets query data
181
+ * Handles mount logic to potentially start fetching
182
+ */
183
+ getQueryData(key, opts) {
184
+ const state = this.ensureState(key, opts);
185
+ this.handleMountLogic(key, state);
186
+ return state.data ?? state.placeholderData;
187
+ }
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
+ getQueryState(key, opts) {
194
+ 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
+ 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
+ };
234
+ const stateKey = serializeKey(key);
235
+ const lastState = this.lastReturnedState?.get(stateKey);
236
+ if (!lastState || !shallowEqual(lastState, currentState)) {
237
+ if (!this.lastReturnedState)
238
+ this.lastReturnedState = /* @__PURE__ */ new Map();
239
+ this.lastReturnedState.set(stateKey, currentState);
240
+ return currentState;
241
+ }
242
+ return lastState;
243
+ }
244
+ /**
245
+ * Marks a query as invalidated, triggering refetch
246
+ */
247
+ invalidateQuery(key) {
248
+ const state = this.ensureState(key);
249
+ state.isInvalidated = true;
250
+ this.emit(key, state);
251
+ this.fetchQuery(key);
252
+ }
253
+ /**
254
+ * Subscribes to query state changes with automatic subscription management
255
+ * Handles mount logic to potentially start fetching
256
+ */
257
+ subscribeQuery(key, cb, opts) {
258
+ const sk = serializeKey(key);
259
+ const state = this.ensureState(key, opts);
260
+ if (!this.subs.has(sk))
261
+ this.subs.set(sk, /* @__PURE__ */ new Set());
262
+ this.subs.get(sk).add(cb);
263
+ this.handleMountLogic(key, state);
264
+ return () => {
265
+ this.subs.get(sk).delete(cb);
266
+ };
267
+ }
268
+ /**
269
+ * Core mount logic that determines when to fetch
270
+ * Implements robust throttling and race condition prevention
271
+ */
272
+ handleMountLogic(key, state) {
273
+ const isThrottled = state.lastFetchTime && Date.now() - state.lastFetchTime < this.throttleTime;
274
+ if (state?.status === "fetching" || !state?.enabled || isThrottled || !state?.fetcher)
275
+ return;
276
+ const now = Date.now();
277
+ const isStale = state?.updatedAt == null || now - (state.updatedAt || 0) > state.staleTime || state.isInvalidated;
278
+ let shouldRefetch = false;
279
+ if (state.refetchOnSubscribe === "always") {
280
+ shouldRefetch = true;
281
+ }
282
+ if (state.refetchOnSubscribe === "stale") {
283
+ shouldRefetch = isStale;
284
+ }
285
+ if (shouldRefetch) {
286
+ this.fetchQuery(key);
287
+ }
288
+ }
289
+ };
290
+ var queryManager = new QueryManager();
291
+ // Annotate the CommonJS export names for ESM import in node:
292
+ 0 && (module.exports = {
293
+ QueryManager,
294
+ queryManager,
295
+ serializeKey
296
+ });
package/index.mjs ADDED
@@ -0,0 +1,267 @@
1
+ // src/constants.ts
2
+ var DEFAULT_STALE_TIME = 0;
3
+ var THROTTLE_TIME = 50;
4
+
5
+ // src/utils.ts
6
+ function serializeKey(key) {
7
+ return Array.isArray(key) ? key.join(",") : String(key);
8
+ }
9
+ function shallowEqual(a, b) {
10
+ if (a === b)
11
+ return true;
12
+ if (a == null || b == null)
13
+ return a === b;
14
+ if (typeof a !== "object" || typeof b !== "object")
15
+ return false;
16
+ try {
17
+ const aAny = a;
18
+ const bAny = b;
19
+ const aKeys = Object.keys(aAny);
20
+ const bKeys = Object.keys(bAny);
21
+ if (aKeys.length !== bKeys.length)
22
+ return false;
23
+ for (let i = 0; i < aKeys.length; i++) {
24
+ const k = aKeys[i];
25
+ if (aAny[k] !== bAny[k])
26
+ return false;
27
+ }
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+ function createDefaultState(opts, refetch) {
34
+ return {
35
+ status: "idle",
36
+ updatedAt: void 0,
37
+ staleTime: opts?.staleTime ?? DEFAULT_STALE_TIME,
38
+ isInvalidated: false,
39
+ fetcher: opts?.fetcher,
40
+ equalityFn: opts?.equalityFn ?? shallowEqual,
41
+ placeholderData: opts?.placeholderData,
42
+ usePreviousDataOnError: opts?.usePreviousDataOnError ?? false,
43
+ usePlaceholderOnError: opts?.usePlaceholderOnError ?? false,
44
+ refetchOnSubscribe: opts?.refetchOnSubscribe ?? "stale",
45
+ enabled: opts?.enabled === false ? false : true,
46
+ refetch: refetch || (() => Promise.resolve(void 0))
47
+ };
48
+ }
49
+
50
+ // src/queryManager.ts
51
+ var QueryManager = class {
52
+ constructor() {
53
+ this.cache = /* @__PURE__ */ new Map();
54
+ this.subs = /* @__PURE__ */ new Map();
55
+ this.lastReturnedState = /* @__PURE__ */ new Map();
56
+ this.defaultConfig = {};
57
+ this.throttleTime = THROTTLE_TIME;
58
+ }
59
+ /**
60
+ * Set default configuration for all queries
61
+ */
62
+ setDefaultConfig({ throttleTime, ...config }) {
63
+ this.defaultConfig = { ...this.defaultConfig, ...config };
64
+ if (throttleTime !== void 0) {
65
+ this.throttleTime = throttleTime;
66
+ }
67
+ }
68
+ /**
69
+ * Ensures a query state exists in cache, creating it if necessary
70
+ * User-friendly with any fallback for better developer experience
71
+ */
72
+ ensureState(key, opts = {}) {
73
+ const sk = serializeKey(key);
74
+ const state = this.cache.get(sk);
75
+ const mergedOpts = { ...this.defaultConfig, ...opts };
76
+ if (state) {
77
+ Object.assign(state, mergedOpts);
78
+ state.enabled = mergedOpts.enabled === false ? false : true;
79
+ this.cache.set(sk, state);
80
+ } else {
81
+ const newState = createDefaultState(mergedOpts, () => this.fetchQuery(key));
82
+ this.cache.set(sk, newState);
83
+ }
84
+ return this.cache.get(sk);
85
+ }
86
+ /**
87
+ * Notifies all subscribers of a query state change
88
+ */
89
+ emit(key, state) {
90
+ this.cache.set(serializeKey(key), state);
91
+ const set = this.subs.get(serializeKey(key));
92
+ if (!set)
93
+ return;
94
+ for (const cb of Array.from(set))
95
+ cb();
96
+ }
97
+ registerFetcher(key, opts) {
98
+ this.ensureState(key, opts);
99
+ if (opts.enabled !== false) {
100
+ try {
101
+ void this.fetchQuery(key);
102
+ } catch {
103
+ }
104
+ }
105
+ }
106
+ fetchQuery(key, opts) {
107
+ const state = this.ensureState(key, opts);
108
+ if (state.fetchPromise)
109
+ return state.fetchPromise;
110
+ const fetcher = state.fetcher;
111
+ if (!fetcher) {
112
+ console.error("No fetcher found for key", key);
113
+ return Promise.resolve(state.data);
114
+ }
115
+ ;
116
+ state.status = "fetching";
117
+ state.lastFetchTime = Date.now();
118
+ this.emit(key, state);
119
+ const result = fetcher();
120
+ const promise = Promise.resolve(result);
121
+ state.fetchPromise = promise;
122
+ promise.then((result2) => {
123
+ state.data = result2;
124
+ state.status = "success";
125
+ state.updatedAt = Date.now();
126
+ }).catch((error) => {
127
+ state.error = error;
128
+ state.status = "error";
129
+ }).finally(() => {
130
+ state.fetchPromise = void 0;
131
+ this.emit(key, state);
132
+ });
133
+ return promise;
134
+ }
135
+ /**
136
+ * Manually sets query data without triggering a fetch
137
+ * Marks query as successful
138
+ */
139
+ setQueryData(key, data) {
140
+ const state = this.ensureState(key);
141
+ const old = state.data;
142
+ if (state.equalityFn(old, data))
143
+ return;
144
+ state.data = data;
145
+ state.updatedAt = Date.now();
146
+ state.error = void 0;
147
+ state.status = "success";
148
+ state.isInvalidated = false;
149
+ this.emit(key, state);
150
+ }
151
+ /**
152
+ * Gets query data
153
+ * Handles mount logic to potentially start fetching
154
+ */
155
+ getQueryData(key, opts) {
156
+ const state = this.ensureState(key, opts);
157
+ this.handleMountLogic(key, state);
158
+ return state.data ?? state.placeholderData;
159
+ }
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
+ getQueryState(key, opts) {
166
+ 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
+ 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
+ };
206
+ const stateKey = serializeKey(key);
207
+ const lastState = this.lastReturnedState?.get(stateKey);
208
+ if (!lastState || !shallowEqual(lastState, currentState)) {
209
+ if (!this.lastReturnedState)
210
+ this.lastReturnedState = /* @__PURE__ */ new Map();
211
+ this.lastReturnedState.set(stateKey, currentState);
212
+ return currentState;
213
+ }
214
+ return lastState;
215
+ }
216
+ /**
217
+ * Marks a query as invalidated, triggering refetch
218
+ */
219
+ invalidateQuery(key) {
220
+ const state = this.ensureState(key);
221
+ state.isInvalidated = true;
222
+ this.emit(key, state);
223
+ this.fetchQuery(key);
224
+ }
225
+ /**
226
+ * Subscribes to query state changes with automatic subscription management
227
+ * Handles mount logic to potentially start fetching
228
+ */
229
+ subscribeQuery(key, cb, opts) {
230
+ const sk = serializeKey(key);
231
+ const state = this.ensureState(key, opts);
232
+ if (!this.subs.has(sk))
233
+ this.subs.set(sk, /* @__PURE__ */ new Set());
234
+ this.subs.get(sk).add(cb);
235
+ this.handleMountLogic(key, state);
236
+ return () => {
237
+ this.subs.get(sk).delete(cb);
238
+ };
239
+ }
240
+ /**
241
+ * Core mount logic that determines when to fetch
242
+ * Implements robust throttling and race condition prevention
243
+ */
244
+ handleMountLogic(key, state) {
245
+ const isThrottled = state.lastFetchTime && Date.now() - state.lastFetchTime < this.throttleTime;
246
+ if (state?.status === "fetching" || !state?.enabled || isThrottled || !state?.fetcher)
247
+ return;
248
+ const now = Date.now();
249
+ const isStale = state?.updatedAt == null || now - (state.updatedAt || 0) > state.staleTime || state.isInvalidated;
250
+ let shouldRefetch = false;
251
+ if (state.refetchOnSubscribe === "always") {
252
+ shouldRefetch = true;
253
+ }
254
+ if (state.refetchOnSubscribe === "stale") {
255
+ shouldRefetch = isStale;
256
+ }
257
+ if (shouldRefetch) {
258
+ this.fetchQuery(key);
259
+ }
260
+ }
261
+ };
262
+ var queryManager = new QueryManager();
263
+ export {
264
+ QueryManager,
265
+ queryManager,
266
+ serializeKey
267
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "qortex-core",
3
+ "version": "0.1.0",
4
+ "description": "Framework-agnostic query cache & fetch registry (MFE friendly).",
5
+ "main": "index.js",
6
+ "module": "index.mjs",
7
+ "types": "index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./index.d.ts",
11
+ "import": "./index.mjs",
12
+ "require": "./index.js"
13
+ }
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "files": [
19
+ "index.js",
20
+ "index.mjs",
21
+ "index.d.ts",
22
+ "README.md"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/Darshan-Naik/qortex.git"
27
+ },
28
+ "homepage": "https://github.com/Darshan-Naik/qortex#readme",
29
+ "bugs": {
30
+ "url": "https://github.com/Darshan-Naik/qortex/issues"
31
+ },
32
+ "keywords": [
33
+ "qortex",
34
+ "query",
35
+ "cache",
36
+ "runtime",
37
+ "mfe",
38
+ "data-fetching",
39
+ "typescript"
40
+ ],
41
+ "author": "Darshan Naik",
42
+ "license": "MIT",
43
+ "dependencies": {},
44
+ "peerDependencies": {}
45
+ }