@revealui/sync 0.0.0-canary-20260409021642

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 (75) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +201 -0
  3. package/dist/collab/agent-client-factory.d.ts +16 -0
  4. package/dist/collab/agent-client-factory.d.ts.map +1 -0
  5. package/dist/collab/agent-client-factory.js +24 -0
  6. package/dist/collab/agent-client.d.ts +56 -0
  7. package/dist/collab/agent-client.d.ts.map +1 -0
  8. package/dist/collab/agent-client.js +262 -0
  9. package/dist/collab/index.d.ts +8 -0
  10. package/dist/collab/index.d.ts.map +1 -0
  11. package/dist/collab/index.js +4 -0
  12. package/dist/collab/protocol-constants.d.ts +5 -0
  13. package/dist/collab/protocol-constants.d.ts.map +1 -0
  14. package/dist/collab/protocol-constants.js +4 -0
  15. package/dist/collab/server-index.d.ts +6 -0
  16. package/dist/collab/server-index.d.ts.map +1 -0
  17. package/dist/collab/server-index.js +3 -0
  18. package/dist/collab/use-collab-document.d.ts +8 -0
  19. package/dist/collab/use-collab-document.d.ts.map +1 -0
  20. package/dist/collab/use-collab-document.js +49 -0
  21. package/dist/collab/use-collaboration.d.ts +26 -0
  22. package/dist/collab/use-collaboration.d.ts.map +1 -0
  23. package/dist/collab/use-collaboration.js +65 -0
  24. package/dist/collab/yjs-websocket-provider.d.ts +36 -0
  25. package/dist/collab/yjs-websocket-provider.d.ts.map +1 -0
  26. package/dist/collab/yjs-websocket-provider.js +172 -0
  27. package/dist/components/SyncStatusIndicator.d.ts +17 -0
  28. package/dist/components/SyncStatusIndicator.d.ts.map +1 -0
  29. package/dist/components/SyncStatusIndicator.js +65 -0
  30. package/dist/fetch-with-timeout.d.ts +7 -0
  31. package/dist/fetch-with-timeout.d.ts.map +1 -0
  32. package/dist/fetch-with-timeout.js +27 -0
  33. package/dist/hooks/index.d.ts +4 -0
  34. package/dist/hooks/index.d.ts.map +1 -0
  35. package/dist/hooks/index.js +2 -0
  36. package/dist/hooks/useAgentContexts.d.ts +29 -0
  37. package/dist/hooks/useAgentContexts.d.ts.map +1 -0
  38. package/dist/hooks/useAgentContexts.js +22 -0
  39. package/dist/hooks/useAgentMemory.d.ts +34 -0
  40. package/dist/hooks/useAgentMemory.d.ts.map +1 -0
  41. package/dist/hooks/useAgentMemory.js +37 -0
  42. package/dist/hooks/useConversations.d.ts +31 -0
  43. package/dist/hooks/useConversations.d.ts.map +1 -0
  44. package/dist/hooks/useConversations.js +26 -0
  45. package/dist/hooks/useCoordinationSessions.d.ts +35 -0
  46. package/dist/hooks/useCoordinationSessions.d.ts.map +1 -0
  47. package/dist/hooks/useCoordinationSessions.js +22 -0
  48. package/dist/hooks/useCoordinationWorkItems.d.ts +41 -0
  49. package/dist/hooks/useCoordinationWorkItems.d.ts.map +1 -0
  50. package/dist/hooks/useCoordinationWorkItems.js +22 -0
  51. package/dist/hooks/useOfflineCache.d.ts +32 -0
  52. package/dist/hooks/useOfflineCache.d.ts.map +1 -0
  53. package/dist/hooks/useOfflineCache.js +129 -0
  54. package/dist/hooks/useOnlineStatus.d.ts +21 -0
  55. package/dist/hooks/useOnlineStatus.d.ts.map +1 -0
  56. package/dist/hooks/useOnlineStatus.js +74 -0
  57. package/dist/index.d.ts +26 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +18 -0
  60. package/dist/mutations.d.ts +14 -0
  61. package/dist/mutations.d.ts.map +1 -0
  62. package/dist/mutations.js +53 -0
  63. package/dist/offline-queue.d.ts +55 -0
  64. package/dist/offline-queue.d.ts.map +1 -0
  65. package/dist/offline-queue.js +126 -0
  66. package/dist/provider/index.d.ts +35 -0
  67. package/dist/provider/index.d.ts.map +1 -0
  68. package/dist/provider/index.js +29 -0
  69. package/dist/shape-utils.d.ts +11 -0
  70. package/dist/shape-utils.d.ts.map +1 -0
  71. package/dist/shape-utils.js +14 -0
  72. package/dist/test-setup.d.ts +2 -0
  73. package/dist/test-setup.d.ts.map +1 -0
  74. package/dist/test-setup.js +1 -0
  75. package/package.json +69 -0
