use-abcd 1.4.2 → 1.6.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.
Files changed (48) hide show
  1. package/README.md +225 -406
  2. package/dist/chunks/client-lHRDCo64.js +30 -0
  3. package/dist/chunks/client-lHRDCo64.js.map +1 -0
  4. package/dist/chunks/utils-DL5mgkvm.js +828 -0
  5. package/dist/chunks/utils-DL5mgkvm.js.map +1 -0
  6. package/dist/collection.d.ts +13 -5
  7. package/dist/examples/Benchmark.d.ts +2 -0
  8. package/dist/examples/LocalNotes.d.ts +2 -0
  9. package/dist/index.d.ts +5 -4
  10. package/dist/index.js +1074 -1791
  11. package/dist/index.js.map +1 -1
  12. package/dist/item.d.ts +3 -2
  13. package/dist/mocks/handlers.d.ts +0 -1
  14. package/dist/node.d.ts +5 -1
  15. package/dist/runtime/client.d.ts +10 -48
  16. package/dist/runtime/client.js +5 -13
  17. package/dist/runtime/client.js.map +1 -1
  18. package/dist/runtime/index.d.ts +5 -5
  19. package/dist/runtime/local.d.ts +32 -0
  20. package/dist/runtime/server.d.ts +25 -35
  21. package/dist/runtime/server.js +59 -108
  22. package/dist/runtime/server.js.map +1 -1
  23. package/dist/runtime/types.d.ts +28 -63
  24. package/dist/sync-queue.d.ts +10 -42
  25. package/dist/types.d.ts +22 -18
  26. package/dist/useCrud.d.ts +5 -3
  27. package/dist/useCrudTree.d.ts +3 -3
  28. package/dist/useItem.d.ts +3 -1
  29. package/dist/useNode.d.ts +3 -0
  30. package/dist/useSyncState.d.ts +2 -2
  31. package/dist/utils.d.ts +3 -4
  32. package/package.json +4 -2
  33. package/dist/cache.test.d.ts +0 -1
  34. package/dist/chunks/client-DReyDQ23.js +0 -144
  35. package/dist/chunks/client-DReyDQ23.js.map +0 -1
  36. package/dist/chunks/types-Dy4rYb2N.js +0 -19
  37. package/dist/chunks/types-Dy4rYb2N.js.map +0 -1
  38. package/dist/collection.e2e.test.d.ts +0 -1
  39. package/dist/fetch-handler.test.d.ts +0 -1
  40. package/dist/item-cache.test.d.ts +0 -1
  41. package/dist/node.test.d.ts +0 -1
  42. package/dist/runtime/client.test.d.ts +0 -1
  43. package/dist/runtime/server.test.d.ts +0 -1
  44. package/dist/sync-queue.test.d.ts +0 -1
  45. package/dist/useCrud.test.d.ts +0 -1
  46. package/dist/useCrudTree.test.d.ts +0 -1
  47. /package/dist/{App.d.ts → examples/App.d.ts} +0 -0
  48. /package/dist/{main.d.ts → examples/main.d.ts} +0 -0
