sprae 8.1.3 → 9.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.
@@ -1,150 +0,0 @@
1
- // proxy-based store implementation: more flexible, less optimal and with memory leak now
2
- // FIXME: make simpler prototype chain, likely manual
3
- // FIXME: detect circular updates
4
- import { queueMicrotask } from "./util.js"
5
-
6
- // currentFx stack of listeners
7
- let currentFx, batch = [], pendingUpdate
8
-
9
- const targetFxs = new WeakMap
10
- const targetProxy = new WeakMap
11
- const proxyTarget = new WeakMap
12
- const _parent = Symbol('parent')
13
-
14
- // default root sandbox
15
- export const sandbox = {
16
- Array, Object, Number, String, Boolean, Date,
17
- console, window, document, history, location
18
- }
19
-
20
- const callArg = (fn) => fn(), nop = () => { }
21
- export { callArg as batch };
22
-
23
- let lastProp
24
-
25
- const handler = {
26
- has() {
27
- // sandbox everything
28
- return true
29
- },
30
-
31
- get(target, prop) {
32
- if (typeof prop === 'symbol') return lastProp = null, target[prop]
33
- if (!(prop in target)) return target[_parent]?.[prop]
34
-
35
- // ignore .length effects called within .push
36
- if (lastProp && prop === 'length') return lastProp = null, target[prop];
37
-
38
- // .constructor, .slice etc
39
- if (Object.prototype[prop]) return target[prop]
40
- if (Array.isArray(target) && Array.prototype[prop] && prop !== 'length') return lastProp = prop, target[prop];
41
-
42
- let value = target[prop]
43
-
44
- if (currentFx) {
45
- // get actual target from prototype chain
46
- // while (!target.hasOwnProperty(prop)) target = Object.getPrototypeOf(target)
47
-
48
- // register an fx for target-prop path
49
- let propFxs = targetFxs.get(target)
50
- if (!propFxs) targetFxs.set(target, propFxs = {})
51
- if (!propFxs[prop]) propFxs[prop] = [currentFx]
52
- else if (!propFxs[prop].includes(currentFx)) propFxs[prop].push(currentFx)
53
- }
54
-
55
- // if internal is trackable path - return proxy
56
- if ((value && value.constructor === Object) || Array.isArray(value)) {
57
- let proxy = targetProxy.get(value)
58
- // FIXME: we can avoid saving it here, since it's created by new state
59
- if (!proxy) targetProxy.set(value, proxy = new Proxy(value, handler))
60
- return proxy
61
- }
62
-
63
- return value
64
- },
65
-
66
- set(target, prop, value) {
67
- // "fake" prototype chain, since regular one doesn't fit
68
- if (!(prop in target) && (target[_parent] && prop in target[_parent])) return target[_parent][prop] = value
69
-
70
- // avoid bumping unchanged values
71
- if (!Array.isArray(target) && Object.is(target[prop], value)) return true
72
-
73
- target[prop] = value
74
-
75
- // whenever target prop is set, call all dependent fxs
76
- let propFxs = targetFxs.get(target)?.[prop]
77
-
78
- if (propFxs) for (let fx of propFxs) {
79
- // put fx latest
80
- if (fx.planned != null) batch[fx.planned] = null
81
- fx.planned = batch.push(fx) - 1
82
- }
83
- planUpdate()
84
-
85
- // FIXME: unsubscribe / delete effects by setting null/undefined
86
- // if (value == null) targetFxs.delete(prev)
87
-
88
- return true
89
- },
90
-
91
- deleteProperty(target, prop) {
92
- target[prop] = undefined
93
- delete target[prop]
94
- return true
95
- }
96
- }
97
-
98
- export default function state(obj, parent) {
99
- if (targetProxy.has(obj)) return targetProxy.get(obj)
100
- if (proxyTarget.has(obj)) return obj // is proxy already
101
-
102
- let proxy = new Proxy(obj, handler)
103
- targetProxy.set(obj, proxy)
104
- proxyTarget.set(proxy, obj)
105
-
106
- // bind all getters & functions here to proxy
107
- // FIXME: alternatively we can store getters somewhere
108
- let descriptors = Object.getOwnPropertyDescriptors(obj)
109
- for (let name in descriptors) {
110
- let desc = descriptors[name]
111
- if (desc.get) {
112
- if (desc.get) desc.get = desc.get.bind(proxy), Object.defineProperty(obj, name, desc)
113
- }
114
- // else if (typeof desc.value === 'function') {
115
- // desc.value = desc.value.bind(proxy), Object.defineProperty(obj, name, desc)
116
- // }
117
- }
118
-
119
- // inherit from parent state
120
- obj[_parent] = parent ? state(parent) : sandbox
121
-
122
- return proxy
123
- }
124
-
125
- const fx = (fn) => {
126
- const call = () => {
127
- let prev = currentFx
128
- currentFx = call
129
- fn()
130
- currentFx = prev
131
- }
132
-
133
- // collect deps from the first call
134
- call()
135
-
136
- return call
137
- }
138
-
139
- const planUpdate = () => {
140
- // if (!pendingUpdate) {
141
- // pendingUpdate = true
142
- // queueMicrotask(() => {
143
- let fx
144
- while (batch.length) fx = batch.shift(), fx && (fx(), fx.planned = null)
145
- // pendingUpdate = false
146
- // })
147
- // }
148
- }
149
-
150
- export { fx as effect };
@@ -1,151 +0,0 @@
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'), _change = Symbol('length');
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
-
37
- // ignore existing state as argument
38
- if (values[_signals] && !parent) return values;
39
- const initSignals = values[_signals]
40
-
41
- // .length signal is stored outside, since it cannot be replaced
42
- // _len stores total values length (for objects as well)
43
- const isArr = Array.isArray(values), _len = signal(isArr ? values.length : Object.values(values).length),
44
- // dict with signals storing values
45
- signals = parent ? Object.create((parent = createState(parent))[_signals]) : Array.isArray(values) ? [] : {},
46
- proto = signals.constructor.prototype;
47
-
48
- if (parent) for (let key in parent) parent[key] // touch parent keys
49
-
50
- // proxy conducts prop access to signals
51
- const state = new Proxy(values, {
52
- // sandbox everything
53
- has() { return true },
54
-
55
- get(values, key) {
56
- // if .length is read within .push/etc - peek signal (don't subscribe)
57
- if (isArr)
58
- if (key === 'length') return (proto[lastProp]) ? _len.peek() : _len.value;
59
- else lastProp = key;
60
- if (proto[key]) return proto[key]
61
- if (key === _signals) return signals
62
- if (key === _change) return _len.value
63
- const s = signals[key] || initSignal(key)
64
- if (s) return s.value // existing property
65
- return sandbox[key] // Array, window etc
66
- },
67
-
68
- set(values, key, v) {
69
- if (isArr) {
70
- // .length
71
- if (key === 'length') {
72
- batch(() => {
73
- // force cleaning up tail
74
- for (let i = v, l = signals.length; i < l; i++) delete state[i]
75
- _len.value = signals.length = values.length = v;
76
- })
77
- return true
78
- }
79
- }
80
-
81
- let newProp = false
82
- const s = signals[key] || initSignal(key, v) || (newProp = true, signal())
83
- const cur = s.peek()
84
-
85
- // skip unchanged (although can be handled by last condition - we skip a few checks this way)
86
- if (v === cur);
87
- // stashed _set for values with getter/setter
88
- else if (s._set) s._set(v)
89
- // patch array
90
- else if (Array.isArray(v) && Array.isArray(cur)) {
91
- untracked(() => batch(() => {
92
- let i = 0, l = v.length, vals = values[key];
93
- for (; i < l; i++) cur[i] = vals[i] = v[i]
94
- cur.length = l // forces deleting tail signals
95
- }))
96
- }
97
- // .x = y
98
- else {
99
- // reflect change in values
100
- s.value = createState(values[key] = v)
101
- }
102
-
103
- // force changing length, if eg. a=[]; a[1]=1 - need to come after setting the item
104
- if (isArr) {
105
- if (key >= _len.peek()) _len.value = signals.length = values.length = Number(key) + 1
106
- }
107
- // bump _change for object
108
- else if (newProp) {
109
- _len.value++
110
- }
111
-
112
- return true
113
- },
114
-
115
- deleteProperty(values, key) {
116
- const s = signals[key]
117
- if (s) {
118
- const { _del } = s
119
- delete s._del
120
- delete signals[key]
121
- _del?.()
122
- }
123
- delete values[key]
124
- if (!isArr) _len.value--
125
- return true
126
- }
127
- })
128
-
129
- // init signals placeholders (instead of ownKeys & getOwnPropertyDescriptor handlers)
130
- // if values are existing proxy (in case of extending parent) - take its signals instead of creating new ones
131
- for (let key in values) signals[key] = initSignals?.[key] ?? initSignal(key);
132
-
133
- // initialize signal for provided key
134
- function initSignal(key) {
135
- // init existing value
136
- if (values.hasOwnProperty(key)) {
137
- // create signal from descriptor
138
- const desc = Object.getOwnPropertyDescriptor(values, key)
139
- // getter turns into computed
140
- if (desc?.get) {
141
- // stash setter
142
- (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
143
- return signals[key]
144
- }
145
- // take over existing signal or create new signal
146
- return signals[key] = desc.value?.peek ? desc.value : signal(createState(desc.value))
147
- }
148
- }
149
-
150
- return state
151
- }
@@ -1,82 +0,0 @@
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
- }
package/src/util.js DELETED
@@ -1,13 +0,0 @@
1
-
2
- // reset stacktrace or plan for fastest next call
3
- // https://twitter.com/_developit/status/1634033380940455936
4
- export const queueMicrotask = Promise.prototype.then.bind(Promise.resolve());
5
-
6
- // pick first prop
7
- export function getFirstProperty(obj) {
8
- for (let key in obj) {
9
- if (obj.hasOwnProperty(key)) {
10
- return obj[key];
11
- }
12
- }
13
- }