@jayethian/axiom 0.1.1 β 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/README.md +242 -36
- package/package.json +26 -5
- package/.editorconfig +0 -8
- package/.gitattributes +0 -4
- package/assets/logo.png +0 -0
- package/src/adapters/index.ts +0 -15
- package/src/adapters/memory.ts +0 -22
- package/src/engine/fetcher.ts +0 -197
- package/src/engine/sync.ts +0 -112
- package/src/index.ts +0 -6
- package/src/react/AxiomProvider.tsx +0 -57
- package/src/react/useAxiomQueue.ts +0 -12
- package/src/types.ts +0 -45
- package/tsconfig.json +0 -16
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="
|
|
2
|
+
<img src="https://raw.githubusercontent.com/jayethian/axiom/main/assets/logo.png" alt="axiom logo" width="400" />
|
|
3
3
|
<h1>Axiom</h1>
|
|
4
4
|
|
|
5
|
-
<h3>Resilient, offline-first networking for modern React apps.</h3>
|
|
5
|
+
<h3>Resilient, offline-first networking for modern React, Next.js, and React Native apps.</h3>
|
|
6
6
|
|
|
7
7
|
<p>
|
|
8
8
|
<a href="https://www.npmjs.com/package/@jayethian/axiom">
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
<br />
|
|
22
22
|
|
|
23
23
|
## The Problem
|
|
24
|
-
Standard HTTP clients like **Axios** or **Fetch** assume a stable connection. When a user submits data in a dead zone (elevators, basements, rural areas), the request simply fails. Without a complex manual retry system, **that data is gone forever.**
|
|
24
|
+
Standard HTTP clients like **Axios** or **Fetch** assume a stable connection. When a user submits data in a dead zone (elevators, basements, rural areas), the request simply fails. Without a complex, manual retry system written from scratch, **that data is gone forever.**
|
|
25
25
|
|
|
26
26
|
## The Axiom Way
|
|
27
|
-
Axiom intercepts network failures and timeouts. Instead of throwing an error, it serializes the request and moves it to a persistent local queue. When the connection returns, Axiom flushes the queue automatically.
|
|
27
|
+
Axiom intercepts network failures and timeouts. Instead of throwing an error, it serializes the request and safely moves it to a persistent local queue. When the connection returns, Axiom flushes the queue automatically, in the background.
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
30
|
// Standard Fetch: Fails and loses data when offline.
|
|
@@ -37,101 +37,242 @@ await axiom.post('/api/orders', data);
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
-
##
|
|
40
|
+
## Table of Contents
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
1. [Features](https://www.google.com/search?q=%23-features)
|
|
43
|
+
2. [Installation](https://www.google.com/search?q=%23-installation)
|
|
44
|
+
3. [React / Next.js Setup](https://www.google.com/search?q=%23-react--nextjs-setup-zero-config)
|
|
45
|
+
4. [React Native Setup](https://www.google.com/search?q=%23-react-native-setup)
|
|
46
|
+
5. [Vanilla JS / Node Setup](https://www.google.com/search?q=%23-vanilla-js--node-setup)
|
|
47
|
+
6. [Core Hooks (`useAxiomQueue`)](https://www.google.com/search?q=%23-core-hooks)
|
|
48
|
+
7. [Advanced Architecture](https://www.google.com/search?q=%23-advanced-architecture)
|
|
49
|
+
* [Global Interceptors](https://www.google.com/search?q=%23global-interceptors)
|
|
50
|
+
* [Priority Lanes](https://www.google.com/search?q=%23priority-lanes)
|
|
51
|
+
* [Queue Inspection & "Outbox" UI](https://www.google.com/search?q=%23queue-inspection--outbox-ui)
|
|
52
|
+
* [Storage Adapters (MMKV, IndexedDB)](https://www.google.com/search?q=%23storage-adapters)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
8. [API Reference](https://www.google.com/search?q=%23-api-reference)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Features
|
|
60
|
+
|
|
61
|
+
* **π± Mobile-First Resilience:** Specifically tuned to handle spotty connectivity, aggressive timeouts, and background execution.
|
|
62
|
+
* **π§ Smart Fallback Storage:** Automatically detects your environment and falls back to the safest storage (`IndexedDB` for Web, `Memory` for SSR/React Native) without crashing.
|
|
43
63
|
* **π Autonomous Background Sync:** Replays the queue the moment a signal is detected.
|
|
44
64
|
* **β‘ Priority Lanes:** Ensure critical data (e.g., payments) jumps to the front of the queue ahead of background tasks (e.g., analytics).
|
|
45
|
-
* **π‘οΈ Just-In-Time Headers:** Refresh Auth Tokens immediately before syncing to prevent `401 Unauthorized` errors on
|
|
46
|
-
*
|
|
47
|
-
* **πͺ¦ Dead Letter Queues:** Protects your app from infinite loops by isolating permanently failing requests.
|
|
65
|
+
* **π‘οΈ Just-In-Time Headers:** Refresh Auth Tokens immediately before syncing to prevent `401 Unauthorized` errors on delayed requests.
|
|
66
|
+
* **π Global Interceptors:** Catch success and error events globally, even when requests resolve in the background hours later.
|
|
67
|
+
* **πͺ¦ Dead Letter Queues:** Protects your app from infinite loops by isolating permanently failing requests and exposing them to the UI for user intervention.
|
|
48
68
|
|
|
49
69
|
---
|
|
50
70
|
|
|
51
71
|
## Installation
|
|
52
72
|
|
|
53
73
|
```bash
|
|
74
|
+
npm install @jayethian/axiom
|
|
75
|
+
# or
|
|
54
76
|
yarn add @jayethian/axiom
|
|
55
77
|
# or
|
|
56
|
-
|
|
78
|
+
pnpm add @jayethian/axiom
|
|
57
79
|
|
|
58
80
|
```
|
|
59
81
|
|
|
60
82
|
---
|
|
61
83
|
|
|
62
|
-
##
|
|
84
|
+
## React & Next.js Setup (Zero-Config)
|
|
85
|
+
Axiom includes a built-in event listener that automatically binds to the browser's `window.addEventListener('online')` APIs. For Next.js and standard React Web apps, setup requires zero boilerplate.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// App.tsx or layout.tsx
|
|
89
|
+
import { AxiomProvider } from '@jayethian/axiom';
|
|
90
|
+
|
|
91
|
+
export default function App({ children }) {
|
|
92
|
+
return (
|
|
93
|
+
<AxiomProvider
|
|
94
|
+
config={{
|
|
95
|
+
baseURL: '[https://api.myapp.com](https://api.myapp.com)',
|
|
96
|
+
timeout: 8000
|
|
97
|
+
}}
|
|
98
|
+
// Automatically uses IndexedDB, falls back to LocalStorage if in Private Browsing
|
|
99
|
+
fallbackAdapter="indexeddb"
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</AxiomProvider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
63
109
|
|
|
64
|
-
|
|
110
|
+
## React Native Setup
|
|
65
111
|
|
|
66
|
-
|
|
112
|
+
React Native does not have a native DOM `window`, so you must provide a network listener (like `@react-native-community/netinfo`) and a persistent storage adapter (like `react-native-mmkv` or `AsyncStorage`).
|
|
67
113
|
|
|
68
114
|
```tsx
|
|
69
115
|
import { AxiomProvider } from '@jayethian/axiom';
|
|
70
116
|
import NetInfo from '@react-native-community/netinfo';
|
|
117
|
+
import { MMKVAdapter } from './my-adapters'; // See Storage Adapters below
|
|
71
118
|
|
|
72
|
-
export default function App() {
|
|
119
|
+
export default function App({ children }) {
|
|
73
120
|
return (
|
|
74
121
|
<AxiomProvider
|
|
75
|
-
config={{ baseURL: '[https://api.myapp.com](https://api.myapp.com)'
|
|
122
|
+
config={{ baseURL: '[https://api.myapp.com](https://api.myapp.com)' }}
|
|
123
|
+
storageAdapter={new MMKVAdapter()}
|
|
76
124
|
networkListener={(callback) => {
|
|
77
125
|
return NetInfo.addEventListener(state => callback(!!state.isConnected));
|
|
78
126
|
}}
|
|
79
127
|
>
|
|
80
|
-
|
|
128
|
+
{children}
|
|
81
129
|
</AxiomProvider>
|
|
82
130
|
);
|
|
83
131
|
}
|
|
84
132
|
|
|
85
133
|
```
|
|
86
134
|
|
|
87
|
-
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Vanilla JS / Node Setup
|
|
138
|
+
|
|
139
|
+
You do not need React to use Axiom. You can instantiate the engine directly and use our built-in **Event Emitter** to listen for background syncs.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { axiom } from '@jayethian/axiom';
|
|
143
|
+
|
|
144
|
+
// 1. Initialize
|
|
145
|
+
axiom.create({ baseURL: '[https://api.myapp.com](https://api.myapp.com)' });
|
|
88
146
|
|
|
89
|
-
|
|
147
|
+
// 2. Listen to Background Events
|
|
148
|
+
axiom.on('syncSuccess', (data, req) => {
|
|
149
|
+
console.log(`Background sync finished for ${req.url}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
axiom.on('deadLetter', (req) => {
|
|
153
|
+
console.error(`Request permanently failed after 3 retries:`, req);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// 3. Make Requests
|
|
157
|
+
const response = await axiom.post('/users', { name: 'John' });
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Core Hooks
|
|
164
|
+
|
|
165
|
+
The `useAxiomQueue` hook gives your UI complete visibility into the background engine. Keep your users informed when they are working offline.
|
|
90
166
|
|
|
91
167
|
```tsx
|
|
92
168
|
import { axiom, useAxiomQueue } from '@jayethian/axiom';
|
|
93
169
|
|
|
94
|
-
|
|
170
|
+
export function CheckoutButton() {
|
|
171
|
+
const { isOnline, deadLetters, clearDeadLetters } = useAxiomQueue();
|
|
95
172
|
|
|
96
|
-
const onSave = async (data) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
173
|
+
const onSave = async (data) => {
|
|
174
|
+
// If offline, returns a 202 and flags isQueued: true
|
|
175
|
+
const res = await axiom.post('/checkout', data, { priority: 'urgent' });
|
|
176
|
+
|
|
177
|
+
if (res.isQueued) {
|
|
178
|
+
alert("Working offline. Your order will sync automatically!");
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div>
|
|
184
|
+
{!isOnline && <Banner>You are offline. Actions will be saved.</Banner>}
|
|
185
|
+
{deadLetters.length > 0 && <ErrorBanner>Some actions failed to save.</ErrorBanner>}
|
|
186
|
+
|
|
187
|
+
<button onClick={onSave}>Checkout</button>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
103
191
|
|
|
104
192
|
```
|
|
105
193
|
|
|
106
194
|
---
|
|
107
195
|
|
|
108
|
-
## Advanced
|
|
196
|
+
## Advanced Architecture
|
|
197
|
+
|
|
198
|
+
### Global Interceptors
|
|
199
|
+
|
|
200
|
+
Axiom allows you to intercept requests exactly like Axios, but it applies these rules to **background syncs** as well.
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
<AxiomProvider
|
|
204
|
+
config={{
|
|
205
|
+
// Triggered globally whenever a request hard-fails (e.g., 500, 401)
|
|
206
|
+
onError: (status, error, request) => {
|
|
207
|
+
if (status === 401) {
|
|
208
|
+
AuthService.logout(); // Global logout on token expiration
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
// Triggered globally whenever ANY request succeeds (immediate or background)
|
|
212
|
+
onResponse: (data, status, request) => {
|
|
213
|
+
if (request.url.includes('/payment')) {
|
|
214
|
+
Analytics.track('Payment Successful');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
|
|
220
|
+
```
|
|
109
221
|
|
|
110
222
|
### Priority Lanes
|
|
111
223
|
|
|
112
|
-
|
|
224
|
+
By default, Axiom queues requests First-In-First-Out (FIFO). However, you can force critical requests to jump to the front of the line when the network returns.
|
|
113
225
|
|
|
114
226
|
```typescript
|
|
115
|
-
// This stays at the back of the line
|
|
227
|
+
// This stays at the back of the line (queued in the background)
|
|
116
228
|
axiom.post('/analytics', logData, { priority: 'background' });
|
|
117
229
|
|
|
118
|
-
// This jumps to the front
|
|
230
|
+
// This jumps to the front and syncs first when the connection returns
|
|
119
231
|
axiom.post('/chat/send', message, { priority: 'urgent' });
|
|
120
232
|
|
|
121
233
|
```
|
|
122
234
|
|
|
235
|
+
### Queue Inspection & "Outbox" UI
|
|
236
|
+
|
|
237
|
+
Give your users the ability to see what is waiting to sync, and let them cancel actions before the network returns.
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { useAxiomQueue } from '@jayethian/axiom';
|
|
241
|
+
|
|
242
|
+
export function Outbox() {
|
|
243
|
+
const { inspectQueue, cancelRequest } = useAxiomQueue();
|
|
244
|
+
const [pending, setPending] = useState([]);
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
inspectQueue().then(setPending);
|
|
248
|
+
}, []);
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<ul>
|
|
252
|
+
{pending.map(req => (
|
|
253
|
+
<li key={req.id}>
|
|
254
|
+
Pending: {req.method} {req.url}
|
|
255
|
+
<button onClick={() => cancelRequest(req.id)}>Cancel</button>
|
|
256
|
+
</li>
|
|
257
|
+
))}
|
|
258
|
+
</ul>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
|
|
123
264
|
### Just-In-Time Headers
|
|
124
265
|
|
|
125
|
-
|
|
266
|
+
If a user is offline for 4 hours, their JWT will likely expire. If Axiom attempts to sync the old request, the server will reject it. `onBeforeSync` allows you to inject fresh tokens *milliseconds* before the queue flushes.
|
|
126
267
|
|
|
127
268
|
```tsx
|
|
128
269
|
<AxiomProvider
|
|
129
270
|
config={{
|
|
130
271
|
onBeforeSync: async (request) => {
|
|
131
|
-
const
|
|
272
|
+
const freshToken = await getValidAuthToken(); // Your logic
|
|
132
273
|
return {
|
|
133
274
|
...request,
|
|
134
|
-
headers: { ...request.headers, Authorization: `Bearer ${
|
|
275
|
+
headers: { ...request.headers, Authorization: `Bearer ${freshToken}` }
|
|
135
276
|
};
|
|
136
277
|
}
|
|
137
278
|
}}
|
|
@@ -139,12 +280,77 @@ Refresh your JWT right before the queue fires to ensure every request is authori
|
|
|
139
280
|
|
|
140
281
|
```
|
|
141
282
|
|
|
283
|
+
### Storage Adapters
|
|
284
|
+
|
|
285
|
+
Axiom comes with `IndexedDB`, `LocalStorage`, and `Memory` adapters out of the box. For React Native, building a custom adapter using a high-performance library like MMKV is incredibly simple.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { MMKV } from 'react-native-mmkv';
|
|
289
|
+
import { AxiomStorageAdapter, QueuedRequest } from '@jayethian/axiom';
|
|
290
|
+
|
|
291
|
+
const mmkv = new MMKV();
|
|
292
|
+
|
|
293
|
+
export class MMKVAdapter implements AxiomStorageAdapter {
|
|
294
|
+
private key = 'axiom_queue';
|
|
295
|
+
|
|
296
|
+
private getQ(): QueuedRequest[] {
|
|
297
|
+
const data = mmkv.getString(this.key);
|
|
298
|
+
return data ? JSON.parse(data) : [];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async save(req: QueuedRequest) {
|
|
302
|
+
const q = this.getQ();
|
|
303
|
+
q.push(req);
|
|
304
|
+
mmkv.set(this.key, JSON.stringify(q));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async getAll() { return this.getQ(); }
|
|
308
|
+
|
|
309
|
+
async remove(id: string) {
|
|
310
|
+
const q = this.getQ().filter(r => r.id !== id);
|
|
311
|
+
mmkv.set(this.key, JSON.stringify(q));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async clearAll() { mmkv.delete(this.key); }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Pass it to the provider:
|
|
318
|
+
<AxiomProvider storageAdapter={new MMKVAdapter()} {...props} />
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## API Reference
|
|
325
|
+
|
|
326
|
+
### `AxiomConfig`
|
|
327
|
+
|
|
328
|
+
| Property | Type | Default | Description |
|
|
329
|
+
| --- | --- | --- | --- |
|
|
330
|
+
| `baseURL` | `string` | `undefined` | Prepend this to all request URLs. |
|
|
331
|
+
| `defaultHeaders` | `Record<string, string>` | `{}` | Global headers applied to all requests. |
|
|
332
|
+
| `timeout` | `number` | `8000` | MS before a request is aborted and moved to the offline queue. |
|
|
333
|
+
| `maxRetries` | `number` | `3` | Attempts before a background sync fails permanently. |
|
|
334
|
+
| `fallbackAdapter` | `'indexeddb' | 'localstorage' | 'memory'` | `'memory'` | The internal adapter to use if `storageAdapter` is omitted. |
|
|
335
|
+
| `debug` | `boolean` | `false` | Prints verbose engine logs to the console. |
|
|
336
|
+
|
|
337
|
+
### `AxiomRequestOptions`
|
|
338
|
+
|
|
339
|
+
Passed as the third parameter to `axiom.post`, `axiom.get`, etc.
|
|
340
|
+
|
|
341
|
+
| Property | Type | Description |
|
|
342
|
+
| --- | --- | --- |
|
|
343
|
+
| `priority` | `'urgent' | 'background'` | Determines sort order when the queue flushes. |
|
|
344
|
+
| `timeout` | `number` | Overrides the global timeout for this specific request. |
|
|
345
|
+
| `headers` | `Record<string, string>` | Append or overwrite global headers for this request. |
|
|
346
|
+
| `metadata` | `any` | Attach custom UI data to the request. Survives serialization. |
|
|
347
|
+
|
|
142
348
|
---
|
|
143
349
|
|
|
144
350
|
## Contributing
|
|
145
351
|
|
|
146
|
-
|
|
352
|
+
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://www.google.com/search?q=https://github.com/jayethian/axiom/issues).
|
|
147
353
|
|
|
148
|
-
## License
|
|
354
|
+
## π License
|
|
149
355
|
|
|
150
|
-
|
|
356
|
+
This project is [MIT](https://opensource.org/licenses/MIT) licensed. Built with β‘οΈ by [Jayetheus](https://www.google.com/search?q=https://github.com/jayethian).
|
package/package.json
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayethian/axiom",
|
|
3
|
-
"version": "0.1.
|
|
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
|
-
|
|
33
|
-
}
|
|
53
|
+
}
|
|
54
|
+
}
|
package/.editorconfig
DELETED
package/.gitattributes
DELETED
package/assets/logo.png
DELETED
|
Binary file
|
package/src/adapters/index.ts
DELETED
|
@@ -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
|
-
}
|
package/src/adapters/memory.ts
DELETED
|
@@ -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
|
-
}
|
package/src/engine/fetcher.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { AxiomConfig, AxiomRequestOptions, 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 global configuration and a storage adapter.
|
|
13
|
-
* This must be called before making any requests to enable persistence.
|
|
14
|
-
* * @param config - Global configuration (baseURL, timeouts, custom headers, etc.)
|
|
15
|
-
* @param storageAdapter - Optional custom adapter (e.g., MMKV). Defaults to in-memory storage.
|
|
16
|
-
*/
|
|
17
|
-
public create(config: AxiomConfig, storageAdapter?: AxiomStorageAdapter): void {
|
|
18
|
-
this.config = config;
|
|
19
|
-
if (storageAdapter) {
|
|
20
|
-
this.storage = storageAdapter;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
this.syncManager = new SyncManager(this.storage, this.config);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Manually triggers the background sync manager to flush all pending queued requests.
|
|
28
|
-
* Note: This is automatically handled by `AxiomProvider` when the network reconnects.
|
|
29
|
-
*/
|
|
30
|
-
public async forceSync(): Promise<void> {
|
|
31
|
-
if (!this.syncManager) {
|
|
32
|
-
console.error("[Axiom] Engine not initialized. Call axiom.create() first.");
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
await this.syncManager.flushQueue();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Generates a unique collision-resistant ID for queued requests.
|
|
40
|
-
*/
|
|
41
|
-
private generateId(): string {
|
|
42
|
-
return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Executes an HTTP GET request.
|
|
47
|
-
* If the network is unavailable or times out, the request is safely queued.
|
|
48
|
-
* * @param url - The endpoint URL (appended to baseURL if configured).
|
|
49
|
-
* @param options - Request-specific options (priority lanes, timeout overrides).
|
|
50
|
-
* @returns A promise resolving to the response data, status code, and queue state.
|
|
51
|
-
*/
|
|
52
|
-
public async get<T>(
|
|
53
|
-
url: string,
|
|
54
|
-
options?: AxiomRequestOptions
|
|
55
|
-
): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
56
|
-
return this.prepareRequest<T>('GET', url, undefined, options);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Executes an HTTP POST request.
|
|
61
|
-
* If the network is unavailable or times out, the payload is safely queued.
|
|
62
|
-
* * @param url - The endpoint URL.
|
|
63
|
-
* @param data - The payload object to be serialized and sent.
|
|
64
|
-
* @param options - Request-specific options.
|
|
65
|
-
*/
|
|
66
|
-
public async post<T>(
|
|
67
|
-
url: string,
|
|
68
|
-
data?: any,
|
|
69
|
-
options?: AxiomRequestOptions
|
|
70
|
-
): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
71
|
-
return this.prepareRequest<T>('POST', url, data, options);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Executes an HTTP PUT request to entirely replace a resource.
|
|
76
|
-
* * @param url - The endpoint URL.
|
|
77
|
-
* @param data - The payload object to be serialized and sent.
|
|
78
|
-
* @param options - Request-specific options.
|
|
79
|
-
*/
|
|
80
|
-
public async put<T>(
|
|
81
|
-
url: string,
|
|
82
|
-
data?: any,
|
|
83
|
-
options?: AxiomRequestOptions
|
|
84
|
-
): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
85
|
-
return this.prepareRequest<T>('PUT', url, data, options);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Executes an HTTP PATCH request to partially update a resource.
|
|
90
|
-
* * @param url - The endpoint URL.
|
|
91
|
-
* @param data - The partial payload object to be serialized and sent.
|
|
92
|
-
* @param options - Request-specific options.
|
|
93
|
-
*/
|
|
94
|
-
public async patch<T>(
|
|
95
|
-
url: string,
|
|
96
|
-
data?: any,
|
|
97
|
-
options?: AxiomRequestOptions
|
|
98
|
-
): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
99
|
-
return this.prepareRequest<T>('PATCH', url, data, options);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Executes an HTTP DELETE request.
|
|
104
|
-
* * @param url - The endpoint URL.
|
|
105
|
-
* @param options - Request-specific options.
|
|
106
|
-
*/
|
|
107
|
-
public async delete<T>(
|
|
108
|
-
url: string,
|
|
109
|
-
options?: AxiomRequestOptions
|
|
110
|
-
): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
111
|
-
return this.prepareRequest<T>('DELETE', url, undefined, options);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Internal helper to consolidate request preparation and keep the engine DRY.
|
|
116
|
-
*/
|
|
117
|
-
private async prepareRequest<T>(
|
|
118
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
|
|
119
|
-
url: string,
|
|
120
|
-
data?: any,
|
|
121
|
-
options?: AxiomRequestOptions
|
|
122
|
-
): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
123
|
-
const fullUrl = this.config.baseURL ? `${this.config.baseURL}${url}` : url;
|
|
124
|
-
|
|
125
|
-
const headers: Record<string, string> = { ...(this.config.defaultHeaders || {}) };
|
|
126
|
-
if (options?.headers) {
|
|
127
|
-
Object.assign(headers, options.headers);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const request: QueuedRequest = {
|
|
131
|
-
id: this.generateId(),
|
|
132
|
-
timestamp: Date.now(),
|
|
133
|
-
url: fullUrl,
|
|
134
|
-
method,
|
|
135
|
-
headers,
|
|
136
|
-
body: data ? JSON.stringify(data) : undefined,
|
|
137
|
-
priority: options?.priority || 'urgent',
|
|
138
|
-
retryCount: 0
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const timeoutMs = options?.timeout || this.config.timeout || 8000;
|
|
142
|
-
|
|
143
|
-
return this.attemptFetch<T>(request, timeoutMs);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Internal logic to fire the request or catch the network drop.
|
|
148
|
-
* Handles timeout cancellations via AbortController.
|
|
149
|
-
*/
|
|
150
|
-
private async attemptFetch<T>(request: QueuedRequest, timeoutMs: number): Promise<{ data?: T; status: number; isQueued: boolean }> {
|
|
151
|
-
const controller = new AbortController();
|
|
152
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const response = await fetch(request.url, {
|
|
156
|
-
method: request.method,
|
|
157
|
-
headers: request.headers,
|
|
158
|
-
body: request.body,
|
|
159
|
-
signal: controller.signal
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
clearTimeout(timeoutId);
|
|
163
|
-
|
|
164
|
-
if (response.ok) {
|
|
165
|
-
const responseData = await response.json().catch(() => null);
|
|
166
|
-
return { data: responseData, status: response.status, isQueued: false };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (response.status >= 500) {
|
|
170
|
-
throw new Error('Server Error');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { status: response.status, isQueued: false };
|
|
174
|
-
|
|
175
|
-
} catch (error: any) {
|
|
176
|
-
clearTimeout(timeoutId);
|
|
177
|
-
|
|
178
|
-
if(error.name === 'AbortError') {
|
|
179
|
-
console.warn(`[Axiom] Request to ${request.url} timed out after ${timeoutMs}ms. Queuing for retry.`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
await this.enqueueRequest(request);
|
|
183
|
-
|
|
184
|
-
return { status: 202, isQueued: true };
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Saves the request to the configured storage adapter.
|
|
190
|
-
*/
|
|
191
|
-
private async enqueueRequest(request: QueuedRequest): Promise<void> {
|
|
192
|
-
console.warn(`[Axiom] Network unreachable. Queuing request ${request.id}`);
|
|
193
|
-
await this.storage.save(request);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export const axiom = new AxiomEngine();
|
package/src/engine/sync.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { AxiomStorageAdapter } from '../adapters';
|
|
2
|
-
import { AxiomConfig, QueuedRequest } from '../types';
|
|
3
|
-
|
|
4
|
-
export class SyncManager {
|
|
5
|
-
private isSyncing = false;
|
|
6
|
-
|
|
7
|
-
constructor(
|
|
8
|
-
private storage: AxiomStorageAdapter,
|
|
9
|
-
private config: AxiomConfig
|
|
10
|
-
) {}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The master trigger. Call this when the OS reports network is back online.
|
|
14
|
-
* Automatically sorts requests so 'urgent' items bypass 'background' items.
|
|
15
|
-
*/
|
|
16
|
-
public async flushQueue(): Promise<void> {
|
|
17
|
-
if (this.isSyncing) return;
|
|
18
|
-
this.isSyncing = true;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const pending = await this.storage.getAll();
|
|
22
|
-
|
|
23
|
-
if (pending.length === 0) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.log(`[Axiom] Network restored. Syncing ${pending.length} queued requests...`);
|
|
28
|
-
|
|
29
|
-
// MITIGATION 3: Priority Lanes (Urgent requests jump the line)
|
|
30
|
-
// If priorities match, it falls back to timestamp (FIFO) to maintain action order.
|
|
31
|
-
const sortedQueue = pending.sort((a, b) => {
|
|
32
|
-
if (a.priority === 'urgent' && b.priority !== 'urgent') return -1;
|
|
33
|
-
if (a.priority !== 'urgent' && b.priority === 'urgent') return 1;
|
|
34
|
-
return a.timestamp - b.timestamp;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
for (const request of sortedQueue) {
|
|
38
|
-
await this.processRequest(request);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
} finally {
|
|
42
|
-
this.isSyncing = false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Attempts to execute a single saved request.
|
|
48
|
-
*/
|
|
49
|
-
private async processRequest(request: QueuedRequest): Promise<void> {
|
|
50
|
-
let reqToSync = request;
|
|
51
|
-
|
|
52
|
-
// MITIGATION 1: Just-in-Time Headers (Refresh Auth Tokens)
|
|
53
|
-
if (this.config.onBeforeSync) {
|
|
54
|
-
try {
|
|
55
|
-
reqToSync = await this.config.onBeforeSync(request);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.error(`[Axiom] onBeforeSync failed for ${request.id}. Marking as failure.`);
|
|
58
|
-
await this.handleFailure(request); // FIX: Ensure we increment retry count if this fails
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Apply the global timeout to background syncing as well
|
|
64
|
-
const controller = new AbortController();
|
|
65
|
-
const timeoutMs = this.config.timeout || 10000; // Default 10s for background syncs
|
|
66
|
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const response = await fetch(reqToSync.url, {
|
|
70
|
-
method: reqToSync.method,
|
|
71
|
-
headers: reqToSync.headers,
|
|
72
|
-
body: reqToSync.body,
|
|
73
|
-
signal: controller.signal
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
clearTimeout(timeoutId);
|
|
77
|
-
|
|
78
|
-
// If it's a success OR a permanent 400 error (like bad data), remove it.
|
|
79
|
-
if (response.ok || (response.status >= 400 && response.status < 500)) {
|
|
80
|
-
await this.storage.remove(reqToSync.id);
|
|
81
|
-
console.log(`[Axiom] Request ${reqToSync.id} synced successfully.`);
|
|
82
|
-
} else {
|
|
83
|
-
// It's a 500 Server Error. Treat as a failure and retry later.
|
|
84
|
-
await this.handleFailure(reqToSync);
|
|
85
|
-
}
|
|
86
|
-
} catch (error) {
|
|
87
|
-
clearTimeout(timeoutId);
|
|
88
|
-
// Network dropped again mid-sync or timeout was reached.
|
|
89
|
-
await this.handleFailure(reqToSync);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* MITIGATION 2: The Dead Letter Queue logic
|
|
95
|
-
*/
|
|
96
|
-
private async handleFailure(request: QueuedRequest): Promise<void> {
|
|
97
|
-
request.retryCount += 1;
|
|
98
|
-
const maxRetries = this.config.maxRetries ?? 3; // Use nullish coalescing so 0 is valid
|
|
99
|
-
|
|
100
|
-
if (request.retryCount >= maxRetries) {
|
|
101
|
-
console.warn(`[Axiom] Request ${request.id} failed ${maxRetries} times. Moving to Dead Letter.`);
|
|
102
|
-
await this.storage.remove(request.id);
|
|
103
|
-
|
|
104
|
-
if (this.config.onDeadLetter) {
|
|
105
|
-
this.config.onDeadLetter(request, new Error('Max retries exceeded'));
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
// Save the updated retry count back to storage
|
|
109
|
-
await this.storage.save(request);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
-
import { axiom } from '../engine/fetcher';
|
|
3
|
-
import { AxiomConfig } from '../types';
|
|
4
|
-
import { AxiomStorageAdapter } from '../adapters';
|
|
5
|
-
|
|
6
|
-
interface AxiomContextType {
|
|
7
|
-
isOnline: boolean;
|
|
8
|
-
forceSync: () => Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Create a safe default context
|
|
12
|
-
const AxiomContext = createContext<AxiomContextType>({
|
|
13
|
-
isOnline: true,
|
|
14
|
-
forceSync: async () => {},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export const AxiomProvider: React.FC<{
|
|
18
|
-
config: AxiomConfig;
|
|
19
|
-
storageAdapter?: AxiomStorageAdapter;
|
|
20
|
-
/** * A function that takes a callback, calls it whenever network state changes,
|
|
21
|
-
* and returns an unsubscribe function.
|
|
22
|
-
*/
|
|
23
|
-
networkListener: (callback: (isOnline: boolean) => void) => any;
|
|
24
|
-
children: React.ReactNode;
|
|
25
|
-
}> = ({ config, storageAdapter, networkListener, children }) => {
|
|
26
|
-
const [isOnline, setIsOnline] = useState(true);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
// 1. Boot up the Axiom engine
|
|
30
|
-
axiom.create(config, storageAdapter);
|
|
31
|
-
|
|
32
|
-
// 2. Attach the OS-level network listener
|
|
33
|
-
const unsubscribe = networkListener((onlineStatus) => {
|
|
34
|
-
setIsOnline(onlineStatus);
|
|
35
|
-
|
|
36
|
-
// 3. The Magic: If the internet comes back, automatically flush the queue
|
|
37
|
-
if (onlineStatus) {
|
|
38
|
-
axiom.forceSync();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// Cleanup listener on unmount
|
|
43
|
-
return () => {
|
|
44
|
-
if (typeof unsubscribe === 'function') {
|
|
45
|
-
unsubscribe();
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}, [config, storageAdapter, networkListener]);
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<AxiomContext.Provider value={{ isOnline, forceSync: () => axiom.forceSync() }}>
|
|
52
|
-
{children}
|
|
53
|
-
</AxiomContext.Provider>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export const useAxiomContext = () => useContext(AxiomContext);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { useAxiomContext } from './AxiomProvider';
|
|
2
|
-
|
|
3
|
-
export function useAxiomQueue() {
|
|
4
|
-
const { isOnline, forceSync } = useAxiomContext();
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
/** Boolean indicating if the device currently has an active connection */
|
|
8
|
-
isOnline,
|
|
9
|
-
/** Manually trigger the background sync manager */
|
|
10
|
-
forceSync,
|
|
11
|
-
};
|
|
12
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/** * Defines how the request should be treated when the queue flushes.
|
|
2
|
-
* Urgent requests bypass the background queue entirely if the network is active.
|
|
3
|
-
*/
|
|
4
|
-
export type RequestPriority = 'urgent' | 'background';
|
|
5
|
-
|
|
6
|
-
/** * Represents a serialized HTTP request frozen in offline storage.
|
|
7
|
-
*/
|
|
8
|
-
export interface QueuedRequest {
|
|
9
|
-
id: string;
|
|
10
|
-
timestamp: number;
|
|
11
|
-
url: string;
|
|
12
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
13
|
-
headers: Record<string, string>;
|
|
14
|
-
body?: string;
|
|
15
|
-
priority: RequestPriority;
|
|
16
|
-
retryCount: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** * Global configuration for the Axiom engine initialized on startup.
|
|
20
|
-
*/
|
|
21
|
-
export interface AxiomConfig {
|
|
22
|
-
/** The base URL prepended to all request paths. */
|
|
23
|
-
baseURL?: string;
|
|
24
|
-
/** Global headers applied to every request (e.g., Auth tokens). */
|
|
25
|
-
defaultHeaders?: Record<string, string>;
|
|
26
|
-
/** The maximum number of times a queued request will attempt to sync before failing permanently. */
|
|
27
|
-
maxRetries?: number;
|
|
28
|
-
/** Global timeout in milliseconds before a request is aborted and queued. */
|
|
29
|
-
timeout?: number;
|
|
30
|
-
/** Middleware hook triggered immediately before a queued request is synced. Ideal for refreshing Auth tokens. */
|
|
31
|
-
onBeforeSync?: (request: QueuedRequest) => Promise<QueuedRequest>;
|
|
32
|
-
/** Callback triggered when a request exceeds maxRetries and is permanently removed from the queue. */
|
|
33
|
-
onDeadLetter?: (request: QueuedRequest, error: Error) => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** * Per-request configuration options that override the global configuration.
|
|
37
|
-
*/
|
|
38
|
-
export interface AxiomRequestOptions {
|
|
39
|
-
/** Overrides the queue sorting behavior for this specific request. */
|
|
40
|
-
priority?: RequestPriority;
|
|
41
|
-
/** Overrides the global timeout limit for this specific request. */
|
|
42
|
-
timeout?: number;
|
|
43
|
-
/** Specific headers to append to this single request (e.g., custom Content-Type). */
|
|
44
|
-
headers?: Record<string, string>;
|
|
45
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"strict": true,
|
|
7
|
-
"jsx": "react-jsx",
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"ignoreDeprecations": "6.0"
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"],
|
|
15
|
-
"exclude": ["node_modules", "dist", "tests"]
|
|
16
|
-
}
|