sprae 12.0.1 → 12.1.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/store.js CHANGED
@@ -1,18 +1,24 @@
1
1
  // signals-based proxy
2
- import { signal, computed, batch, untracked } from './core.js'
2
+ import { signal, computed, batch, untracked, _add } from './core.js'
3
3
 
4
4
 
5
5
  // _signals allows both storing signals and checking instance, which would be difficult with WeakMap
6
6
  export const _signals = Symbol('signals'),
7
+ // _change is a signal that tracks changes to the object keys or array length
7
8
  _change = Symbol('change'),
9
+ // _set is stashed setter for computed values
8
10
  _set = Symbol('set')
9
11
 
12
+ // a hack to simulate sandbox for `with` in evaluator
13
+ let sandbox = true
14
+
10
15
  // object store is not lazy
11
16
  // parent defines parent scope or sandbox
12
- export const store = (values, parent = globalThis) => {
17
+ export const store = (values, parent) => {
13
18
  if (!values) return values
14
19
 
15
20
  // ignore globals
21
+ // FIXME: handle via has trap
16
22
  if (values[Symbol.toStringTag]) return values;
17
23
 
18
24
  // bypass existing store
@@ -22,23 +28,64 @@ export const store = (values, parent = globalThis) => {
22
28
  if (values.constructor !== Object) return Array.isArray(values) ? list(values) : values
23
29
 
24
30
  // _change stores total number of keys to track new props
25
- // NOTE: be careful
26
31
  let keyCount = Object.keys(values).length,
27
- signals = { }
32
+ signals = {}
28
33
 
29
34
  // proxy conducts prop access to signals
30
- let state = new Proxy(Object.assign(signals, {[_change]: signal(keyCount), [_signals]: signals}), {
31
- get: (_, k) => (k in signals ? (signals[k] ? signals[k].valueOf() : signals[k]) : parent[k]),
32
- set: (_, k, v, _s) => (k in signals ? set(signals, k, v) : (create(signals, k, v), signals[_change].value = ++keyCount), 1), // bump length for new signal
33
- // FIXME: try to avild calling Symbol.dispose here
34
- deleteProperty: (_, k) => (k in signals && (k[0] != '_' && signals[k]?.[Symbol.dispose]?.(), delete signals[k], signals[_change].value = --keyCount), 1),
35
- // subscribe to length when object is spread
35
+ let state = new Proxy(Object.assign(signals, {
36
+ [_change]: signal(keyCount),
37
+ [_signals]: signals
38
+ }), {
39
+ get: (_, k) => {
40
+ // console.log('GET', k, signals)
41
+ if (k in signals) return (signals[k] ? signals[k].valueOf() : signals[k])
42
+ return parent ? parent[k] : globalThis[k]
43
+ },
44
+
45
+ set: (_, k, v, _s) => {
46
+ // console.group('SET', k, v, signals, k in signals)
47
+ if (k in signals) return set(signals, k, v), 1
48
+
49
+ // turn off sandbox to check if parents have the prop - we don't want to create new prop in global scope
50
+ sandbox = false
51
+
52
+ // write transparency for parent scope, unlike prototype chain
53
+ // if prop is defined in parent scope (except global) - write there
54
+ if (parent && k in parent) {
55
+ parent[k] = v
56
+ }
57
+ // else create in current scope
58
+ else {
59
+ create(signals, k, v)
60
+ signals[_change].value = ++keyCount
61
+ }
62
+
63
+ sandbox = true
64
+
65
+ // console.groupEnd()
66
+ // bump length for new signal
67
+ return 1
68
+ },
69
+
70
+ // FIXME: try to avild calling Symbol.dispose here. Maybe _delete method?
71
+ deleteProperty: (_, k) => {
72
+ k in signals && (k[0] != '_' && signals[k]?.[Symbol.dispose]?.(), delete signals[k], signals[_change].value = --keyCount)
73
+ return 1
74
+ },
75
+
76
+ // subscribe to length when spreading
36
77
  ownKeys: () => (signals[_change].value, Reflect.ownKeys(signals)),
37
- has: _ => 1 // sandbox prevents writing to global
38
- }),
39
78
 
40
- // init signals for values
41
- descs = Object.getOwnPropertyDescriptors(values)
79
+ // sandbox prevents writing to global
80
+ has: (_, k) => {
81
+ if (k in signals) return true
82
+ if (parent) return k in parent
83
+ return sandbox
84
+ }
85
+ })
86
+
87
+ // init signals for values
88
+ const descs = Object.getOwnPropertyDescriptors(values)
42
89
 
43
90
  for (let k in values) {
44
91
  // getter turns into computed
@@ -53,8 +100,6 @@ export const store = (values, parent = globalThis) => {
53
100
  return state
54
101
  }
55
102
 
56
- const mut = ['push', 'pop', 'shift', 'unshift', 'splice']
57
-
58
103
  // array store - signals are lazy since arrays can be very large & expensive
59
104
  const list = (values, parent = globalThis) => {
60
105
 
@@ -65,15 +110,16 @@ const list = (values, parent = globalThis) => {
65
110
  isMut = false,
66
111
 
67
112
  // since array mutator methods read .length internally only once, we disable it on the moment of call, allowing rest of operations to be reactive
68
- mut = fn => function () {isMut = true; return fn.apply(this, arguments); },
113
+ mut = fn => function () { isMut = true; return fn.apply(this, arguments); },
69
114
 
70
115
  length = signal(values.length),
71
116
 
72
- // proxy conducts prop access to signals
117
+ // proxy passes prop access to signals
73
118
  state = new Proxy(
74
119
  Object.assign(signals, {
75
120
  [_change]: length,
76
121
  [_signals]: signals,
122
+ // patch mutators
77
123
  push: mut(signals.push),
78
124
  pop: mut(signals.pop),
79
125
  shift: mut(signals.shift),
@@ -124,7 +170,7 @@ const list = (values, parent = globalThis) => {
124
170
  }
125
171
 
126
172
  // create signal value, skip untracked
127
- const create = (signals, k, v) => (signals[k] = k[0] == '_' || v?.peek ? v : signal(store(v)))
173
+ const create = (signals, k, v) => (signals[k] = (k[0] == '_' || v?.peek) ? v : signal(store(v)))
128
174
 
129
175
  // set/update signal value
130
176
  const set = (signals, k, v, _s, _v) => {
@@ -138,10 +184,12 @@ const set = (signals, k, v, _s, _v) => {
138
184
  // if we update plain array (stored in signal) - take over value instead
139
185
  // since input value can be store, we have to make sure we don't subscribe to its length or values
140
186
  // FIXME: generalize to objects
141
- _change in _v ? untracked(() => batch(() => {
142
- for (let i = 0; i < v.length; i++) _v[i] = v[i]
143
- _v.length = v.length // forces deleting tail signals
144
- })) : _s.value = v :
187
+ _change in _v ?
188
+ untracked(() => batch(() => {
189
+ for (let i = 0; i < v.length; i++) _v[i] = v[i]
190
+ _v.length = v.length // forces deleting tail signals
191
+ })) :
192
+ (_s.value = v) :
145
193
  // .x = y
146
194
  (_s.value = store(v))
147
195
  )
@@ -150,6 +198,6 @@ const set = (signals, k, v, _s, _v) => {
150
198
 
151
199
  // make sure state contains first element of path, eg. `a` from `a.b[c]`
152
200
  // NOTE: we don't need since we force proxy sandbox
153
- // export const ensure = (state, expr, name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => name && (state[_signals][name[0]] ??= null)
201
+ // export const ensure = (state, expr, _name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => _name && (state[_signals][_name[0]] ??= null)
154
202
 
155
203
  export default store