@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/README.md +356 -1
- package/dist/index.d.mts +85 -13
- package/dist/index.d.ts +85 -13
- package/dist/index.js +92 -34
- package/dist/index.mjs +92 -34
- package/package.json +26 -5
- package/.editorconfig +0 -8
- package/.gitattributes +0 -4
- package/src/adapters/index.ts +0 -15
- package/src/adapters/memory.ts +0 -22
- package/src/engine/fetcher.ts +0 -131
- package/src/engine/sync.ts +0 -96
- 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 -20
- package/tsconfig.json +0 -16
package/src/engine/sync.ts
DELETED
|
@@ -1,96 +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
|
-
*/
|
|
15
|
-
public async flushQueue(): Promise<void> {
|
|
16
|
-
// Prevent overlapping syncs if the network toggles rapidly
|
|
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
|
-
this.isSyncing = false;
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
console.log(`[Axiom] Network restored. Syncing ${pending.length} queued requests...`);
|
|
29
|
-
|
|
30
|
-
// We process sequentially to maintain the order of user actions
|
|
31
|
-
for (const request of pending) {
|
|
32
|
-
await this.processRequest(request);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
} finally {
|
|
36
|
-
this.isSyncing = false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Attempts to execute a single saved request.
|
|
42
|
-
*/
|
|
43
|
-
private async processRequest(request: QueuedRequest): Promise<void> {
|
|
44
|
-
let reqToSync = request;
|
|
45
|
-
|
|
46
|
-
// MITIGATION 1: Just-in-Time Headers (Refresh Auth Tokens)
|
|
47
|
-
if (this.config.onBeforeSync) {
|
|
48
|
-
try {
|
|
49
|
-
reqToSync = await this.config.onBeforeSync(request);
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.error(`[Axiom] onBeforeSync failed for ${request.id}. Skipping.`);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const response = await fetch(reqToSync.url, {
|
|
58
|
-
method: reqToSync.method,
|
|
59
|
-
headers: reqToSync.headers,
|
|
60
|
-
body: reqToSync.body
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// If it's a success OR a permanent 400 error (like bad data), remove it.
|
|
64
|
-
if (response.ok || (response.status >= 400 && response.status < 500)) {
|
|
65
|
-
await this.storage.remove(reqToSync.id);
|
|
66
|
-
console.log(`[Axiom] Request ${reqToSync.id} synced successfully.`);
|
|
67
|
-
} else {
|
|
68
|
-
// It's a 500 Server Error. Treat as a failure and retry later.
|
|
69
|
-
await this.handleFailure(reqToSync);
|
|
70
|
-
}
|
|
71
|
-
} catch (error) {
|
|
72
|
-
// Network dropped again mid-sync.
|
|
73
|
-
await this.handleFailure(reqToSync);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* MITIGATION 2: The Dead Letter Queue logic
|
|
79
|
-
*/
|
|
80
|
-
private async handleFailure(request: QueuedRequest): Promise<void> {
|
|
81
|
-
request.retryCount += 1;
|
|
82
|
-
const maxRetries = this.config.maxRetries || 3;
|
|
83
|
-
|
|
84
|
-
if (request.retryCount >= maxRetries) {
|
|
85
|
-
console.warn(`[Axiom] Request ${request.id} failed ${maxRetries} times. Moving to Dead Letter.`);
|
|
86
|
-
await this.storage.remove(request.id);
|
|
87
|
-
|
|
88
|
-
if (this.config.onDeadLetter) {
|
|
89
|
-
this.config.onDeadLetter(request, new Error('Max retries exceeded'));
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
// Save the updated retry count back to storage
|
|
93
|
-
await this.storage.save(request);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
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,20 +0,0 @@
|
|
|
1
|
-
export type RequestPriority = 'urgent' | 'background';
|
|
2
|
-
|
|
3
|
-
export interface QueuedRequest {
|
|
4
|
-
id: string;
|
|
5
|
-
timestamp: number;
|
|
6
|
-
url: string;
|
|
7
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
8
|
-
headers: Record<string, string>;
|
|
9
|
-
body?: string;
|
|
10
|
-
priority: RequestPriority;
|
|
11
|
-
retryCount: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface AxiomConfig {
|
|
15
|
-
baseURL?: string;
|
|
16
|
-
defaultHeaders?: Record<string, string>;
|
|
17
|
-
maxRetries?: number;
|
|
18
|
-
onBeforeSync?: (request: QueuedRequest) => Promise<QueuedRequest>;
|
|
19
|
-
onDeadLetter?: (request: QueuedRequest, error: Error) => void;
|
|
20
|
-
}
|
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
|
-
}
|