@voidhash/mimic-react 1.0.0-beta.13 → 1.0.0-beta.15

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 (42) hide show
  1. package/.turbo/turbo-build.log +36 -36
  2. package/dist/zustand/middleware.d.cts +1 -1
  3. package/dist/zustand/middleware.d.mts +1 -1
  4. package/dist/zustand/types.d.cts +1 -1
  5. package/dist/zustand/types.d.mts +1 -1
  6. package/dist/zustand/useDraft.cjs +14 -11
  7. package/dist/zustand/useDraft.d.cts +6 -5
  8. package/dist/zustand/useDraft.d.cts.map +1 -1
  9. package/dist/zustand/useDraft.d.mts +6 -5
  10. package/dist/zustand/useDraft.d.mts.map +1 -1
  11. package/dist/zustand/useDraft.mjs +14 -11
  12. package/dist/zustand/useDraft.mjs.map +1 -1
  13. package/dist/zustand-commander/commander.cjs +59 -22
  14. package/dist/zustand-commander/commander.d.cts +16 -6
  15. package/dist/zustand-commander/commander.d.cts.map +1 -1
  16. package/dist/zustand-commander/commander.d.mts +16 -6
  17. package/dist/zustand-commander/commander.d.mts.map +1 -1
  18. package/dist/zustand-commander/commander.mjs +57 -22
  19. package/dist/zustand-commander/commander.mjs.map +1 -1
  20. package/dist/zustand-commander/hooks.cjs +18 -4
  21. package/dist/zustand-commander/hooks.d.cts +2 -1
  22. package/dist/zustand-commander/hooks.d.cts.map +1 -1
  23. package/dist/zustand-commander/hooks.d.mts +2 -1
  24. package/dist/zustand-commander/hooks.d.mts.map +1 -1
  25. package/dist/zustand-commander/hooks.mjs +18 -4
  26. package/dist/zustand-commander/hooks.mjs.map +1 -1
  27. package/dist/zustand-commander/index.cjs +2 -0
  28. package/dist/zustand-commander/index.d.cts +2 -2
  29. package/dist/zustand-commander/index.d.mts +2 -2
  30. package/dist/zustand-commander/index.mjs +2 -2
  31. package/dist/zustand-commander/types.d.cts +23 -14
  32. package/dist/zustand-commander/types.d.cts.map +1 -1
  33. package/dist/zustand-commander/types.d.mts +23 -14
  34. package/dist/zustand-commander/types.d.mts.map +1 -1
  35. package/dist/zustand-commander/types.mjs.map +1 -1
  36. package/package.json +3 -3
  37. package/src/zustand/useDraft.ts +15 -19
  38. package/src/zustand-commander/commander.ts +107 -21
  39. package/src/zustand-commander/hooks.ts +38 -12
  40. package/src/zustand-commander/index.ts +2 -0
  41. package/src/zustand-commander/types.ts +34 -24
  42. package/tests/zustand-commander/commander.test.ts +321 -0
@@ -7,6 +7,8 @@
7
7
  */
8
8
 
9
9
  import type { StoreApi } from "zustand";
10
+ import type { Primitive } from "@voidhash/mimic";
11
+ import type { ClientDocument } from "@voidhash/mimic/client";
10
12
  import {
11
13
  COMMAND_SYMBOL,
12
14
  UNDOABLE_COMMAND_SYMBOL,
@@ -31,6 +33,34 @@ const DEFAULT_OPTIONS: Required<CommanderOptions> = {
31
33
  maxUndoStackSize: 100,
32
34
  };
33
35
 
36
+ // =============================================================================
37
+ // Transaction Helper
38
+ // =============================================================================
39
+
40
+ /**
41
+ * Build a transaction function that routes to draft or document.
42
+ */
43
+ function buildTransaction<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
44
+ storeApi: StoreApi<TStore>
45
+ ): (fn: (root: Primitive.InferProxy<TSchema>) => void) => void {
46
+ return (fn) => {
47
+ const state = storeApi.getState();
48
+ const draft = state._commander.activeDraft;
49
+ if (draft) {
50
+ draft.update(fn);
51
+ } else {
52
+ // Access mimic.document from the store
53
+ const mimic = (state as any).mimic;
54
+ if (!mimic?.document) {
55
+ throw new Error(
56
+ "Commander: No active draft and no mimic document found on the store."
57
+ );
58
+ }
59
+ mimic.document.transaction(fn);
60
+ }
61
+ };
62
+ }
63
+
34
64
  // =============================================================================
