sprae 6.1.2 → 8.0.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.
@@ -0,0 +1,133 @@
1
+ // signals-based proxy
2
+ // + we need proxy for sandbox & arrays anyways
3
+ // + it seems faster than defining a bunch of props on sealed state object
4
+ // + we need support signal inputs
5
+ // + signals provide nice tracking mechanism, unlike own arrays
6
+ // + signals detect cycles
7
+ // + it's just robust
8
+ // ? must it modify initial store
9
+
10
+ import { signal, computed, effect, batch, untracked } from '@preact/signals-core'
11
+ // import { signal, computed } from 'usignal/sync'
12
+ // import { signal, computed } from '@webreflection/signal'
13
+
14
+ export { effect, computed, batch, untracked }
15
+
16
+ export const _dispose = (Symbol.dispose ||= Symbol('dispose'));
17
+ export const _signals = Symbol('signals');
18
+
19
+
20
+ // default root sandbox
21
+ export const sandbox = {
22
+ Array, Object, Number, String, Boolean, Date,
23
+ console, window, document, history, navigator, location, screen, localStorage, sessionStorage,
24
+ alert, prompt, confirm, fetch, performance,
25
+ setTimeout, setInterval, requestAnimationFrame
26
+ }
27
+
28
+ const isObject = v => v?.constructor === Object
29
+ const isPrimitive = (value) => value !== Object(value);
30
+
31
+ // track last accessed property to figure out if .length was directly accessed from expression or via .push/etc method
32
+ let lastProp
33
+
34
+ export default function createState(values, parent) {
35
+ if (!isObject(values) && !Array.isArray(values)) return values;
36
+ // ignore existing state as argument
37
+ if (values[_signals] && !parent) return values;
38
+ const initSignals = values[_signals]
39
+
40
+ // .length signal is stored outside, since cannot be replaced
41
+ const _len = Array.isArray(values) && signal((initSignals || values).length),
42
+ // dict with signals storing values
43
+ signals = parent ? Object.create((parent = createState(parent))[_signals]) : Array.isArray(values) ? [] : {},
44
+ proto = signals.constructor.prototype;
45
+
46
+ // proxy conducts prop access to signals
47
+ const state = new Proxy(values, {
48
+ // sandbox everything
49
+ has() { return true },
50
+ get(values, key) {
51
+ // if .length is read within .push/etc - peek signal (don't subscribe)
52
+ if (_len)
53
+ if (key === 'length') return (proto[lastProp]) ? _len.peek() : _len.value;
54
+ else lastProp = key;
55
+ if (proto[key]) return proto[key]
56
+ if (key === _signals) return signals
57
+ const s = signals[key] || initSignal(key)
58
+ if (s) return s.value // existing property
59
+ if (parent) return parent[key]; // touch parent
60
+ if (sandbox.hasOwnProperty(key)) return sandbox[key] // Array, window etc
61
+ },
62
+ set(values, key, v) {
63
+ if (_len) {
64
+ // .length
65
+ if (key === 'length') {
66
+ batch(() => {
67
+ // force cleaning up tail
68
+ for (let i = v, l = signals.length; i < l; i++) delete state[i]
69
+ // force init new signals
70
+ for (let i = signals.length; i < v; i++) state[i] = null
71
+ _len.value = signals.length = values.length = v;
72
+ })
73
+ return true
74
+ }
75
+ }
76
+
77
+ const s = signals[key] || initSignal(key, v) || signal()
78
+ const cur = s.peek()
79
+
80
+ // skip unchanged (although can be handled by last condition - we skip a few checks this way)
81
+ if (v === cur);
82
+ // stashed _set for values with getter/setter
83
+ else if (s._set) s._set(v)
84
+ // patch array
85
+ else if (Array.isArray(v) && Array.isArray(cur)) {
86
+ untracked(() => batch(() => {
87
+ let i = 0, l = v.length;
88
+ for (; i < l; i++) cur[i] = values[key][i] = v[i]
89
+ cur.length = l // forces deleting tail signals
90
+ s.value = null, s.value = cur // bump effects
91
+ }))
92
+ }
93
+ // .x = y
94
+ else {
95
+ // reflect change in values
96
+ if (Array.isArray(cur)) cur.length = 0 // cleanup array subs
97
+ s.value = createState(values[key] = v)
98
+ }
99
+
100
+ // force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
101
+ if (_len && key >= _len.peek()) _len.value = signals.length = values.length = Number(key) + 1
102
+
103
+ return true
104
+ },
105
+ deleteProperty(values, key) {
106
+ if (key in signals) signals[key]._del?.(), delete signals[key], delete values[key]
107
+ return true
108
+ }
109
+ })
110
+
111
+ // init signals placeholders (instead of ownKeys & getOwnPropertyDescriptor handlers)
112
+ // if values are existing proxy (in case of extending parent) - take its signals instead of creating new ones
113
+ for (let key in values) values[key], signals[key] = initSignals?.[key] ?? null;
114
+
115
+ // initialize signal for provided key
116
+ function initSignal(key) {
117
+ // init existing value
118
+ if (values.hasOwnProperty(key)) {
119
+ // create signal from descriptor
120
+ const desc = Object.getOwnPropertyDescriptor(values, key)
121
+ // getter turns into computed
122
+ if (desc?.get) {
123
+ // stash setter
124
+ (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
125
+ return signals[key]
126
+ }
127
+ // take over existing signal or create new signal
128
+ return signals[key] = desc.value?.peek ? desc.value : signal(createState(desc.value))
129
+ }
130
+ }
131
+
132
+ return state
133
+ }
@@ -0,0 +1,82 @@
1
+ // signals-based store implementation
2
+ import { signal, computed, effect, batch } from '@preact/signals-core'
3
+ // import { signal, computed } from 'usignal/sync'
4
+ // import { signal, computed } from '@webreflection/signal'
5
+
6
+ const isSignal = v => v?.peek
7
+ const isState = v => v?.[_st]
8
+ const isObject = v => v?.constructor === Object
9
+
10
+ const _st = Symbol('state')
11
+
12
+ export { effect, batch }
13
+
14
+ export default function createState(values, proto) {
15
+ if (isState(values) && !proto) return values;
16
+
17
+ // define signal accessors - creates signals for all object props
18
+
19
+ if (isObject(values)) {
20
+ const
21
+ state = Object.create(proto || Object.getPrototypeOf(values)),
22
+ signals = {},
23
+ descs = Object.getOwnPropertyDescriptors(values)
24
+
25
+ // define signal accessors for exported object
26
+ for (let key in descs) {
27
+ let desc = descs[key]
28
+
29
+ // getter turns into computed
30
+ if (desc.get) {
31
+ let s = signals[key] = computed(desc.get.bind(state))
32
+ Object.defineProperty(state, key, {
33
+ get() { return s.value },
34
+ set: desc.set?.bind(state),
35
+ configurable: false,
36
+ enumerable: true
37
+ })
38
+ }
39
+ // regular value creates signal accessor
40
+ else {
41
+ let value = desc.value
42
+ let s = signals[key] = isSignal(value) ? value :
43
+ signal(
44
+ // if initial value is an object - we turn it into sealed struct
45
+ isObject(value) ? Object.seal(createState(value)) :
46
+ Array.isArray(value) ? createState(value) :
47
+ value
48
+ )
49
+
50
+ // define property accessor on struct
51
+ Object.defineProperty(state, key, {
52
+ get() { return s.value },
53
+ set(v) {
54
+ if (isObject(v)) {
55
+ // new object can have another schema than the new one
56
+ // so if it throws due to new props access then we fall back to creating new struct
57
+ if (isObject(s.value)) try { Object.assign(s.value, v); return } catch (e) { }
58
+ s.value = Object.seal(createState(v));
59
+ }
60
+ else if (Array.isArray(v)) s.value = createState(v)
61
+ else s.value = v;
62
+
63
+ },
64
+ enumerable: true,
65
+ configurable: false
66
+ })
67
+ }
68
+ }
69
+
70
+ Object.defineProperty(state, _st, { configurable: false, enumerable: false, value: true })
71
+
72
+ return state
73
+ }
74
+
75
+ // for arrays we turn internals into signal structs
76
+ // FIXME: make proxy to intercept length and other single values
77
+ if (Array.isArray(values)) {
78
+ for (let i = 0; i < values.length; i++) values[i] = createState(values[i])
79
+ }
80
+
81
+ return values
82
+ }