@voidhash/mimic-react 0.0.1-alpha.10

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 (37) hide show
  1. package/.turbo/turbo-build.log +35 -0
  2. package/LICENSE.md +663 -0
  3. package/dist/index.cjs +0 -0
  4. package/dist/index.d.cts +1 -0
  5. package/dist/index.d.mts +1 -0
  6. package/dist/index.mjs +1 -0
  7. package/dist/objectSpread2-CIP_6jda.cjs +73 -0
  8. package/dist/objectSpread2-CxTyNSYl.mjs +67 -0
  9. package/dist/zustand/index.cjs +95 -0
  10. package/dist/zustand/index.d.cts +115 -0
  11. package/dist/zustand/index.d.cts.map +1 -0
  12. package/dist/zustand/index.d.mts +115 -0
  13. package/dist/zustand/index.d.mts.map +1 -0
  14. package/dist/zustand/index.mjs +96 -0
  15. package/dist/zustand/index.mjs.map +1 -0
  16. package/dist/zustand-commander/index.cjs +364 -0
  17. package/dist/zustand-commander/index.d.cts +325 -0
  18. package/dist/zustand-commander/index.d.cts.map +1 -0
  19. package/dist/zustand-commander/index.d.mts +325 -0
  20. package/dist/zustand-commander/index.d.mts.map +1 -0
  21. package/dist/zustand-commander/index.mjs +355 -0
  22. package/dist/zustand-commander/index.mjs.map +1 -0
  23. package/package.json +53 -0
  24. package/src/index.ts +0 -0
  25. package/src/zustand/index.ts +24 -0
  26. package/src/zustand/middleware.ts +171 -0
  27. package/src/zustand/types.ts +117 -0
  28. package/src/zustand-commander/commander.ts +395 -0
  29. package/src/zustand-commander/hooks.ts +259 -0
  30. package/src/zustand-commander/index.ts +139 -0
  31. package/src/zustand-commander/types.ts +347 -0
  32. package/tests/zustand/middleware.test.ts +584 -0
  33. package/tests/zustand-commander/commander.test.ts +774 -0
  34. package/tsconfig.build.json +24 -0
  35. package/tsconfig.json +8 -0
  36. package/tsdown.config.ts +18 -0
  37. package/vitest.mts +11 -0