35
65
  // Commander Implementation
36
66
  // =============================================================================
@@ -47,8 +77,7 @@ const DEFAULT_OPTIONS: Required<CommanderOptions> = {
47
77
  * const addItem = commander.action(
48
78
  * Schema.Struct({ name: Schema.String }),
49
79
  * (ctx, params) => {
50
- * const { mimic } = ctx.getState();
51
- * mimic.document.transaction(root => {
80
+ * ctx.transaction(root => {
52
81
  * // add item
53
82
  * });
54
83
  * }
@@ -64,9 +93,9 @@ const DEFAULT_OPTIONS: Required<CommanderOptions> = {
64
93
  * );
65
94
  * ```
66
95
  */
67
- export function createCommander<TStore extends object>(
96
+ export function createCommander<TStore extends object, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
68
97
  options: CommanderOptions = {}
69
- ): Commander<TStore & CommanderSlice> {
98
+ ): Commander<TStore & CommanderSlice, TSchema> {
70
99
  const { maxUndoStackSize } = { ...DEFAULT_OPTIONS, ...options };
71
100
 
72
101
  // Track the store API once middleware is applied
@@ -75,9 +104,9 @@ export function createCommander<TStore extends object>(
75
104
  /**
76
105
  * Creates the dispatch function for use within command handlers.
77
106
  */
78
- const createDispatch = (): CommandDispatch<TStore & CommanderSlice> => {
107
+ const createDispatch = (): CommandDispatch<TStore & CommanderSlice, TSchema> => {
79
108
  return <TParams, TReturn>(
80
- command: Command<TStore & CommanderSlice, TParams, TReturn>
109
+ command: Command<TStore & CommanderSlice, TParams, TReturn, TSchema>
81
110
  ) => {
82
111
  return (params: TParams): TReturn => {
83
112
  if (!_storeApi) {
@@ -87,17 +116,21 @@ export function createCommander<TStore extends object>(
87
116
  }
88
117
 
89
118
  // Create context for the command
90
- const ctx: CommandContext<TStore & CommanderSlice> = {
119
+ const ctx: CommandContext<TStore & CommanderSlice, TSchema> = {
91
120
  getState: () => _storeApi!.getState(),
92
121
  setState: (partial) => _storeApi!.setState(partial as any),
93
122
  dispatch: createDispatch(),
123
+ transaction: buildTransaction<TStore & CommanderSlice, TSchema>(_storeApi!),
94
124
  };
95
125
 
96
126
  // Execute the command
97
127
  const result = command.fn(ctx, params);
98
128
 
99
- // If it's an undoable command, add to undo stack
100
- if (isUndoableCommand(command)) {
129
+ // Skip undo stack when a draft is active
130
+ const hasDraft = _storeApi!.getState()._commander.activeDraft !== null;
131
+
132
+ // If it's an undoable command and no draft is active, add to undo stack
133
+ if (isUndoableCommand(command) && !hasDraft) {
101
134
  const entry: UndoEntry<TParams, TReturn> = {
102
135
  command,
103
136
  params,
@@ -106,7 +139,7 @@ export function createCommander<TStore extends object>(
106
139
  };
107
140
 
108
141
  _storeApi.setState((state: TStore & CommanderSlice) => {
109
- const { undoStack, redoStack } = state._commander;
142
+ const { undoStack, redoStack: _redoStack } = state._commander;
110
143
 
111
144
  // Add to undo stack, respecting max size
112
145
  const newUndoStack = [...undoStack, entry].slice(-maxUndoStackSize);
@@ -115,6 +148,7 @@ export function createCommander<TStore extends object>(
115
148
  return {
116
149
  ...state,
117
150
  _commander: {
151
+ ...state._commander,
118
152
  undoStack: newUndoStack,
119
153
  redoStack: [],
120
154
  },
@@ -131,8 +165,8 @@ export function createCommander<TStore extends object>(
131
165
  * Create a regular command (no undo support).
132
166
  */
133
167
  function action<TParams, TReturn = void>(
134
- fn: CommandFn<TStore & CommanderSlice, TParams, TReturn>
135
- ): Command<TStore & CommanderSlice, TParams, TReturn> {
168
+ fn: CommandFn<TStore & CommanderSlice, TParams, TReturn, TSchema>
169
+ ): Command<TStore & CommanderSlice, TParams, TReturn, TSchema> {
136
170
  return {
137
171
  [COMMAND_SYMBOL]: true,
138
172
  fn,
@@ -143,9 +177,9 @@ export function createCommander<TStore extends object>(
143
177
  * Create an undoable command with undo/redo support.
144
178
  */
145
179
  function undoableAction<TParams, TReturn>(
146
- fn: CommandFn<TStore & CommanderSlice, TParams, TReturn>,
147
- revert: RevertFn<TStore & CommanderSlice, TParams, TReturn>
148
- ): UndoableCommand<TStore & CommanderSlice, TParams, TReturn> {
180
+ fn: CommandFn<TStore & CommanderSlice, TParams, TReturn, TSchema>,
181
+ revert: RevertFn<TStore & CommanderSlice, TParams, TReturn, TSchema>
182
+ ): UndoableCommand<TStore & CommanderSlice, TParams, TReturn, TSchema> {
149
183
  return {
150
184
  [COMMAND_SYMBOL]: true,
151
185
  [UNDOABLE_COMMAND_SYMBOL]: true,
@@ -181,6 +215,7 @@ export function createCommander<TStore extends object>(
181
215
  _commander: {
182
216
  undoStack: [],
183
217
  redoStack: [],
218
+ activeDraft: null,
184
219
  },
185
220
  };
186
221
  };
@@ -189,23 +224,64 @@ export function createCommander<TStore extends object>(
189
224
  return {
190
225
  action,
191
226
  undoableAction,
192
- middleware: middleware as Commander<TStore & CommanderSlice>["middleware"],
227
+ middleware: middleware as Commander<TStore & CommanderSlice, TSchema>["middleware"],
193
228
  };
194
229
  }
195
230
 
231
+ // =============================================================================
232
+ // Draft Helpers
233
+ // =============================================================================
234
+
235
+ /**
236
+ * Set the active draft on the commander slice.
237
+ * While a draft is active, transactions route through `draft.update()` and undo is disabled.
238
+ */
239
+ export function setActiveDraft<TStore extends CommanderSlice>(
240
+ storeApi: StoreApi<TStore>,
241
+ draft: ClientDocument.DraftHandle<any>
242
+ ): void {
243
+ storeApi.setState((state: TStore) => ({
244
+ ...state,
245
+ _commander: {
246
+ ...state._commander,
247
+ activeDraft: draft,
248
+ },
249
+ }));
250
+ }
251
+
252
+ /**
253
+ * Clear the active draft from the commander slice.
254
+ */
255
+ export function clearActiveDraft<TStore extends CommanderSlice>(
256
+ storeApi: StoreApi<TStore>
257
+ ): void {
258
+ storeApi.setState((state: TStore) => ({
259
+ ...state,
260
+ _commander: {
261
+ ...state._commander,
262
+ activeDraft: null,
263
+ },
264
+ }));
265
+ }
266
+
196
267
  // =============================================================================
197
268
  // Undo/Redo Functions
198
269
  // =============================================================================
199
270
 
200
271
  /**
201
272
  * Perform an undo operation on the store.
202
- * Returns true if an undo was performed, false if undo stack was empty.
273
+ * Returns true if an undo was performed, false if undo stack was empty or a draft is active.
203
274
  */
204
275
  export function performUndo<TStore extends CommanderSlice>(
205
276
  storeApi: StoreApi<TStore>
206
277
  ): boolean {
207
278
  const state = storeApi.getState();
208
- const { undoStack, redoStack } = state._commander;
279
+ const { undoStack, redoStack, activeDraft } = state._commander;
280
+
281
+ // Undo is disabled while a draft is active
282
+ if (activeDraft) {
283
+ return false;
284
+ }
209
285
 
210
286
  // Pop the last entry from undo stack
211
287
  const entry = undoStack[undoStack.length - 1];
@@ -220,6 +296,7 @@ export function performUndo<TStore extends CommanderSlice>(
220
296
  getState: () => storeApi.getState(),
221
297
  setState: (partial) => storeApi.setState(partial as any),
222
298
  dispatch: createDispatchForUndo(storeApi),
299
+ transaction: buildTransaction(storeApi),
223
300
  };
224
301
 
225
302
  // Execute the revert function
@@ -229,6 +306,7 @@ export function performUndo<TStore extends CommanderSlice>(
229
306
  storeApi.setState((state: TStore) => ({
230
307
  ...state,
231
308
  _commander: {
309
+ ...state._commander,
232
310
  undoStack: newUndoStack,
233
311
  redoStack: [...redoStack, entry],
234
312
  },
@@ -239,13 +317,18 @@ export function performUndo<TStore extends CommanderSlice>(
239
317
 
240
318
  /**
241
319
  * Perform a redo operation on the store.
242
- * Returns true if a redo was performed, false if redo stack was empty.
320
+ * Returns true if a redo was performed, false if redo stack was empty or a draft is active.
243
321
  */
244
322
  export function performRedo<TStore extends CommanderSlice>(
245
323
  storeApi: StoreApi<TStore>
246
324
  ): boolean {
247
325
  const state = storeApi.getState();
248
- const { undoStack, redoStack } = state._commander;
326
+ const { undoStack, redoStack, activeDraft } = state._commander;
327
+
328
+ // Redo is disabled while a draft is active
329
+ if (activeDraft) {
330
+ return false;
331
+ }
249
332
 
250
333
  // Pop the last entry from redo stack
251
334
  const entry = redoStack[redoStack.length - 1];
@@ -260,6 +343,7 @@ export function performRedo<TStore extends CommanderSlice>(
260
343
  getState: () => storeApi.getState(),
261
344
  setState: (partial) => storeApi.setState(partial as any),
262
345
  dispatch: createDispatchForUndo(storeApi),
346
+ transaction: buildTransaction(storeApi),
263
347
  };
264
348
 
265
349
  // Re-execute the command
@@ -277,6 +361,7 @@ export function performRedo<TStore extends CommanderSlice>(
277
361
  storeApi.setState((state: TStore) => ({
278
362
  ...state,
279
363
  _commander: {
364
+ ...state._commander,
280
365
  undoStack: [...undoStack, newEntry],
281
366
  redoStack: newRedoStack,
282
367
  },
@@ -298,6 +383,7 @@ function createDispatchForUndo<TStore>(
298
383
  getState: () => storeApi.getState(),
299
384
  setState: (partial) => storeApi.setState(partial as any),
300
385
  dispatch: createDispatchForUndo(storeApi),
386
+ transaction: buildTransaction(storeApi),
301
387
  };
302
388
 
303
389
  // Execute without adding to undo stack
@@ -315,9 +401,9 @@ export function clearUndoHistory<TStore extends CommanderSlice>(
315
401
  storeApi.setState((state: TStore) => ({
316
402
  ...state,
317
403
  _commander: {
404
+ ...state._commander,
318
405
  undoStack: [],
319
406
  redoStack: [],
320
407
  },
321
408
  }));
322
409
  }
323
-
@@ -8,7 +8,8 @@
8
8
 
9
9
  import { useCallback, useEffect, useMemo } from "react";
10
10
  import { useStore, type StoreApi, type UseBoundStore } from "zustand";
11
- import { performRedo, performUndo, clearUndoHistory } from "./commander";
11
+ import type { Primitive } from "@voidhash/mimic";
12
+ import { performRedo, performUndo, clearUndoHistory, setActiveDraft, clearActiveDraft } from "./commander";
12
13
  import {
13
14
  isUndoableCommand,
14
15
  type Command,
@@ -26,26 +27,50 @@ import {
26
27
  * Creates a dispatch function for commands.
27
28
  * This is for use outside of React components (e.g., in command handlers).
28
29
  */
29
- function createDispatchFromApi<TStore extends CommanderSlice>(
30
+ function buildTransactionFromApi<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
31
+ storeApi: StoreApi<TStore>
32
+ ): (fn: (root: Primitive.InferProxy<TSchema>) => void) => void {
33
+ return (fn) => {
34
+ const state = storeApi.getState();
35
+ const draft = state._commander.activeDraft;
36
+ if (draft) {
37
+ draft.update(fn);
38
+ } else {
39
+ const mimic = (state as any).mimic;
40
+ if (!mimic?.document) {
41
+ throw new Error(
42
+ "Commander: No active draft and no mimic document found on the store."
43
+ );
44
+ }
45
+ mimic.document.transaction(fn);
46
+ }
47
+ };
48
+ }
49
+
50
+ function createDispatchFromApi<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
30
51
  storeApi: StoreApi<TStore>,
31
52
  maxUndoStackSize = 100
32
- ): CommandDispatch<TStore> {
33
- const dispatch: CommandDispatch<TStore> = <TParams, TReturn>(
34
- command: Command<TStore, TParams, TReturn>
53
+ ): CommandDispatch<TStore, TSchema> {
54
+ const dispatch: CommandDispatch<TStore, TSchema> = <TParams, TReturn>(
55
+ command: Command<TStore, TParams, TReturn, TSchema>
35
56
  ) => {
36
57
  return (params: TParams): TReturn => {
37
58
  // Create context for the command
38
- const ctx: CommandContext<TStore> = {
59
+ const ctx: CommandContext<TStore, TSchema> = {
39
60
  getState: () => storeApi.getState(),
40
61
  setState: (partial) => storeApi.setState(partial as Partial<TStore>),
41
62
  dispatch,
63
+ transaction: buildTransactionFromApi<TStore, TSchema>(storeApi),
42
64
  };
43
65
 
44
66
  // Execute the command
45
67
  const result = command.fn(ctx, params);
46
68
 
47
- // If it's an undoable command, add to undo stack
48
- if (isUndoableCommand(command)) {
69
+ // Skip undo stack when a draft is active
70
+ const hasDraft = storeApi.getState()._commander.activeDraft !== null;
71
+
72
+ // If it's an undoable command and no draft is active, add to undo stack
73
+ if (isUndoableCommand(command) && !hasDraft) {
49
74
  storeApi.setState((state: TStore) => {
50
75
  const { undoStack } = state._commander;
51
76
 
@@ -64,6 +89,7 @@ function createDispatchFromApi<TStore extends CommanderSlice>(
64
89
  return {
65
90
  ...state,
66
91
  _commander: {
92
+ ...state._commander,
67
93
  undoStack: newUndoStack,
68
94
  redoStack: [],
69
95
  },
@@ -91,9 +117,9 @@ function createDispatchFromApi<TStore extends CommanderSlice>(
91
117
  * };
92
118
  * ```
93
119
  */
94
- export function useCommander<TStore extends CommanderSlice>(
120
+ export function useCommander<TStore extends CommanderSlice, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>(
95
121
  store: UseBoundStore<StoreApi<TStore>>
96
- ): CommandDispatch<TStore> {
122
+ ): CommandDispatch<TStore, TSchema> {
97
123
  // Get the store API
98
124
  const storeApi = useMemo(() => {
99
125
  // UseBoundStore has the StoreApi attached
@@ -102,11 +128,11 @@ export function useCommander<TStore extends CommanderSlice>(
102
128
 
103
129
  // Create a stable dispatch function
104
130
  const dispatch = useMemo(
105
- () => createDispatchFromApi<TStore>(storeApi),
131
+ () => createDispatchFromApi<TStore, TSchema>(storeApi),
106
132
  [storeApi]
107
133
  );
108
134
 
109
- return dispatch as CommandDispatch<TStore>;
135
+ return dispatch as CommandDispatch<TStore, TSchema>;
110
136
  }
111
137
 
112
138
  // =============================================================================
@@ -80,6 +80,8 @@ export {
80
80
  performUndo,
81
81
  performRedo,
82
82
  clearUndoHistory,
83
+ setActiveDraft,
84
+ clearActiveDraft,
83
85
  } from "./commander";
84
86
 
85
87
  // =============================================================================
@@ -7,6 +7,8 @@
7
7
  */
8
8
 
9
9
  import type { StoreApi, UseBoundStore } from "zustand";
10
+ import type { Primitive } from "@voidhash/mimic";
11
+ import type { ClientDocument } from "@voidhash/mimic/client";
10
12
 
11
13
  // =============================================================================
12
14
  // Command Symbol & Type Guard
@@ -32,7 +34,7 @@ export const UNDOABLE_COMMAND_SYMBOL = Symbol.for(
32
34
  * Context provided to command functions.
33
35
  * Gives access to store state and dispatch capabilities.
34
36
  */
35
- export interface CommandContext<TStore> {
37
+ export interface CommandContext<TStore, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
36
38
  /**
37
39
  * Get the current store state.
38
40
  */
@@ -50,7 +52,13 @@ export interface CommandContext<TStore> {
50
52
  * @example
51
53
  * dispatch(otherCommand)({ param: "value" });
52
54
  */
53
- readonly dispatch: CommandDispatch<TStore>;
55
+ readonly dispatch: CommandDispatch<TStore, TSchema>;
56
+
57
+ /**
58
+ * Run a transaction on the document.
59
+ * Routes to the active draft if one is linked, otherwise to the document directly.
60
+ */
61
+ readonly transaction: (fn: (root: Primitive.InferProxy<TSchema>) => void) => void;
54
62
  }
55
63
 
56
64
  // =============================================================================
@@ -60,8 +68,8 @@ export interface CommandContext<TStore> {
60
68
  /**
61
69
  * The function signature for a command handler.
62
70
  */
63
- export type CommandFn<TStore, TParams, TReturn> = (
64
- ctx: CommandContext<TStore>,
71
+ export type CommandFn<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> = (
72
+ ctx: CommandContext<TStore, TSchema>,
65
73
  params: TParams
66
74
  ) => TReturn;
67
75
 
@@ -69,8 +77,8 @@ export type CommandFn<TStore, TParams, TReturn> = (
69
77
  * The function signature for an undoable command's revert handler.
70
78
  * Receives the original params and the result from the forward execution.
71
79
  */
72
- export type RevertFn<TStore, TParams, TReturn> = (
73
- ctx: CommandContext<TStore>,
80
+ export type RevertFn<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> = (
81
+ ctx: CommandContext<TStore, TSchema>,
74
82
  params: TParams,
75
83
  result: TReturn
76
84
  ) => void;
@@ -83,19 +91,19 @@ export type RevertFn<TStore, TParams, TReturn> = (
83
91
  * A command that can be dispatched to modify store state.
84
92
  * Regular commands do not support undo/redo.
85
93
  */
86
- export interface Command<TStore, TParams, TReturn> {
94
+ export interface Command<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
87
95
  readonly [COMMAND_SYMBOL]: true;
88
- readonly fn: CommandFn<TStore, TParams, TReturn>;
96
+ readonly fn: CommandFn<TStore, TParams, TReturn, TSchema>;
89
97
  }
90
98
 
91
99
  /**
92
100
  * An undoable command that supports undo/redo.
93
101
  * Must provide a revert function that knows how to undo the change.
94
102
  */
95
- export interface UndoableCommand<TStore, TParams, TReturn>
96
- extends Command<TStore, TParams, TReturn> {
103
+ export interface UndoableCommand<TStore, TParams, TReturn, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive>
104
+ extends Command<TStore, TParams, TReturn, TSchema> {
97
105
  readonly [UNDOABLE_COMMAND_SYMBOL]: true;
98
- readonly revert: RevertFn<TStore, TParams, TReturn>;
106
+ readonly revert: RevertFn<TStore, TParams, TReturn, TSchema>;
99
107
  }
100
108
 
101
109
  /**
@@ -119,8 +127,8 @@ export type AnyUndoableCommand = UndoableCommand<any, any, any>;
119
127
  * @example
120
128
  * const result = dispatch(myCommand)({ param: "value" });
121
129
  */
122
- export type CommandDispatch<TStore> = <TParams, TReturn>(
123
- command: Command<TStore, TParams, TReturn>
130
+ export type CommandDispatch<TStore, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> = <TParams, TReturn>(
131
+ command: Command<TStore, TParams, TReturn, TSchema>
124
132
  ) => (params: TParams) => TReturn;
125
133
 
126
134
  // =============================================================================
@@ -151,6 +159,8 @@ export interface CommanderSlice {
151
159
  readonly undoStack: ReadonlyArray<UndoEntry>;
152
160
  /** Stack of commands that can be redone */
153
161
  readonly redoStack: ReadonlyArray<UndoEntry>;
162
+ /** Active draft handle, if any. When set, transactions route through the draft and undo is disabled. */
163
+ readonly activeDraft: ClientDocument.DraftHandle<any> | null;
154
164
  };
155
165
  }
156
166
 
@@ -173,7 +183,7 @@ export interface CommanderOptions {
173
183
  * A commander instance bound to a specific store type.
174
184
  * Used to create commands and the middleware.
175
185
  */
176
- export interface Commander<TStore> {
186
+ export interface Commander<TStore, TSchema extends Primitive.AnyPrimitive = Primitive.AnyPrimitive> {
177
187
  /**
178
188
  * Create a regular command (no undo support).
179
189
  *
@@ -193,13 +203,13 @@ export interface Commander<TStore> {
193
203
  readonly action: {
194
204
  // With params (explicit type parameter)
195
205
  <TParams, TReturn = void>(
196
- fn: CommandFn<TStore, TParams, TReturn>
197
- ): Command<TStore, TParams, TReturn>;
206
+ fn: CommandFn<TStore, TParams, TReturn, TSchema>
207
+ ): Command<TStore, TParams, TReturn, TSchema>;
198
208
 
199
209
  // Without params (void) - inferred when no type param provided
200
210
  <TReturn = void>(
201
- fn: CommandFn<TStore, void, TReturn>
202
- ): Command<TStore, void, TReturn>;
211
+ fn: CommandFn<TStore, void, TReturn, TSchema>
212
+ ): Command<TStore, void, TReturn, TSchema>;
203
213
  };
204
214
 
205
215
  /**
@@ -222,15 +232,15 @@ export interface Commander<TStore> {
222
232
  readonly undoableAction: {
223
233
  // With params (explicit type parameter)
224
234
  <TParams, TReturn>(
225
- fn: CommandFn<TStore, TParams, TReturn>,
226
- revert: RevertFn<TStore, TParams, TReturn>
227
- ): UndoableCommand<TStore, TParams, TReturn>;
235
+ fn: CommandFn<TStore, TParams, TReturn, TSchema>,
236
+ revert: RevertFn<TStore, TParams, TReturn, TSchema>
237
+ ): UndoableCommand<TStore, TParams, TReturn, TSchema>;
228
238
 
229
239
  // Without params (void)
230
240
  <TReturn>(
231
- fn: CommandFn<TStore, void, TReturn>,
232
- revert: RevertFn<TStore, void, TReturn>
233
- ): UndoableCommand<TStore, void, TReturn>;
241
+ fn: CommandFn<TStore, void, TReturn, TSchema>,
242
+ revert: RevertFn<TStore, void, TReturn, TSchema>
243
+ ): UndoableCommand<TStore, void, TReturn, TSchema>;
234
244
  };
235
245
 
236
246
  /**