state-sync-log 0.9.0 → 0.10.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 (38) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +368 -277
  3. package/dist/state-sync-log.esm.js +929 -136
  4. package/dist/state-sync-log.esm.mjs +929 -136
  5. package/dist/state-sync-log.umd.js +928 -135
  6. package/dist/types/createOps/constant.d.ts +6 -0
  7. package/dist/types/createOps/createOps.d.ts +25 -0
  8. package/dist/types/createOps/current.d.ts +13 -0
  9. package/dist/types/createOps/draft.d.ts +14 -0
  10. package/dist/types/createOps/draftify.d.ts +5 -0
  11. package/dist/types/createOps/index.d.ts +12 -0
  12. package/dist/types/createOps/interface.d.ts +74 -0
  13. package/dist/types/createOps/original.d.ts +15 -0
  14. package/dist/types/createOps/pushOp.d.ts +9 -0
  15. package/dist/types/createOps/setHelpers.d.ts +25 -0
  16. package/dist/types/createOps/utils.d.ts +95 -0
  17. package/dist/types/draft.d.ts +2 -2
  18. package/dist/types/index.d.ts +1 -0
  19. package/dist/types/json.d.ts +1 -1
  20. package/dist/types/operations.d.ts +2 -2
  21. package/dist/types/utils.d.ts +5 -0
  22. package/package.json +1 -1
  23. package/src/createOps/constant.ts +10 -0
  24. package/src/createOps/createOps.ts +97 -0
  25. package/src/createOps/current.ts +85 -0
  26. package/src/createOps/draft.ts +606 -0
  27. package/src/createOps/draftify.ts +45 -0
  28. package/src/createOps/index.ts +18 -0
  29. package/src/createOps/interface.ts +95 -0
  30. package/src/createOps/original.ts +24 -0
  31. package/src/createOps/pushOp.ts +42 -0
  32. package/src/createOps/setHelpers.ts +93 -0
  33. package/src/createOps/utils.ts +325 -0
  34. package/src/draft.ts +306 -288
  35. package/src/index.ts +1 -0
  36. package/src/json.ts +1 -1
  37. package/src/operations.ts +33 -11
  38. package/src/utils.ts +67 -55