@@ -0,0 +1,31 @@
1
+ import type { MutationResult } from '../mutations.js';
2
+ export interface ConversationRecord {
3
+ id: string;
4
+ user_id: string;
5
+ agent_id: string;
6
+ title?: string | null;
7
+ status: string;
8
+ device_id?: string | null;
9
+ version: number;
10
+ created_at: string;
11
+ updated_at: string;
12
+ }
13
+ export interface CreateConversationInput {
14
+ agent_id: string;
15
+ title?: string;
16
+ device_id?: string;
17
+ }
18
+ export interface UpdateConversationInput {
19
+ title?: string;
20
+ status?: string;
21
+ }
22
+ export interface UseConversationsResult {
23
+ conversations: ConversationRecord[];
24
+ isLoading: boolean;
25
+ error: Error | null;
26
+ create: (data: CreateConversationInput) => Promise<MutationResult<ConversationRecord>>;
27
+ update: (id: string, data: UpdateConversationInput) => Promise<MutationResult<ConversationRecord>>;
28
+ remove: (id: string) => Promise<MutationResult<void>>;
29
+ }
30
+ export declare function useConversations(_userId: string): UseConversationsResult;
31
+ //# sourceMappingURL=useConversations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversations.d.ts","sourceRoot":"","sources":["../../src/hooks/useConversations.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKtD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACvF,MAAM,EAAE,CACN,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,uBAAuB,KAC1B,OAAO,CAAC,cAAc,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;CACvD;AAID,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,sBAAsB,CAuBxE"}
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+ import { useShape } from '@electric-sql/react';
3
+ import { fetchWithTimeout } from '../fetch-with-timeout.js';
4
+ import { useSyncMutations } from '../mutations.js';
5
+ import { useElectricConfig } from '../provider/index.js';
6
+ import { toRecords } from '../shape-utils.js';
7
+ // _userId kept for API compatibility — filtering is enforced by the server-side
8
+ // proxy at /api/shapes/conversations, which reads the session cookie directly.
9
+ export function useConversations(_userId) {
10
+ const { proxyBaseUrl } = useElectricConfig();
11
+ // The proxy validates the session and enforces row-level filtering server-side.
12
+ // Client-provided params are not forwarded — the proxy overrides them.
13
+ const { data, isLoading, error } = useShape({
14
+ url: `${proxyBaseUrl}/api/shapes/conversations`,
15
+ fetchClient: fetchWithTimeout,
16
+ });
17
+ const { create, update, remove } = useSyncMutations('conversations');
18
+ return {
19
+ conversations: toRecords(data),
20
+ isLoading,
21
+ error: error || null,
22
+ create,
23
+ update,
24
+ remove,
25
+ };
26
+ }
@@ -0,0 +1,35 @@
1
+ import type { MutationResult } from '../mutations.js';
2
+ export interface CoordinationSessionRecord {
3
+ id: string;
4
+ agent_id: string;
5
+ started_at: string;
6
+ ended_at: string | null;
7
+ task: string;
8
+ status: string;
9
+ pid: number | null;
10
+ tools: Record<string, number> | null;
11
+ metadata: Record<string, unknown> | null;
12
+ }
13
+ export interface CreateCoordinationSessionInput {
14
+ agent_id: string;
15
+ task?: string;
16
+ pid?: number;
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ export interface UpdateCoordinationSessionInput {
20
+ task?: string;
21
+ status?: string;
22
+ ended_at?: string;
23
+ tools?: Record<string, number>;
24
+ metadata?: Record<string, unknown>;
25
+ }
26
+ export interface UseCoordinationSessionsResult {
27
+ sessions: CoordinationSessionRecord[];
28
+ isLoading: boolean;
29
+ error: Error | null;
30
+ create: (data: CreateCoordinationSessionInput) => Promise<MutationResult<CoordinationSessionRecord>>;
31
+ update: (id: string, data: UpdateCoordinationSessionInput) => Promise<MutationResult<CoordinationSessionRecord>>;
32
+ remove: (id: string) => Promise<MutationResult<void>>;
33
+ }
34
+ export declare function useCoordinationSessions(): UseCoordinationSessionsResult;
35
+ //# sourceMappingURL=useCoordinationSessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCoordinationSessions.d.ts","sourceRoot":"","sources":["../../src/hooks/useCoordinationSessions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKtD,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,8BAA8B;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,CACN,IAAI,EAAE,8BAA8B,KACjC,OAAO,CAAC,cAAc,CAAC,yBAAyB,CAAC,CAAC,CAAC;IACxD,MAAM,EAAE,CACN,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,8BAA8B,KACjC,OAAO,CAAC,cAAc,CAAC,yBAAyB,CAAC,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;CACvD;AAED,wBAAgB,uBAAuB,IAAI,6BAA6B,CAqBvE"}
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { useShape } from '@electric-sql/react';
3
+ import { fetchWithTimeout } from '../fetch-with-timeout.js';
4
+ import { useSyncMutations } from '../mutations.js';
5
+ import { useElectricConfig } from '../provider/index.js';
6
+ import { toRecords } from '../shape-utils.js';
7
+ export function useCoordinationSessions() {
8
+ const { proxyBaseUrl } = useElectricConfig();
9
+ const { data, isLoading, error } = useShape({
10
+ url: `${proxyBaseUrl}/api/shapes/coordination-sessions`,
11
+ fetchClient: fetchWithTimeout,
12
+ });
13
+ const { create, update, remove } = useSyncMutations('coordination-sessions');
14
+ return {
15
+ sessions: toRecords(data),
16
+ isLoading,
17
+ error: error || null,
18
+ create,
19
+ update,
20
+ remove,
21
+ };
22
+ }
@@ -0,0 +1,41 @@
1
+ import type { MutationResult } from '../mutations.js';
2
+ export interface CoordinationWorkItemRecord {
3
+ id: string;
4
+ title: string;
5
+ description: string | null;
6
+ status: string;
7
+ priority: number;
8
+ owner_agent: string | null;
9
+ owner_session: string | null;
10
+ parent_id: string | null;
11
+ metadata: Record<string, unknown> | null;
12
+ created_at: string;
13
+ updated_at: string;
14
+ completed_at: string | null;
15
+ }
16
+ export interface CreateCoordinationWorkItemInput {
17
+ title: string;
18
+ description?: string;
19
+ priority?: number;
20
+ owner_agent?: string;
21
+ parent_id?: string;
22
+ metadata?: Record<string, unknown>;
23
+ }
24
+ export interface UpdateCoordinationWorkItemInput {
25
+ title?: string;
26
+ description?: string;
27
+ status?: string;
28
+ priority?: number;
29
+ owner_agent?: string;
30
+ metadata?: Record<string, unknown>;
31
+ }
32
+ export interface UseCoordinationWorkItemsResult {
33
+ workItems: CoordinationWorkItemRecord[];
34
+ isLoading: boolean;
35
+ error: Error | null;
36
+ create: (data: CreateCoordinationWorkItemInput) => Promise<MutationResult<CoordinationWorkItemRecord>>;
37
+ update: (id: string, data: UpdateCoordinationWorkItemInput) => Promise<MutationResult<CoordinationWorkItemRecord>>;
38
+ remove: (id: string) => Promise<MutationResult<void>>;
39
+ }
40
+ export declare function useCoordinationWorkItems(): UseCoordinationWorkItemsResult;
41
+ //# sourceMappingURL=useCoordinationWorkItems.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCoordinationWorkItems.d.ts","sourceRoot":"","sources":["../../src/hooks/useCoordinationWorkItems.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAKtD,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,0BAA0B,EAAE,CAAC;IACxC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,CACN,IAAI,EAAE,+BAA+B,KAClC,OAAO,CAAC,cAAc,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,CACN,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,+BAA+B,KAClC,OAAO,CAAC,cAAc,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACzD,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;CACvD;AAED,wBAAgB,wBAAwB,IAAI,8BAA8B,CAqBzE"}
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { useShape } from '@electric-sql/react';
3
+ import { fetchWithTimeout } from '../fetch-with-timeout.js';
4
+ import { useSyncMutations } from '../mutations.js';
5
+ import { useElectricConfig } from '../provider/index.js';
6
+ import { toRecords } from '../shape-utils.js';
7
+ export function useCoordinationWorkItems() {
8
+ const { proxyBaseUrl } = useElectricConfig();
9
+ const { data, isLoading, error } = useShape({
10
+ url: `${proxyBaseUrl}/api/shapes/coordination-work-items`,
11
+ fetchClient: fetchWithTimeout,
12
+ });
13
+ const { create, update, remove } = useSyncMutations('coordination-work-items');
14
+ return {
15
+ workItems: toRecords(data),
16
+ isLoading,
17
+ error: error || null,
18
+ create,
19
+ update,
20
+ remove,
21
+ };
22
+ }
@@ -0,0 +1,32 @@
1
+ interface UseOfflineCacheOptions {
2
+ /** ElectricSQL shape subscription URL. */
3
+ shapeUrl: string;
4
+ /** Unique key for the localStorage cache entry. */
5
+ cacheKey: string;
6
+ /** How long cached data is considered fresh (seconds). Defaults to 3600. */
7
+ ttlSeconds?: number;
8
+ }
9
+ interface UseOfflineCacheResult<T> {
10
+ /** The current data — live from the shape when online, cached when offline. */
11
+ data: T[];
12
+ /** Whether the browser has network connectivity. */
13
+ isOnline: boolean;
14
+ /** Whether the shape subscription is currently loading fresh data. */
15
+ isSyncing: boolean;
16
+ /** Timestamp of the most recent successful sync to cache. */
17
+ lastSyncedAt: Date | null;
18
+ /** Shape subscription or cache-read error, if any. */
19
+ error: Error | null;
20
+ }
21
+ /**
22
+ * Wrap an ElectricSQL `useShape` subscription with offline-first caching.
23
+ *
24
+ * When online the hook delegates to `useShape` and mirrors results into
25
+ * `localStorage`. When offline (or during initial load) it returns the
26
+ * most recent cached snapshot if one exists within the TTL window.
27
+ *
28
+ * @typeParam T - Row type returned by the shape subscription.
29
+ */
30
+ export declare function useOfflineCache<T>(options: UseOfflineCacheOptions): UseOfflineCacheResult<T>;
31
+ export {};
32
+ //# sourceMappingURL=useOfflineCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOfflineCache.d.ts","sourceRoot":"","sources":["../../src/hooks/useOfflineCache.ts"],"names":[],"mappings":"AAmBA,UAAU,sBAAsB;IAC9B,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,qBAAqB,CAAC,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAClB,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,sDAAsD;IACtD,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAqED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,sBAAsB,GAAG,qBAAqB,CAAC,CAAC,CAAC,CAiD5F"}
@@ -0,0 +1,129 @@
1
+ 'use client';
2
+ import { useShape } from '@electric-sql/react';
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { fetchWithTimeout } from '../fetch-with-timeout.js';
5
+ import { toRecords } from '../shape-utils.js';
6
+ import { useOnlineStatus } from './useOnlineStatus.js';
7
+ /** Prefix for all offline-cache localStorage keys. */
8
+ const CACHE_PREFIX = 'revealui:cache:';
9
+ /** Default time-to-live for cached data (seconds). */
10
+ const DEFAULT_TTL_SECONDS = 3600;
11
+ /**
12
+ * Check whether `localStorage` is usable.
13
+ */
14
+ function hasLocalStorage() {
15
+ if (typeof window === 'undefined') {
16
+ return false;
17
+ }
18
+ try {
19
+ const testKey = '__revealui_oc_test__';
20
+ window.localStorage.setItem(testKey, '1');
21
+ window.localStorage.removeItem(testKey);
22
+ return true;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ /**
29
+ * Read cached data from localStorage. Returns `null` when the entry is
30
+ * missing, expired, or unreadable.
31
+ */
32
+ function readCache(cacheKey, ttlSeconds) {
33
+ if (!hasLocalStorage()) {
34
+ return null;
35
+ }
36
+ try {
37
+ const raw = window.localStorage.getItem(CACHE_PREFIX + cacheKey);
38
+ if (raw === null) {
39
+ return null;
40
+ }
41
+ const parsed = JSON.parse(raw);
42
+ if (!Array.isArray(parsed.data)) {
43
+ return null;
44
+ }
45
+ // Check TTL.
46
+ const cachedTime = new Date(parsed.cachedAt).getTime();
47
+ if (Number.isNaN(cachedTime)) {
48
+ return null;
49
+ }
50
+ const ageSeconds = (Date.now() - cachedTime) / 1_000;
51
+ if (ageSeconds > ttlSeconds) {
52
+ return null;
53
+ }
54
+ return parsed;
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Write data to the localStorage cache. Silently ignores failures.
62
+ */
63
+ function writeCache(cacheKey, data) {
64
+ if (!hasLocalStorage()) {
65
+ return;
66
+ }
67
+ try {
68
+ const payload = {
69
+ data,
70
+ cachedAt: new Date().toISOString(),
71
+ };
72
+ window.localStorage.setItem(CACHE_PREFIX + cacheKey, JSON.stringify(payload));
73
+ }
74
+ catch {
75
+ // Quota exceeded or private browsing — drop silently.
76
+ }
77
+ }
78
+ /**
79
+ * Wrap an ElectricSQL `useShape` subscription with offline-first caching.
80
+ *
81
+ * When online the hook delegates to `useShape` and mirrors results into
82
+ * `localStorage`. When offline (or during initial load) it returns the
83
+ * most recent cached snapshot if one exists within the TTL window.
84
+ *
85
+ * @typeParam T - Row type returned by the shape subscription.
86
+ */
87
+ export function useOfflineCache(options) {
88
+ const { shapeUrl, cacheKey, ttlSeconds = DEFAULT_TTL_SECONDS } = options;
89
+ const { isOnline } = useOnlineStatus();
90
+ // Shape subscription — runs continuously; ElectricSQL handles reconnection.
91
+ const shape = useShape({ url: shapeUrl, fetchClient: fetchWithTimeout });
92
+ const [lastSyncedAt, setLastSyncedAt] = useState(null);
93
+ // Keep a ref to avoid stale closures in the sync effect.
94
+ const cacheKeyRef = useRef(cacheKey);
95
+ cacheKeyRef.current = cacheKey;
96
+ // Persist live data to cache whenever the shape delivers fresh rows.
97
+ const shapeData = shape.data;
98
+ useEffect(() => {
99
+ if (!isOnline) {
100
+ return;
101
+ }
102
+ if (!Array.isArray(shapeData) || shapeData.length === 0) {
103
+ return;
104
+ }
105
+ const typed = toRecords(shapeData);
106
+ writeCache(cacheKeyRef.current, typed);
107
+ setLastSyncedAt(new Date());
108
+ }, [shapeData, isOnline]);
109
+ // Determine what to return.
110
+ if (isOnline && Array.isArray(shapeData) && shapeData.length > 0) {
111
+ return {
112
+ data: toRecords(shapeData),
113
+ isOnline,
114
+ isSyncing: shape.isLoading,
115
+ lastSyncedAt,
116
+ error: shape.error || null,
117
+ };
118
+ }
119
+ // Offline or shape has not loaded yet — try the cache.
120
+ const cached = readCache(cacheKey, ttlSeconds);
121
+ const cachedSyncDate = cached !== null ? new Date(cached.cachedAt) : null;
122
+ return {
123
+ data: cached?.data ?? [],
124
+ isOnline,
125
+ isSyncing: isOnline && shape.isLoading,
126
+ lastSyncedAt: lastSyncedAt ?? cachedSyncDate,
127
+ error: shape.error || null,
128
+ };
129
+ }
@@ -0,0 +1,21 @@
1
+ export interface OnlineStatusResult {
2
+ /** Whether the browser currently has network connectivity. */
3
+ isOnline: boolean;
4
+ /** Whether the connection was recently restored (resets after 5 s). */
5
+ wasOffline: boolean;
6
+ /** Timestamp of the last time the browser was confirmed online. */
7
+ lastOnlineAt: Date | null;
8
+ }
9
+ /**
10
+ * Track browser online/offline status with reconnection awareness.
11
+ *
12
+ * - `isOnline` reflects `navigator.onLine` and live `online`/`offline` events.
13
+ * - `wasOffline` becomes `true` when connectivity is restored and resets to
14
+ * `false` after 5 seconds.
15
+ * - `lastOnlineAt` records the most recent reconnection timestamp.
16
+ *
17
+ * During SSR (when `window` is not available) the hook returns
18
+ * `{ isOnline: true, wasOffline: false, lastOnlineAt: null }`.
19
+ */
20
+ export declare function useOnlineStatus(): OnlineStatusResult;
21
+ //# sourceMappingURL=useOnlineStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useOnlineStatus.d.ts","sourceRoot":"","sources":["../../src/hooks/useOnlineStatus.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,8DAA8D;IAC9D,QAAQ,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,mEAAmE;IACnE,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;CAC3B;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,IAAI,kBAAkB,CAuDpD"}
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ /** Duration in ms before `wasOffline` resets after reconnection. */
4
+ const WAS_OFFLINE_RESET_MS = 5_000;
5
+ /** SSR-safe default: assume online when `window` is unavailable. */
6
+ const SSR_DEFAULT = {
7
+ isOnline: true,
8
+ wasOffline: false,
9
+ lastOnlineAt: null,
10
+ };
11
+ /**
12
+ * Check whether the current environment is a browser.
13
+ * Returns `false` during SSR / Node.js test runs without a DOM.
14
+ */
15
+ function isBrowser() {
16
+ return typeof window !== 'undefined';
17
+ }
18
+ /**
19
+ * Track browser online/offline status with reconnection awareness.
20
+ *
21
+ * - `isOnline` reflects `navigator.onLine` and live `online`/`offline` events.
22
+ * - `wasOffline` becomes `true` when connectivity is restored and resets to
23
+ * `false` after 5 seconds.
24
+ * - `lastOnlineAt` records the most recent reconnection timestamp.
25
+ *
26
+ * During SSR (when `window` is not available) the hook returns
27
+ * `{ isOnline: true, wasOffline: false, lastOnlineAt: null }`.
28
+ */
29
+ export function useOnlineStatus() {
30
+ const [isOnline, setIsOnline] = useState(() => (isBrowser() ? navigator.onLine : true));
31
+ const [wasOffline, setWasOffline] = useState(false);
32
+ const [lastOnlineAt, setLastOnlineAt] = useState(null);
33
+ const resetTimerRef = useRef(null);
34
+ useEffect(() => {
35
+ // No-op during SSR — the effect only runs in the browser.
36
+ if (!isBrowser()) {
37
+ return;
38
+ }
39
+ function handleOnline() {
40
+ setIsOnline(true);
41
+ setLastOnlineAt(new Date());
42
+ setWasOffline(true);
43
+ // Clear any existing timer before setting a new one.
44
+ if (resetTimerRef.current !== null) {
45
+ clearTimeout(resetTimerRef.current);
46
+ }
47
+ resetTimerRef.current = setTimeout(() => {
48
+ setWasOffline(false);
49
+ resetTimerRef.current = null;
50
+ }, WAS_OFFLINE_RESET_MS);
51
+ }
52
+ function handleOffline() {
53
+ setIsOnline(false);
54
+ setWasOffline(false);
55
+ if (resetTimerRef.current !== null) {
56
+ clearTimeout(resetTimerRef.current);
57
+ resetTimerRef.current = null;
58
+ }
59
+ }
60
+ window.addEventListener('online', handleOnline);
61
+ window.addEventListener('offline', handleOffline);
62
+ return () => {
63
+ window.removeEventListener('online', handleOnline);
64
+ window.removeEventListener('offline', handleOffline);
65
+ if (resetTimerRef.current !== null) {
66
+ clearTimeout(resetTimerRef.current);
67
+ }
68
+ };
69
+ }, []);
70
+ if (!isBrowser()) {
71
+ return SSR_DEFAULT;
72
+ }
73
+ return { isOnline, wasOffline, lastOnlineAt };
74
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @revealui/sync — Real-time collaboration and sync primitives.
3
+ *
4
+ * The collab layer (Yjs-based) is fully functional.
5
+ * ElectricProvider provides proxyBaseUrl config to child hooks. All hooks route
6
+ * through the authenticated CMS proxy at /api/shapes/* — no direct Electric client.
7
+ *
8
+ * Reads use ElectricSQL shape subscriptions for real-time updates.
9
+ * Writes use REST mutations via /api/sync/* — changes propagate to all
10
+ * subscribers automatically through ElectricSQL replication.
11
+ */
12
+ export type { CollabDocumentState, UseCollaborationOptions, UseCollaborationResult, } from './collab/index.js';
13
+ export { CollabProvider, useCollabDocument, useCollaboration, } from './collab/index.js';
14
+ export type { AgentContextRecord, CreateAgentContextInput, UpdateAgentContextInput, UseAgentContextsResult, } from './hooks/useAgentContexts.js';
15
+ export { useAgentContexts } from './hooks/useAgentContexts.js';
16
+ export type { AgentMemoryRecord, CreateAgentMemoryInput, UpdateAgentMemoryInput, UseAgentMemoryResult, } from './hooks/useAgentMemory.js';
17
+ export { useAgentMemory } from './hooks/useAgentMemory.js';
18
+ export type { ConversationRecord, CreateConversationInput, UpdateConversationInput, UseConversationsResult, } from './hooks/useConversations.js';
19
+ export { useConversations } from './hooks/useConversations.js';
20
+ export type { CoordinationSessionRecord, CreateCoordinationSessionInput, UpdateCoordinationSessionInput, UseCoordinationSessionsResult, } from './hooks/useCoordinationSessions.js';
21
+ export { useCoordinationSessions } from './hooks/useCoordinationSessions.js';
22
+ export type { CoordinationWorkItemRecord, CreateCoordinationWorkItemInput, UpdateCoordinationWorkItemInput, UseCoordinationWorkItemsResult, } from './hooks/useCoordinationWorkItems.js';
23
+ export { useCoordinationWorkItems } from './hooks/useCoordinationWorkItems.js';
24
+ export type { MutationResult } from './mutations.js';
25
+ export { ElectricProvider, useElectricConfig } from './provider/index.js';
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,YAAY,EACV,yBAAyB,EACzB,8BAA8B,EAC9B,8BAA8B,EAC9B,6BAA6B,GAC9B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,YAAY,EACV,0BAA0B,EAC1B,+BAA+B,EAC/B,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @revealui/sync — Real-time collaboration and sync primitives.
3
+ *
4
+ * The collab layer (Yjs-based) is fully functional.
5
+ * ElectricProvider provides proxyBaseUrl config to child hooks. All hooks route
6
+ * through the authenticated CMS proxy at /api/shapes/* — no direct Electric client.
7
+ *
8
+ * Reads use ElectricSQL shape subscriptions for real-time updates.
9
+ * Writes use REST mutations via /api/sync/* — changes propagate to all
10
+ * subscribers automatically through ElectricSQL replication.
11
+ */
12
+ export { CollabProvider, useCollabDocument, useCollaboration, } from './collab/index.js';
13
+ export { useAgentContexts } from './hooks/useAgentContexts.js';
14
+ export { useAgentMemory } from './hooks/useAgentMemory.js';
15
+ export { useConversations } from './hooks/useConversations.js';
16
+ export { useCoordinationSessions } from './hooks/useCoordinationSessions.js';
17
+ export { useCoordinationWorkItems } from './hooks/useCoordinationWorkItems.js';
18
+ export { ElectricProvider, useElectricConfig } from './provider/index.js';
@@ -0,0 +1,14 @@
1
+ export interface MutationResult<T = unknown> {
2
+ success: boolean;
3
+ data?: T;
4
+ error?: string;
5
+ }
6
+ /**
7
+ * Hook that returns mutation functions for a given sync API endpoint.
8
+ */
9
+ export declare function useSyncMutations<TCreate, TUpdate, TRecord>(endpoint: string): {
10
+ create: (data: TCreate) => Promise<MutationResult<TRecord>>;
11
+ update: (id: string, data: TUpdate) => Promise<MutationResult<TRecord>>;
12
+ remove: (id: string) => Promise<MutationResult<void>>;
13
+ };
14
+ //# sourceMappingURL=mutations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../src/mutations.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0CD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM;mBAK3D,OAAO,KAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;iBAO5C,MAAM,QAAQ,OAAO,KAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;iBAOxD,MAAM,KAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;EAOpD"}
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+ import { useCallback } from 'react';
3
+ import { useElectricConfig } from './provider/index.js';
4
+ /** Default timeout for mutation fetch requests (milliseconds). */
5
+ const MUTATION_FETCH_TIMEOUT_MS = 10_000;
6
+ /**
7
+ * Make an authenticated mutation request to the CMS API.
8
+ * Credentials are included so the session cookie is sent cross-origin.
9
+ * Requests are aborted after {@link MUTATION_FETCH_TIMEOUT_MS} (10 s).
10
+ */
11
+ async function mutationFetch(url, method, body) {
12
+ const controller = new AbortController();
13
+ const timeout = setTimeout(() => controller.abort(), MUTATION_FETCH_TIMEOUT_MS);
14
+ let response;
15
+ try {
16
+ response = await fetch(url, {
17
+ method,
18
+ credentials: 'include',
19
+ signal: controller.signal,
20
+ headers: body !== undefined ? { 'Content-Type': 'application/json' } : undefined,
21
+ body: body !== undefined ? JSON.stringify(body) : undefined,
22
+ });
23
+ }
24
+ finally {
25
+ clearTimeout(timeout);
26
+ }
27
+ if (!response.ok) {
28
+ const errorData = (await response.json().catch(() => null));
29
+ return {
30
+ success: false,
31
+ error: errorData?.error ?? `Request failed with status ${response.status}`,
32
+ };
33
+ }
34
+ const data = (await response.json());
35
+ return { success: true, data };
36
+ }
37
+ /**
38
+ * Hook that returns mutation functions for a given sync API endpoint.
39
+ */
40
+ export function useSyncMutations(endpoint) {
41
+ const { proxyBaseUrl } = useElectricConfig();
42
+ const baseUrl = `${proxyBaseUrl}/api/sync/${endpoint}`;
43
+ const create = useCallback(async (data) => {
44
+ return mutationFetch(baseUrl, 'POST', data);
45
+ }, [baseUrl]);
46
+ const update = useCallback(async (id, data) => {
47
+ return mutationFetch(`${baseUrl}/${encodeURIComponent(id)}`, 'PATCH', data);
48
+ }, [baseUrl]);
49
+ const remove = useCallback(async (id) => {
50
+ return mutationFetch(`${baseUrl}/${encodeURIComponent(id)}`, 'DELETE');
51
+ }, [baseUrl]);
52
+ return { create, update, remove };
53
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Offline mutation queue — stores pending mutations in localStorage
3
+ * so they survive page reloads and can be flushed when connectivity returns.
4
+ */
5
+ /** A single pending mutation waiting to be sent to the server. */
6
+ export interface OfflineMutation {
7
+ /** Unique identifier for this mutation (UUID). */
8
+ id: string;
9
+ /** Database table the mutation targets. */
10
+ table: string;
11
+ /** Type of operation. */
12
+ operation: 'insert' | 'update' | 'delete';
13
+ /** Mutation payload (row data or partial update). */
14
+ data: Record<string, unknown>;
15
+ /** ISO-8601 timestamp when the mutation was enqueued. */
16
+ timestamp: string;
17
+ }
18
+ /**
19
+ * Manages a FIFO queue of mutations that were created while the browser
20
+ * was offline. Mutations are persisted in `localStorage` under the key
21
+ * `revealui:offline-queue`.
22
+ *
23
+ * All operations are no-ops when `localStorage` is unavailable (SSR,
24
+ * private browsing that throws, etc.).
25
+ */
26
+ export declare class OfflineMutationQueue {
27
+ /**
28
+ * Add a mutation to the end of the queue.
29
+ *
30
+ * @param mutation - The mutation to enqueue (must include a unique `id`).
31
+ */
32
+ enqueue(mutation: OfflineMutation): void;
33
+ /**
34
+ * Execute all queued mutations in order via the provided executor.
35
+ * Each mutation is removed from the persisted queue only after the
36
+ * executor resolves successfully. Processing stops at the first failure
37
+ * so ordering is preserved.
38
+ *
39
+ * @param executor - Async function that sends a single mutation to the server.
40
+ */
41
+ flush(executor: (mutation: OfflineMutation) => Promise<void>): Promise<void>;
42
+ /**
43
+ * Return all pending mutations without removing them.
44
+ */
45
+ peek(): OfflineMutation[];
46
+ /**
47
+ * Number of mutations currently in the queue.
48
+ */
49
+ get size(): number;
50
+ /**
51
+ * Remove all pending mutations from the queue.
52
+ */
53
+ clear(): void;
54
+ }
55
+ //# sourceMappingURL=offline-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-queue.d.ts","sourceRoot":"","sources":["../src/offline-queue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;CACnB;AA0DD;;;;;;;GAOG;AACH,qBAAa,oBAAoB;IAC/B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI;IAMxC;;;;;;;OAOG;IACG,KAAK,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlF;;OAEG;IACH,IAAI,IAAI,eAAe,EAAE;IAIzB;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;CAUd"}