use-abcd 0.2.1 → 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.
Files changed (40) hide show
  1. package/README.md +459 -85
  2. package/dist/App.d.ts +2 -0
  3. package/dist/cache.d.ts +12 -0
  4. package/dist/cache.test.d.ts +1 -0
  5. package/dist/chunks/client-VrsFvEIA.js +144 -0
  6. package/dist/chunks/client-VrsFvEIA.js.map +1 -0
  7. package/dist/chunks/types-Dy4rYb2N.js +19 -0
  8. package/dist/chunks/types-Dy4rYb2N.js.map +1 -0
  9. package/dist/collection.d.ts +54 -0
  10. package/dist/collection.e2e.test.d.ts +1 -0
  11. package/dist/examples/OptimisticComments.d.ts +2 -0
  12. package/dist/examples/PaginatedUsers.d.ts +2 -0
  13. package/dist/examples/Products.d.ts +2 -0
  14. package/dist/fetch-handler.d.ts +34 -0
  15. package/dist/fetch-handler.test.d.ts +1 -0
  16. package/dist/index.d.ts +14 -0
  17. package/dist/index.js +2283 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/item.d.ts +21 -0
  20. package/dist/main.d.ts +1 -0
  21. package/dist/mocks/browser.d.ts +1 -0
  22. package/dist/mocks/handlers.d.ts +2 -0
  23. package/dist/runtime/client.d.ts +51 -0
  24. package/dist/runtime/client.js +16 -0
  25. package/dist/runtime/client.js.map +1 -0
  26. package/dist/runtime/client.test.d.ts +1 -0
  27. package/dist/runtime/index.d.ts +5 -0
  28. package/dist/runtime/server.d.ts +33 -0
  29. package/dist/runtime/server.js +121 -0
  30. package/dist/runtime/server.js.map +1 -0
  31. package/dist/runtime/server.test.d.ts +1 -0
  32. package/dist/runtime/types.d.ts +70 -0
  33. package/dist/sync-queue.d.ts +29 -0
  34. package/dist/sync-queue.test.d.ts +1 -0
  35. package/dist/types.d.ts +61 -0
  36. package/dist/useCrud.d.ts +28 -169
  37. package/dist/useItem.d.ts +11 -0
  38. package/dist/utils.d.ts +4 -0
  39. package/package.json +25 -7
  40. package/dist/useCrud.js +0 -2135