@@ -0,0 +1,606 @@
1
+ /**
2
+ * Proxy draft implementation.
3
+ * Adapted from mutative - removed Map/Set/unsafe/mark support.
4
+ * Modified to use eager op logging - ops are pushed immediately when mutations happen.
5
+ */
6
+
7
+ import { parseArrayIndex } from "../utils"
8
+ import { PROXY_DRAFT } from "./constant"
9
+ import { DraftType, type Finalities, type JSONValue, type Op, type ProxyDraft } from "./interface"
10
+ import { pushOp } from "./pushOp"
11
+ import {
12
+ deepClone,
13
+ ensureShallowCopy,
14
+ get,
15
+ getDescriptor,
16
+ getPathOrThrow,
17
+ getProxyDraft,
18
+ getType,
19
+ getValue,
20
+ handleValue,
21
+ has,
22
+ isDraft,
23
+ isDraftable,
24
+ isEqual,
25
+ latest,
26
+ markChanged,
27
+ peek,
28
+ revokeProxy,
29
+ set,
30
+ } from "./utils"
31
+
32
+ // Note: getValue is used in finalizeDraft, deepClone uses it internally for drafts
33
+
34
+ /**
35
+ * Increment aliasCount for a draft (if the value is a draft).
36
+ */
37
+ function incAliasCount(value: unknown): void {
38
+ const draft = getProxyDraft(value)
39
+ if (draft) draft.aliasCount++
40
+ }
41
+
42
+ /**
43
+ * Decrement aliasCount for a draft (if the value is a draft).
44
+ */
45
+ function decAliasCount(value: unknown): void {
46
+ const draft = getProxyDraft(value)
47
+ if (draft) draft.aliasCount--
48
+ }
49
+
50
+ /**
51
+ * Normalize an array index like native array methods do.
52
+ * Handles negative indices and clamps to [0, len].
53
+ * @param index - The index to normalize (can be negative or undefined)
54
+ * @param len - The array length
55
+ * @param defaultValue - Value to use if index is undefined (typically 0 or len)
56
+ */
57
+ function normalizeIndex(index: number | undefined, len: number, defaultValue: number): number {
58
+ if (index === undefined) return defaultValue
59
+ return index < 0 ? Math.max(len + index, 0) : Math.min(index, len)
60
+ }
61
+
62
+ /**
63
+ * Array methods that mutate the array and need to be intercepted for eager op logging.
64
+ */
65
+ const MUTATING_ARRAY_METHODS = new Set([
66
+ "push",
67
+ "pop",
68
+ "shift",
69
+ "unshift",
70
+ "splice",
71
+ "sort",
72
+ "reverse",
73
+ "fill",
74
+ "copyWithin",
75
+ ])
76
+
77
+ /**
78
+ * Create a wrapped array method that logs ops eagerly.
79
+ * @param proxyDraft - The ProxyDraft for the array (used for ops and state)
80
+ * @param proxyRef - The actual proxy (used to access elements as drafts)
81
+ * @param method - The method name being wrapped
82
+ */
83
+ function createArrayMethodWrapper(
84
+ proxyDraft: ProxyDraft,
85
+ proxyRef: unknown,
86
+ method: string
87
+ ): (...args: unknown[]) => unknown {
88
+ return function (this: unknown[], ...args: unknown[]): unknown {
89
+ ensureShallowCopy(proxyDraft)
90
+ markChanged(proxyDraft)
91
+
92
+ const arr = proxyDraft.copy as unknown[]
93
+ const originalLength = arr.length
94
+ const proxy = proxyRef as unknown[]
95
+
96
+ switch (method) {
97
+ case "push": {
98
+ // push(items...) -> splice at end
99
+ // Track aliasCount for drafts being inserted
100
+ for (const arg of args) {
101
+ incAliasCount(arg)
102
+ }
103
+ // Store raw values in array (preserving aliasing in the draft)
104
+ const result = arr.push(...args)
105
+ // Clone for the op (capture value at this moment)
106
+ pushOp(proxyDraft, {
107
+ kind: "splice",
108
+ path: getPathOrThrow(proxyDraft),
109
+ index: originalLength,
110
+ deleteCount: 0,
111
+ inserts: args.map(deepClone) as JSONValue[],
112
+ })
113
+ return result
114
+ }
115
+
116
+ case "pop": {
117
+ if (originalLength === 0) {
118
+ return arr.pop()
119
+ }
120
+ // Get the element through the proxy to ensure it's a draft if draftable
121
+ const returnValue = proxy[originalLength - 1]
122
+ // Track aliasCount for draft being removed
123
+ decAliasCount(arr[originalLength - 1])
124
+ // Now perform the actual mutation
125
+ arr.pop()
126
+ pushOp(proxyDraft, {
127
+ kind: "splice",
128
+ path: getPathOrThrow(proxyDraft),
129
+ index: originalLength - 1,
130
+ deleteCount: 1,
131
+ inserts: [],
132
+ })
133
+ return returnValue
134
+ }
135
+
136
+ case "shift": {
137
+ if (originalLength === 0) {
138
+ return arr.shift()
139
+ }
140
+ // Get the element through the proxy to ensure it's a draft if draftable
141
+ const returnValue = proxy[0]
142
+ // Track aliasCount for draft being removed
143
+ decAliasCount(arr[0])
144
+ // Now perform the actual mutation
145
+ arr.shift()
146
+ pushOp(proxyDraft, {
147
+ kind: "splice",
148
+ path: getPathOrThrow(proxyDraft),
149
+ index: 0,
150
+ deleteCount: 1,
151
+ inserts: [],
152
+ })
153
+ return returnValue
154
+ }
155
+
156
+ case "unshift": {
157
+ // Track aliasCount for drafts being inserted
158
+ for (const arg of args) {
159
+ incAliasCount(arg)
160
+ }
161
+ // Store raw values in array (preserving aliasing in the draft)
162
+ const result = arr.unshift(...args)
163
+ // Clone for the op (capture value at this moment)
164
+ pushOp(proxyDraft, {
165
+ kind: "splice",
166
+ path: getPathOrThrow(proxyDraft),
167
+ index: 0,
168
+ deleteCount: 0,
169
+ inserts: args.map(deepClone) as JSONValue[],
170
+ })
171
+ return result
172
+ }
173
+
174
+ case "splice": {
175
+ const start = args[0] as number | undefined
176
+ const deleteCountArg = args[1] as number | undefined
177
+ const inserts = args.slice(2)
178
+
179
+ // Normalize start index to get elements through proxy
180
+ const index =
181
+ start === undefined ? 0 : start < 0 ? Math.max(originalLength + start, 0) : start
182
+ const deleteCount = deleteCountArg ?? originalLength - index
183
+
184
+ // Get elements through proxy to ensure they're drafts if draftable
185
+ const returnValues: unknown[] = []
186
+ for (let i = 0; i < deleteCount && index + i < originalLength; i++) {
187
+ returnValues.push(proxy[index + i])
188
+ // Track aliasCount for drafts being removed
189
+ decAliasCount(arr[index + i])
190
+ }
191
+
192
+ // Track aliasCount for drafts being inserted
193
+ for (const insert of inserts) {
194
+ incAliasCount(insert)
195
+ }
196
+ // Store raw values in array (preserving aliasing in the draft)
197
+ ;(arr.splice as (...args: unknown[]) => unknown[])(...args)
198
+
199
+ // Clone for the op (capture value at this moment)
200
+ pushOp(proxyDraft, {
201
+ kind: "splice",
202
+ path: getPathOrThrow(proxyDraft),
203
+ index: start ?? 0,
204
+ deleteCount: deleteCountArg ?? originalLength,
205
+ inserts: inserts.map(deepClone) as JSONValue[],
206
+ })
207
+ return returnValues
208
+ }
209
+
210
+ case "fill": {
211
+ const fillValue = args[0]
212
+ const startArg = args[1] as number | undefined
213
+ const endArg = args[2] as number | undefined
214
+
215
+ const len = originalLength
216
+ const start = normalizeIndex(startArg, len, 0)
217
+ const end = normalizeIndex(endArg, len, len)
218
+
219
+ // Use proxy splice to replace the range (handles aliasCount and generates splice op)
220
+ const fillCount = end - start
221
+ if (fillCount > 0) {
222
+ const fillValues = Array(fillCount).fill(fillValue)
223
+ proxy.splice(start, fillCount, ...fillValues)
224
+ }
225
+
226
+ return proxy
227
+ }
228
+
229
+ case "sort": {
230
+ const compareFn = args[0] as ((a: unknown, b: unknown) => number) | undefined
231
+ // Sort a copy to get the sorted order, then use proxy splice
232
+ const sorted = [...arr].sort(compareFn)
233
+ if (originalLength > 0) {
234
+ proxy.splice(0, originalLength, ...sorted)
235
+ }
236
+ return proxy
237
+ }
238
+
239
+ case "reverse": {
240
+ // Reverse a copy to get the reversed order, then use proxy splice
241
+ const reversed = [...arr].reverse()
242
+ if (originalLength > 0) {
243
+ proxy.splice(0, originalLength, ...reversed)
244
+ }
245
+ return proxy
246
+ }
247
+
248
+ case "copyWithin": {
249
+ const targetArg = args[0] as number
250
+ const startArg = args[1] as number | undefined
251
+ const endArg = args[2] as number | undefined
252
+
253
+ const len = originalLength
254
+ const targetIdx = normalizeIndex(targetArg, len, 0)
255
+ const startIdx = normalizeIndex(startArg, len, 0)
256
+ const endIdx = normalizeIndex(endArg, len, len)
257
+
258
+ // Calculate how many elements will be copied
259
+ const copyCount = Math.min(endIdx - startIdx, len - targetIdx)
260
+
261
+ if (copyCount > 0) {
262
+ // Get the elements that will be copied (from the source range)
263
+ const elementsToCopy = arr.slice(startIdx, startIdx + copyCount)
264
+ // Use proxy splice to replace just the affected range
265
+ proxy.splice(targetIdx, copyCount, ...elementsToCopy)
266
+ }
267
+
268
+ return proxy
269
+ }
270
+
271
+ default:
272
+ return (arr as unknown as Record<string, (...args: unknown[]) => unknown>)[method](...args)
273
+ }
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Proxy handler for drafts
279
+ */
280
+ const proxyHandler: ProxyHandler<ProxyDraft> = {
281
+ get(target: ProxyDraft, key: PropertyKey, receiver: unknown) {
282
+ // Return cached draft if available
283
+ const copy = target.copy?.[key as keyof typeof target.copy]
284
+ if (copy && target.finalities.draftsCache.has(copy as object)) {
285
+ return copy
286
+ }
287
+
288
+ // Return the ProxyDraft itself when accessing the symbol
289
+ if (key === PROXY_DRAFT) return target
290
+
291
+ const source = latest(target)
292
+
293
+ // Intercept mutating array methods for eager op logging
294
+ if (
295
+ target.type === DraftType.Array &&
296
+ typeof key === "string" &&
297
+ MUTATING_ARRAY_METHODS.has(key)
298
+ ) {
299
+ const originalMethod = (source as unknown[])[key as keyof unknown[]]
300
+ if (typeof originalMethod === "function") {
301
+ return createArrayMethodWrapper(target, receiver, key)
302
+ }
303
+ }
304
+
305
+ // Property doesn't exist - check prototype chain
306
+ if (!has(source, key)) {
307
+ const desc = getDescriptor(source, key)
308
+ return desc ? ("value" in desc ? desc.value : desc.get?.call(target.proxy)) : undefined
309
+ }
310
+
311
+ const value = (source as Record<PropertyKey, unknown>)[key]
312
+
313
+ // Already finalized or not draftable - return as-is
314
+ if (target.finalized || !isDraftable(value)) {
315
+ return value
316
+ }
317
+
318
+ // If value is same as original, create a nested draft
319
+ if (value === peek(target.original, key)) {
320
+ ensureShallowCopy(target)
321
+ const nestedKey = target.type === DraftType.Array ? Number(key) : key
322
+ ;(target.copy as Record<PropertyKey, unknown>)[key] = createDraft({
323
+ original: (target.original as Record<PropertyKey, unknown>)[key] as object,
324
+ parentDraft: target,
325
+ key: nestedKey as string | number,
326
+ finalities: target.finalities,
327
+ })
328
+ return (target.copy as Record<PropertyKey, unknown>)[key]
329
+ }
330
+
331
+ // Cache drafts that were assigned
332
+ if (isDraft(value)) {
333
+ target.finalities.draftsCache.add(value as object)
334
+ }
335
+
336
+ return value
337
+ },
338
+
339
+ set(target: ProxyDraft, key: PropertyKey, value: unknown) {
340
+ if (typeof key === "symbol") {
341
+ throw new Error(`Cannot set symbol properties on drafts`)
342
+ }
343
+
344
+ // For arrays, convert and validate the key
345
+ let opKey: string | number = key as string | number
346
+ if (target.type === DraftType.Array) {
347
+ if (key === "length") {
348
+ opKey = "length"
349
+ } else {
350
+ const numKey = typeof key === "number" ? key : parseArrayIndex(key as string)
351
+ if (numKey === null) {
352
+ throw new Error(`Only supports setting array indices and the 'length' property.`)
353
+ }
354
+ opKey = numKey
355
+
356
+ // Check for sparse array creation
357
+ const source = latest(target) as unknown[]
358
+ if (numKey > source.length) {
359
+ throw new Error(
360
+ `Cannot create sparse array. Index ${numKey} is out of bounds for array of length ${source.length}.`
361
+ )
362
+ }
363
+ }
364
+ }
365
+
366
+ // Handle setter from prototype
367
+ const desc = getDescriptor(latest(target), key)
368
+ if (desc?.set) {
369
+ desc.set.call(target.proxy, value)
370
+ return true
371
+ }
372
+
373
+ const current = peek(latest(target), key)
374
+ const currentProxyDraft = getProxyDraft(current)
375
+
376
+ // If assigning original draftable value back to its draft, just mark as not assigned
377
+ if (currentProxyDraft && isEqual(currentProxyDraft.original, value)) {
378
+ ;(target.copy as Record<PropertyKey, unknown>)[key] = value
379
+ target.assignedMap = target.assignedMap ?? new Map()
380
+ target.assignedMap.set(key, false)
381
+ return true
382
+ }
383
+
384
+ // No change - skip
385
+ if (isEqual(value, current) && (value !== undefined || has(target.original, key))) {
386
+ return true
387
+ }
388
+
389
+ ensureShallowCopy(target)
390
+ markChanged(target)
391
+
392
+ // Track assignment (still needed for finalization to know what changed)
393
+ if (
394
+ has(target.original, key) &&
395
+ isEqual(value, (target.original as Record<PropertyKey, unknown>)[key])
396
+ ) {
397
+ // Reverting to original value - still log the op since we're doing eager logging
398
+ target.assignedMap!.delete(key)
399
+ } else {
400
+ target.assignedMap!.set(key, true)
401
+ }
402
+
403
+ // Track aliasCount and update copy
404
+ const copy = target.copy as Record<PropertyKey, unknown>
405
+ decAliasCount(copy[key])
406
+ incAliasCount(value)
407
+ copy[key] = value
408
+
409
+ // Eager op logging for set operations
410
+ if (target.type === DraftType.Array && opKey === "length") {
411
+ const oldLength = (target.original as unknown[]).length
412
+ const newLength = value as number
413
+ if (newLength !== oldLength) {
414
+ // Length change - always use set op to capture intent
415
+ pushOp(target, {
416
+ kind: "set",
417
+ path: getPathOrThrow(target),
418
+ key: "length",
419
+ value: newLength,
420
+ })
421
+ }
422
+ } else {
423
+ // Regular property set - use opKey (numeric for arrays)
424
+ pushOp(target, {
425
+ kind: "set",
426
+ path: getPathOrThrow(target),
427
+ key: opKey,
428
+ value: deepClone(value) as JSONValue,
429
+ })
430
+ }
431
+
432
+ return true
433
+ },
434
+
435
+ has(target: ProxyDraft, key: PropertyKey) {
436
+ return key in latest(target)
437
+ },
438
+
439
+ ownKeys(target: ProxyDraft) {
440
+ return Reflect.ownKeys(latest(target))
441
+ },
442
+
443
+ getOwnPropertyDescriptor(target: ProxyDraft, key: PropertyKey) {
444
+ const source = latest(target)
445
+ const descriptor = Reflect.getOwnPropertyDescriptor(source, key)
446
+ if (!descriptor) return descriptor
447
+ return {
448
+ writable: true,
449
+ configurable: target.type !== DraftType.Array || key !== "length",
450
+ enumerable: descriptor.enumerable,
451
+ value: (source as Record<PropertyKey, unknown>)[key],
452
+ }
453
+ },
454
+
455
+ getPrototypeOf(target: ProxyDraft) {
456
+ return Reflect.getPrototypeOf(target.original as object)
457
+ },
458
+
459
+ setPrototypeOf() {
460
+ throw new Error(`Cannot call 'setPrototypeOf()' on drafts`)
461
+ },
462
+
463
+ defineProperty() {
464
+ throw new Error(`Cannot call 'defineProperty()' on drafts`)
465
+ },
466
+
467
+ deleteProperty(target: ProxyDraft, key: PropertyKey) {
468
+ if (typeof key === "symbol") {
469
+ throw new Error(`Cannot delete symbol properties from drafts`)
470
+ }
471
+
472
+ if (target.type === DraftType.Array) {
473
+ // For arrays, deleting a property sets it to undefined
474
+ return proxyHandler.set!.call(this, target, key as string | symbol, undefined, target.proxy)
475
+ }
476
+
477
+ // Check if property exists and get its current value
478
+ const current = peek(latest(target), key)
479
+ const existed = current !== undefined || key in (target.original as object)
480
+
481
+ // For objects, track deletion
482
+ if (existed) {
483
+ ensureShallowCopy(target)
484
+ markChanged(target)
485
+ target.assignedMap!.set(key, false)
486
+
487
+ // Eager op logging for delete
488
+ pushOp(target, {
489
+ kind: "delete",
490
+ path: getPathOrThrow(target),
491
+ key: key,
492
+ })
493
+
494
+ // Track aliasCount and delete from copy
495
+ const copy = target.copy as Record<PropertyKey, unknown>
496
+ decAliasCount(copy[key])
497
+ delete copy[key]
498
+ } else {
499
+ target.assignedMap = target.assignedMap ?? new Map()
500
+ target.assignedMap.delete(key)
501
+ }
502
+ return true
503
+ },
504
+ }
505
+
506
+ /**
507
+ * Create a draft proxy for a value
508
+ */
509
+ export function createDraft<T extends object>(options: {
510
+ original: T
511
+ parentDraft?: ProxyDraft | null
512
+ key?: string | number
513
+ finalities: Finalities
514
+ }): T {
515
+ const { original, parentDraft, key, finalities } = options
516
+ const type = getType(original)
517
+
518
+ const proxyDraft: ProxyDraft<T> = {
519
+ type,
520
+ finalized: false,
521
+ parent: parentDraft ?? null,
522
+ original,
523
+ copy: null,
524
+ proxy: null,
525
+ finalities,
526
+ aliasCount: 1,
527
+ }
528
+
529
+ // Set key if provided
530
+ if (key !== undefined || "key" in options) {
531
+ proxyDraft.key = key
532
+ }
533
+
534
+ // Create revocable proxy
535
+ const { proxy, revoke } = Proxy.revocable<T>(
536
+ (type === DraftType.Array ? Object.assign([], proxyDraft) : proxyDraft) as T,
537
+ proxyHandler as ProxyHandler<T>
538
+ )
539
+
540
+ finalities.revoke.push(revoke)
541
+ proxyDraft.proxy = proxy
542
+
543
+ // Set up finalization callback to unwrap child drafts in parent copy
544
+ if (parentDraft) {
545
+ parentDraft.finalities.draft.push(() => {
546
+ const copy = parentDraft.copy
547
+ if (!copy) return
548
+
549
+ const draft = get(copy as object, key!)
550
+ const childProxyDraft = getProxyDraft(draft)
551
+
552
+ if (childProxyDraft) {
553
+ // Get the updated value
554
+ let updatedValue = childProxyDraft.original
555
+ if (childProxyDraft.operated) {
556
+ updatedValue = getValue(draft as object)
557
+ }
558
+ childProxyDraft.finalized = true
559
+ set(copy as object, key!, updatedValue)
560
+ }
561
+ })
562
+ }
563
+
564
+ return proxy
565
+ }
566
+
567
+ /**
568
+ * Finalize a draft and return the result with ops
569
+ */
570
+ export function finalizeDraft<T>(result: T, returnedValue: [T] | []): [T, Op[]] {
571
+ const proxyDraft = getProxyDraft<T>(result)
572
+ const hasReturnedValue = returnedValue.length > 0
573
+
574
+ // Run finalization callbacks to unwrap child drafts
575
+ if (proxyDraft?.operated) {
576
+ while (proxyDraft.finalities.draft.length > 0) {
577
+ const finalize = proxyDraft.finalities.draft.pop()!
578
+ finalize()
579
+ }
580
+ proxyDraft.finalized = true
581
+ }
582
+
583
+ // Determine final state
584
+ const state = hasReturnedValue
585
+ ? returnedValue[0]
586
+ : proxyDraft
587
+ ? proxyDraft.operated
588
+ ? (proxyDraft.copy as T)
589
+ : proxyDraft.original
590
+ : result
591
+
592
+ // Handle any remaining nested drafts in the state (e.g., from assignments like draft.a = draft.b)
593
+ if (proxyDraft && state && typeof state === "object") {
594
+ handleValue(state, proxyDraft.finalities.handledSet)
595
+ }
596
+
597
+ // Get ops from finalities (eager logging)
598
+ const ops = proxyDraft?.finalities.ops ?? []
599
+
600
+ // Revoke all proxies
601
+ if (proxyDraft) {
602
+ revokeProxy(proxyDraft)
603
+ }
604
+
605
+ return [state as T, ops]
606
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Create a draft from a base state.
3
+ * Simplified from mutative - removed patches/freeze/mark support.
4
+ */
5
+
6
+ import { createDraft, finalizeDraft } from "./draft"
7
+ import type { Finalities, Op } from "./interface"
8
+ import { getProxyDraft, isDraftable } from "./utils"
9
+
10
+ /**
11
+ * Create a draft and return a finalize function
12
+ */
13
+ export function draftify<T extends object>(
14
+ baseState: T
15
+ ): [T, (returnedValue: [T] | []) => [T, Op[]]] {
16
+ const finalities: Finalities = {
17
+ draft: [],
18
+ revoke: [],
19
+ handledSet: new WeakSet<object>(),
20
+ draftsCache: new WeakSet<object>(),
21
+ ops: [],
22
+ rootDraft: null, // Will be set by createDraft
23
+ }
24
+
25
+ // Check if state is draftable
26
+ if (!isDraftable(baseState)) {
27
+ throw new Error(`createOps() only supports plain objects and arrays.`)
28
+ }
29
+
30
+ const draft = createDraft({
31
+ original: baseState,
32
+ parentDraft: null,
33
+ finalities,
34
+ })
35
+
36
+ // Set the root draft for multi-path detection
37
+ finalities.rootDraft = getProxyDraft(draft)
38
+
39
+ return [
40
+ draft,
41
+ (returnedValue: [T] | [] = []) => {
42
+ return finalizeDraft(draft, returnedValue)
43
+ },
44
+ ]
45
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * createOps - Proxy-based mutable-style API for generating operations.
3
+ *
4
+ * Forked from mutative (https://github.com/unadlib/mutative)
5
+ * MIT License
6
+ */
7
+
8
+ // Main API
9
+ export { createOps } from "./createOps"
10
+ export { current } from "./current"
11
+ // Types
12
+ export type { CreateOpsResult, Draft, Immutable, Op, Path } from "./interface"
13
+ // Utilities
14
+ export { original } from "./original"
15
+
16
+ // Set-like helpers
17
+ export { addToSet, deleteFromSet } from "./setHelpers"
18
+ export { isDraft, isDraftable } from "./utils"