use-abcd 1.4.0 → 1.4.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.
@@ -0,0 +1 @@
1
+ export {};
package/dist/item.d.ts CHANGED
@@ -1,15 +1,11 @@
1
1
  import { Draft } from 'mutative';
2
2
  import { Collection } from './collection';
3
3
  import { ItemStatus } from './types';
4
- export declare class Item<T, C = unknown> {
4
+ export declare class Item<T extends object, C = unknown> {
5
5
  private _collection;
6
6
  private _id;
7
7
  private _cachedStatus;
8
- private _refCount;
9
8
  constructor(collection: Collection<T, C>, id: string);
10
- _retain(): void;
11
- _release(): void;
12
- get refCount(): number;
13
9
  get id(): string;
14
10
  get data(): T | undefined;
15
11
  update(mutate: (draft: Draft<T>) => void): void;
package/dist/node.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { Draft } from 'mutative';
2
+ import { Collection } from './collection';
3
+ import { ItemStatus } from './types';
4
+ export type TreeNode<T, NodeType = string> = {
5
+ id: string;
6
+ position: number;
7
+ value: T;
8
+ type: NodeType;
9
+ };
10
+ export declare class Node<T extends object, C = unknown, NodeType = string> {
11
+ private _id;
12
+ private _collection;
13
+ private _cachedStatus;
14
+ constructor(collection: Collection<TreeNode<T, NodeType>, C>, id: string);
15
+ private get _separator();
16
+ get id(): string;
17
+ get data(): TreeNode<T, NodeType> | undefined;
18
+ get collection(): Collection<TreeNode<T, NodeType>, C>;
19
+ get depth(): number;
20
+ exists(): boolean;
21
+ getStatus(): ItemStatus;
22
+ getParent(): Node<T, C, NodeType> | null;
23
+ getChildren(): Node<T, C, NodeType>[];
24
+ private _generateChildId;
25
+ append(value: T, type?: NodeType): string;
26
+ prepend(value: T, type?: NodeType): string;
27
+ /**
28
+ * Move node to a new position within the same parent (reordering)
29
+ * or to a different parent (reparenting).
30
+ *
31
+ * @param targetPosition - The target position among siblings
32
+ * @param targetParent - Optional target parent node. If not provided, moves within current parent.
33
+ */
34
+ move(targetPosition: number, targetParent?: Node<T, C, NodeType>): void;
35
+ /**
36
+ * Clone this node and all its descendants into a Map with remapped IDs.
37
+ * The root of the clone has a temporary ID that will be remapped when merged.
38
+ * @returns Map of cloned nodes keyed by their relative path from clone root
39
+ */
40
+ clone(): Map<string, TreeNode<T, NodeType>>;
41
+ /**
42
+ * Internal method to merge a cloned subset under this node.
43
+ * The cloned nodes will have their IDs prefixed with this node's ID.
44
+ */
45
+ _mergeClonedSubset(clonedSubset: Map<string, TreeNode<T, NodeType>>, position?: number): string | undefined;
46
+ updateProp(mutate: (draft: Draft<T>) => void): void;
47
+ remove(): void;
48
+ select(): void;
49
+ _updateId(newId: string): void;
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,12 @@ import { Change, SyncQueueState, SyncResult, IdMapping } from './types';
2
2
  export type SyncQueueConfig<T> = {
3
3
  debounce: number;
4
4
  maxRetries: number;
5
+ /**
6
+ * Maximum number of changes to send per sync call.
7
+ * Useful for rate limiting or API constraints.
8
+ * Default: Infinity (send all queued changes)
9
+ */
10
+ batchSize?: number;
5
11
  onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
6
12
  onIdRemap?: (mappings: IdMapping[]) => void;
7
13
  };
@@ -24,7 +30,37 @@ export declare class SyncQueue<T> {
24
30
  private _updateState;
25
31
  private _scheduleFlush;
26
32
  private _clearTimer;
33
+ /**
34
+ * Flush pending changes to the server.
35
+ *
36
+ * Flow:
37
+ * 1. Move queued items to inFlight (atomic swap prevents duplicate sends)
38
+ * 2. Flatten all changes and optionally batch by batchSize
39
+ * 3. Send to server via onSync callback
40
+ * 4. Process results (handle success, retries, ID remapping)
41
+ * 5. Schedule next flush if more items queued
42
+ */
27
43
  private _flush;
44
+ /**
45
+ * Process sync results and update state accordingly.
46
+ *
47
+ * For each item in flight:
48
+ * - If ALL operations succeeded: clear errors, collect ID remappings
49
+ * - If ANY operation failed: increment retry counter, re-queue if under maxRetries
50
+ *
51
+ * ID Remapping: When server assigns new IDs to created items (e.g., temp ID -> DB ID),
52
+ * we collect these mappings and notify via onIdRemap callback so the collection
53
+ * can update its local state.
54
+ */
28
55
  private _processResults;
56
+ /**
57
+ * Handle network or unexpected errors during sync.
58
+ *
59
+ * Two scenarios:
60
+ * 1. Aborted (e.g., SyncQueue destroyed): Re-queue all items without incrementing retries
61
+ * 2. Actual error: Increment retries, re-queue if under limit, record error
62
+ *
63
+ * This ensures no data loss - failed operations are preserved for retry.
64
+ */
29
65
  private _handleError;
30
66
  }
package/dist/types.d.ts CHANGED
@@ -45,7 +45,7 @@ export type SyncQueueState<T> = {
45
45
  isSyncing: boolean;
46
46
  };
47
47
  export type FetchState = "idle" | "fetching" | "error";
48
- export type Config<T, C> = {
48
+ export type Config<T extends object, C> = {
49
49
  id: string;
50
50
  initialContext: C;
51
51
  getId: (item: T) => string;
@@ -56,6 +56,9 @@ export type Config<T, C> = {
56
56
  cacheCapacity?: number;
57
57
  cacheTtl?: number;
58
58
  fetchRetries?: number;
59
+ rootId?: string;
60
+ getNodeId?: () => string;
61
+ nodeSeparator?: string;
59
62
  onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
60
63
  onSync?: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
61
64
  };
package/dist/useCrud.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Draft } from 'mutative';
2
2
  import { Config, Mutator } from './types';
3
3
  export type { Config, Change, SyncResult, ItemStatus, SyncQueueState, SyncState, ChangeType, ItemSyncStatus, FetchState, } from './types';
4
- export declare function useCrud<T, C>(config: Config<T, C>): {
4
+ export declare function useCrud<T extends object, C>(config: Config<T, C>): {
5
5
  items: Map<string, T>;
6
6
  context: C;
7
7
  syncState: import('./types').SyncState;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { Node, TreeNode } from './node';
2
+ import { Config, Mutator } from './types';
3
+ /**
4
+ * Config for useCrudTree - same as Config but with rootId required
5
+ */
6
+ export type TreeConfig<T extends object, C, NodeType = string> = Omit<Config<TreeNode<T, NodeType>, C>, "rootId"> & {
7
+ rootId: string;
8
+ };
9
+ /**
10
+ * Hook for tree-based CRUD operations.
11
+ * This is a replacement for useCrud when working with tree structures.
12
+ * It requires rootId in the config and returns the root node along with
13
+ * all the standard useCrud properties.
14
+ *
15
+ * @param config - Tree collection configuration with required rootId
16
+ * @returns Object with crud operations, state, and the root node
17
+ */
18
+ export declare function useCrudTree<T extends object, C, NodeType = string>(config: TreeConfig<T, C, NodeType>): {
19
+ rootNode: Node<T, C, NodeType>;
20
+ items: Map<string, TreeNode<T, NodeType>>;
21
+ context: C;
22
+ syncState: import('./types').SyncState;
23
+ syncQueue: import('./types').SyncQueueState<TreeNode<T, NodeType>>;
24
+ loading: boolean;
25
+ syncing: boolean;
26
+ fetchStatus: import('./types').FetchState;
27
+ fetchError: string;
28
+ getNode: (id: string) => Node<T, C, NodeType>;
29
+ getNodeStatus: (id: string) => {
30
+ type: import('./types').ChangeType;
31
+ status: import('./types').ItemSyncStatus;
32
+ retries: number;
33
+ error?: string;
34
+ };
35
+ setContext: (patchContext: Mutator<C>) => void;
36
+ refresh: () => Promise<void>;
37
+ pauseSync: () => void;
38
+ resumeSync: () => void;
39
+ retrySync: (id?: string) => void;
40
+ selectNode: (id: string) => void;
41
+ deselectNode: () => void;
42
+ selectedNodeId: string;
43
+ selectedNode: Node<T, C, NodeType>;
44
+ toJson: () => object;
45
+ };
@@ -0,0 +1 @@
1
+ export {};
package/dist/useItem.d.ts CHANGED
@@ -8,4 +8,4 @@ export type UseItemResult<T> = {
8
8
  remove: () => void;
9
9
  exists: boolean;
10
10
  };
11
- export declare function useItem<T, C>(item: Item<T, C>): UseItemResult<T>;
11
+ export declare function useItem<T extends object, C>(item: Item<T, C>): UseItemResult<T>;
@@ -0,0 +1,25 @@
1
+ import { Draft } from 'mutative';
2
+ import { Node, TreeNode } from './node';
3
+ import { ItemStatus } from './types';
4
+ /**
5
+ * Result type for useNode hook - contains all node data and operations
6
+ */
7
+ export type UseNodeResult<T extends object, C, NodeType = string> = {
8
+ isPresent: boolean;
9
+ data: TreeNode<T, NodeType> | undefined;
10
+ status: ItemStatus;
11
+ exists: boolean;
12
+ isSelected: boolean;
13
+ depth: number;
14
+ getParent: () => Node<T, C, NodeType> | null;
15
+ children: Node<T, C, NodeType>[];
16
+ append: (value: T, type?: NodeType) => string;
17
+ prepend: (value: T, type?: NodeType) => string;
18
+ move: (targetPosition: number, targetParent?: Node<T, C, NodeType>) => void;
19
+ clone: () => Map<string, TreeNode<T, NodeType>>;
20
+ updateProp: (mutate: (draft: Draft<T>) => void) => void;
21
+ remove: () => void;
22
+ select: () => void;
23
+ deselect: () => void;
24
+ };
25
+ export declare function useNode<T extends object, C, NodeType = string>(node: Node<T, C, NodeType>): UseNodeResult<T, C, NodeType>;
@@ -0,0 +1,12 @@
1
+ import { UseNodeResult } from './useNode';
2
+ /**
3
+ * Hook for accessing and operating on the currently selected node in a collection.
4
+ *
5
+ * This is a wrapper around useNode that automatically tracks the selected node.
6
+ * Use this in components that need to operate on whatever node is currently selected
7
+ * (e.g., toolbars, property panels, context menus).
8
+ *
9
+ * @param collectionId - The ID of the collection to get the selected node from
10
+ * @returns UseNodeResult with isPresent=true when a node is selected, or isPresent=false with empty data
11
+ */
12
+ export declare function useSelectedNode<T extends object, C, NodeType = string>(collectionId: string): UseNodeResult<T, C, NodeType>;
@@ -0,0 +1,20 @@
1
+ import { SyncState, Change, SyncError } from './types';
2
+ export type UseSyncStateResult<T = unknown> = {
3
+ syncState: SyncState;
4
+ syncing: boolean;
5
+ queue: Map<string, Change<T>[]>;
6
+ inFlight: Map<string, Change<T>[]>;
7
+ errors: Map<string, SyncError<T>>;
8
+ isPaused: boolean;
9
+ isSyncing: boolean;
10
+ };
11
+ /**
12
+ * Hook for accessing the sync state of a collection by ID.
13
+ *
14
+ * Use this when you need to display sync status in a component that
15
+ * doesn't have access to the full useCrud hook.
16
+ *
17
+ * @param collectionId - The ID of the collection
18
+ * @returns Flat object with syncState and all syncQueue properties
19
+ */
20
+ export declare function useSyncState<T = unknown>(collectionId: string): UseSyncStateResult<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-abcd",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "Simple react hook for CRUD type apps",
5
5
  "repository": {
6
6
  "url": "https://github.com/smtrd3/use-abcd"
@@ -53,6 +53,7 @@
53
53
  "@vitejs/plugin-react": "^5.0.0",
54
54
  "@vitest/coverage-v8": "^3.2.4",
55
55
  "ajv": "^8.17.1",
56
+ "baseline-browser-mapping": "^2.9.14",
56
57
  "eslint": "^9.33.0",
57
58
  "eslint-config-prettier": "^10.1.8",
58
59
  "eslint-plugin-prettier": "^5.5.4",