package/dist/item.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { Draft } from 'mutative';
2
+ import { Collection } from './collection';
3
+ import { ItemStatus } from './types';
4
+ export declare class Item<T, C = unknown> {
5
+ private _collection;
6
+ private _id;
7
+ private _cachedStatus;
8
+ private _refCount;
9
+ constructor(collection: Collection<T, C>, id: string);
10
+ _retain(): void;
11
+ _release(): void;
12
+ get refCount(): number;
13
+ get id(): string;
14
+ get data(): T | undefined;
15
+ update(mutate: (draft: Draft<T>) => void): void;
16
+ remove(): void;
17
+ getStatus(): ItemStatus;
18
+ exists(): boolean;
19
+ get collection(): Collection<T, C>;
20
+ _updateId(newId: string): void;
21
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const worker: import('msw/browser').SetupWorker;
@@ -0,0 +1,2 @@
1
+ export declare const handlers: import('msw').HttpHandler[];
2
+ export declare const batchHandlers: import('msw').HttpHandler[];
@@ -0,0 +1,51 @@
1
+ import { Change, SyncResult } from '../types';
2
+ import { SyncHandlerResult, SyncBatchResult, categorizeResults } from './types';
3
+ export type { SyncHandlerResult, SyncBatchResult };
4
+ export { categorizeResults };
5
+ export type CreateHandler<T> = (data: T, signal: AbortSignal) => Promise<SyncHandlerResult>;
6
+ export type UpdateHandler<T> = (id: string, data: T, signal: AbortSignal) => Promise<SyncHandlerResult>;
7
+ export type DeleteHandler<T> = (id: string, data: T, signal: AbortSignal) => Promise<SyncHandlerResult>;
8
+ export type SyncBuilderConfig<T> = {
9
+ create?: CreateHandler<T>;
10
+ update?: UpdateHandler<T>;
11
+ delete?: DeleteHandler<T>;
12
+ };
13
+ export type SyncBuilder<T> = {
14
+ onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
15
+ handlers: {
16
+ create?: CreateHandler<T>;
17
+ update?: UpdateHandler<T>;
18
+ delete?: DeleteHandler<T>;
19
+ };
20
+ };
21
+ export type FetchToSyncResultOptions = {
22
+ fetch: Promise<Response>;
23
+ parseResponse?: (response: Response) => Promise<{
24
+ newId?: string;
25
+ }>;
26
+ parseError?: string | ((error: unknown) => string);
27
+ };
28
+ export declare function createSyncClient<T>(config: SyncBuilderConfig<T>): SyncBuilder<T>;
29
+ export declare function createSyncClientWithStats<T>(config: SyncBuilderConfig<T>): {
30
+ onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
31
+ onSyncWithStats: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncBatchResult>;
32
+ handlers: {
33
+ create?: CreateHandler<T>;
34
+ update?: UpdateHandler<T>;
35
+ delete?: DeleteHandler<T>;
36
+ };
37
+ };
38
+ export declare function syncSuccess(options?: {
39
+ newId?: string;
40
+ }): SyncHandlerResult;
41
+ export declare function syncError(error: string): SyncHandlerResult;
42
+ export type EndpointSyncClientConfig = {
43
+ endpoint: string;
44
+ headers?: Record<string, string>;
45
+ };
46
+ export type EndpointSyncClient<T, Q = unknown> = {
47
+ onFetch: (query: Q, signal: AbortSignal) => Promise<T[]>;
48
+ onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
49
+ };
50
+ export declare function createSyncClientFromEndpoint<T, Q = unknown>(config: string | EndpointSyncClientConfig): EndpointSyncClient<T, Q>;
51
+ export declare function fetchToSyncResult(options: FetchToSyncResultOptions): Promise<SyncHandlerResult>;
@@ -0,0 +1,16 @@
1
+ import { c } from "../chunks/types-Dy4rYb2N.js";
2
+ import { c as t, b as a, a as n, f as o, d as S, s as y } from "../chunks/client-VrsFvEIA.js";
3
+ import { createSyncServer as f, serverSyncError as l, serverSyncSuccess as m } from "./server.js";
4
+ export {
5
+ c as categorizeResults,
6
+ t as createSyncClient,
7
+ a as createSyncClientFromEndpoint,
8
+ n as createSyncClientWithStats,
9
+ f as createSyncServer,
10
+ o as fetchToSyncResult,
11
+ l as serverSyncError,
12
+ m as serverSyncSuccess,
13
+ S as syncError,
14
+ y as syncSuccess
15
+ };
16
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export { type SyncHandlerResult, type SyncBatchResult, type Schema, type SyncRequestBody, type SyncResponseBody, categorizeResults, } from './types';
2
+ export { createSyncClient, createSyncClientWithStats, createSyncClientFromEndpoint, syncSuccess, syncError, fetchToSyncResult, } from './client';
3
+ export type { CreateHandler, UpdateHandler, DeleteHandler, SyncBuilderConfig, SyncBuilder, FetchToSyncResultOptions, EndpointSyncClientConfig, EndpointSyncClient, } from './client';
4
+ export { createSyncServer, serverSyncSuccess, serverSyncError } from './server';
5
+ export type { ServerFetchHandler, ServerCreateHandler, ServerUpdateHandler, ServerDeleteHandler, ServerSyncHandlerConfig, ServerSyncHandler, } from './server';
@@ -0,0 +1,33 @@
1
+ import { Change, SyncResult } from '../types';
2
+ import { SyncHandlerResult, SyncBatchResult, Schema, SyncRequestBody, SyncResponseBody, categorizeResults } from './types';
3
+ export type { SyncHandlerResult, SyncBatchResult, Schema, SyncRequestBody, SyncResponseBody };
4
+ export { categorizeResults };
5
+ export type ServerFetchHandler<T, Q> = (query: Q) => Promise<T[]> | T[];
6
+ export type ServerCreateHandler<T> = (data: T) => Promise<SyncHandlerResult> | SyncHandlerResult;
7
+ export type ServerUpdateHandler<T> = (id: string, data: T) => Promise<SyncHandlerResult> | SyncHandlerResult;
8
+ export type ServerDeleteHandler<T> = (id: string, data: T) => Promise<SyncHandlerResult> | SyncHandlerResult;
9
+ export type ServerSyncHandlerConfig<T, Q = unknown> = {
10
+ schema?: Schema<T>;
11
+ querySchema?: Schema<Q>;
12
+ fetch?: ServerFetchHandler<T, Q>;
13
+ create?: ServerCreateHandler<T>;
14
+ update?: ServerUpdateHandler<T>;
15
+ delete?: ServerDeleteHandler<T>;
16
+ };
17
+ export type ServerSyncHandler<T, Q = unknown> = {
18
+ handler: (request: Request) => Promise<Response>;
19
+ fetchItems: (query: Q) => Promise<T[]>;
20
+ processChanges: (changes: Change<T>[]) => Promise<SyncResult[]>;
21
+ processChangesWithStats: (changes: Change<T>[]) => Promise<SyncBatchResult>;
22
+ handlers: {
23
+ fetch?: ServerFetchHandler<T, Q>;
24
+ create?: ServerCreateHandler<T>;
25
+ update?: ServerUpdateHandler<T>;
26
+ delete?: ServerDeleteHandler<T>;
27
+ };
28
+ };
29
+ export declare function serverSyncSuccess(options?: {
30
+ newId?: string;
31
+ }): SyncHandlerResult;
32
+ export declare function serverSyncError(error: string): SyncHandlerResult;
33
+ export declare function createSyncServer<T, Q = unknown>(config: ServerSyncHandlerConfig<T, Q>): ServerSyncHandler<T, Q>;
@@ -0,0 +1,121 @@
1
+ import { c as y } from "../chunks/types-Dy4rYb2N.js";
2
+ const d = (r, e) => new Response(JSON.stringify(r), {
3
+ status: e,
4
+ headers: { "Content-Type": "application/json" }
5
+ }), a = {
6
+ success: (r) => d(r, 200),
7
+ methodNotAllowed: () => d({ error: "Method not allowed. Use POST." }, 405),
8
+ invalidJson: () => d({ error: "Invalid JSON body" }, 400),
9
+ invalidPayload: () => d({ error: "Request body must contain 'query' and/or 'changes'" }, 400),
10
+ validationError: (r) => d({ error: `Validation error: ${r}` }, 400),
11
+ notConfigured: (r) => d({ error: `${r} handler not configured` }, 501),
12
+ serverError: (r) => d({ error: r instanceof Error ? r.message : "Internal server error" }, 500)
13
+ };
14
+ function h() {
15
+ return {
16
+ create: {
17
+ guard: (r) => !!r.create,
18
+ execute: (r, e) => e.create(r.data),
19
+ toResult: (r, e) => e.success === !0 ? { id: r.id, status: "success", newId: e.newId } : { id: r.id, status: "error", error: e.error },
20
+ notConfiguredError: "Create handler not configured"
21
+ },
22
+ update: {
23
+ guard: (r) => !!r.update,
24
+ execute: (r, e) => e.update(r.id, r.data),
25
+ toResult: (r, e) => e.success === !0 ? { id: r.id, status: "success" } : { id: r.id, status: "error", error: e.error },
26
+ notConfiguredError: "Update handler not configured"
27
+ },
28
+ delete: {
29
+ guard: (r) => !!r.delete,
30
+ execute: (r, e) => e.delete(r.id, r.data),
31
+ toResult: (r, e) => e.success === !0 ? { id: r.id, status: "success" } : { id: r.id, status: "error", error: e.error },
32
+ notConfiguredError: "Delete handler not configured"
33
+ }
34
+ };
35
+ }
36
+ function f(r, e) {
37
+ if (!e) return { valid: !0, data: r };
38
+ const o = e.safeParse(r);
39
+ return o.success === !0 ? { valid: !0, data: o.data } : { valid: !1, error: o.error.message };
40
+ }
41
+ async function p(r, e) {
42
+ const n = h()[r.type];
43
+ if (!n)
44
+ return { id: r.id, status: "error", error: `Unknown change type: ${r.type}` };
45
+ if (!n.guard(e))
46
+ return { id: r.id, status: "error", error: n.notConfiguredError };
47
+ const u = f(r.data, e.schema);
48
+ if (u.valid === !1)
49
+ return { id: r.id, status: "error", error: `Validation failed: ${u.error}` };
50
+ try {
51
+ const i = await n.execute({ ...r, data: u.data }, e);
52
+ return n.toResult(r, i);
53
+ } catch (i) {
54
+ return {
55
+ id: r.id,
56
+ status: "error",
57
+ error: i instanceof Error ? i.message : "Unknown error"
58
+ };
59
+ }
60
+ }
61
+ function w(r) {
62
+ return { success: !0, ...r };
63
+ }
64
+ function m(r) {
65
+ return { success: !1, error: r };
66
+ }
67
+ function C(r) {
68
+ const e = async (s) => {
69
+ if (!r.fetch) throw new Error("Fetch handler not configured");
70
+ return r.fetch(s);
71
+ }, o = async (s) => Promise.all(s.map((t) => p(t, r))), n = async (s) => {
72
+ const t = await o(s);
73
+ return y(t);
74
+ }, u = async (s) => {
75
+ let t;
76
+ try {
77
+ t = await s.json();
78
+ } catch {
79
+ return a.invalidJson();
80
+ }
81
+ if (!t.query && !t.changes)
82
+ return a.invalidPayload();
83
+ const c = {};
84
+ if (t.query !== void 0) {
85
+ if (!r.fetch) return a.notConfigured("Fetch");
86
+ const l = f(t.query, r.querySchema);
87
+ if (l.valid === !1) return a.validationError(l.error);
88
+ c.results = await e(l.data);
89
+ }
90
+ if (t.changes !== void 0) {
91
+ if (!Array.isArray(t.changes)) return a.invalidPayload();
92
+ c.syncResults = await o(t.changes);
93
+ }
94
+ return a.success(c);
95
+ };
96
+ return {
97
+ handler: async (s) => {
98
+ try {
99
+ return s.method !== "POST" ? a.methodNotAllowed() : await u(s);
100
+ } catch (t) {
101
+ return a.serverError(t);
102
+ }
103
+ },
104
+ fetchItems: e,
105
+ processChanges: o,
106
+ processChangesWithStats: n,
107
+ handlers: {
108
+ fetch: r.fetch,
109
+ create: r.create,
110
+ update: r.update,
111
+ delete: r.delete
112
+ }
113
+ };
114
+ }
115
+ export {
116
+ y as categorizeResults,
117
+ C as createSyncServer,
118
+ m as serverSyncError,
119
+ w as serverSyncSuccess
120
+ };
121
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sources":["../../src/runtime/server.ts"],"sourcesContent":["import type { Change, SyncResult } from \"../types\";\nimport {\n type SyncHandlerResult,\n type SyncBatchResult,\n type Schema,\n type SyncRequestBody,\n type SyncResponseBody,\n categorizeResults,\n} from \"./types\";\n\n// Re-export shared types for server module consumers\nexport type { SyncHandlerResult, SyncBatchResult, Schema, SyncRequestBody, SyncResponseBody };\nexport { categorizeResults };\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ServerFetchHandler<T, Q> = (query: Q) => Promise<T[]> | T[];\nexport type ServerCreateHandler<T> = (data: T) => Promise<SyncHandlerResult> | SyncHandlerResult;\nexport type ServerUpdateHandler<T> = (\n id: string,\n data: T,\n) => Promise<SyncHandlerResult> | SyncHandlerResult;\nexport type ServerDeleteHandler<T> = (\n id: string,\n data: T,\n) => Promise<SyncHandlerResult> | SyncHandlerResult;\n\nexport type ServerSyncHandlerConfig<T, Q = unknown> = {\n schema?: Schema<T>;\n querySchema?: Schema<Q>;\n fetch?: ServerFetchHandler<T, Q>;\n create?: ServerCreateHandler<T>;\n update?: ServerUpdateHandler<T>;\n delete?: ServerDeleteHandler<T>;\n};\n\nexport type ServerSyncHandler<T, Q = unknown> = {\n handler: (request: Request) => Promise<Response>;\n fetchItems: (query: Q) => Promise<T[]>;\n processChanges: (changes: Change<T>[]) => Promise<SyncResult[]>;\n processChangesWithStats: (changes: Change<T>[]) => Promise<SyncBatchResult>;\n handlers: {\n fetch?: ServerFetchHandler<T, Q>;\n create?: ServerCreateHandler<T>;\n update?: ServerUpdateHandler<T>;\n delete?: ServerDeleteHandler<T>;\n };\n};\n\n// ============================================================================\n// Response Helpers\n// ============================================================================\n\nconst jsonResponse = (body: unknown, status: number): Response =>\n new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n\nconst responses = {\n success: (data: unknown) => jsonResponse(data, 200),\n methodNotAllowed: () => jsonResponse({ error: \"Method not allowed. Use POST.\" }, 405),\n invalidJson: () => jsonResponse({ error: \"Invalid JSON body\" }, 400),\n invalidPayload: () =>\n jsonResponse({ error: \"Request body must contain 'query' and/or 'changes'\" }, 400),\n validationError: (message: string) =>\n jsonResponse({ error: `Validation error: ${message}` }, 400),\n notConfigured: (handler: string) =>\n jsonResponse({ error: `${handler} handler not configured` }, 501),\n serverError: (error: unknown) =>\n jsonResponse({ error: error instanceof Error ? error.message : \"Internal server error\" }, 500),\n} as const;\n\n// ============================================================================\n// Change Processing\n// ============================================================================\n\ntype ChangeProcessor<T, Q> = {\n guard: (config: ServerSyncHandlerConfig<T, Q>) => boolean;\n execute: (\n change: Change<T>,\n config: ServerSyncHandlerConfig<T, Q>,\n ) => Promise<SyncHandlerResult> | SyncHandlerResult;\n toResult: (change: Change<T>, result: SyncHandlerResult) => SyncResult;\n notConfiguredError: string;\n};\n\nfunction createChangeProcessors<T, Q>(): Record<string, ChangeProcessor<T, Q>> {\n return {\n create: {\n guard: (config) => !!config.create,\n execute: (change, config) => config.create!(change.data),\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 notConfiguredError: \"Create handler not configured\",\n },\n\n update: {\n guard: (config) => !!config.update,\n execute: (change, config) => config.update!(change.id, change.data),\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 notConfiguredError: \"Update handler not configured\",\n },\n\n delete: {\n guard: (config) => !!config.delete,\n execute: (change, config) => config.delete!(change.id, change.data),\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 notConfiguredError: \"Delete handler not configured\",\n },\n };\n}\n\nfunction validateData<T>(\n data: unknown,\n schema?: Schema<T>,\n): { valid: true; data: T } | { valid: false; error: string } {\n if (!schema) return { valid: true, data: data as T };\n\n const result = schema.safeParse(data);\n return result.success === true\n ? { valid: true, data: result.data }\n : { valid: false, error: result.error.message };\n}\n\nasync function processServerChange<T, Q>(\n change: Change<T>,\n config: ServerSyncHandlerConfig<T, Q>,\n): Promise<SyncResult> {\n const processors = createChangeProcessors<T, Q>();\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 (!processor.guard(config)) {\n return { id: change.id, status: \"error\", error: processor.notConfiguredError };\n }\n\n const validation = validateData(change.data, config.schema);\n if (validation.valid === false) {\n return { id: change.id, status: \"error\", error: `Validation failed: ${validation.error}` };\n }\n\n try {\n const result = await processor.execute({ ...change, data: validation.data }, config);\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 serverSyncSuccess(options?: { newId?: string }): SyncHandlerResult {\n return { success: true, ...options };\n}\n\nexport function serverSyncError(error: string): SyncHandlerResult {\n return { success: false, error };\n}\n\nexport function createSyncServer<T, Q = unknown>(\n config: ServerSyncHandlerConfig<T, Q>,\n): ServerSyncHandler<T, Q> {\n const fetchItems = async (query: Q): Promise<T[]> => {\n if (!config.fetch) throw new Error(\"Fetch handler not configured\");\n return config.fetch(query);\n };\n\n const processChanges = async (changes: Change<T>[]): Promise<SyncResult[]> => {\n return Promise.all(changes.map((change) => processServerChange(change, config)));\n };\n\n const processChangesWithStats = async (changes: Change<T>[]): Promise<SyncBatchResult> => {\n const results = await processChanges(changes);\n return categorizeResults(results);\n };\n\n const handlePost = async (request: Request): Promise<Response> => {\n let body: SyncRequestBody<T, Q>;\n try {\n body = await request.json();\n } catch {\n return responses.invalidJson();\n }\n\n if (!body.query && !body.changes) {\n return responses.invalidPayload();\n }\n\n const responseBody: SyncResponseBody<T> = {};\n\n if (body.query !== undefined) {\n if (!config.fetch) return responses.notConfigured(\"Fetch\");\n\n const validation = validateData<Q>(body.query, config.querySchema);\n if (validation.valid === false) return responses.validationError(validation.error);\n\n responseBody.results = await fetchItems(validation.data);\n }\n\n if (body.changes !== undefined) {\n if (!Array.isArray(body.changes)) return responses.invalidPayload();\n responseBody.syncResults = await processChanges(body.changes);\n }\n\n return responses.success(responseBody);\n };\n\n const handler = async (request: Request): Promise<Response> => {\n try {\n if (request.method !== \"POST\") return responses.methodNotAllowed();\n return await handlePost(request);\n } catch (error) {\n return responses.serverError(error);\n }\n };\n\n return {\n handler,\n fetchItems,\n processChanges,\n processChangesWithStats,\n handlers: {\n fetch: config.fetch,\n create: config.create,\n update: config.update,\n delete: config.delete,\n },\n };\n}\n"],"names":["jsonResponse","body","status","responses","data","message","handler","error","createChangeProcessors","config","change","result","validateData","schema","processServerChange","processor","validation","serverSyncSuccess","options","serverSyncError","createSyncServer","fetchItems","query","processChanges","changes","processChangesWithStats","results","categorizeResults","handlePost","request","responseBody"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { Change, SyncResult } from '../types';
2
+ /**
3
+ * Result of a sync operation handler.
4
+ * Used by both client-side and server-side handlers.
5
+ */
6
+ export type SyncHandlerResult = {
7
+ success: true;
8
+ newId?: string;
9
+ } | {
10
+ success: false;
11
+ error: string;
12
+ };
13
+ /**
14
+ * Zod-like schema interface (duck typing to avoid hard dependency)
15
+ */
16
+ export type Schema<T> = {
17
+ safeParse: (data: unknown) => {
18
+ success: true;
19
+ data: T;
20
+ } | {
21
+ success: false;
22
+ error: {
23
+ message: string;
24
+ };
25
+ };
26
+ };
27
+ /**
28
+ * Aggregated results from sync operations.
29
+ * Provides categorized results and summary statistics.
30
+ */
31
+ export type SyncBatchResult = {
32
+ /** All results from the sync operation */
33
+ results: SyncResult[];
34
+ /** Results that succeeded */
35
+ successful: SyncResult[];
36
+ /** Results that failed */
37
+ failed: SyncResult[];
38
+ /** Whether all operations succeeded */
39
+ allSucceeded: boolean;
40
+ /** Whether any operations succeeded */
41
+ anySucceeded: boolean;
42
+ /** Summary counts */
43
+ summary: {
44
+ total: number;
45
+ succeeded: number;
46
+ failed: number;
47
+ };
48
+ };
49
+ /**
50
+ * Categorize sync results into successful and failed
51
+ */
52
+ export declare function categorizeResults(results: SyncResult[]): SyncBatchResult;
53
+ /**
54
+ * Request body for the unified POST endpoint
55
+ */
56
+ export type SyncRequestBody<T, Q = unknown> = {
57
+ /** Query parameters for fetching items */
58
+ query?: Q;
59
+ /** Changes to sync */
60
+ changes?: Change<T>[];
61
+ };
62
+ /**
63
+ * Response body from the unified POST endpoint
64
+ */
65
+ export type SyncResponseBody<T> = {
66
+ /** Fetched items (when query was provided) */
67
+ results?: T[];
68
+ /** Sync results (when changes were provided) */
69
+ syncResults?: SyncResult[];
70
+ };
@@ -0,0 +1,29 @@
1
+ import { Change, SyncQueueState, SyncResult, IdMapping } from './types';
2
+ export type SyncQueueConfig<T> = {
3
+ debounce: number;
4
+ maxRetries: number;
5
+ onSync: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
6
+ onIdRemap?: (mappings: IdMapping[]) => void;
7
+ };
8
+ export declare class SyncQueue<T> {
9
+ private _config;
10
+ private _state;
11
+ private _subscribers;
12
+ private _debounceTimer;
13
+ private _abortController;
14
+ constructor(config: SyncQueueConfig<T>);
15
+ enqueue(change: Change<T>): void;
16
+ pause(): void;
17
+ resume(): void;
18
+ retryAll(): void;
19
+ retry(id: string): void;
20
+ resetRetries(id: string): void;
21
+ getState(): SyncQueueState<T>;
22
+ subscribe(callback: () => void): () => void;
23
+ private _updateState;
24
+ private _scheduleFlush;
25
+ private _clearTimer;
26
+ private _flush;
27
+ private _processResults;
28
+ private _handleError;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ export type JsonValue = string | number | boolean | null | undefined;
2
+ export type PlainObject = {
3
+ [key: string]: JsonValue | PlainObject | JsonValue[] | PlainObject[];
4
+ };
5
+ export type Result = {
6
+ status: "success" | "error";
7
+ error?: string;
8
+ };
9
+ export type Fn<A, R = void> = (arg: A) => R;
10
+ export type Mutator<T> = (draft: T) => void;
11
+ export type SyncState = "idle" | "fetching" | "syncing";
12
+ export type ItemSyncStatus = "pending" | "syncing" | "success" | "error";
13
+ export type ChangeType = "create" | "update" | "delete";
14
+ export type Change<T> = {
15
+ id: string;
16
+ type: ChangeType;
17
+ data: T;
18
+ };
19
+ export type ItemStatus = {
20
+ type: ChangeType;
21
+ status: ItemSyncStatus;
22
+ retries: number;
23
+ error?: string;
24
+ } | 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
+ export type SyncError<T> = {
36
+ error: string;
37
+ retries: number;
38
+ operations: Change<T>[];
39
+ };
40
+ export type SyncQueueState<T> = {
41
+ queue: Map<string, Change<T>[]>;
42
+ inFlight: Map<string, Change<T>[]>;
43
+ errors: Map<string, SyncError<T>>;
44
+ isPaused: boolean;
45
+ isSyncing: boolean;
46
+ };
47
+ export type FetchState = "idle" | "fetching" | "error";
48
+ export type Config<T, C> = {
49
+ id: string;
50
+ initialContext: C;
51
+ getId: (item: T) => string;
52
+ setId?: (item: T, newId: string) => T;
53
+ syncDebounce?: number;
54
+ syncRetries?: number;
55
+ refetchOnMutation?: boolean;
56
+ cacheCapacity?: number;
57
+ cacheTtl?: number;
58
+ fetchRetries?: number;
59
+ onFetch: (context: C, signal: AbortSignal) => Promise<T[]>;
60
+ onSync?: (changes: Change<T>[], signal: AbortSignal) => Promise<SyncResult[]>;
61
+ };
package/dist/useCrud.d.ts CHANGED
@@ -1,169 +1,28 @@
1
- declare type CachedItem = {
2
- data: unknown;
3
- ts: number;
4
- };
5
-
6
- export declare type CrudConfig<T extends Item = Item, C extends object = object, M extends object = object> = {
7
- id: string;
8
- context: C;
9
- debounce?: number;
10
- caching?: {
11
- capacity: number;
12
- age: number;
13
- };
14
- fetch?: FetchFn<T, M>;
15
- create?: TransitionFn<T>;
16
- update?: TransitionFn<T>;
17
- remove?: TransitionFn<T>;
18
- getServerSnapshot?: () => StoreState<T>;
19
- };
20
-
21
- /**
22
- * Cache implementation for storing and managing fetch results
23
- * with configurable age and capacity limits.
24
- */
25
- declare class FetchCache {
26
- age: number;
27
- capacity: number;
28
- storage: Map<string, CachedItem>;
29
- constructor(age?: number, capacity?: number);
30
- invalidate(): void;
31
- reset(age: number, capacity: number): void;
32
- get(id: string): unknown;
33
- put(id: string, item: unknown): void;
34
- remove(id: string): void;
35
- withCache: (id: string, callback: () => Promise<unknown>) => Promise<unknown>;
36
- }
37
-
38
- export declare type FetchFn<T extends Item = Item, M extends object = object> = (option: QueryOption) => Promise<{
39
- items: T[];
40
- metadata?: M;
41
- }>;
42
-
43
- export declare type Item = {
44
- id: string;
45
- } & Record<string, any>;
46
-
47
- export declare type ItemWithState<T extends Item = Item, State extends TransitionStates = TransitionStates> = {
48
- data: T;
49
- optimistic: boolean;
50
- errors: Map<string, string[]>;
51
- transitions: Map<string | "default", [State, State extends "idle" ? undefined : T, ts: number]>;
52
- };
53
-
54
- export declare type QueryOption = {
55
- signal: AbortSignal;
56
- context: unknown;
57
- };
58
-
59
- /**
60
- * Core state management store for CRUD operations.
61
- * Handles data fetching, caching, and state transitions for items.
62
- * @template T - Type of items managed by the store, must extend Item base type
63
- */
64
- declare class Store<T extends Item = Item, C extends object = object, M extends object = object> {
65
- private id;
66
- private config;
67
- batched: boolean;
68
- state: StoreState<T, C, M>;
69
- subscribers: Set<() => void>;
70
- controllers: Map<string, AbortController>;
71
- fetchController: AbortController;
72
- fetchCache: FetchCache;
73
- constructor(id: string, config: CrudConfig<T, C, M>);
74
- executeFetch: (newContext: C) => Promise<void>;
75
- executeRemove: (input: T) => Promise<void>;
76
- executeUpdate: ({ item, updater, isOptimistic, skipSave, tag, }: {
77
- item: T;
78
- updater?: Updater<T>;
79
- isOptimistic?: boolean;
80
- skipSave?: boolean;
81
- tag?: string;
82
- }) => Promise<void>;
83
- executeCreate: (input: Partial<T>) => Promise<void>;
84
- private getCacheKey;
85
- private getControlerId;
86
- customLog: (title?: string, ...messages: any[]) => void;
87
- private batch;
88
- private setItems;
89
- private setMetadata;
90
- private setContext;
91
- private remove;
92
- private updateItem;
93
- clearFetchCache: () => void;
94
- private startFetch;
95
- private endFetch;
96
- cancelFetch: () => void;
97
- cancelOperation: (id: string, tag?: string) => void;
98
- private startTransition;
99
- private notify;
100
- getSnapshot: () => StoreState<T, C, M>;
101
- subscribe: (fn: () => void) => () => void;
102
- static instances: Map<string, Store<any>>;
103
- static createStore<T extends Item = Item, C extends object = object, M extends object = object>(config: CrudConfig<T, C, M>): Store<T, C>;
104
- }
105
-
106
- export declare type StoreState<T extends Item = Item, C extends object = object, M extends object = object> = {
107
- context: C;
108
- items: Map<string, ItemWithState<T>>;
109
- fetchState: {
110
- isLoading: boolean;
111
- errors: string[];
112
- metadata?: M;
113
- };
114
- };
115
-
116
- export declare type TransitionFn<T extends Item = Item> = (item: Partial<T>, option: QueryOption) => Promise<{
117
- id: string;
118
- }>;
119
-
120
- export declare type TransitionStates = "create" | "update" | "delete" | "idle" | "changed";
121
-
122
- export declare type Updater<T> = (updatable: T) => void;
123
-
124
- /**
125
- * Main hook for managing CRUD operations with automatic state management.
126
- * Provides access to items, metadata, loading states, and CRUD operations.
127
- * @template T - Type of items to manage, must extend Item base type
128
- * @template C - Type of context object used in operations
129
- * @param config - Configuration object for CRUD operations
130
- * @returns Object containing items, state information, and CRUD operation handlers
131
- */
132
- export declare function useCrud<T extends Item = Item, C extends object = object, M extends object = object>(config: CrudConfig<T, C, M>): {
133
- itemsById: Map<string, ItemWithState<T, TransitionStates>>;
134
- items: ItemWithState<T, TransitionStates>[];
135
- fetchState: {
136
- isLoading: boolean;
137
- errors: string[];
138
- metadata?: M;
139
- };
140
- hasError: boolean;
141
- cancelFetch: () => void;
142
- cancelOperation: (id: string, tag?: string) => void;
143
- refetch: () => void;
144
- create: (item: Partial<T>) => void;
145
- remove: (item: T) => void;
146
- store: Store<T, C, object>;
147
- };
148
-
149
- export declare function useItemState<T extends Item = Item>(storeId: string, item: ItemWithState<T>): [
150
- T,
151
- {
152
- errors: Map<string, string[]>;
153
- states: Set<string>;
154
- save: () => void;
155
- change: (cb: Updater<T>, tag?: string) => void;
156
- update: (cb: Updater<T>, options?: {
157
- tag?: string;
158
- isOptimistic?: boolean;
159
- }) => void;
160
- remove: () => void;
161
- cancel: () => void;
162
- hasError: boolean;
163
- errorCount: number;
164
- itemWithState: ItemWithState<T>;
165
- store: Store<T>;
166
- }
167
- ];
168
-
169
- export { }
1
+ import { Draft } from 'mutative';
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, C>(config: Config<T, C>): {
5
+ items: Map<string, T>;
6
+ context: C;
7
+ syncState: import('./types').SyncState;
8
+ syncQueue: import('./types').SyncQueueState<T>;
9
+ loading: boolean;
10
+ syncing: boolean;
11
+ fetchStatus: import('./types').FetchState;
12
+ fetchError: string;
13
+ create: (item: T) => void;
14
+ update: (id: string, mutate: (draft: Draft<T>) => void) => void;
15
+ remove: (id: string) => void;
16
+ getItem: (id: string) => import('./item').Item<T, C>;
17
+ getItemStatus: (id: string) => {
18
+ type: import('./types').ChangeType;
19
+ status: import('./types').ItemSyncStatus;
20
+ retries: number;
21
+ error?: string;
22
+ };
23
+ setContext: (patchContext: Mutator<C>) => void;
24
+ refresh: () => Promise<void>;
25
+ pauseSync: () => void;
26
+ resumeSync: () => void;
27
+ retrySync: (id?: string) => void;
28
+ };