@@ -0,0 +1,584 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { createStore } from "zustand";
3
+ import { mimic } from "../../src/zustand/middleware";
4
+ import type { ClientDocument } from "@voidhash/mimic/client";
5
+ import type { Primitive, Presence } from "@voidhash/mimic";
6
+
7
+ // =============================================================================
8
+ // Mock ClientDocument
9
+ // =============================================================================
10
+
11
+ interface MockClientDocumentState {
12
+ snapshot: { title: string; count: number };
13
+ isConnected: boolean;
14
+ isReady: boolean;
15
+ pendingCount: number;
16
+ hasPendingChanges: boolean;
17
+ // Presence
18
+ presenceEnabled: boolean;
19
+ presenceSelfId?: string;
20
+ presenceSelf?: unknown;
21
+ presenceOthers: Map<string, Presence.PresenceEntry<unknown>>;
22
+ }
23
+
24
+ interface MockClientDocument {
25
+ root: { toSnapshot: () => { title: string; count: number } };
26
+ isConnected: () => boolean;
27
+ isReady: () => boolean;
28
+ getPendingCount: () => number;
29
+ hasPendingChanges: () => boolean;
30
+ connect: () => void;
31
+ subscribe: (listener: {
32
+ onStateChange?: () => void;
33
+ onConnectionChange?: () => void;
34
+ onReady?: () => void;
35
+ }) => () => void;
36
+ presence?: {
37
+ selfId: () => string | undefined;
38
+ self: () => unknown | undefined;
39
+ others: () => ReadonlyMap<string, Presence.PresenceEntry<unknown>>;
40
+ all: () => ReadonlyMap<string, Presence.PresenceEntry<unknown>>;
41
+ subscribe: (listener: { onPresenceChange?: () => void }) => () => void;
42
+ };
43
+ // Helpers to update state and trigger events
44
+ _setState: (updates: Partial<MockClientDocumentState>) => void;
45
+ _triggerStateChange: () => void;
46
+ _triggerConnectionChange: () => void;
47
+ _triggerReady: () => void;
48
+ _triggerPresenceChange: () => void;
49
+ _getSubscriberCount: () => number;
50
+ }
51
+
52
+ const createMockClientDocument = (
53
+ initial?: Partial<MockClientDocumentState>
54
+ ): MockClientDocument => {
55
+ let state: MockClientDocumentState = {
56
+ snapshot: { title: "Test", count: 0 },
57
+ isConnected: false,
58
+ isReady: false,
59
+ pendingCount: 0,
60
+ hasPendingChanges: false,
61
+ presenceEnabled: false,
62
+ presenceOthers: new Map(),
63
+ ...initial,
64
+ };
65
+
66
+ const listeners = new Set<{
67
+ onStateChange?: () => void;
68
+ onConnectionChange?: () => void;
69
+ onReady?: () => void;
70
+ }>();
71
+
72
+ const presenceListeners = new Set<{ onPresenceChange?: () => void }>();
73
+
74
+ return {
75
+ root: {
76
+ toSnapshot: () => state.snapshot,
77
+ },
78
+ isConnected: () => state.isConnected,
79
+ isReady: () => state.isReady,
80
+ getPendingCount: () => state.pendingCount,
81
+ hasPendingChanges: () => state.hasPendingChanges,
82
+ connect: () => {
83
+ // Mock connect - does nothing in tests
84
+ },
85
+ subscribe: (listener) => {
86
+ listeners.add(listener);
87
+ return () => {
88
+ listeners.delete(listener);
89
+ };
90
+ },
91
+ presence: state.presenceEnabled
92
+ ? {
93
+ selfId: () => state.presenceSelfId,
94
+ self: () => state.presenceSelf,
95
+ others: () => state.presenceOthers,
96
+ all: () => {
97
+ const all = new Map<string, Presence.PresenceEntry<unknown>>();
98
+ for (const [id, entry] of state.presenceOthers) {
99
+ all.set(id, entry);
100
+ }
101
+ if (state.presenceSelfId !== undefined && state.presenceSelf !== undefined) {
102
+ all.set(state.presenceSelfId, { data: state.presenceSelf });
103
+ }
104
+ return all;
105
+ },
106
+ subscribe: (listener) => {
107
+ presenceListeners.add(listener);
108
+ return () => {
109
+ presenceListeners.delete(listener);
110
+ };
111
+ },
112
+ }
113
+ : undefined,
114
+ _setState: (updates) => {
115
+ state = { ...state, ...updates };
116
+ },
117
+ _triggerStateChange: () => {
118
+ for (const listener of listeners) {
119
+ listener.onStateChange?.();
120
+ }
121
+ },
122
+ _triggerConnectionChange: () => {
123
+ for (const listener of listeners) {
124
+ listener.onConnectionChange?.();
125
+ }
126
+ },
127
+ _triggerReady: () => {
128
+ for (const listener of listeners) {
129
+ listener.onReady?.();
130
+ }
131
+ },
132
+ _triggerPresenceChange: () => {
133
+ for (const listener of presenceListeners) {
134
+ listener.onPresenceChange?.();
135
+ }
136
+ },
137
+ _getSubscriberCount: () => listeners.size,
138
+ };
139
+ };
140
+
141
+ // =============================================================================
142
+ // Test Schema Type (for type inference)
143
+ // =============================================================================
144
+
145
+ type TestSchema = Primitive.Primitive<
146
+ { title: string; count: number },
147
+ { toSnapshot: () => { title: string; count: number } }
148
+ >;
149
+
150
+ // =============================================================================
151
+ // Tests
152
+ // =============================================================================
153
+
154
+ describe("mimic middleware", () => {
155
+ let mockDocument: MockClientDocument;
156
+
157
+ beforeEach(() => {
158
+ mockDocument = createMockClientDocument();
159
+ });
160
+
161
+ describe("Basic Integration", () => {
162
+ it("should create store with initial mimic slice", () => {
163
+ mockDocument._setState({
164
+ snapshot: { title: "Initial", count: 42 },
165
+ isConnected: true,
166
+ isReady: true,
167
+ pendingCount: 0,
168
+ hasPendingChanges: false,
169
+ });
170
+
171
+ const store = createStore(
172
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
173
+ );
174
+
175
+ const state = store.getState();
176
+
177
+ expect(state.mimic).toBeDefined();
178
+ expect(state.mimic.snapshot).toEqual({ title: "Initial", count: 42 });
179
+ expect(state.mimic.isConnected).toBe(true);
180
+ expect(state.mimic.isReady).toBe(true);
181
+ expect(state.mimic.pendingCount).toBe(0);
182
+ expect(state.mimic.hasPendingChanges).toBe(false);
183
+ });
184
+
185
+ it("should preserve user state alongside mimic slice", () => {
186
+ const store = createStore(
187
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({
188
+ customField: "hello",
189
+ customNumber: 123,
190
+ }))
191
+ );
192
+
193
+ const state = store.getState();
194
+
195
+ expect(state.customField).toBe("hello");
196
+ expect(state.customNumber).toBe(123);
197
+ expect(state.mimic).toBeDefined();
198
+ });
199
+
200
+ it("should provide document reference via state.mimic.document", () => {
201
+ const store = createStore(
202
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
203
+ );
204
+
205
+ const state = store.getState();
206
+
207
+ expect(state.mimic.document).toBe(mockDocument);
208
+ });
209
+
210
+ it("should reflect initial document state correctly", () => {
211
+ mockDocument._setState({
212
+ snapshot: { title: "Doc Title", count: 99 },
213
+ isConnected: false,
214
+ isReady: false,
215
+ pendingCount: 3,
216
+ hasPendingChanges: true,
217
+ });
218
+
219
+ const store = createStore(
220
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
221
+ );
222
+
223
+ const state = store.getState();
224
+
225
+ expect(state.mimic.snapshot).toEqual({ title: "Doc Title", count: 99 });
226
+ expect(state.mimic.isConnected).toBe(false);
227
+ expect(state.mimic.isReady).toBe(false);
228
+ expect(state.mimic.pendingCount).toBe(3);
229
+ expect(state.mimic.hasPendingChanges).toBe(true);
230
+ });
231
+ });
232
+
233
+ describe("Reactive Updates", () => {
234
+ it("should update store when onStateChange fires", () => {
235
+ mockDocument._setState({
236
+ snapshot: { title: "Before", count: 1 },
237
+ });
238
+
239
+ const store = createStore(
240
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
241
+ );
242
+
243
+ expect(store.getState().mimic.snapshot).toEqual({ title: "Before", count: 1 });
244
+
245
+ // Update document state and trigger change
246
+ mockDocument._setState({
247
+ snapshot: { title: "After", count: 2 },
248
+ });
249
+ mockDocument._triggerStateChange();
250
+
251
+ expect(store.getState().mimic.snapshot).toEqual({ title: "After", count: 2 });
252
+ });
253
+
254
+ it("should update store when onConnectionChange fires", () => {
255
+ mockDocument._setState({
256
+ isConnected: false,
257
+ });
258
+
259
+ const store = createStore(
260
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
261
+ );
262
+
263
+ expect(store.getState().mimic.isConnected).toBe(false);
264
+
265
+ // Update connection status and trigger change
266
+ mockDocument._setState({
267
+ isConnected: true,
268
+ });
269
+ mockDocument._triggerConnectionChange();
270
+
271
+ expect(store.getState().mimic.isConnected).toBe(true);
272
+ });
273
+
274
+ it("should update store when onReady fires", () => {
275
+ mockDocument._setState({
276
+ isReady: false,
277
+ });
278
+
279
+ const store = createStore(
280
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
281
+ );
282
+
283
+ expect(store.getState().mimic.isReady).toBe(false);
284
+
285
+ // Update ready status and trigger change
286
+ mockDocument._setState({
287
+ isReady: true,
288
+ });
289
+ mockDocument._triggerReady();
290
+
291
+ expect(store.getState().mimic.isReady).toBe(true);
292
+ });
293
+
294
+ it("should update pendingCount and hasPendingChanges on state change", () => {
295
+ mockDocument._setState({
296
+ pendingCount: 0,
297
+ hasPendingChanges: false,
298
+ });
299
+
300
+ const store = createStore(
301
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
302
+ );
303
+
304
+ expect(store.getState().mimic.pendingCount).toBe(0);
305
+ expect(store.getState().mimic.hasPendingChanges).toBe(false);
306
+
307
+ // Simulate pending transactions
308
+ mockDocument._setState({
309
+ pendingCount: 5,
310
+ hasPendingChanges: true,
311
+ });
312
+ mockDocument._triggerStateChange();
313
+
314
+ expect(store.getState().mimic.pendingCount).toBe(5);
315
+ expect(store.getState().mimic.hasPendingChanges).toBe(true);
316
+ });
317
+
318
+ it("should update all mimic properties on any event", () => {
319
+ mockDocument._setState({
320
+ snapshot: { title: "V1", count: 1 },
321
+ isConnected: false,
322
+ isReady: false,
323
+ pendingCount: 0,
324
+ hasPendingChanges: false,
325
+ });
326
+
327
+ const store = createStore(
328
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
329
+ );
330
+
331
+ // Update all state at once
332
+ mockDocument._setState({
333
+ snapshot: { title: "V2", count: 2 },
334
+ isConnected: true,
335
+ isReady: true,
336
+ pendingCount: 3,
337
+ hasPendingChanges: true,
338
+ });
339
+
340
+ // Triggering any event should refresh all mimic state
341
+ mockDocument._triggerConnectionChange();
342
+
343
+ const state = store.getState().mimic;
344
+ expect(state.snapshot).toEqual({ title: "V2", count: 2 });
345
+ expect(state.isConnected).toBe(true);
346
+ expect(state.isReady).toBe(true);
347
+ expect(state.pendingCount).toBe(3);
348
+ expect(state.hasPendingChanges).toBe(true);
349
+ });
350
+ });
351
+
352
+ describe("Presence", () => {
353
+ it("should expose reactive self + others presence when enabled", () => {
354
+ mockDocument = createMockClientDocument({
355
+ presenceEnabled: true,
356
+ presenceSelfId: "self",
357
+ presenceSelf: { name: "me" },
358
+ presenceOthers: new Map([
359
+ ["other-1", { data: { name: "alice" }, userId: "u1" }],
360
+ ]),
361
+ });
362
+
363
+ const store = createStore(
364
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
365
+ );
366
+
367
+ const presence = (store.getState().mimic as any).presence;
368
+ expect(presence).toBeDefined();
369
+ expect(presence.selfId).toBe("self");
370
+ expect(presence.self).toEqual({ name: "me" });
371
+ expect(presence.others).toBeInstanceOf(Map);
372
+ expect(presence.all).toBeInstanceOf(Map);
373
+ expect(presence.others.get("other-1")).toEqual({ data: { name: "alice" }, userId: "u1" });
374
+ expect(presence.all.get("self")).toEqual({ data: { name: "me" } });
375
+ });
376
+
377
+ it("should update store when onPresenceChange fires (and clone Map references)", () => {
378
+ mockDocument = createMockClientDocument({
379
+ presenceEnabled: true,
380
+ presenceSelfId: "self",
381
+ presenceSelf: { name: "me" },
382
+ presenceOthers: new Map(),
383
+ });
384
+
385
+ const store = createStore(
386
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
387
+ );
388
+
389
+ const beforePresence = (store.getState().mimic as any).presence;
390
+ const beforeOthersRef = beforePresence.others;
391
+ expect(beforePresence.self).toEqual({ name: "me" });
392
+ expect(beforePresence.others.size).toBe(0);
393
+
394
+ // Update others + self and trigger presence change
395
+ mockDocument._setState({
396
+ presenceSelf: { name: "me-2" },
397
+ presenceOthers: new Map([
398
+ ["other-1", { data: { name: "alice" }, userId: "u1" }],
399
+ ]),
400
+ });
401
+ mockDocument._triggerPresenceChange();
402
+
403
+ const afterPresence = (store.getState().mimic as any).presence;
404
+ expect(afterPresence.self).toEqual({ name: "me-2" });
405
+ expect(afterPresence.others.get("other-1")).toEqual({ data: { name: "alice" }, userId: "u1" });
406
+
407
+ // Critical: maps should be new references so selectors re-render
408
+ expect(afterPresence.others).not.toBe(beforeOthersRef);
409
+ });
410
+ });
411
+
412
+ describe("Options", () => {
413
+ it("should subscribe to document events by default (autoSubscribe: true)", () => {
414
+ const store = createStore(
415
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
416
+ );
417
+
418
+ expect(mockDocument._getSubscriberCount()).toBe(1);
419
+
420
+ // Verify subscription is active by updating and triggering
421
+ mockDocument._setState({ snapshot: { title: "Updated", count: 10 } });
422
+ mockDocument._triggerStateChange();
423
+
424
+ expect(store.getState().mimic.snapshot).toEqual({ title: "Updated", count: 10 });
425
+ });
426
+
427
+ it("should subscribe to document events when autoSubscribe: true explicitly", () => {
428
+ const store = createStore(
429
+ mimic(
430
+ mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
431
+ () => ({}),
432
+ { autoSubscribe: true }
433
+ )
434
+ );
435
+
436
+ expect(mockDocument._getSubscriberCount()).toBe(1);
437
+ });
438
+
439
+ it("should not subscribe to document events when autoSubscribe: false", () => {
440
+ const store = createStore(
441
+ mimic(
442
+ mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
443
+ () => ({}),
444
+ { autoSubscribe: false }
445
+ )
446
+ );
447
+
448
+ expect(mockDocument._getSubscriberCount()).toBe(0);
449
+
450
+ // Verify store doesn't update when events fire
451
+ const initialSnapshot = store.getState().mimic.snapshot;
452
+ mockDocument._setState({ snapshot: { title: "Should Not Update", count: 999 } });
453
+ mockDocument._triggerStateChange();
454
+
455
+ expect(store.getState().mimic.snapshot).toEqual(initialSnapshot);
456
+ });
457
+ });
458
+
459
+ describe("User State Integration", () => {
460
+ it("should allow user state to use set function", () => {
461
+ interface UserState {
462
+ localCount: number;
463
+ increment: () => void;
464
+ }
465
+
466
+ const store = createStore(
467
+ mimic<TestSchema, undefined, UserState>(
468
+ mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
469
+ (set) => ({
470
+ localCount: 0,
471
+ increment: () => set((state) => ({ ...state, localCount: state.localCount + 1 })),
472
+ })
473
+ )
474
+ );
475
+
476
+ expect(store.getState().localCount).toBe(0);
477
+
478
+ store.getState().increment();
479
+
480
+ expect(store.getState().localCount).toBe(1);
481
+ // Mimic state should still be present
482
+ expect(store.getState().mimic).toBeDefined();
483
+ });
484
+
485
+ it("should allow user state to use get function", () => {
486
+ interface UserState {
487
+ localCount: number;
488
+ doubleCount: () => number;
489
+ }
490
+
491
+ const store = createStore(
492
+ mimic<TestSchema, undefined, UserState>(
493
+ mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
494
+ (set, get) => ({
495
+ localCount: 5,
496
+ doubleCount: () => get().localCount * 2,
497
+ })
498
+ )
499
+ );
500
+
501
+ expect(store.getState().doubleCount()).toBe(10);
502
+ });
503
+
504
+ it("should preserve mimic state when user state changes", () => {
505
+ mockDocument._setState({
506
+ snapshot: { title: "Preserved", count: 42 },
507
+ });
508
+
509
+ interface UserState {
510
+ value: string;
511
+ setValue: (v: string) => void;
512
+ }
513
+
514
+ const store = createStore(
515
+ mimic<TestSchema, undefined, UserState>(
516
+ mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>,
517
+ (set) => ({
518
+ value: "initial",
519
+ setValue: (v: string) => set((state) => ({ ...state, value: v })),
520
+ })
521
+ )
522
+ );
523
+
524
+ expect(store.getState().mimic.snapshot).toEqual({ title: "Preserved", count: 42 });
525
+
526
+ store.getState().setValue("updated");
527
+
528
+ expect(store.getState().value).toBe("updated");
529
+ expect(store.getState().mimic.snapshot).toEqual({ title: "Preserved", count: 42 });
530
+ });
531
+ });
532
+
533
+ describe("Multiple Events", () => {
534
+ it("should handle rapid successive events correctly", () => {
535
+ const store = createStore(
536
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
537
+ );
538
+
539
+ // Fire multiple events in quick succession
540
+ for (let i = 0; i < 10; i++) {
541
+ mockDocument._setState({ snapshot: { title: `Update ${i}`, count: i } });
542
+ mockDocument._triggerStateChange();
543
+ }
544
+
545
+ expect(store.getState().mimic.snapshot).toEqual({ title: "Update 9", count: 9 });
546
+ });
547
+
548
+ it("should handle interleaved event types", () => {
549
+ const store = createStore(
550
+ mimic(mockDocument as unknown as ClientDocument.ClientDocument<TestSchema>, () => ({}))
551
+ );
552
+
553
+ mockDocument._setState({ isConnected: true });
554
+ mockDocument._triggerConnectionChange();
555
+
556
+ mockDocument._setState({ snapshot: { title: "New", count: 1 } });
557
+ mockDocument._triggerStateChange();
558
+
559
+ mockDocument._setState({ isReady: true });
560
+ mockDocument._triggerReady();
561
+
562
+ const state = store.getState().mimic;
563
+ expect(state.isConnected).toBe(true);
564
+ expect(state.snapshot).toEqual({ title: "New", count: 1 });
565
+ expect(state.isReady).toBe(true);
566
+ });
567
+ });
568
+ });
569
+
570
+ // =============================================================================
571
+ // Type Export Tests (compile-time verification)
572
+ // =============================================================================
573
+
574
+ describe("Type Exports", () => {
575
+ it("should export all expected types from index", async () => {
576
+ // This test verifies that the types are properly exported
577
+ // by importing them - if they're missing, TypeScript will error
578
+ const exports = await import("../../src/zustand/index");
579
+
580
+ expect(exports.mimic).toBeDefined();
581
+ // Type exports are verified at compile time - if this file compiles,
582
+ // the types are exported correctly
583
+ });
584
+ });