@standardbeagle/data-router 1.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.
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # @standardbeagle/data-router
2
+
3
+ A memory-only, XPath-based router for React that operates with data structures instead of URLs. Perfect for complex React components that need internal routing and data manipulation without interfering with the parent application's routing system.
4
+
5
+ ## Features
6
+
7
+ - **XPath Navigation**: Navigate through data structures using XPath expressions
8
+ - **Data Manipulation**: Built-in CRUD operations (merge, replace, append, delete)
9
+ - **Form Integration**: Form components that automatically update data at specified XPaths
10
+ - **Memory-only**: No browser URL integration - purely internal routing
11
+ - **TypeScript Support**: Full TypeScript support with strict typing
12
+ - **React Hooks**: Hook-based API for easy integration
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @standardbeagle/data-router
18
+ ```
19
+
20
+ ## Basic Usage
21
+
22
+ ```tsx
23
+ import { DataProvider, useXPath, useData, Link, Form, Button } from '@standardbeagle/data-router';
24
+
25
+ // Initialize with some data
26
+ const initialData = {
27
+ users: [
28
+ { id: 1, name: 'John', profile: { email: 'john@example.com' } }
29
+ ],
30
+ settings: { theme: 'light' }
31
+ };
32
+
33
+ function App() {
34
+ return (
35
+ <DataProvider initialData={initialData} initialXPath="/users[0]">
36
+ <UserProfile />
37
+ </DataProvider>
38
+ );
39
+ }
40
+
41
+ function UserProfile() {
42
+ const xpath = useXPath(); // "/users[0]"
43
+ const targetData = useTargetData(); // { id: 1, name: 'John', profile: {...} }
44
+
45
+ return (
46
+ <div>
47
+ <h1>Current Location: {xpath}</h1>
48
+ <p>User: {targetData.name}</p>
49
+
50
+ {/* Navigate to user's profile */}
51
+ <Link to="profile">View Profile</Link>
52
+
53
+ {/* Navigate to settings */}
54
+ <Link to="/settings">Go to Settings</Link>
55
+
56
+ {/* Form to update user data */}
57
+ <Form targetXPath="/users[0]" operation="merge">
58
+ <input name="name" defaultValue={targetData.name} />
59
+ <Button dataAction="merge">Update User</Button>
60
+ </Form>
61
+ </div>
62
+ );
63
+ }
64
+ ```
65
+
66
+ ## Core Concepts
67
+
68
+ ### XPath Navigation
69
+ - **Absolute paths**: `/users[0]/profile/email`
70
+ - **Relative paths**: `profile/settings` (relative to current XPath)
71
+ - **Parent navigation**: `..` (go up one level), `../..` (go up two levels)
72
+
73
+ ### Data Operations
74
+ - **merge**: Combine form data with existing object
75
+ - **replace**: Replace entire object/value at XPath
76
+ - **append**: Add to arrays or create new properties
77
+ - **delete**: Remove properties/array elements
78
+
79
+ ### Components
80
+
81
+ #### DataProvider
82
+ Provides the data context and routing state.
83
+
84
+ ```tsx
85
+ <DataProvider
86
+ initialData={myData}
87
+ initialXPath="/users[0]"
88
+ >
89
+ {children}
90
+ </DataProvider>
91
+ ```
92
+
93
+ #### Link
94
+ Navigate to different XPaths (read-only navigation).
95
+
96
+ ```tsx
97
+ <Link to="/users[1]">Go to User 1</Link>
98
+ <Link to="../settings">Go to Settings</Link>
99
+ ```
100
+
101
+ #### Form
102
+ Wrap form elements for data manipulation.
103
+
104
+ ```tsx
105
+ <Form targetXPath="/users[0]" operation="merge">
106
+ <input name="email" />
107
+ <Button dataAction="merge">Save</Button>
108
+ </Form>
109
+ ```
110
+
111
+ #### Button
112
+ Submit button with data manipulation capabilities.
113
+
114
+ ```tsx
115
+ <Button
116
+ dataAction="replace"
117
+ targetXPath="/settings/theme"
118
+ navigateTo="/settings"
119
+ >
120
+ Update Theme
121
+ </Button>
122
+ ```
123
+
124
+ ### Hooks
125
+
126
+ - `useXPath()` - Get current XPath
127
+ - `useData()` - Get entire data object
128
+ - `useTargetData()` - Get data at current XPath
129
+ - `useNavigate()` - Get navigation function
130
+ - `useDataManipulation()` - Get data manipulation functions
131
+
132
+ ## License
133
+
134
+ ISC
@@ -0,0 +1,19 @@
1
+ import { ReactNode, ButtonHTMLAttributes } from 'react';
2
+ import { FormData, DataOperationType } from './types';
3
+ interface FormHandlerProps {
4
+ xpath?: string;
5
+ operation?: DataOperationType;
6
+ onSubmit?: (data: FormData, navigate: (xpath: string) => void) => void;
7
+ children: ReactNode;
8
+ className?: string;
9
+ id?: string;
10
+ }
11
+ export declare function Form({ xpath, operation, onSubmit, children, ...props }: FormHandlerProps): import("react/jsx-runtime").JSX.Element;
12
+ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
13
+ dataAction?: DataOperationType;
14
+ targetXPath?: string;
15
+ navigateTo?: string;
16
+ children: ReactNode;
17
+ }
18
+ export declare function Button({ dataAction, targetXPath, navigateTo, children, onClick, type, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
19
+ export {};
@@ -0,0 +1,46 @@
1
+ export interface BatchOperation<T = any> {
2
+ xpath: string;
3
+ fetcher: () => Promise<T>;
4
+ priority?: 'low' | 'normal' | 'high';
5
+ onSuccess?: (data: T) => void;
6
+ onError?: (error: Error) => void;
7
+ }
8
+ export interface BatchConfig {
9
+ concurrency?: number;
10
+ failFast?: boolean;
11
+ retryCount?: number;
12
+ retryDelay?: number;
13
+ onBatchComplete?: (results: BatchResult[]) => void;
14
+ onBatchError?: (error: Error, results: BatchResult[]) => void;
15
+ }
16
+ export interface BatchResult<T = any> {
17
+ xpath: string;
18
+ status: 'success' | 'error' | 'pending';
19
+ data?: T;
20
+ error?: Error;
21
+ }
22
+ export interface AsyncBatchResult {
23
+ execute: () => Promise<BatchResult[]>;
24
+ cancel: () => void;
25
+ isLoading: boolean;
26
+ isError: boolean;
27
+ isSuccess: boolean;
28
+ isIdle: boolean;
29
+ results: BatchResult[];
30
+ progress: {
31
+ total: number;
32
+ completed: number;
33
+ failed: number;
34
+ pending: number;
35
+ percentage: number;
36
+ };
37
+ }
38
+ export declare function useAsyncBatch(operations: BatchOperation[], config?: BatchConfig): AsyncBatchResult;
39
+ export declare function useAsyncParallel<T = any>(operations: Array<{
40
+ xpath: string;
41
+ fetcher: () => Promise<T>;
42
+ }>, config?: Omit<BatchConfig, 'concurrency'>): AsyncBatchResult;
43
+ export declare function useAsyncSequential<T = any>(operations: Array<{
44
+ xpath: string;
45
+ fetcher: () => Promise<T>;
46
+ }>, config?: Omit<BatchConfig, 'concurrency'>): AsyncBatchResult;
@@ -0,0 +1,23 @@
1
+ export interface AsyncDataConfig {
2
+ enabled?: boolean;
3
+ staleTime?: number;
4
+ cacheTime?: number;
5
+ refetchOnWindowFocus?: boolean;
6
+ refetchOnReconnect?: boolean;
7
+ retryCount?: number;
8
+ retryDelay?: number;
9
+ onSuccess?: (data: any) => void;
10
+ onError?: (error: Error) => void;
11
+ }
12
+ export interface AsyncDataResult<T = any> {
13
+ data: T | undefined;
14
+ isLoading: boolean;
15
+ isError: boolean;
16
+ isSuccess: boolean;
17
+ isIdle: boolean;
18
+ error: Error | undefined;
19
+ refetch: () => Promise<T>;
20
+ cancel: () => void;
21
+ invalidate: () => void;
22
+ }
23
+ export declare function useAsyncData<T = any>(xpath: string, fetcher: () => Promise<T>, config?: AsyncDataConfig): AsyncDataResult<T>;
@@ -0,0 +1,22 @@
1
+ export interface AsyncMutationConfig<TData = any, TVariables = any> {
2
+ optimisticUpdate?: (currentData: TData, variables: TVariables) => TData;
3
+ rollbackOnError?: boolean;
4
+ invalidate?: string[];
5
+ retryCount?: number;
6
+ retryDelay?: number;
7
+ onSuccess?: (data: TData, variables: TVariables) => void;
8
+ onError?: (error: Error, variables: TVariables) => void;
9
+ onSettled?: (data: TData | undefined, error: Error | undefined, variables: TVariables) => void;
10
+ }
11
+ export interface AsyncMutationResult<TData = any, TVariables = any> {
12
+ mutate: (variables: TVariables) => Promise<TData>;
13
+ mutateAsync: (variables: TVariables) => Promise<TData>;
14
+ reset: () => void;
15
+ isLoading: boolean;
16
+ isError: boolean;
17
+ isSuccess: boolean;
18
+ isIdle: boolean;
19
+ data: TData | undefined;
20
+ error: Error | undefined;
21
+ }
22
+ export declare function useAsyncMutation<TData = any, TVariables = any>(xpath: string, mutationFn: (variables: TVariables) => Promise<TData>, config?: AsyncMutationConfig<TData, TVariables>): AsyncMutationResult<TData, TVariables>;
@@ -0,0 +1,44 @@
1
+ import { AsyncState } from '../types';
2
+ export interface AsyncStateInfo extends AsyncState {
3
+ xpath: string;
4
+ hasPendingOperation: boolean;
5
+ isStale: (staleTime?: number) => boolean;
6
+ }
7
+ export interface AsyncStateResult<T = any> {
8
+ data: T | undefined;
9
+ asyncState: AsyncStateInfo | undefined;
10
+ isLoading: boolean;
11
+ isError: boolean;
12
+ isSuccess: boolean;
13
+ isIdle: boolean;
14
+ error: Error | undefined;
15
+ isPending: boolean;
16
+ lastUpdated: number | undefined;
17
+ }
18
+ export declare function useAsyncState<T = any>(xpath: string): AsyncStateResult<T>;
19
+ export declare function useAsyncStates(xpaths: string[]): Record<string, AsyncStateResult>;
20
+ export declare function useGlobalAsyncState(): {
21
+ summary: {
22
+ totalStates: number;
23
+ loadingStates: number;
24
+ errorStates: number;
25
+ successStates: number;
26
+ idleStates: number;
27
+ pendingOperations: number;
28
+ executingOperations: number;
29
+ queuedOperations: number;
30
+ hasAnyLoading: boolean;
31
+ hasAnyError: boolean;
32
+ optimisticUpdateCount: number;
33
+ };
34
+ statesByStatus: {
35
+ loading: [string, AsyncState][];
36
+ error: [string, AsyncState][];
37
+ success: [string, AsyncState][];
38
+ idle: [string, AsyncState][];
39
+ };
40
+ asyncStates: Record<string, AsyncState>;
41
+ pendingOperations: ReadonlySet<string>;
42
+ commandQueue: import("../types").CommandQueue;
43
+ optimisticUpdates: Record<string, import("../types").OptimisticUpdate>;
44
+ };
@@ -0,0 +1,3 @@
1
+ export declare function useData(): Record<string, any>;
2
+ export declare function useTargetData(): any;
3
+ export declare function useDataAtXPath(xpath: string): any;
@@ -0,0 +1,2 @@
1
+ import { DataManipulationHook } from '../types';
2
+ export declare function useDataManipulation(): DataManipulationHook;
@@ -0,0 +1,28 @@
1
+ export interface InvalidationConfig {
2
+ includeChildren?: boolean;
3
+ includeParents?: boolean;
4
+ clearData?: boolean;
5
+ force?: boolean;
6
+ }
7
+ export interface InvalidationResult {
8
+ invalidate: (xpath: string, config?: InvalidationConfig) => void;
9
+ invalidateMany: (xpaths: string[], config?: InvalidationConfig) => void;
10
+ invalidatePattern: (pattern: string, config?: InvalidationConfig) => void;
11
+ invalidateAll: () => void;
12
+ revalidate: (xpath: string) => void;
13
+ revalidateMany: (xpaths: string[]) => void;
14
+ getInvalidationCount: () => number;
15
+ }
16
+ export declare function useInvalidation(): InvalidationResult;
17
+ export declare function useAutoInvalidation(dependencies: string[], config?: InvalidationConfig & {
18
+ debounceMs?: number;
19
+ onInvalidate?: (paths: string[]) => void;
20
+ }): {
21
+ trigger: () => void;
22
+ invalidateDependencies: () => void;
23
+ };
24
+ export declare function useQueryInvalidation(): {
25
+ invalidateQueries: (queryKey: string | string[]) => void;
26
+ invalidateQueriesMatching: (predicate: (path: string) => boolean) => void;
27
+ getQueries: (queryKey?: string | string[]) => string[];
28
+ };
@@ -0,0 +1,4 @@
1
+ import { NavigationObject } from '../types';
2
+ export declare function useNavigate(): (xpath: string) => void;
3
+ export declare function useNavigation(): NavigationObject;
4
+ export declare function useHistory(): readonly string[];
@@ -0,0 +1,27 @@
1
+ export interface OptimisticUpdateConfig<T = any> {
2
+ rollbackOnError?: boolean;
3
+ onRollback?: (originalData: T) => void;
4
+ onCommit?: (newData: T) => void;
5
+ }
6
+ export interface OptimisticUpdateResult<T = any> {
7
+ apply: (newData: T) => string;
8
+ rollback: (updateId: string) => void;
9
+ commit: (updateId: string) => void;
10
+ rollbackAll: () => void;
11
+ getOriginalData: (updateId: string) => T | undefined;
12
+ hasOptimisticUpdates: boolean;
13
+ optimisticUpdateIds: string[];
14
+ }
15
+ export declare function useOptimisticUpdates<T = any>(xpath: string, config?: OptimisticUpdateConfig<T>): OptimisticUpdateResult<T>;
16
+ export declare function useOptimisticList<T = any>(xpath: string, config?: OptimisticUpdateConfig<T[]>): OptimisticUpdateResult<T[]> & {
17
+ addItem: (item: T, position?: number) => string;
18
+ updateItem: (index: number, item: T) => string;
19
+ removeItem: (index: number) => string;
20
+ moveItem: (fromIndex: number, toIndex: number) => string;
21
+ };
22
+ export declare function useOptimisticObject<T extends Record<string, any>>(xpath: string, config?: OptimisticUpdateConfig<T>): OptimisticUpdateResult<T> & {
23
+ updateProperty: <K extends keyof T>(key: K, value: T[K]) => string;
24
+ updateProperties: (updates: Partial<T>) => string;
25
+ removeProperty: <K extends keyof T>(key: K) => string;
26
+ mergeObject: (updates: Partial<T>) => string;
27
+ };
@@ -0,0 +1 @@
1
+ export declare function useXPath(): string;
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import type { ComponentProps } from 'react';
3
+ import { ReactNode } from 'react';
4
+ export { DataProvider } from './provider';
5
+ export { useXPath } from './hooks/useXPath';
6
+ export { useData, useTargetData, useDataAtXPath } from './hooks/useData';
7
+ export { useNavigate, useNavigation, useHistory } from './hooks/useNavigate';
8
+ export { useDataManipulation } from './hooks/useDataManipulation';
9
+ export { useAsyncData } from './hooks/useAsyncData';
10
+ export { useAsyncMutation } from './hooks/useAsyncMutation';
11
+ export { useAsyncState, useAsyncStates, useGlobalAsyncState } from './hooks/useAsyncState';
12
+ export { useAsyncBatch, useAsyncParallel, useAsyncSequential } from './hooks/useAsyncBatch';
13
+ export { useOptimisticUpdates, useOptimisticList, useOptimisticObject } from './hooks/useOptimisticUpdates';
14
+ export { useInvalidation, useAutoInvalidation, useQueryInvalidation } from './hooks/useInvalidation';
15
+ export { asyncStart, asyncSuccess, asyncError, asyncCancel, commandQueueUpdate, generateRequestId } from './state/actions';
16
+ export { CommandQueueManager } from './state/command-queue';
17
+ export type { AsyncState, AsyncCommand, CommandQueue, OptimisticUpdate } from './types';
18
+ export type { AsyncDataConfig, AsyncDataResult } from './hooks/useAsyncData';
19
+ export type { AsyncMutationConfig, AsyncMutationResult } from './hooks/useAsyncMutation';
20
+ export type { AsyncStateInfo, AsyncStateResult } from './hooks/useAsyncState';
21
+ export type { BatchOperation, BatchConfig, BatchResult, AsyncBatchResult } from './hooks/useAsyncBatch';
22
+ export type { OptimisticUpdateConfig, OptimisticUpdateResult } from './hooks/useOptimisticUpdates';
23
+ export type { InvalidationConfig, InvalidationResult } from './hooks/useInvalidation';
24
+ export { Form, Button } from './FormHandler';
25
+ interface LinkProps extends Omit<ComponentProps<'a'>, 'href' | 'onClick'> {
26
+ to: string;
27
+ children: ReactNode;
28
+ }
29
+ export declare const Link: React.ForwardRefExoticComponent<Omit<LinkProps, "ref"> & React.RefAttributes<HTMLAnchorElement>>;
@@ -0,0 +1 @@
1
+ export * from './index'