@@ -1,23 +1,21 @@
1
- import { Change, SyncQueueState, SyncResult, IdMapping } from './types';
2
- export type SyncQueueConfig<T> = {
1
+ import { Change, SyncQueueState, SyncResponse } from './types';
2
+ export type SyncQueueConfig<T, C = unknown> = {
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
5
  batchSize?: number;
11
- onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
12
- onIdRemap?: (mappings: IdMapping[]) => void;
6
+ getContext?: () => C;
7
+ onSync: (changes: Change<T>[], context: C, signal: AbortSignal) => Promise<SyncResponse<T>>;
8
+ onServerItems?: (items: T[]) => void;
13
9
  };
14
- export declare class SyncQueue<T> {
10
+ export declare class SyncQueue<T extends {
11
+ id: string;
12
+ }, C = unknown> {
15
13
  private _config;
16
14
  private _state;
17
15
  private _subscribers;
18
16
  private _debounceTimer;
19
17
  private _abortController;
20
- constructor(config: SyncQueueConfig<T>);
18
+ constructor(config: SyncQueueConfig<T, C>);
21
19
  enqueue(change: Change<T>): void;
22
20
  pause(): void;
23
21
  resume(): void;
@@ -30,37 +28,7 @@ export declare class SyncQueue<T> {
30
28
  private _updateState;
31
29
  private _scheduleFlush;
32
30
  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
- */
43
31
  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
- */
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
- */
32
+ private _processResponse;
65
33
  private _handleError;
66
34
  }
package/dist/types.d.ts CHANGED
@@ -3,9 +3,17 @@ export type PlainObject = {
3
3
  [key: string]: JsonValue | PlainObject | JsonValue[] | PlainObject[];
4
4
  };
5
5
  export type Result = {
6
+ id: string;
7
+ type: ChangeType;
6
8
  status: "success" | "error";
9
+ serverSyncedAt: string;
7
10
  error?: string;
8
11
  };
12
+ export type SyncResponse<T> = {
13
+ items?: T[];
14
+ syncResults?: Result[];
15
+ serverSyncedAt: string;
16
+ };
9
17
  export type Fn<A, R = void> = (arg: A) => R;
10
18
  export type Mutator<T> = (draft: T) => void;
11
19
  export type SyncState = "idle" | "fetching" | "syncing";
@@ -22,34 +30,31 @@ export type ItemStatus = {
22
30
  retries: number;
23
31
  error?: string;
24
32
  } | null;
25
- export type SyncResult = {
26
- id: string;
27
- status: "success" | "error";
28
- error?: string;
29
- newId?: string;
30
- };
31
- export type IdMapping = {
32
- tempId: string;
33
- newId: string;
34
- };
35
33
  export type SyncError<T> = {
36
34
  error: string;
37
35
  retries: number;
38
- operations: Change<T>[];
36
+ operation: Change<T>;
39
37
  };
40
38
  export type SyncQueueState<T> = {
41
- queue: Map<string, Change<T>[]>;
42
- inFlight: Map<string, Change<T>[]>;
39
+ queue: Map<string, Change<T>>;
40
+ inFlight: Map<string, Change<T>>;
43
41
  errors: Map<string, SyncError<T>>;
44
42
  isPaused: boolean;
45
43
  isSyncing: boolean;
46
44
  };
47
45
  export type FetchState = "idle" | "fetching" | "error";
48
- export type Config<T extends object, C> = {
46
+ export type CrudHandler<T extends {
47
+ id: string;
48
+ }, C> = (params: {
49
+ query?: C;
50
+ changes?: Change<T>[];
51
+ }, signal?: AbortSignal) => Promise<SyncResponse<T>>;
52
+ export type Config<T extends {
53
+ id: string;
54
+ }, C> = {
49
55
  id: string;
50
56
  initialContext: C;
51
- getId: (item: T) => string;
52
- setId?: (item: T, newId: string) => T;
57
+ serverItems?: T[];
53
58
  syncDebounce?: number;
54
59
  syncRetries?: number;
55
60
  refetchOnMutation?: boolean;
@@ -59,6 +64,5 @@ export type Config<T extends object, C> = {
59
64
  rootId?: string;
60
65
  getNodeId?: () => string;
61
66
  nodeSeparator?: string;
62
- onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
63
- onSync?: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
67
+ handler?: CrudHandler<T, C>;
64
68
  };
package/dist/useCrud.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { Draft } from 'mutative';
2
2
  import { Config, Mutator } from './types';
3
- export type { Config, Change, SyncResult, ItemStatus, SyncQueueState, SyncState, ChangeType, ItemSyncStatus, FetchState, } from './types';
4
- export declare function useCrud<T extends object, C>(config: Config<T, C>): {
3
+ export type { Config, CrudHandler, Change, Result, ItemStatus, SyncQueueState, SyncState, ChangeType, ItemSyncStatus, FetchState, } from './types';
4
+ export declare function useCrud<T extends {
5
+ id: string;
6
+ }, C>(config: Config<T, C>): {
5
7
  items: Map<string, T>;
6
8
  context: C;
7
9
  syncState: import('./types').SyncState;
@@ -10,7 +12,7 @@ export declare function useCrud<T extends object, C>(config: Config<T, C>): {
10
12
  syncing: boolean;
11
13
  fetchStatus: import('./types').FetchState;
12
14
  fetchError: string;
13
- create: (item: T) => void;
15
+ create: (item: Omit<T, "id">) => string;
14
16
  update: (id: string, mutate: (draft: Draft<T>) => void) => void;
15
17
  remove: (id: string) => void;
16
18
  getItem: (id: string) => import('./item').Item<T, C>;
@@ -16,7 +16,7 @@ export type TreeConfig<T extends object, C, NodeType = string> = Omit<Config<Tre
16
16
  * @returns Object with crud operations, state, and the root node
17
17
  */
18
18
  export declare function useCrudTree<T extends object, C, NodeType = string>(config: TreeConfig<T, C, NodeType>): {
19
- rootNode: Node<T, C, NodeType>;
19
+ rootNode: any;
20
20
  items: Map<string, TreeNode<T, NodeType>>;
21
21
  context: C;
22
22
  syncState: import('./types').SyncState;
@@ -39,7 +39,7 @@ export declare function useCrudTree<T extends object, C, NodeType = string>(conf
39
39
  retrySync: (id?: string) => void;
40
40
  selectNode: (id: string) => void;
41
41
  deselectNode: () => void;
42
- selectedNodeId: string;
43
- selectedNode: Node<T, C, NodeType>;
42
+ selectedNodeId: any;
43
+ selectedNode: any;
44
44
  toJson: () => object;
45
45
  };
package/dist/useItem.d.ts CHANGED
@@ -8,4 +8,6 @@ export type UseItemResult<T> = {
8
8
  remove: () => void;
9
9
  exists: boolean;
10
10
  };
11
- export declare function useItem<T extends object, C>(item: Item<T, C>): UseItemResult<T>;
11
+ export declare function useItem<T extends {
12
+ id: string;
13
+ }, C>(item: Item<T, C>): UseItemResult<T>;
package/dist/useNode.d.ts CHANGED
@@ -16,6 +16,9 @@ export type UseNodeResult<T extends object, C, NodeType = string> = {
16
16
  append: (value: T, type?: NodeType) => string;
17
17
  prepend: (value: T, type?: NodeType) => string;
18
18
  move: (targetPosition: number, targetParent?: Node<T, C, NodeType>) => void;
19
+ moveUp: () => void;
20
+ moveDown: () => void;
21
+ setPosition: (targetIndex: number) => void;
19
22
  clone: () => Map<string, TreeNode<T, NodeType>>;
20
23
  updateProp: (mutate: (draft: Draft<T>) => void) => void;
21
24
  remove: () => void;
@@ -2,8 +2,8 @@ import { SyncState, Change, SyncError } from './types';
2
2
  export type UseSyncStateResult<T = unknown> = {
3
3
  syncState: SyncState;
4
4
  syncing: boolean;
5
- queue: Map<string, Change<T>[]>;
6
- inFlight: Map<string, Change<T>[]>;
5
+ queue: Map<string, Change<T>>;
6
+ inFlight: Map<string, Change<T>>;
7
7
  errors: Map<string, SyncError<T>>;
8
8
  isPaused: boolean;
9
9
  isSyncing: boolean;
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- export declare const CANCEL_RECOVERABLE = "CANCEL_RECOVERABLE";
2
- export declare const CANCELLED_BY_USER = "CANCELLED_BY_USER";
3
- export declare const useMemoDeepEquals: <T>(value: T) => T;
4
- export declare function wait(ms: number, signal: AbortSignal, onAbort?: () => void): Promise<unknown>;
1
+ export declare function getTimeFromId(id: string): number;
2
+ export declare function getIdFromTime(time?: number): string;
3
+ export declare function compareTimeIds(a: string, b: string): -1 | 0 | 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-abcd",
3
- "version": "1.4.2",
3
+ "version": "1.6.0",
4
4
  "description": "Simple react hook for CRUD type apps",
5
5
  "repository": {
6
6
  "url": "https://github.com/smtrd3/use-abcd"
@@ -37,9 +37,11 @@
37
37
  "test:cov": "vitest run --coverage"
38
38
  },
39
39
  "dependencies": {
40
+ "fake-indexeddb": "^6.2.5",
41
+ "idb": "^8.0.3",
40
42
  "lodash-es": "^4.17.21",
41
43
  "mutative": "^1.2.0",
42
- "nanoid": "^5.1.5"
44
+ "ulid": "^2.3.0"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@eslint/js": "^9.33.0",
@@ -1 +0,0 @@
1
- export {};
@@ -1,144 +0,0 @@
1
- import { c as f } from "./types-Dy4rYb2N.js";
2
- function l() {
3
- return {
4
- create: {
5
- guard: (r) => !!r.create,
6
- execute: (r, e, s) => e.create(r.data, s),
7
- toResult: (r, e) => e.success === !0 ? { id: r.id, status: "success", newId: e.newId } : { id: r.id, status: "error", error: e.error }
8
- },
9
- update: {
10
- guard: (r) => !!r.update,
11
- execute: (r, e, s) => e.update(r.id, r.data, s),
12
- toResult: (r, e) => e.success === !0 ? { id: r.id, status: "success" } : { id: r.id, status: "error", error: e.error }
13
- },
14
- delete: {
15
- guard: (r) => !!r.delete,
16
- execute: (r, e, s) => e.delete(r.id, r.data, s),
17
- toResult: (r, e) => e.success === !0 ? { id: r.id, status: "success" } : { id: r.id, status: "error", error: e.error }
18
- }
19
- };
20
- }
21
- async function w(r, e, s) {
22
- if (s.aborted)
23
- return { id: r.id, status: "error", error: "Operation aborted" };
24
- const a = l()[r.type];
25
- if (!a)
26
- return { id: r.id, status: "error", error: `Unknown change type: ${r.type}` };
27
- if (!a.guard(e))
28
- return { id: r.id, status: "success" };
29
- try {
30
- const t = await a.execute(r, e, s);
31
- return a.toResult(r, t);
32
- } catch (t) {
33
- return {
34
- id: r.id,
35
- status: "error",
36
- error: t instanceof Error ? t.message : "Unknown error"
37
- };
38
- }
39
- }
40
- function m(r) {
41
- return {
42
- onSync: async (s, c) => Promise.all(s.map((a) => w(a, r, c))),
43
- handlers: { create: r.create, update: r.update, delete: r.delete }
44
- };
45
- }
46
- function E(r) {
47
- const { onSync: e, handlers: s } = m(r);
48
- return { onSync: e, onSyncWithStats: async (a, t) => {
49
- const o = await e(a, t);
50
- return f(o);
51
- }, handlers: s };
52
- }
53
- function b(r) {
54
- return { success: !0, ...r };
55
- }
56
- function h(r) {
57
- return { success: !1, error: r };
58
- }
59
- function O(r) {
60
- const e = typeof r == "string" ? r : r.endpoint, s = typeof r == "string" ? {} : r.headers ?? {}, c = typeof r == "string" ? void 0 : r.scope;
61
- return { onFetch: async (o, u) => {
62
- if (u.aborted)
63
- throw new Error("Operation aborted");
64
- try {
65
- const n = { scope: c, query: o }, i = await fetch(e, {
66
- method: "POST",
67
- headers: { "Content-Type": "application/json", ...s },
68
- body: JSON.stringify(n),
69
- signal: u
70
- });
71
- if (!i.ok) {
72
- let d = "Fetch request failed";
73
- try {
74
- const y = await i.json();
75
- y.error && (d = y.error);
76
- } catch {
77
- }
78
- throw new Error(d);
79
- }
80
- return (await i.json()).results ?? [];
81
- } catch (n) {
82
- throw n instanceof Error && n.name === "AbortError" ? new Error("Operation aborted") : n;
83
- }
84
- }, onSync: async (o, u) => {
85
- if (u.aborted)
86
- return o.map((n) => ({
87
- id: n.id,
88
- status: "error",
89
- error: "Operation aborted"
90
- }));
91
- try {
92
- const n = { scope: c, changes: o }, i = await fetch(e, {
93
- method: "POST",
94
- headers: { "Content-Type": "application/json", ...s },
95
- body: JSON.stringify(n),
96
- signal: u
97
- });
98
- if (!i.ok) {
99
- let d = "Sync request failed";
100
- try {
101
- const y = await i.json();
102
- y.error && (d = y.error);
103
- } catch {
104
- }
105
- return o.map((y) => ({ id: y.id, status: "error", error: d }));
106
- }
107
- return (await i.json()).syncResults ?? o.map((d) => ({
108
- id: d.id,
109
- status: "error",
110
- error: "No sync results returned"
111
- }));
112
- } catch (n) {
113
- const i = n instanceof Error && n.name === "AbortError" ? "Operation aborted" : n instanceof Error ? n.message : "Unknown error";
114
- return o.map((p) => ({ id: p.id, status: "error", error: i }));
115
- }
116
- } };
117
- }
118
- async function R(r) {
119
- const { fetch: e, parseResponse: s, parseError: c } = r, a = (t) => typeof c == "function" ? c(t) : typeof c == "string" ? c : t instanceof Error ? t.message : "Request failed";
120
- try {
121
- const t = await e;
122
- if (!t.ok) {
123
- let o = new Error("Request failed");
124
- try {
125
- const u = await t.json();
126
- u.message ? o = new Error(u.message) : u.error && (o = new Error(u.error));
127
- } catch {
128
- }
129
- return { success: !1, error: a(o) };
130
- }
131
- return s ? { success: !0, ...await s(t) } : { success: !0 };
132
- } catch (t) {
133
- return t instanceof Error && t.name === "AbortError" ? { success: !1, error: "Operation aborted" } : { success: !1, error: a(t) };
134
- }
135
- }
136
- export {
137
- E as a,
138
- O as b,
139
- m as c,
140
- h as d,
141
- R as f,
142
- b as s
143
- };
144
- //# sourceMappingURL=client-DReyDQ23.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-DReyDQ23.js","sources":["../../src/runtime/client.ts"],"sourcesContent":["import type { Change, SyncResult } from \"../types\";\nimport {\n type SyncHandlerResult,\n type SyncBatchResult,\n type SyncRequestBody,\n type SyncResponseBody,\n categorizeResults,\n} from \"./types\";\n\nexport type { SyncHandlerResult, SyncBatchResult };\nexport { categorizeResults };\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type CreateHandler<T> = (data: T, signal: AbortSignal) => Promise<SyncHandlerResult>;\nexport type UpdateHandler<T> = (\n id: string,\n data: T,\n signal: AbortSignal,\n) => Promise<SyncHandlerResult>;\nexport type DeleteHandler<T> = (\n id: string,\n data: T,\n signal: AbortSignal,\n) => Promise<SyncHandlerResult>;\n\nexport type SyncBuilderConfig<T> = {\n create?: CreateHandler<T>;\n update?: UpdateHandler<T>;\n delete?: DeleteHandler<T>;\n};\n\nexport type SyncBuilder<T> = {\n onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;\n handlers: {\n create?: CreateHandler<T>;\n update?: UpdateHandler<T>;\n delete?: DeleteHandler<T>;\n };\n};\n\nexport type FetchToSyncResultOptions = {\n fetch: Promise<Response>;\n parseResponse?: (response: Response) => Promise<{ newId?: string }>;\n parseError?: string | ((error: unknown) => string);\n};\n\n// ============================================================================\n// Change Processing\n// ============================================================================\n\ntype ChangeProcessor<T> = {\n guard: (config: SyncBuilderConfig<T>) => boolean;\n execute: (\n change: Change<T>,\n config: SyncBuilderConfig<T>,\n signal: AbortSignal,\n ) => Promise<SyncHandlerResult>;\n toResult: (change: Change<T>, result: SyncHandlerResult) => SyncResult;\n};\n\nfunction createChangeProcessors<T>(): Record<string, ChangeProcessor<T>> {\n return {\n create: {\n guard: (config) => !!config.create,\n execute: (change, config, signal) => config.create!(change.data, signal),\n toResult: (change, result) =>\n result.success === true\n ? { id: change.id, status: \"success\" as const, newId: result.newId }\n : { id: change.id, status: \"error\" as const, error: result.error },\n },\n\n update: {\n guard: (config) => !!config.update,\n execute: (change, config, signal) => config.update!(change.id, change.data, signal),\n toResult: (change, result) =>\n result.success === true\n ? { id: change.id, status: \"success\" as const }\n : { id: change.id, status: \"error\" as const, error: result.error },\n },\n\n delete: {\n guard: (config) => !!config.delete,\n execute: (change, config, signal) => config.delete!(change.id, change.data, signal),\n toResult: (change, result) =>\n result.success === true\n ? { id: change.id, status: \"success\" as const }\n : { id: change.id, status: \"error\" as const, error: result.error },\n },\n };\n}\n\nasync function processChange<T>(\n change: Change<T>,\n config: SyncBuilderConfig<T>,\n signal: AbortSignal,\n): Promise<SyncResult> {\n if (signal.aborted) {\n return { id: change.id, status: \"error\", error: \"Operation aborted\" };\n }\n\n const processors = createChangeProcessors<T>();\n const processor = processors[change.type];\n\n if (!processor) {\n return { id: change.id, status: \"error\", error: `Unknown change type: ${change.type}` };\n }\n\n // If handler not configured, treat as success (offline-first support)\n if (!processor.guard(config)) {\n return { id: change.id, status: \"success\" };\n }\n\n try {\n const result = await processor.execute(change, config, signal);\n return processor.toResult(change, result);\n } catch (error) {\n return {\n id: change.id,\n status: \"error\",\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport function createSyncClient<T>(config: SyncBuilderConfig<T>): SyncBuilder<T> {\n const onSync = async (changes: Change<T>[], signal: AbortSignal): Promise<SyncResult[]> => {\n return Promise.all(changes.map((change) => processChange(change, config, signal)));\n };\n\n return {\n onSync,\n handlers: { create: config.create, update: config.update, delete: config.delete },\n };\n}\n\nexport function createSyncClientWithStats<T>(config: SyncBuilderConfig<T>): {\n onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;\n onSyncWithStats: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncBatchResult>;\n handlers: { create?: CreateHandler<T>; update?: UpdateHandler<T>; delete?: DeleteHandler<T> };\n} {\n const { onSync, handlers } = createSyncClient(config);\n\n const onSyncWithStats = async (\n changes: Change<T>[],\n signal: AbortSignal,\n ): Promise<SyncBatchResult> => {\n const results = await onSync(changes, signal);\n return categorizeResults(results);\n };\n\n return { onSync, onSyncWithStats, handlers };\n}\n\nexport function syncSuccess(options?: { newId?: string }): SyncHandlerResult {\n return { success: true, ...options };\n}\n\nexport function syncError(error: string): SyncHandlerResult {\n return { success: false, error };\n}\n\nexport type EndpointSyncClientConfig = {\n endpoint: string;\n headers?: Record<string, string>;\n scope?: string;\n};\n\nexport type EndpointSyncClient<T, Q = unknown> = {\n onFetch: (query: Q, signal: AbortSignal) => Promise<T[]>;\n onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;\n};\n\nexport function createSyncClientFromEndpoint<T, Q = unknown>(\n config: string | EndpointSyncClientConfig,\n): EndpointSyncClient<T, Q> {\n const endpoint = typeof config === \"string\" ? config : config.endpoint;\n const headers = typeof config === \"string\" ? {} : (config.headers ?? {});\n const scope = typeof config === \"string\" ? undefined : config.scope;\n\n const onFetch = async (query: Q, signal: AbortSignal): Promise<T[]> => {\n if (signal.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n try {\n const body: SyncRequestBody<T, Q> = { scope, query };\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...headers },\n body: JSON.stringify(body),\n signal,\n });\n\n if (!response.ok) {\n let errorMsg = \"Fetch request failed\";\n try {\n const errorBody = await response.json();\n if (errorBody.error) errorMsg = errorBody.error;\n } catch {\n // Ignore JSON parse errors\n }\n throw new Error(errorMsg);\n }\n\n const responseBody: SyncResponseBody<T> = await response.json();\n return responseBody.results ?? [];\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new Error(\"Operation aborted\");\n }\n throw error;\n }\n };\n\n const onSync = async (changes: Change<T>[], signal: AbortSignal): Promise<SyncResult[]> => {\n if (signal.aborted) {\n return changes.map((c) => ({\n id: c.id,\n status: \"error\" as const,\n error: \"Operation aborted\",\n }));\n }\n\n try {\n const body: SyncRequestBody<T, Q> = { scope, changes };\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", ...headers },\n body: JSON.stringify(body),\n signal,\n });\n\n if (!response.ok) {\n let errorMsg = \"Sync request failed\";\n try {\n const errorBody = await response.json();\n if (errorBody.error) errorMsg = errorBody.error;\n } catch {\n // Ignore JSON parse errors\n }\n return changes.map((c) => ({ id: c.id, status: \"error\" as const, error: errorMsg }));\n }\n\n const responseBody: SyncResponseBody<T> = await response.json();\n return (\n responseBody.syncResults ??\n changes.map((c) => ({\n id: c.id,\n status: \"error\" as const,\n error: \"No sync results returned\",\n }))\n );\n } catch (error) {\n const errorMsg =\n error instanceof Error && error.name === \"AbortError\"\n ? \"Operation aborted\"\n : error instanceof Error\n ? error.message\n : \"Unknown error\";\n return changes.map((c) => ({ id: c.id, status: \"error\" as const, error: errorMsg }));\n }\n };\n\n return { onFetch, onSync };\n}\n\nexport async function fetchToSyncResult(\n options: FetchToSyncResultOptions,\n): Promise<SyncHandlerResult> {\n const { fetch: fetchPromise, parseResponse, parseError } = options;\n\n const getErrorMessage = (error: unknown): string => {\n if (typeof parseError === \"function\") return parseError(error);\n if (typeof parseError === \"string\") return parseError;\n return error instanceof Error ? error.message : \"Request failed\";\n };\n\n try {\n const response = await fetchPromise;\n\n if (!response.ok) {\n let error: unknown = new Error(\"Request failed\");\n try {\n const body = await response.json();\n if (body.message) error = new Error(body.message);\n else if (body.error) error = new Error(body.error);\n } catch {\n // Ignore JSON parse errors\n }\n return { success: false, error: getErrorMessage(error) };\n }\n\n if (parseResponse) {\n const result = await parseResponse(response);\n return { success: true, ...result };\n }\n\n return { success: true };\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n return { success: false, error: \"Operation aborted\" };\n }\n return { success: false, error: getErrorMessage(error) };\n }\n}\n"],"names":["createChangeProcessors","config","change","signal","result","processChange","processor","error","createSyncClient","changes","createSyncClientWithStats","onSync","handlers","results","categorizeResults","syncSuccess","options","syncError","createSyncClientFromEndpoint","endpoint","headers","scope","query","body","response","errorMsg","errorBody","c","fetchToSyncResult","fetchPromise","parseResponse","parseError","getErrorMessage"],"mappings":"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"}
@@ -1,19 +0,0 @@
1
- function n(e) {
2
- const t = e.filter((l) => l.status === "success"), c = e.filter((l) => l.status === "error");
3
- return {
4
- results: e,
5
- successful: t,
6
- failed: c,
7
- allSucceeded: c.length === 0,
8
- anySucceeded: t.length > 0,
9
- summary: {
10
- total: e.length,
11
- succeeded: t.length,
12
- failed: c.length
13
- }
14
- };
15
- }
16
- export {
17
- n as c
18
- };
19
- //# sourceMappingURL=types-Dy4rYb2N.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types-Dy4rYb2N.js","sources":["../../src/runtime/types.ts"],"sourcesContent":["import type { Change, SyncResult } from \"../types\";\n\n// ============================================================================\n// Shared Types (used by both client and server)\n// ============================================================================\n\n/**\n * Result of a sync operation handler.\n * Used by both client-side and server-side handlers.\n */\nexport type SyncHandlerResult =\n | { success: true; newId?: string }\n | { success: false; error: string };\n\n/**\n * Zod-like schema interface (duck typing to avoid hard dependency)\n */\nexport type Schema<T> = {\n safeParse: (\n data: unknown,\n ) => { success: true; data: T } | { success: false; error: { message: string } };\n};\n\n/**\n * Aggregated results from sync operations.\n * Provides categorized results and summary statistics.\n */\nexport type SyncBatchResult = {\n /** All results from the sync operation */\n results: SyncResult[];\n /** Results that succeeded */\n successful: SyncResult[];\n /** Results that failed */\n failed: SyncResult[];\n /** Whether all operations succeeded */\n allSucceeded: boolean;\n /** Whether any operations succeeded */\n anySucceeded: boolean;\n /** Summary counts */\n summary: {\n total: number;\n succeeded: number;\n failed: number;\n };\n};\n\n/**\n * Categorize sync results into successful and failed\n */\nexport function categorizeResults(results: SyncResult[]): SyncBatchResult {\n const successful = results.filter((r) => r.status === \"success\");\n const failed = results.filter((r) => r.status === \"error\");\n\n return {\n results,\n successful,\n failed,\n allSucceeded: failed.length === 0,\n anySucceeded: successful.length > 0,\n summary: {\n total: results.length,\n succeeded: successful.length,\n failed: failed.length,\n },\n };\n}\n\n// ============================================================================\n// Request/Response Types (for client-server communication)\n// ============================================================================\n\n/**\n * Request body for the unified POST endpoint\n */\nexport type SyncRequestBody<T, Q = unknown> = {\n /** Optional scope identifier for selecting storage on the backend */\n scope?: string;\n /** Query parameters for fetching items */\n query?: Q;\n /** Changes to sync */\n changes?: Change<T>[];\n};\n\n/**\n * Response body from the unified POST endpoint\n */\nexport type SyncResponseBody<T> = {\n /** Fetched items (when query was provided) */\n results?: T[];\n /** Sync results (when changes were provided) */\n syncResults?: SyncResult[];\n};\n"],"names":["categorizeResults","results","successful","r","failed"],"mappings":"AAiDO,SAASA,EAAkBC,GAAwC;AACxE,QAAMC,IAAaD,EAAQ,OAAO,CAACE,MAAMA,EAAE,WAAW,SAAS,GACzDC,IAASH,EAAQ,OAAO,CAACE,MAAMA,EAAE,WAAW,OAAO;AAEzD,SAAO;AAAA,IACL,SAAAF;AAAA,IACA,YAAAC;AAAA,IACA,QAAAE;AAAA,IACA,cAAcA,EAAO,WAAW;AAAA,IAChC,cAAcF,EAAW,SAAS;AAAA,IAClC,SAAS;AAAA,MACP,OAAOD,EAAQ;AAAA,MACf,WAAWC,EAAW;AAAA,MACtB,QAAQE,EAAO;AAAA,IAAA;AAAA,EACjB;AAEJ;"}
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
File without changes
File without changes