@jayethian/axiom 0.1.0 → 0.1.2

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/dist/index.js CHANGED
@@ -56,6 +56,7 @@ var SyncManager = class {
56
56
  }
57
57
  /**
58
58
  * The master trigger. Call this when the OS reports network is back online.
59
+ * Automatically sorts requests so 'urgent' items bypass 'background' items.
59
60
  */
60
61
  async flushQueue() {
61
62
  if (this.isSyncing) return;
@@ -63,11 +64,15 @@ var SyncManager = class {
63
64
  try {
64
65
  const pending = await this.storage.getAll();
65
66
  if (pending.length === 0) {
66
- this.isSyncing = false;
67
67
  return;
68
68
  }
69
69
  console.log(`[Axiom] Network restored. Syncing ${pending.length} queued requests...`);
70
- for (const request of pending) {
70
+ const sortedQueue = pending.sort((a, b) => {
71
+ if (a.priority === "urgent" && b.priority !== "urgent") return -1;
72
+ if (a.priority !== "urgent" && b.priority === "urgent") return 1;
73
+ return a.timestamp - b.timestamp;
74
+ });
75
+ for (const request of sortedQueue) {
71
76
  await this.processRequest(request);
72
77
  }
73
78
  } finally {
@@ -83,16 +88,22 @@ var SyncManager = class {
83
88
  try {
84
89
  reqToSync = await this.config.onBeforeSync(request);
85
90
  } catch (error) {
86
- console.error(`[Axiom] onBeforeSync failed for ${request.id}. Skipping.`);
91
+ console.error(`[Axiom] onBeforeSync failed for ${request.id}. Marking as failure.`);
92
+ await this.handleFailure(request);
87
93
  return;
88
94
  }
89
95
  }
96
+ const controller = new AbortController();
97
+ const timeoutMs = this.config.timeout || 1e4;
98
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
90
99
  try {
91
100
  const response = await fetch(reqToSync.url, {
92
101
  method: reqToSync.method,
93
102
  headers: reqToSync.headers,
94
- body: reqToSync.body
103
+ body: reqToSync.body,
104
+ signal: controller.signal
95
105
  });
106
+ clearTimeout(timeoutId);
96
107
  if (response.ok || response.status >= 400 && response.status < 500) {
97
108
  await this.storage.remove(reqToSync.id);
98
109
  console.log(`[Axiom] Request ${reqToSync.id} synced successfully.`);
@@ -100,6 +111,7 @@ var SyncManager = class {
100
111
  await this.handleFailure(reqToSync);
101
112
  }
102
113
  } catch (error) {
114
+ clearTimeout(timeoutId);
103
115
  await this.handleFailure(reqToSync);
104
116
  }
105
117
  }
@@ -108,7 +120,7 @@ var SyncManager = class {
108
120
  */
109
121
  async handleFailure(request) {
110
122
  request.retryCount += 1;
111
- const maxRetries = this.config.maxRetries || 3;
123
+ const maxRetries = this.config.maxRetries ?? 3;
112
124
  if (request.retryCount >= maxRetries) {
113
125
  console.warn(`[Axiom] Request ${request.id} failed ${maxRetries} times. Moving to Dead Letter.`);
114
126
  await this.storage.remove(request.id);
@@ -128,7 +140,10 @@ var AxiomEngine = class {
128
140
  this.storage = new MemoryStorageAdapter();
129
141
  }
130
142
  /**
131
- * Initializes the Axiom engine with your specific rules and storage.
143
+ * Initializes the Axiom engine with global configuration and a storage adapter.
144
+ * This must be called before making any requests to enable persistence.
145
+ * * @param config - Global configuration (baseURL, timeouts, custom headers, etc.)
146
+ * @param storageAdapter - Optional custom adapter (e.g., MMKV). Defaults to in-memory storage.
132
147
  */
133
148
  create(config, storageAdapter) {
134
149
  this.config = config;
@@ -137,6 +152,10 @@ var AxiomEngine = class {
137
152
  }
138
153
  this.syncManager = new SyncManager(this.storage, this.config);
139
154
  }
155
+ /**
156
+ * Manually triggers the background sync manager to flush all pending queued requests.
157
+ * Note: This is automatically handled by `AxiomProvider` when the network reconnects.
158
+ */
140
159
  async forceSync() {
141
160
  if (!this.syncManager) {
142
161
  console.error("[Axiom] Engine not initialized. Call axiom.create() first.");
@@ -145,59 +164,94 @@ var AxiomEngine = class {
145
164
  await this.syncManager.flushQueue();
146
165
  }
147
166
  /**
148
- * Generates a unique ID for queued requests.
167
+ * Generates a unique collision-resistant ID for queued requests.
149
168
  */
150
169
  generateId() {
151
170
  return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
152
171
  }
153
172
  /**
154
- * The core POST method.
173
+ * Executes an HTTP GET request.
174
+ * If the network is unavailable or times out, the request is safely queued.
175
+ * * @param url - The endpoint URL (appended to baseURL if configured).
176
+ * @param options - Request-specific options (priority lanes, timeout overrides).
177
+ * @returns A promise resolving to the response data, status code, and queue state.
178
+ */
179
+ async get(url, options) {
180
+ return this.prepareRequest("GET", url, void 0, options);
181
+ }
182
+ /**
183
+ * Executes an HTTP POST request.
184
+ * If the network is unavailable or times out, the payload is safely queued.
185
+ * * @param url - The endpoint URL.
186
+ * @param data - The payload object to be serialized and sent.
187
+ * @param options - Request-specific options.
155
188
  */
156
189
  async post(url, data, options) {
157
- const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
158
- const request = {
159
- id: this.generateId(),
160
- timestamp: Date.now(),
161
- url: fullUrl,
162
- method: "POST",
163
- headers: {
164
- "Content-Type": "application/json",
165
- ...this.config.defaultHeaders || {}
166
- },
167
- body: data ? JSON.stringify(data) : void 0,
168
- priority: options?.priority || "urgent",
169
- retryCount: 0
170
- };
171
- return this.attemptFetch(request);
190
+ return this.prepareRequest("POST", url, data, options);
172
191
  }
173
- /** The core GET method.
174
- * Note: GET requests typically don't have a body, but we still want to queue them if offline.
192
+ /**
193
+ * Executes an HTTP PUT request to entirely replace a resource.
194
+ * * @param url - The endpoint URL.
195
+ * @param data - The payload object to be serialized and sent.
196
+ * @param options - Request-specific options.
175
197
  */
176
- async get(url, options) {
198
+ async put(url, data, options) {
199
+ return this.prepareRequest("PUT", url, data, options);
200
+ }
201
+ /**
202
+ * Executes an HTTP PATCH request to partially update a resource.
203
+ * * @param url - The endpoint URL.
204
+ * @param data - The partial payload object to be serialized and sent.
205
+ * @param options - Request-specific options.
206
+ */
207
+ async patch(url, data, options) {
208
+ return this.prepareRequest("PATCH", url, data, options);
209
+ }
210
+ /**
211
+ * Executes an HTTP DELETE request.
212
+ * * @param url - The endpoint URL.
213
+ * @param options - Request-specific options.
214
+ */
215
+ async delete(url, options) {
216
+ return this.prepareRequest("DELETE", url, void 0, options);
217
+ }
218
+ /**
219
+ * Internal helper to consolidate request preparation and keep the engine DRY.
220
+ */
221
+ async prepareRequest(method, url, data, options) {
177
222
  const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
223
+ const headers = { ...this.config.defaultHeaders || {} };
224
+ if (options?.headers) {
225
+ Object.assign(headers, options.headers);
226
+ }
178
227
  const request = {
179
228
  id: this.generateId(),
180
229
  timestamp: Date.now(),
181
230
  url: fullUrl,
182
- method: "GET",
183
- headers: {
184
- ...this.config.defaultHeaders || {}
185
- },
231
+ method,
232
+ headers,
233
+ body: data ? JSON.stringify(data) : void 0,
186
234
  priority: options?.priority || "urgent",
187
235
  retryCount: 0
188
236
  };
189
- return this.attemptFetch(request);
237
+ const timeoutMs = options?.timeout || this.config.timeout || 8e3;
238
+ return this.attemptFetch(request, timeoutMs);
190
239
  }
191
240
  /**
192
241
  * Internal logic to fire the request or catch the network drop.
242
+ * Handles timeout cancellations via AbortController.
193
243
  */
194
- async attemptFetch(request) {
244
+ async attemptFetch(request, timeoutMs) {
245
+ const controller = new AbortController();
246
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
195
247
  try {
196
248
  const response = await fetch(request.url, {
197
249
  method: request.method,
198
250
  headers: request.headers,
199
- body: request.body
251
+ body: request.body,
252
+ signal: controller.signal
200
253
  });
254
+ clearTimeout(timeoutId);
201
255
  if (response.ok) {
202
256
  const responseData = await response.json().catch(() => null);
203
257
  return { data: responseData, status: response.status, isQueued: false };
@@ -207,12 +261,16 @@ var AxiomEngine = class {
207
261
  }
208
262
  return { status: response.status, isQueued: false };
209
263
  } catch (error) {
264
+ clearTimeout(timeoutId);
265
+ if (error.name === "AbortError") {
266
+ console.warn(`[Axiom] Request to ${request.url} timed out after ${timeoutMs}ms. Queuing for retry.`);
267
+ }
210
268
  await this.enqueueRequest(request);
211
269
  return { status: 202, isQueued: true };
212
270
  }
213
271
  }
214
272
  /**
215
- * Saves the request to whatever storage adapter was provided on startup.
273
+ * Saves the request to the configured storage adapter.
216
274
  */
217
275
  async enqueueRequest(request) {
218
276
  console.warn(`[Axiom] Network unreachable. Queuing request ${request.id}`);
package/dist/index.mjs CHANGED
@@ -26,6 +26,7 @@ var SyncManager = class {
26
26
  }
27
27
  /**
28
28
  * The master trigger. Call this when the OS reports network is back online.
29
+ * Automatically sorts requests so 'urgent' items bypass 'background' items.
29
30
  */
30
31
  async flushQueue() {
31
32
  if (this.isSyncing) return;
@@ -33,11 +34,15 @@ var SyncManager = class {
33
34
  try {
34
35
  const pending = await this.storage.getAll();
35
36
  if (pending.length === 0) {
36
- this.isSyncing = false;
37
37
  return;
38
38
  }
39
39
  console.log(`[Axiom] Network restored. Syncing ${pending.length} queued requests...`);
40
- for (const request of pending) {
40
+ const sortedQueue = pending.sort((a, b) => {
41
+ if (a.priority === "urgent" && b.priority !== "urgent") return -1;
42
+ if (a.priority !== "urgent" && b.priority === "urgent") return 1;
43
+ return a.timestamp - b.timestamp;
44
+ });
45
+ for (const request of sortedQueue) {
41
46
  await this.processRequest(request);
42
47
  }
43
48
  } finally {
@@ -53,16 +58,22 @@ var SyncManager = class {
53
58
  try {
54
59
  reqToSync = await this.config.onBeforeSync(request);
55
60
  } catch (error) {
56
- console.error(`[Axiom] onBeforeSync failed for ${request.id}. Skipping.`);
61
+ console.error(`[Axiom] onBeforeSync failed for ${request.id}. Marking as failure.`);
62
+ await this.handleFailure(request);
57
63
  return;
58
64
  }
59
65
  }
66
+ const controller = new AbortController();
67
+ const timeoutMs = this.config.timeout || 1e4;
68
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
60
69
  try {
61
70
  const response = await fetch(reqToSync.url, {
62
71
  method: reqToSync.method,
63
72
  headers: reqToSync.headers,
64
- body: reqToSync.body
73
+ body: reqToSync.body,
74
+ signal: controller.signal
65
75
  });
76
+ clearTimeout(timeoutId);
66
77
  if (response.ok || response.status >= 400 && response.status < 500) {
67
78
  await this.storage.remove(reqToSync.id);
68
79
  console.log(`[Axiom] Request ${reqToSync.id} synced successfully.`);
@@ -70,6 +81,7 @@ var SyncManager = class {
70
81
  await this.handleFailure(reqToSync);
71
82
  }
72
83
  } catch (error) {
84
+ clearTimeout(timeoutId);
73
85
  await this.handleFailure(reqToSync);
74
86
  }
75
87
  }
@@ -78,7 +90,7 @@ var SyncManager = class {
78
90
  */
79
91
  async handleFailure(request) {
80
92
  request.retryCount += 1;
81
- const maxRetries = this.config.maxRetries || 3;
93
+ const maxRetries = this.config.maxRetries ?? 3;
82
94
  if (request.retryCount >= maxRetries) {
83
95
  console.warn(`[Axiom] Request ${request.id} failed ${maxRetries} times. Moving to Dead Letter.`);
84
96
  await this.storage.remove(request.id);
@@ -98,7 +110,10 @@ var AxiomEngine = class {
98
110
  this.storage = new MemoryStorageAdapter();
99
111
  }
100
112
  /**
101
- * Initializes the Axiom engine with your specific rules and storage.
113
+ * Initializes the Axiom engine with global configuration and a storage adapter.
114
+ * This must be called before making any requests to enable persistence.
115
+ * * @param config - Global configuration (baseURL, timeouts, custom headers, etc.)
116
+ * @param storageAdapter - Optional custom adapter (e.g., MMKV). Defaults to in-memory storage.
102
117
  */
103
118
  create(config, storageAdapter) {
104
119
  this.config = config;
@@ -107,6 +122,10 @@ var AxiomEngine = class {
107
122
  }
108
123
  this.syncManager = new SyncManager(this.storage, this.config);
109
124
  }
125
+ /**
126
+ * Manually triggers the background sync manager to flush all pending queued requests.
127
+ * Note: This is automatically handled by `AxiomProvider` when the network reconnects.
128
+ */
110
129
  async forceSync() {
111
130
  if (!this.syncManager) {
112
131
  console.error("[Axiom] Engine not initialized. Call axiom.create() first.");
@@ -115,59 +134,94 @@ var AxiomEngine = class {
115
134
  await this.syncManager.flushQueue();
116
135
  }
117
136
  /**
118
- * Generates a unique ID for queued requests.
137
+ * Generates a unique collision-resistant ID for queued requests.
119
138
  */
120
139
  generateId() {
121
140
  return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
122
141
  }
123
142
  /**
124
- * The core POST method.
143
+ * Executes an HTTP GET request.
144
+ * If the network is unavailable or times out, the request is safely queued.
145
+ * * @param url - The endpoint URL (appended to baseURL if configured).
146
+ * @param options - Request-specific options (priority lanes, timeout overrides).
147
+ * @returns A promise resolving to the response data, status code, and queue state.
148
+ */
149
+ async get(url, options) {
150
+ return this.prepareRequest("GET", url, void 0, options);
151
+ }
152
+ /**
153
+ * Executes an HTTP POST request.
154
+ * If the network is unavailable or times out, the payload is safely queued.
155
+ * * @param url - The endpoint URL.
156
+ * @param data - The payload object to be serialized and sent.
157
+ * @param options - Request-specific options.
125
158
  */
126
159
  async post(url, data, options) {
127
- const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
128
- const request = {
129
- id: this.generateId(),
130
- timestamp: Date.now(),
131
- url: fullUrl,
132
- method: "POST",
133
- headers: {
134
- "Content-Type": "application/json",
135
- ...this.config.defaultHeaders || {}
136
- },
137
- body: data ? JSON.stringify(data) : void 0,
138
- priority: options?.priority || "urgent",
139
- retryCount: 0
140
- };
141
- return this.attemptFetch(request);
160
+ return this.prepareRequest("POST", url, data, options);
142
161
  }
143
- /** The core GET method.
144
- * Note: GET requests typically don't have a body, but we still want to queue them if offline.
162
+ /**
163
+ * Executes an HTTP PUT request to entirely replace a resource.
164
+ * * @param url - The endpoint URL.
165
+ * @param data - The payload object to be serialized and sent.
166
+ * @param options - Request-specific options.
145
167
  */
146
- async get(url, options) {
168
+ async put(url, data, options) {
169
+ return this.prepareRequest("PUT", url, data, options);
170
+ }
171
+ /**
172
+ * Executes an HTTP PATCH request to partially update a resource.
173
+ * * @param url - The endpoint URL.
174
+ * @param data - The partial payload object to be serialized and sent.
175
+ * @param options - Request-specific options.
176
+ */
177
+ async patch(url, data, options) {
178
+ return this.prepareRequest("PATCH", url, data, options);
179
+ }
180
+ /**
181
+ * Executes an HTTP DELETE request.
182
+ * * @param url - The endpoint URL.
183
+ * @param options - Request-specific options.
184
+ */
185
+ async delete(url, options) {
186
+ return this.prepareRequest("DELETE", url, void 0, options);
187
+ }
188
+ /**
189
+ * Internal helper to consolidate request preparation and keep the engine DRY.
190
+ */
191
+ async prepareRequest(method, url, data, options) {
147
192
  const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
193
+ const headers = { ...this.config.defaultHeaders || {} };
194
+ if (options?.headers) {
195
+ Object.assign(headers, options.headers);
196
+ }
148
197
  const request = {
149
198
  id: this.generateId(),
150
199
  timestamp: Date.now(),
151
200
  url: fullUrl,
152
- method: "GET",
153
- headers: {
154
- ...this.config.defaultHeaders || {}
155
- },
201
+ method,
202
+ headers,
203
+ body: data ? JSON.stringify(data) : void 0,
156
204
  priority: options?.priority || "urgent",
157
205
  retryCount: 0
158
206
  };
159
- return this.attemptFetch(request);
207
+ const timeoutMs = options?.timeout || this.config.timeout || 8e3;
208
+ return this.attemptFetch(request, timeoutMs);
160
209
  }
161
210
  /**
162
211
  * Internal logic to fire the request or catch the network drop.
212
+ * Handles timeout cancellations via AbortController.
163
213
  */
164
- async attemptFetch(request) {
214
+ async attemptFetch(request, timeoutMs) {
215
+ const controller = new AbortController();
216
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
165
217
  try {
166
218
  const response = await fetch(request.url, {
167
219
  method: request.method,
168
220
  headers: request.headers,
169
- body: request.body
221
+ body: request.body,
222
+ signal: controller.signal
170
223
  });
224
+ clearTimeout(timeoutId);
171
225
  if (response.ok) {
172
226
  const responseData = await response.json().catch(() => null);
173
227
  return { data: responseData, status: response.status, isQueued: false };
@@ -177,12 +231,16 @@ var AxiomEngine = class {
177
231
  }
178
232
  return { status: response.status, isQueued: false };
179
233
  } catch (error) {
234
+ clearTimeout(timeoutId);
235
+ if (error.name === "AbortError") {
236
+ console.warn(`[Axiom] Request to ${request.url} timed out after ${timeoutMs}ms. Queuing for retry.`);
237
+ }
180
238
  await this.enqueueRequest(request);
181
239
  return { status: 202, isQueued: true };
182
240
  }
183
241
  }
184
242
  /**
185
- * Saves the request to whatever storage adapter was provided on startup.
243
+ * Saves the request to the configured storage adapter.
186
244
  */
187
245
  async enqueueRequest(request) {
188
246
  console.warn(`[Axiom] Network unreachable. Queuing request ${request.id}`);
package/package.json CHANGED
@@ -1,13 +1,35 @@
1
1
  {
2
2
  "name": "@jayethian/axiom",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "maintainers": [
5
5
  "Jayetheus"
6
6
  ],
7
- "description": "A resilient, offline-first fetch wrapper",
7
+ "description": "A resilient, offline-first fetch wrapper for React Native & Next.js.",
8
+ "keywords": [
9
+ "react-native",
10
+ "nextjs",
11
+ "fetch",
12
+ "axios",
13
+ "offline-first",
14
+ "sync",
15
+ "network",
16
+ "queue",
17
+ "indexeddb"
18
+ ],
19
+ "homepage": "https://github.com/jayethian/axiom#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/jayethian/axiom/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/jayethian/axiom.git"
26
+ },
8
27
  "publishConfig": {
9
28
  "access": "public"
10
29
  },
30
+ "files": [
31
+ "dist"
32
+ ],
11
33
  "main": "./dist/index.js",
12
34
  "module": "./dist/index.mjs",
13
35
  "types": "./dist/index.d.ts",
@@ -28,6 +50,5 @@
28
50
  },
29
51
  "peerDependencies": {
30
52
  "react": ">=17.0.0"
31
- },
32
- "packageManager": "yarn@4.12.0"
33
- }
53
+ }
54
+ }
package/.editorconfig DELETED
@@ -1,8 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- charset = utf-8
5
- end_of_line = lf
6
- indent_size = 2
7
- indent_style = space
8
- insert_final_newline = true
package/.gitattributes DELETED
@@ -1,4 +0,0 @@
1
- /.yarn/** linguist-vendored
2
- /.yarn/releases/* binary
3
- /.yarn/plugins/**/* binary
4
- /.pnp.* binary linguist-generated
@@ -1,15 +0,0 @@
1
- import { QueuedRequest } from '../types';
2
-
3
- export interface AxiomStorageAdapter {
4
- /** Saves a request to the persistent queue */
5
- save(request: QueuedRequest): Promise<void>;
6
-
7
- /** Retrieves all pending requests, usually ordered by timestamp */
8
- getAll(): Promise<QueuedRequest[]>;
9
-
10
- /** Removes a specific request after it successfully syncs */
11
- remove(id: string): Promise<void>;
12
-
13
- /** Wipes the queue entirely (useful for user logout) */
14
- clearAll(): Promise<void>;
15
- }
@@ -1,22 +0,0 @@
1
- import { AxiomStorageAdapter } from './index';
2
- import { QueuedRequest } from '../types';
3
-
4
- export class MemoryStorageAdapter implements AxiomStorageAdapter {
5
- private queue: Map<string, QueuedRequest> = new Map();
6
-
7
- async save(request: QueuedRequest): Promise<void> {
8
- this.queue.set(request.id, request);
9
- }
10
-
11
- async getAll(): Promise<QueuedRequest[]> {
12
- return Array.from(this.queue.values()).sort((a, b) => a.timestamp - b.timestamp);
13
- }
14
-
15
- async remove(id: string): Promise<void> {
16
- this.queue.delete(id);
17
- }
18
-
19
- async clearAll(): Promise<void> {
20
- this.queue.clear();
21
- }
22
- }
@@ -1,131 +0,0 @@
1
- import { AxiomConfig, QueuedRequest, RequestPriority } from '../types';
2
- import { AxiomStorageAdapter } from '../adapters';
3
- import { MemoryStorageAdapter } from '../adapters/memory';
4
- import { SyncManager } from './sync';
5
-
6
- export class AxiomEngine {
7
- private config: AxiomConfig = {};
8
- private storage: AxiomStorageAdapter = new MemoryStorageAdapter();
9
- private syncManager!: SyncManager;
10
-
11
- /**
12
- * Initializes the Axiom engine with your specific rules and storage.
13
- */
14
- public create(config: AxiomConfig, storageAdapter?: AxiomStorageAdapter): void {
15
- this.config = config;
16
- if (storageAdapter) {
17
- this.storage = storageAdapter;
18
- }
19
-
20
- this.syncManager = new SyncManager(this.storage, this.config);
21
- }
22
-
23
- public async forceSync(): Promise<void> {
24
- if (!this.syncManager) {
25
- console.error("[Axiom] Engine not initialized. Call axiom.create() first.");
26
- return;
27
- }
28
- await this.syncManager.flushQueue();
29
- }
30
-
31
- /**
32
- * Generates a unique ID for queued requests.
33
- */
34
- private generateId(): string {
35
- return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
36
- }
37
-
38
- /**
39
- * The core POST method.
40
- */
41
- public async post<T>(
42
- url: string,
43
- data?: any,
44
- options?: { priority?: RequestPriority }
45
- ): Promise<{ data?: T; status: number; isQueued: boolean }> {
46
-
47
- const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
48
-
49
- const request: QueuedRequest = {
50
- id: this.generateId(),
51
- timestamp: Date.now(),
52
- url: fullUrl,
53
- method: 'POST',
54
- headers: {
55
- 'Content-Type': 'application/json',
56
- ...(this.config.defaultHeaders || {})
57
- },
58
- body: data ? JSON.stringify(data) : undefined,
59
- priority: options?.priority || 'urgent',
60
- retryCount: 0
61
- };
62
-
63
- return this.attemptFetch<T>(request);
64
- }
65
-
66
- /** The core GET method.
67
- * Note: GET requests typically don't have a body, but we still want to queue them if offline.
68
- */
69
- public async get<T>(
70
- url: string,
71
- options?: { priority?: RequestPriority }
72
- ): Promise<{ data?: T; status: number; isQueued: boolean }> {
73
-
74
- const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
75
-
76
- const request: QueuedRequest = {
77
- id: this.generateId(),
78
- timestamp: Date.now(),
79
- url: fullUrl,
80
- method: 'GET',
81
- headers: {
82
- ...(this.config.defaultHeaders || {})
83
- },
84
- priority: options?.priority || 'urgent',
85
- retryCount: 0
86
- };
87
-
88
- return this.attemptFetch<T>(request);
89
- }
90
-
91
- /**
92
- * Internal logic to fire the request or catch the network drop.
93
- */
94
- private async attemptFetch<T>(request: QueuedRequest): Promise<{ data?: T; status: number; isQueued: boolean }> {
95
- try {
96
- const response = await fetch(request.url, {
97
- method: request.method,
98
- headers: request.headers,
99
- body: request.body
100
- });
101
-
102
- if (response.ok) {
103
- const responseData = await response.json().catch(() => null);
104
- return { data: responseData, status: response.status, isQueued: false };
105
- }
106
-
107
-
108
- if (response.status >= 500) {
109
- throw new Error('Server Error');
110
- }
111
-
112
-
113
- return { status: response.status, isQueued: false };
114
-
115
- } catch (error) {
116
- await this.enqueueRequest(request);
117
-
118
- return { status: 202, isQueued: true };
119
- }
120
- }
121
-
122
- /**
123
- * Saves the request to whatever storage adapter was provided on startup.
124
- */
125
- private async enqueueRequest(request: QueuedRequest): Promise<void> {
126
- console.warn(`[Axiom] Network unreachable. Queuing request ${request.id}`);
127
- await this.storage.save(request);
128
- }
129
- }
130
-
131
- export const axiom = new AxiomEngine();