web-toolkit-x 0.0.1 → 0.71.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.
- package/AUTHORS +3 -0
- package/LICENSE +1 -1
- package/NOTICE +7 -0
- package/README.md +169 -6
- package/dist/release/global.d.ts +10 -0
- package/dist/release/index.d.ts +650 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +2837 -0
- package/dist/release/index.js.map +1 -0
- package/dist/release/toolkit-0.71.0.js +2840 -0
- package/dist/release/toolkit-0.71.0.js.map +1 -0
- package/package.json +75 -18
- package/src/core/bragi.ts +122 -0
- package/src/core/browser.ts +555 -0
- package/src/core/child-nodes.ts +77 -0
- package/src/core/comment.ts +24 -0
- package/src/core/component.ts +190 -0
- package/src/core/created-roots.ts +38 -0
- package/src/core/custom-element.ts +191 -0
- package/src/core/description.ts +241 -0
- package/src/core/diff.ts +590 -0
- package/src/core/dispatcher.ts +221 -0
- package/src/core/dom.ts +124 -0
- package/src/core/draft.ts +609 -0
- package/src/core/lifecycle.ts +301 -0
- package/src/core/nodes.ts +13 -0
- package/src/core/patch.ts +412 -0
- package/src/core/plugins.ts +231 -0
- package/src/core/reconciler.ts +185 -0
- package/src/core/renderer.ts +75 -0
- package/src/core/runtime.ts +36 -0
- package/src/core/sandbox.ts +128 -0
- package/src/core/service.ts +39 -0
- package/src/core/template.ts +472 -0
- package/src/core/text.ts +24 -0
- package/src/core/toolkit.ts +220 -0
- package/src/core/utils.ts +148 -0
- package/src/core/virtual-dom.ts +118 -0
- package/src/core/virtual-element.ts +72 -0
- package/src/core/virtual-node.ts +87 -0
- package/src/core/web-component.ts +285 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +62 -0
- package/src/plugins/logger.ts +38 -0
- package/src/release.ts +3 -0
- package/index.js +0 -1
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
import { invariant } from './utils.js'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Drafts let commands change the state as if it was mutable. A draft is
|
|
5
|
+
* a proxy recording the changes on a shallow copy of its object, with the
|
|
6
|
+
* nested objects drafted when accessed. The next state is made of the
|
|
7
|
+
* copies, sharing the parts left unchanged with the previous state.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type Kind = 'object' | 'array' | 'map' | 'set' | 'date' | 'typed-array'
|
|
11
|
+
|
|
12
|
+
type Entries = Record<PropertyKey, unknown>
|
|
13
|
+
|
|
14
|
+
/* Any of the typed arrays, as Uint8Array or Float32Array. */
|
|
15
|
+
interface TypedArray extends ArrayBufferView {
|
|
16
|
+
slice(): TypedArray
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Draft {
|
|
20
|
+
kind: Kind
|
|
21
|
+
base: object
|
|
22
|
+
copy: object | null
|
|
23
|
+
parent: Draft | null
|
|
24
|
+
scope: Scope
|
|
25
|
+
modified: boolean
|
|
26
|
+
proxy: object
|
|
27
|
+
result: object | null
|
|
28
|
+
// the drafts of the items of a set, replacing them in the copy
|
|
29
|
+
items: Map<unknown, object> | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Scope {
|
|
33
|
+
revokes: (() => void)[]
|
|
34
|
+
// the new values checked for drafts when finalizing
|
|
35
|
+
visited: WeakSet<object>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* The drafts by their proxies. */
|
|
39
|
+
const drafts = new WeakMap<object, Draft>()
|
|
40
|
+
|
|
41
|
+
/* The drafts by the targets of their proxies. */
|
|
42
|
+
const targets = new WeakMap<object, Draft>()
|
|
43
|
+
|
|
44
|
+
// matching the original objects of the drafts as well as the drafts
|
|
45
|
+
const ARRAY_SEARCHES = new Set<PropertyKey>([
|
|
46
|
+
'includes',
|
|
47
|
+
'indexOf',
|
|
48
|
+
'lastIndexOf',
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
// with subarray() writing to the memory of the array, as a view of it
|
|
52
|
+
const TYPED_ARRAY_MUTATORS = new Set<PropertyKey>([
|
|
53
|
+
'copyWithin',
|
|
54
|
+
'fill',
|
|
55
|
+
'reverse',
|
|
56
|
+
'set',
|
|
57
|
+
'sort',
|
|
58
|
+
'subarray',
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
const getKind = (value: unknown): Kind | null => {
|
|
62
|
+
if (typeof value !== 'object' || value === null) {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
if (ArrayBuffer.isView(value)) {
|
|
66
|
+
return value instanceof DataView ? null : 'typed-array'
|
|
67
|
+
}
|
|
68
|
+
switch (Object.getPrototypeOf(value)) {
|
|
69
|
+
case Object.prototype:
|
|
70
|
+
case null:
|
|
71
|
+
return 'object'
|
|
72
|
+
case Array.prototype:
|
|
73
|
+
return 'array'
|
|
74
|
+
case Map.prototype:
|
|
75
|
+
return 'map'
|
|
76
|
+
case Set.prototype:
|
|
77
|
+
return 'set'
|
|
78
|
+
case Date.prototype:
|
|
79
|
+
return 'date'
|
|
80
|
+
default:
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const INVALID_VALUE =
|
|
86
|
+
'The state can contain only primitives, functions, plain objects, arrays, maps, sets, dates and typed arrays'
|
|
87
|
+
|
|
88
|
+
/* Returns the kind of a value allowed in the state, null when not drafted. */
|
|
89
|
+
const checkValue = (value: unknown): Kind | null => {
|
|
90
|
+
const kind = getKind(value)
|
|
91
|
+
invariant(kind || typeof value !== 'object' || value === null, INVALID_VALUE)
|
|
92
|
+
return kind
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const current = (draft: Draft) => draft.copy ?? draft.base
|
|
96
|
+
|
|
97
|
+
const createDraft = (
|
|
98
|
+
base: object,
|
|
99
|
+
kind: Kind,
|
|
100
|
+
parent: Draft | null,
|
|
101
|
+
scope: Scope,
|
|
102
|
+
): object => {
|
|
103
|
+
// arrays need an array target to be recognized by Array.isArray()
|
|
104
|
+
const target: object =
|
|
105
|
+
kind === 'array'
|
|
106
|
+
? []
|
|
107
|
+
: (Object.create(Object.getPrototypeOf(base) as object | null) as object)
|
|
108
|
+
const { proxy, revoke } = Proxy.revocable(target, handler)
|
|
109
|
+
const draft: Draft = {
|
|
110
|
+
kind,
|
|
111
|
+
base,
|
|
112
|
+
copy: null,
|
|
113
|
+
parent,
|
|
114
|
+
scope,
|
|
115
|
+
modified: false,
|
|
116
|
+
proxy,
|
|
117
|
+
result: null,
|
|
118
|
+
items: null,
|
|
119
|
+
}
|
|
120
|
+
drafts.set(proxy, draft)
|
|
121
|
+
targets.set(target, draft)
|
|
122
|
+
scope.revokes.push(revoke)
|
|
123
|
+
return proxy
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const prepareCopy = (draft: Draft) => {
|
|
127
|
+
if (draft.copy) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
const { base } = draft
|
|
131
|
+
switch (draft.kind) {
|
|
132
|
+
case 'object':
|
|
133
|
+
draft.copy = Object.assign(
|
|
134
|
+
Object.create(Object.getPrototypeOf(base) as object | null) as object,
|
|
135
|
+
base,
|
|
136
|
+
)
|
|
137
|
+
break
|
|
138
|
+
case 'array':
|
|
139
|
+
draft.copy = (base as unknown[]).slice()
|
|
140
|
+
break
|
|
141
|
+
case 'map':
|
|
142
|
+
draft.copy = new Map(base as Map<unknown, unknown>)
|
|
143
|
+
break
|
|
144
|
+
case 'set': {
|
|
145
|
+
// the items are drafted up front, as sets have no keys to draft them by
|
|
146
|
+
const copy = new Set<unknown>()
|
|
147
|
+
draft.items = new Map()
|
|
148
|
+
for (const item of base as Set<unknown>) {
|
|
149
|
+
const kind = checkValue(item)
|
|
150
|
+
if (kind) {
|
|
151
|
+
const itemDraft = createDraft(
|
|
152
|
+
item as object,
|
|
153
|
+
kind,
|
|
154
|
+
draft,
|
|
155
|
+
draft.scope,
|
|
156
|
+
)
|
|
157
|
+
draft.items.set(item, itemDraft)
|
|
158
|
+
copy.add(itemDraft)
|
|
159
|
+
} else {
|
|
160
|
+
copy.add(item)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
draft.copy = copy
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
case 'date':
|
|
167
|
+
draft.copy = new Date((base as Date).getTime())
|
|
168
|
+
break
|
|
169
|
+
case 'typed-array':
|
|
170
|
+
// of the same type, as slice() creates the array with its constructor
|
|
171
|
+
draft.copy = (base as TypedArray).slice()
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const markModified = (draft: Draft | null) => {
|
|
177
|
+
while (draft && !draft.modified) {
|
|
178
|
+
prepareCopy(draft)
|
|
179
|
+
draft.modified = true
|
|
180
|
+
draft = draft.parent
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* Returns the value under the key, drafted when it is a draftable value
|
|
186
|
+
* of the base object, left unchanged.
|
|
187
|
+
*/
|
|
188
|
+
const getValue = (
|
|
189
|
+
draft: Draft,
|
|
190
|
+
value: unknown,
|
|
191
|
+
baseValue: unknown,
|
|
192
|
+
store: (copy: object, child: object) => void,
|
|
193
|
+
): unknown => {
|
|
194
|
+
if (drafts.has(value as object)) {
|
|
195
|
+
return value
|
|
196
|
+
}
|
|
197
|
+
const kind = checkValue(value)
|
|
198
|
+
if (!kind || value !== baseValue) {
|
|
199
|
+
return value
|
|
200
|
+
}
|
|
201
|
+
prepareCopy(draft)
|
|
202
|
+
const child = createDraft(value as object, kind, draft, draft.scope)
|
|
203
|
+
store(draft.copy!, child)
|
|
204
|
+
return child
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Returns the original object of a draft, or the value itself. */
|
|
208
|
+
const getOriginal = (value: unknown): unknown =>
|
|
209
|
+
(typeof value === 'object' && value !== null && drafts.get(value)?.base) ||
|
|
210
|
+
value
|
|
211
|
+
|
|
212
|
+
const getProperty = (draft: Draft, key: PropertyKey): unknown => {
|
|
213
|
+
const source = current(draft) as Entries
|
|
214
|
+
if (draft.kind === 'array' && ARRAY_SEARCHES.has(key)) {
|
|
215
|
+
// the objects passed to commands, e.g. from props, are not the drafts
|
|
216
|
+
return (value: unknown, ...args: [number?]) => {
|
|
217
|
+
const items = (current(draft) as unknown[]).map(getOriginal)
|
|
218
|
+
return items[key as 'indexOf'](getOriginal(value), ...args)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (!Object.hasOwn(source, key)) {
|
|
222
|
+
// the array methods work through the proxy
|
|
223
|
+
return Reflect.get(source, key, draft.proxy)
|
|
224
|
+
}
|
|
225
|
+
const base = draft.base as Entries
|
|
226
|
+
return getValue(
|
|
227
|
+
draft,
|
|
228
|
+
source[key],
|
|
229
|
+
Object.hasOwn(base, key) ? base[key] : undefined,
|
|
230
|
+
(copy, child) => ((copy as Entries)[key] = child),
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const getMapValue = (draft: Draft, key: unknown): unknown => {
|
|
235
|
+
const source = current(draft) as Map<unknown, unknown>
|
|
236
|
+
return getValue(
|
|
237
|
+
draft,
|
|
238
|
+
source.get(key),
|
|
239
|
+
(draft.base as Map<unknown, unknown>).get(key),
|
|
240
|
+
(copy, child) => (copy as Map<unknown, unknown>).set(key, child),
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* The methods of a map draft, keeping the copy in sync. */
|
|
245
|
+
const mapMethods = new Map<PropertyKey, (draft: Draft) => unknown>([
|
|
246
|
+
['get', draft => (key: unknown) => getMapValue(draft, key)],
|
|
247
|
+
[
|
|
248
|
+
'set',
|
|
249
|
+
draft => (key: unknown, value: unknown) => {
|
|
250
|
+
const source = current(draft) as Map<unknown, unknown>
|
|
251
|
+
if (!source.has(key) || !Object.is(source.get(key), value)) {
|
|
252
|
+
markModified(draft)
|
|
253
|
+
;(draft.copy as Map<unknown, unknown>).set(key, value)
|
|
254
|
+
}
|
|
255
|
+
return draft.proxy
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
[
|
|
259
|
+
'delete',
|
|
260
|
+
draft => (key: unknown) => {
|
|
261
|
+
if (!(current(draft) as Map<unknown, unknown>).has(key)) {
|
|
262
|
+
return false
|
|
263
|
+
}
|
|
264
|
+
markModified(draft)
|
|
265
|
+
return (draft.copy as Map<unknown, unknown>).delete(key)
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
'clear',
|
|
270
|
+
draft => () => {
|
|
271
|
+
if ((current(draft) as Map<unknown, unknown>).size) {
|
|
272
|
+
markModified(draft)
|
|
273
|
+
;(draft.copy as Map<unknown, unknown>).clear()
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
[
|
|
278
|
+
'forEach',
|
|
279
|
+
draft =>
|
|
280
|
+
(
|
|
281
|
+
callback: (value: unknown, key: unknown, map: object) => void,
|
|
282
|
+
thisArg?: unknown,
|
|
283
|
+
) => {
|
|
284
|
+
for (const key of [
|
|
285
|
+
...(current(draft) as Map<unknown, unknown>).keys(),
|
|
286
|
+
]) {
|
|
287
|
+
callback.call(thisArg, getMapValue(draft, key), key, draft.proxy)
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
[
|
|
292
|
+
'keys',
|
|
293
|
+
draft => () =>
|
|
294
|
+
[...(current(draft) as Map<unknown, unknown>).keys()].values(),
|
|
295
|
+
],
|
|
296
|
+
[
|
|
297
|
+
'values',
|
|
298
|
+
draft =>
|
|
299
|
+
function* () {
|
|
300
|
+
for (const key of [
|
|
301
|
+
...(current(draft) as Map<unknown, unknown>).keys(),
|
|
302
|
+
]) {
|
|
303
|
+
yield getMapValue(draft, key)
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
['entries', draft => () => mapEntries(draft)],
|
|
308
|
+
[Symbol.iterator, draft => () => mapEntries(draft)],
|
|
309
|
+
])
|
|
310
|
+
|
|
311
|
+
function* mapEntries(draft: Draft) {
|
|
312
|
+
for (const key of [...(current(draft) as Map<unknown, unknown>).keys()]) {
|
|
313
|
+
yield [key, getMapValue(draft, key)]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const hasSetItem = (draft: Draft, item: unknown) => {
|
|
318
|
+
const source = current(draft) as Set<unknown>
|
|
319
|
+
if (source.has(item)) {
|
|
320
|
+
return true
|
|
321
|
+
}
|
|
322
|
+
const itemDraft = draft.items?.get(item)
|
|
323
|
+
return itemDraft !== undefined && source.has(itemDraft)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* The methods of a set draft, iterating over the drafted items. */
|
|
327
|
+
const setMethods = new Map<PropertyKey, (draft: Draft) => unknown>([
|
|
328
|
+
['has', draft => (item: unknown) => hasSetItem(draft, item)],
|
|
329
|
+
[
|
|
330
|
+
'add',
|
|
331
|
+
draft => (item: unknown) => {
|
|
332
|
+
if (!hasSetItem(draft, item)) {
|
|
333
|
+
markModified(draft)
|
|
334
|
+
;(draft.copy as Set<unknown>).add(item)
|
|
335
|
+
}
|
|
336
|
+
return draft.proxy
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
[
|
|
340
|
+
'delete',
|
|
341
|
+
draft => (item: unknown) => {
|
|
342
|
+
if (!hasSetItem(draft, item)) {
|
|
343
|
+
return false
|
|
344
|
+
}
|
|
345
|
+
markModified(draft)
|
|
346
|
+
const copy = draft.copy as Set<unknown>
|
|
347
|
+
return copy.delete(item) || copy.delete(draft.items!.get(item))
|
|
348
|
+
},
|
|
349
|
+
],
|
|
350
|
+
[
|
|
351
|
+
'clear',
|
|
352
|
+
draft => () => {
|
|
353
|
+
if ((current(draft) as Set<unknown>).size) {
|
|
354
|
+
markModified(draft)
|
|
355
|
+
;(draft.copy as Set<unknown>).clear()
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
[
|
|
360
|
+
'forEach',
|
|
361
|
+
draft =>
|
|
362
|
+
(
|
|
363
|
+
callback: (value: unknown, key: unknown, set: object) => void,
|
|
364
|
+
thisArg?: unknown,
|
|
365
|
+
) => {
|
|
366
|
+
for (const item of setItems(draft)) {
|
|
367
|
+
callback.call(thisArg, item, item, draft.proxy)
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
],
|
|
371
|
+
['keys', draft => () => setItems(draft).values()],
|
|
372
|
+
['values', draft => () => setItems(draft).values()],
|
|
373
|
+
[Symbol.iterator, draft => () => setItems(draft).values()],
|
|
374
|
+
[
|
|
375
|
+
'entries',
|
|
376
|
+
draft => () =>
|
|
377
|
+
setItems(draft)
|
|
378
|
+
.map(item => [item, item] as const)
|
|
379
|
+
.values(),
|
|
380
|
+
],
|
|
381
|
+
])
|
|
382
|
+
|
|
383
|
+
const setItems = (draft: Draft) => {
|
|
384
|
+
prepareCopy(draft)
|
|
385
|
+
return [...(draft.copy as Set<unknown>)]
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/*
|
|
389
|
+
* Reads the property of a map, set, date or typed array, keeping their
|
|
390
|
+
* internal slots out of the proxy.
|
|
391
|
+
*/
|
|
392
|
+
const getSlotProperty = (draft: Draft, key: PropertyKey): unknown => {
|
|
393
|
+
if (draft.kind === 'map' || draft.kind === 'set') {
|
|
394
|
+
const method = (draft.kind === 'map' ? mapMethods : setMethods).get(key)
|
|
395
|
+
if (method) {
|
|
396
|
+
return method(draft)
|
|
397
|
+
}
|
|
398
|
+
if (draft.kind === 'set' && key !== 'size') {
|
|
399
|
+
// the other methods, as union(), take the drafted items
|
|
400
|
+
prepareCopy(draft)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (draft.kind === 'typed-array' && key === 'buffer') {
|
|
404
|
+
// the memory of the copy, as it can be written to
|
|
405
|
+
markModified(draft)
|
|
406
|
+
return (draft.copy as TypedArray).buffer
|
|
407
|
+
}
|
|
408
|
+
const source = current(draft) as Entries
|
|
409
|
+
if (draft.kind === 'typed-array' && Object.hasOwn(source, key)) {
|
|
410
|
+
return source[key]
|
|
411
|
+
}
|
|
412
|
+
const value = Reflect.get(source, key, source)
|
|
413
|
+
if (typeof value !== 'function' || key === 'constructor') {
|
|
414
|
+
return value
|
|
415
|
+
}
|
|
416
|
+
const method = value as (...args: unknown[]) => unknown
|
|
417
|
+
const mutates =
|
|
418
|
+
draft.kind === 'date'
|
|
419
|
+
? typeof key === 'string' && key.startsWith('set')
|
|
420
|
+
: draft.kind === 'typed-array' && TYPED_ARRAY_MUTATORS.has(key)
|
|
421
|
+
if (!mutates) {
|
|
422
|
+
return method.bind(source)
|
|
423
|
+
}
|
|
424
|
+
return (...args: unknown[]) => {
|
|
425
|
+
prepareCopy(draft)
|
|
426
|
+
const copy = draft.copy!
|
|
427
|
+
const time = draft.kind === 'date' ? (copy as Date).getTime() : null
|
|
428
|
+
const result = method.apply(copy, args)
|
|
429
|
+
if (draft.kind !== 'date' || !Object.is(time, (copy as Date).getTime())) {
|
|
430
|
+
markModified(draft)
|
|
431
|
+
}
|
|
432
|
+
return result === copy ? draft.proxy : result
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const handler: ProxyHandler<object> = {
|
|
437
|
+
get(target, key) {
|
|
438
|
+
const draft = targets.get(target)!
|
|
439
|
+
return draft.kind === 'object' || draft.kind === 'array'
|
|
440
|
+
? getProperty(draft, key)
|
|
441
|
+
: getSlotProperty(draft, key)
|
|
442
|
+
},
|
|
443
|
+
set(target, key, value) {
|
|
444
|
+
const draft = targets.get(target)!
|
|
445
|
+
const source = current(draft) as Entries
|
|
446
|
+
if (Object.hasOwn(source, key) && Object.is(source[key], value)) {
|
|
447
|
+
return true
|
|
448
|
+
}
|
|
449
|
+
markModified(draft)
|
|
450
|
+
;(draft.copy as Entries)[key] = value
|
|
451
|
+
return true
|
|
452
|
+
},
|
|
453
|
+
deleteProperty(target, key) {
|
|
454
|
+
const draft = targets.get(target)!
|
|
455
|
+
if (Object.hasOwn(current(draft), key)) {
|
|
456
|
+
markModified(draft)
|
|
457
|
+
delete (draft.copy as Entries)[key]
|
|
458
|
+
}
|
|
459
|
+
return true
|
|
460
|
+
},
|
|
461
|
+
has(target, key) {
|
|
462
|
+
return key in current(targets.get(target)!)
|
|
463
|
+
},
|
|
464
|
+
ownKeys(target) {
|
|
465
|
+
return Reflect.ownKeys(current(targets.get(target)!))
|
|
466
|
+
},
|
|
467
|
+
getOwnPropertyDescriptor(target, key) {
|
|
468
|
+
const draft = targets.get(target)!
|
|
469
|
+
const source = current(draft) as Entries
|
|
470
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(source, key)
|
|
471
|
+
if (!descriptor) {
|
|
472
|
+
return undefined
|
|
473
|
+
}
|
|
474
|
+
// the length of the array target is not configurable
|
|
475
|
+
const configurable = draft.kind !== 'array' || key !== 'length'
|
|
476
|
+
return 'value' in descriptor
|
|
477
|
+
? { ...descriptor, value: source[key], writable: true, configurable }
|
|
478
|
+
: { ...descriptor, configurable }
|
|
479
|
+
},
|
|
480
|
+
defineProperty(target, key, descriptor) {
|
|
481
|
+
const draft = targets.get(target)!
|
|
482
|
+
markModified(draft)
|
|
483
|
+
return Reflect.defineProperty(draft.copy!, key, descriptor)
|
|
484
|
+
},
|
|
485
|
+
setPrototypeOf() {
|
|
486
|
+
return false
|
|
487
|
+
},
|
|
488
|
+
preventExtensions() {
|
|
489
|
+
return false
|
|
490
|
+
},
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/*
|
|
494
|
+
* Replaces the drafts with their results, also in the new values assigned
|
|
495
|
+
* to the drafts, as the drafts are revoked once the next state is created.
|
|
496
|
+
*/
|
|
497
|
+
const finalize = (value: unknown, scope: Scope): unknown => {
|
|
498
|
+
if (typeof value !== 'object' || value === null) {
|
|
499
|
+
return value
|
|
500
|
+
}
|
|
501
|
+
const draft = drafts.get(value)
|
|
502
|
+
if (draft) {
|
|
503
|
+
return finalizeDraft(draft)
|
|
504
|
+
}
|
|
505
|
+
const kind = checkValue(value)
|
|
506
|
+
if (scope.visited.has(value) || Object.isFrozen(value)) {
|
|
507
|
+
return value
|
|
508
|
+
}
|
|
509
|
+
scope.visited.add(value)
|
|
510
|
+
switch (kind) {
|
|
511
|
+
case 'object':
|
|
512
|
+
case 'array': {
|
|
513
|
+
const entries = value as Entries
|
|
514
|
+
for (const key of Reflect.ownKeys(entries)) {
|
|
515
|
+
const next = finalize(entries[key], scope)
|
|
516
|
+
if (next !== entries[key]) {
|
|
517
|
+
entries[key] = next
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
break
|
|
521
|
+
}
|
|
522
|
+
case 'map': {
|
|
523
|
+
const map = value as Map<unknown, unknown>
|
|
524
|
+
for (const [key, item] of map) {
|
|
525
|
+
const next = finalize(item, scope)
|
|
526
|
+
if (next !== item) {
|
|
527
|
+
map.set(key, next)
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
break
|
|
531
|
+
}
|
|
532
|
+
case 'set': {
|
|
533
|
+
const set = value as Set<unknown>
|
|
534
|
+
const items = [...set]
|
|
535
|
+
const nextItems = items.map(item => finalize(item, scope))
|
|
536
|
+
if (nextItems.some((item, index) => item !== items[index])) {
|
|
537
|
+
set.clear()
|
|
538
|
+
nextItems.forEach(item => set.add(item))
|
|
539
|
+
}
|
|
540
|
+
break
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return value
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const finalizeDraft = (draft: Draft): object => {
|
|
547
|
+
if (draft.result) {
|
|
548
|
+
return draft.result
|
|
549
|
+
}
|
|
550
|
+
if (!draft.modified) {
|
|
551
|
+
return (draft.result = draft.base)
|
|
552
|
+
}
|
|
553
|
+
const { base, copy, scope } = draft
|
|
554
|
+
draft.result = copy
|
|
555
|
+
switch (draft.kind) {
|
|
556
|
+
case 'object':
|
|
557
|
+
case 'array': {
|
|
558
|
+
const entries = copy as Entries
|
|
559
|
+
for (const key of Reflect.ownKeys(entries)) {
|
|
560
|
+
const value = entries[key]
|
|
561
|
+
if (value !== (base as Entries)[key]) {
|
|
562
|
+
const next = finalize(value, scope)
|
|
563
|
+
if (next !== value) {
|
|
564
|
+
entries[key] = next
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
break
|
|
569
|
+
}
|
|
570
|
+
case 'map': {
|
|
571
|
+
const map = copy as Map<unknown, unknown>
|
|
572
|
+
for (const [key, value] of map) {
|
|
573
|
+
if (value !== (base as Map<unknown, unknown>).get(key)) {
|
|
574
|
+
map.set(key, finalize(value, scope))
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
break
|
|
578
|
+
}
|
|
579
|
+
case 'set': {
|
|
580
|
+
// in place, for the items referring to the set to get the result
|
|
581
|
+
const set = copy as Set<unknown>
|
|
582
|
+
const items = [...set]
|
|
583
|
+
set.clear()
|
|
584
|
+
items.forEach(item => set.add(finalize(item, scope)))
|
|
585
|
+
break
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return draft.result!
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/*
|
|
592
|
+
* Calls the recipe with a draft of the base object and returns the result
|
|
593
|
+
* of the changes made to it, or the base object when nothing has changed.
|
|
594
|
+
* Plain objects, arrays, maps, sets, dates and typed arrays are drafted,
|
|
595
|
+
* primitives and functions are kept as they are, to be replaced only,
|
|
596
|
+
* and other values, as class instances, throw when read or assigned.
|
|
597
|
+
*/
|
|
598
|
+
export const produce = <T>(base: T, recipe: (draft: T) => void): T => {
|
|
599
|
+
const kind = getKind(base)
|
|
600
|
+
invariant(kind, INVALID_VALUE)
|
|
601
|
+
const scope: Scope = { revokes: [], visited: new WeakSet() }
|
|
602
|
+
const root = createDraft(base as object, kind, null, scope)
|
|
603
|
+
try {
|
|
604
|
+
recipe(root as T)
|
|
605
|
+
return finalizeDraft(drafts.get(root)!) as T
|
|
606
|
+
} finally {
|
|
607
|
+
scope.revokes.forEach(revoke => revoke())
|
|
608
|
+
}
|
|
609
|
+
}
|