@bquery/bquery 1.3.0 → 1.4.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/README.md +527 -501
- package/dist/{batch-4LAvfLE7.js → batch-x7b2eZST.js} +2 -2
- package/dist/{batch-4LAvfLE7.js.map → batch-x7b2eZST.js.map} +1 -1
- package/dist/component.es.mjs +1 -1
- package/dist/core/collection.d.ts +19 -3
- package/dist/core/collection.d.ts.map +1 -1
- package/dist/core/element.d.ts +23 -4
- package/dist/core/element.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/utils/function.d.ts +21 -4
- package/dist/core/utils/function.d.ts.map +1 -1
- package/dist/{core-COenAZjD.js → core-BhpuvPhy.js} +62 -37
- package/dist/core-BhpuvPhy.js.map +1 -0
- package/dist/core.es.mjs +174 -131
- package/dist/core.es.mjs.map +1 -1
- package/dist/full.es.mjs +7 -7
- package/dist/full.iife.js +2 -2
- package/dist/full.iife.js.map +1 -1
- package/dist/full.umd.js +2 -2
- package/dist/full.umd.js.map +1 -1
- package/dist/index.es.mjs +7 -7
- package/dist/motion.es.mjs.map +1 -1
- package/dist/{persisted-Dz_ryNuC.js → persisted-DHoi3uEs.js} +4 -4
- package/dist/{persisted-Dz_ryNuC.js.map → persisted-DHoi3uEs.js.map} +1 -1
- package/dist/platform/storage.d.ts.map +1 -1
- package/dist/platform.es.mjs +12 -7
- package/dist/platform.es.mjs.map +1 -1
- package/dist/reactive/core.d.ts +12 -0
- package/dist/reactive/core.d.ts.map +1 -1
- package/dist/reactive/effect.d.ts.map +1 -1
- package/dist/reactive/internals.d.ts +6 -0
- package/dist/reactive/internals.d.ts.map +1 -1
- package/dist/reactive.es.mjs +6 -6
- package/dist/router.es.mjs +1 -1
- package/dist/{sanitize-1FBEPAFH.js → sanitize-Cxvxa-DX.js} +50 -39
- package/dist/sanitize-Cxvxa-DX.js.map +1 -0
- package/dist/security/sanitize-core.d.ts.map +1 -1
- package/dist/security.es.mjs +2 -2
- package/dist/store.es.mjs +2 -2
- package/dist/type-guards-BdKlYYlS.js +32 -0
- package/dist/type-guards-BdKlYYlS.js.map +1 -0
- package/dist/untrack-DNnnqdlR.js +6 -0
- package/dist/{untrack-BuEQKH7_.js.map → untrack-DNnnqdlR.js.map} +1 -1
- package/dist/view/evaluate.d.ts.map +1 -1
- package/dist/view.es.mjs +157 -151
- package/dist/view.es.mjs.map +1 -1
- package/dist/{watch-CXyaBC_9.js → watch-DXXv3iAI.js} +3 -3
- package/dist/{watch-CXyaBC_9.js.map → watch-DXXv3iAI.js.map} +1 -1
- package/package.json +132 -132
- package/src/core/collection.ts +628 -588
- package/src/core/element.ts +774 -746
- package/src/core/index.ts +48 -47
- package/src/core/utils/function.ts +151 -110
- package/src/motion/animate.ts +113 -113
- package/src/motion/flip.ts +176 -176
- package/src/motion/scroll.ts +57 -57
- package/src/motion/spring.ts +150 -150
- package/src/motion/timeline.ts +246 -246
- package/src/motion/transition.ts +51 -51
- package/src/platform/storage.ts +215 -208
- package/src/reactive/core.ts +114 -93
- package/src/reactive/effect.ts +54 -43
- package/src/reactive/internals.ts +122 -105
- package/src/security/sanitize-core.ts +364 -343
- package/src/view/evaluate.ts +290 -274
- package/dist/core-COenAZjD.js.map +0 -1
- package/dist/sanitize-1FBEPAFH.js.map +0 -1
- package/dist/type-guards-DRma3-Kc.js +0 -16
- package/dist/type-guards-DRma3-Kc.js.map +0 -1
- package/dist/untrack-BuEQKH7_.js +0 -6
package/src/view/evaluate.ts
CHANGED
|
@@ -1,274 +1,290 @@
|
|
|
1
|
-
import { isComputed, isSignal, type Signal } from '../reactive/index';
|
|
2
|
-
import type { BindingContext } from './types';
|
|
3
|
-
|
|
4
|
-
/** Maximum number of cached expression functions before LRU eviction */
|
|
5
|
-
const MAX_CACHE_SIZE = 500;
|
|
6
|
-
|
|
7
|
-
/** Compiled function type for expression evaluation */
|
|
8
|
-
type CompiledFn = (ctx: BindingContext) => unknown;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Simple LRU cache for compiled expression functions.
|
|
12
|
-
* Uses Map's insertion order to track recency - accessed items are re-inserted.
|
|
13
|
-
* @internal
|
|
14
|
-
*/
|
|
15
|
-
class LRUCache {
|
|
16
|
-
private cache = new Map<string, CompiledFn>();
|
|
17
|
-
private maxSize: number;
|
|
18
|
-
|
|
19
|
-
constructor(maxSize: number) {
|
|
20
|
-
this.maxSize = maxSize;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get(key: string): CompiledFn | undefined {
|
|
24
|
-
const value = this.cache.get(key);
|
|
25
|
-
if (value !== undefined) {
|
|
26
|
-
// Move to end (most recently used) by re-inserting
|
|
27
|
-
this.cache.delete(key);
|
|
28
|
-
this.cache.set(key, value);
|
|
29
|
-
}
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
set(key: string, value: CompiledFn): void {
|
|
34
|
-
// Delete first if exists to update insertion order
|
|
35
|
-
if (this.cache.has(key)) {
|
|
36
|
-
this.cache.delete(key);
|
|
37
|
-
} else if (this.cache.size >= this.maxSize) {
|
|
38
|
-
// Evict oldest (first) entry
|
|
39
|
-
const oldest = this.cache.keys().next().value;
|
|
40
|
-
if (oldest !== undefined) {
|
|
41
|
-
this.cache.delete(oldest);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
this.cache.set(key, value);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
clear(): void {
|
|
48
|
-
this.cache.clear();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
get size(): number {
|
|
52
|
-
return this.cache.size;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** LRU cache for compiled evaluate functions, keyed by expression string */
|
|
57
|
-
const evaluateCache = new LRUCache(MAX_CACHE_SIZE);
|
|
58
|
-
|
|
59
|
-
/** LRU cache for compiled evaluateRaw functions, keyed by expression string */
|
|
60
|
-
const evaluateRawCache = new LRUCache(MAX_CACHE_SIZE);
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Clears all cached compiled expression functions.
|
|
64
|
-
* Call this when unmounting views or to free memory after heavy template usage.
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* ```ts
|
|
68
|
-
* import { clearExpressionCache } from 'bquery/view';
|
|
69
|
-
*
|
|
70
|
-
* // After destroying a view or when cleaning up
|
|
71
|
-
* clearExpressionCache();
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export const clearExpressionCache = (): void => {
|
|
75
|
-
evaluateCache.clear();
|
|
76
|
-
evaluateRawCache.clear();
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Creates a proxy that lazily unwraps signals/computed only when accessed.
|
|
81
|
-
* This avoids subscribing to signals that aren't referenced in the expression.
|
|
82
|
-
* @internal
|
|
83
|
-
*/
|
|
84
|
-
const createLazyContext = (context: BindingContext): BindingContext =>
|
|
85
|
-
new Proxy(context, {
|
|
86
|
-
get(target, prop: string | symbol) {
|
|
87
|
-
// Only handle string keys for BindingContext indexing
|
|
88
|
-
if (typeof prop !== 'string') {
|
|
89
|
-
return Reflect.get(target, prop);
|
|
90
|
-
}
|
|
91
|
-
const value = target[prop];
|
|
92
|
-
// Auto-unwrap signals/computed only when actually accessed
|
|
93
|
-
if (isSignal(value) || isComputed(value)) {
|
|
94
|
-
return (value as Signal<unknown>).value;
|
|
95
|
-
}
|
|
96
|
-
return value;
|
|
97
|
-
},
|
|
98
|
-
has(target, prop: string | symbol) {
|
|
99
|
-
// Required for `with` statement to resolve identifiers correctly
|
|
100
|
-
if (typeof prop !== 'string') {
|
|
101
|
-
return Reflect.has(target, prop);
|
|
102
|
-
}
|
|
103
|
-
return prop in target;
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Evaluates an expression in the given context using `new Function()`.
|
|
109
|
-
*
|
|
110
|
-
* Signals and computed values in the context are lazily unwrapped only when
|
|
111
|
-
* accessed by the expression, avoiding unnecessary subscriptions to unused values.
|
|
112
|
-
*
|
|
113
|
-
* @security **WARNING:** This function uses dynamic code execution via `new Function()`.
|
|
114
|
-
* - NEVER pass expressions derived from user input or untrusted sources
|
|
115
|
-
* - Expressions should only come from developer-controlled templates
|
|
116
|
-
* - Malicious expressions can access and exfiltrate context data
|
|
117
|
-
* - Consider this equivalent to `eval()` in terms of security implications
|
|
118
|
-
*
|
|
119
|
-
* @internal
|
|
120
|
-
*/
|
|
121
|
-
export const evaluate = <T = unknown>(expression: string, context: BindingContext): T => {
|
|
122
|
-
try {
|
|
123
|
-
// Create a proxy that lazily unwraps signals/computed on access
|
|
124
|
-
const lazyContext = createLazyContext(context);
|
|
125
|
-
|
|
126
|
-
// Use cached function or compile and cache a new one
|
|
127
|
-
let fn = evaluateCache.get(expression);
|
|
128
|
-
if (!fn) {
|
|
129
|
-
// Use `with` to enable direct property access from proxy scope.
|
|
130
|
-
// Note: `new Function()` runs in non-strict mode, so `with` is allowed.
|
|
131
|
-
fn = new Function('$ctx', `with($ctx) { return (${expression}); }`) as (
|
|
132
|
-
ctx: BindingContext
|
|
133
|
-
) => unknown;
|
|
134
|
-
evaluateCache.set(expression, fn);
|
|
135
|
-
}
|
|
136
|
-
return fn(lazyContext) as T;
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error(`bQuery view: Error evaluating "${expression}"`, error);
|
|
139
|
-
return undefined as T;
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Evaluates an expression and returns the raw value (for signal access).
|
|
145
|
-
*
|
|
146
|
-
* @security **WARNING:** Uses dynamic code execution. See {@link evaluate} for security notes.
|
|
147
|
-
* @internal
|
|
148
|
-
*/
|
|
149
|
-
export const evaluateRaw = <T = unknown>(expression: string, context: BindingContext): T => {
|
|
150
|
-
try {
|
|
151
|
-
// Use cached function or compile and cache a new one
|
|
152
|
-
let fn = evaluateRawCache.get(expression);
|
|
153
|
-
if (!fn) {
|
|
154
|
-
// Use `with` to enable direct property access from context scope.
|
|
155
|
-
// Unlike `evaluate`, we don't use a lazy proxy - values are accessed directly.
|
|
156
|
-
fn = new Function('$ctx', `with($ctx) { return (${expression}); }`) as (
|
|
157
|
-
ctx: BindingContext
|
|
158
|
-
) => unknown;
|
|
159
|
-
evaluateRawCache.set(expression, fn);
|
|
160
|
-
}
|
|
161
|
-
return fn(context) as T;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error(`bQuery view: Error evaluating "${expression}"`, error);
|
|
164
|
-
return undefined as T;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Parses object expression like "{ active: isActive, disabled: !enabled }".
|
|
170
|
-
* Handles nested structures like function calls, arrays, and template literals.
|
|
171
|
-
* @internal
|
|
172
|
-
*/
|
|
173
|
-
export const parseObjectExpression = (expression: string): Record<string, string> => {
|
|
174
|
-
const result: Record<string, string> = {};
|
|
175
|
-
|
|
176
|
-
// Remove outer braces and trim
|
|
177
|
-
const inner = expression
|
|
178
|
-
.trim()
|
|
179
|
-
.replace(/^\{|\}$/g, '')
|
|
180
|
-
.trim();
|
|
181
|
-
if (!inner) return result;
|
|
182
|
-
|
|
183
|
-
// Split by comma at depth 0, respecting strings and nesting
|
|
184
|
-
const parts: string[] = [];
|
|
185
|
-
let current = '';
|
|
186
|
-
let depth = 0;
|
|
187
|
-
let inString: string | null = null;
|
|
188
|
-
|
|
189
|
-
for (let i = 0; i < inner.length; i++) {
|
|
190
|
-
const char = inner[i];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
//
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
current += char;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
1
|
+
import { isComputed, isSignal, type Signal } from '../reactive/index';
|
|
2
|
+
import type { BindingContext } from './types';
|
|
3
|
+
|
|
4
|
+
/** Maximum number of cached expression functions before LRU eviction */
|
|
5
|
+
const MAX_CACHE_SIZE = 500;
|
|
6
|
+
|
|
7
|
+
/** Compiled function type for expression evaluation */
|
|
8
|
+
type CompiledFn = (ctx: BindingContext) => unknown;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Simple LRU cache for compiled expression functions.
|
|
12
|
+
* Uses Map's insertion order to track recency - accessed items are re-inserted.
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
class LRUCache {
|
|
16
|
+
private cache = new Map<string, CompiledFn>();
|
|
17
|
+
private maxSize: number;
|
|
18
|
+
|
|
19
|
+
constructor(maxSize: number) {
|
|
20
|
+
this.maxSize = maxSize;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get(key: string): CompiledFn | undefined {
|
|
24
|
+
const value = this.cache.get(key);
|
|
25
|
+
if (value !== undefined) {
|
|
26
|
+
// Move to end (most recently used) by re-inserting
|
|
27
|
+
this.cache.delete(key);
|
|
28
|
+
this.cache.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set(key: string, value: CompiledFn): void {
|
|
34
|
+
// Delete first if exists to update insertion order
|
|
35
|
+
if (this.cache.has(key)) {
|
|
36
|
+
this.cache.delete(key);
|
|
37
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
38
|
+
// Evict oldest (first) entry
|
|
39
|
+
const oldest = this.cache.keys().next().value;
|
|
40
|
+
if (oldest !== undefined) {
|
|
41
|
+
this.cache.delete(oldest);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
this.cache.set(key, value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
clear(): void {
|
|
48
|
+
this.cache.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get size(): number {
|
|
52
|
+
return this.cache.size;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** LRU cache for compiled evaluate functions, keyed by expression string */
|
|
57
|
+
const evaluateCache = new LRUCache(MAX_CACHE_SIZE);
|
|
58
|
+
|
|
59
|
+
/** LRU cache for compiled evaluateRaw functions, keyed by expression string */
|
|
60
|
+
const evaluateRawCache = new LRUCache(MAX_CACHE_SIZE);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Clears all cached compiled expression functions.
|
|
64
|
+
* Call this when unmounting views or to free memory after heavy template usage.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* import { clearExpressionCache } from 'bquery/view';
|
|
69
|
+
*
|
|
70
|
+
* // After destroying a view or when cleaning up
|
|
71
|
+
* clearExpressionCache();
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export const clearExpressionCache = (): void => {
|
|
75
|
+
evaluateCache.clear();
|
|
76
|
+
evaluateRawCache.clear();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a proxy that lazily unwraps signals/computed only when accessed.
|
|
81
|
+
* This avoids subscribing to signals that aren't referenced in the expression.
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
const createLazyContext = (context: BindingContext): BindingContext =>
|
|
85
|
+
new Proxy(context, {
|
|
86
|
+
get(target, prop: string | symbol) {
|
|
87
|
+
// Only handle string keys for BindingContext indexing
|
|
88
|
+
if (typeof prop !== 'string') {
|
|
89
|
+
return Reflect.get(target, prop);
|
|
90
|
+
}
|
|
91
|
+
const value = target[prop];
|
|
92
|
+
// Auto-unwrap signals/computed only when actually accessed
|
|
93
|
+
if (isSignal(value) || isComputed(value)) {
|
|
94
|
+
return (value as Signal<unknown>).value;
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
},
|
|
98
|
+
has(target, prop: string | symbol) {
|
|
99
|
+
// Required for `with` statement to resolve identifiers correctly
|
|
100
|
+
if (typeof prop !== 'string') {
|
|
101
|
+
return Reflect.has(target, prop);
|
|
102
|
+
}
|
|
103
|
+
return prop in target;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Evaluates an expression in the given context using `new Function()`.
|
|
109
|
+
*
|
|
110
|
+
* Signals and computed values in the context are lazily unwrapped only when
|
|
111
|
+
* accessed by the expression, avoiding unnecessary subscriptions to unused values.
|
|
112
|
+
*
|
|
113
|
+
* @security **WARNING:** This function uses dynamic code execution via `new Function()`.
|
|
114
|
+
* - NEVER pass expressions derived from user input or untrusted sources
|
|
115
|
+
* - Expressions should only come from developer-controlled templates
|
|
116
|
+
* - Malicious expressions can access and exfiltrate context data
|
|
117
|
+
* - Consider this equivalent to `eval()` in terms of security implications
|
|
118
|
+
*
|
|
119
|
+
* @internal
|
|
120
|
+
*/
|
|
121
|
+
export const evaluate = <T = unknown>(expression: string, context: BindingContext): T => {
|
|
122
|
+
try {
|
|
123
|
+
// Create a proxy that lazily unwraps signals/computed on access
|
|
124
|
+
const lazyContext = createLazyContext(context);
|
|
125
|
+
|
|
126
|
+
// Use cached function or compile and cache a new one
|
|
127
|
+
let fn = evaluateCache.get(expression);
|
|
128
|
+
if (!fn) {
|
|
129
|
+
// Use `with` to enable direct property access from proxy scope.
|
|
130
|
+
// Note: `new Function()` runs in non-strict mode, so `with` is allowed.
|
|
131
|
+
fn = new Function('$ctx', `with($ctx) { return (${expression}); }`) as (
|
|
132
|
+
ctx: BindingContext
|
|
133
|
+
) => unknown;
|
|
134
|
+
evaluateCache.set(expression, fn);
|
|
135
|
+
}
|
|
136
|
+
return fn(lazyContext) as T;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(`bQuery view: Error evaluating "${expression}"`, error);
|
|
139
|
+
return undefined as T;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Evaluates an expression and returns the raw value (for signal access).
|
|
145
|
+
*
|
|
146
|
+
* @security **WARNING:** Uses dynamic code execution. See {@link evaluate} for security notes.
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
export const evaluateRaw = <T = unknown>(expression: string, context: BindingContext): T => {
|
|
150
|
+
try {
|
|
151
|
+
// Use cached function or compile and cache a new one
|
|
152
|
+
let fn = evaluateRawCache.get(expression);
|
|
153
|
+
if (!fn) {
|
|
154
|
+
// Use `with` to enable direct property access from context scope.
|
|
155
|
+
// Unlike `evaluate`, we don't use a lazy proxy - values are accessed directly.
|
|
156
|
+
fn = new Function('$ctx', `with($ctx) { return (${expression}); }`) as (
|
|
157
|
+
ctx: BindingContext
|
|
158
|
+
) => unknown;
|
|
159
|
+
evaluateRawCache.set(expression, fn);
|
|
160
|
+
}
|
|
161
|
+
return fn(context) as T;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error(`bQuery view: Error evaluating "${expression}"`, error);
|
|
164
|
+
return undefined as T;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Parses object expression like "{ active: isActive, disabled: !enabled }".
|
|
170
|
+
* Handles nested structures like function calls, arrays, and template literals.
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
export const parseObjectExpression = (expression: string): Record<string, string> => {
|
|
174
|
+
const result: Record<string, string> = {};
|
|
175
|
+
|
|
176
|
+
// Remove outer braces and trim
|
|
177
|
+
const inner = expression
|
|
178
|
+
.trim()
|
|
179
|
+
.replace(/^\{|\}$/g, '')
|
|
180
|
+
.trim();
|
|
181
|
+
if (!inner) return result;
|
|
182
|
+
|
|
183
|
+
// Split by comma at depth 0, respecting strings and nesting
|
|
184
|
+
const parts: string[] = [];
|
|
185
|
+
let current = '';
|
|
186
|
+
let depth = 0;
|
|
187
|
+
let inString: string | null = null;
|
|
188
|
+
|
|
189
|
+
for (let i = 0; i < inner.length; i++) {
|
|
190
|
+
const char = inner[i];
|
|
191
|
+
|
|
192
|
+
// Handle string literals: count consecutive backslashes before a quote
|
|
193
|
+
// to correctly distinguish escaped quotes from end-of-string
|
|
194
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
195
|
+
let backslashCount = 0;
|
|
196
|
+
let j = i - 1;
|
|
197
|
+
while (j >= 0 && inner[j] === '\\') {
|
|
198
|
+
backslashCount++;
|
|
199
|
+
j--;
|
|
200
|
+
}
|
|
201
|
+
// Quote is escaped only if preceded by an odd number of backslashes
|
|
202
|
+
if (backslashCount % 2 === 0) {
|
|
203
|
+
if (inString === null) {
|
|
204
|
+
inString = char;
|
|
205
|
+
} else if (inString === char) {
|
|
206
|
+
inString = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
current += char;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Skip if inside string
|
|
214
|
+
if (inString !== null) {
|
|
215
|
+
current += char;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Track nesting depth for parentheses, brackets, and braces
|
|
220
|
+
if (char === '(' || char === '[' || char === '{') {
|
|
221
|
+
depth++;
|
|
222
|
+
current += char;
|
|
223
|
+
} else if (char === ')' || char === ']' || char === '}') {
|
|
224
|
+
depth--;
|
|
225
|
+
current += char;
|
|
226
|
+
} else if (char === ',' && depth === 0) {
|
|
227
|
+
// Top-level comma - split point
|
|
228
|
+
parts.push(current.trim());
|
|
229
|
+
current = '';
|
|
230
|
+
} else {
|
|
231
|
+
current += char;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Add the last part
|
|
236
|
+
if (current.trim()) {
|
|
237
|
+
parts.push(current.trim());
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Parse each part to extract key and value
|
|
241
|
+
for (const part of parts) {
|
|
242
|
+
// Find the first colon at depth 0 (to handle ternary operators in values)
|
|
243
|
+
let colonIndex = -1;
|
|
244
|
+
let partDepth = 0;
|
|
245
|
+
let partInString: string | null = null;
|
|
246
|
+
|
|
247
|
+
for (let i = 0; i < part.length; i++) {
|
|
248
|
+
const char = part[i];
|
|
249
|
+
|
|
250
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
251
|
+
let backslashCount = 0;
|
|
252
|
+
let j = i - 1;
|
|
253
|
+
while (j >= 0 && part[j] === '\\') {
|
|
254
|
+
backslashCount++;
|
|
255
|
+
j--;
|
|
256
|
+
}
|
|
257
|
+
if (backslashCount % 2 === 0) {
|
|
258
|
+
if (partInString === null) {
|
|
259
|
+
partInString = char;
|
|
260
|
+
} else if (partInString === char) {
|
|
261
|
+
partInString = null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (partInString !== null) continue;
|
|
268
|
+
|
|
269
|
+
if (char === '(' || char === '[' || char === '{') {
|
|
270
|
+
partDepth++;
|
|
271
|
+
} else if (char === ')' || char === ']' || char === '}') {
|
|
272
|
+
partDepth--;
|
|
273
|
+
} else if (char === ':' && partDepth === 0) {
|
|
274
|
+
colonIndex = i;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (colonIndex > -1) {
|
|
280
|
+
const key = part
|
|
281
|
+
.slice(0, colonIndex)
|
|
282
|
+
.trim()
|
|
283
|
+
.replace(/^['"]|['"]$/g, '');
|
|
284
|
+
const value = part.slice(colonIndex + 1).trim();
|
|
285
|
+
result[key] = value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return result;
|
|
290
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core-COenAZjD.js","sources":["../src/reactive/internals.ts","../src/reactive/computed.ts","../src/reactive/core.ts"],"sourcesContent":["/**\n * Internal reactive plumbing shared across primitives.\n * @internal\n */\n\nexport type Observer = () => void;\nexport type CleanupFn = () => void;\n\n/**\n * Interface for reactive sources (Signals, Computed) that can unsubscribe observers.\n * @internal\n */\nexport interface ReactiveSource {\n unsubscribe(observer: Observer): void;\n}\n\nconst observerStack: Observer[] = [];\nlet batchDepth = 0;\nconst pendingObservers = new Set<Observer>();\n\n// Track dependencies for each observer to enable cleanup\nconst observerDependencies = new WeakMap<Observer, Set<ReactiveSource>>();\n\nexport const track = <T>(observer: Observer, fn: () => T): T => {\n observerStack.push(observer);\n try {\n return fn();\n } finally {\n observerStack.pop();\n }\n};\n\nexport const getCurrentObserver = (): Observer | undefined =>\n observerStack[observerStack.length - 1];\n\n/**\n * Executes a function without exposing the current observer to dependencies.\n * Unlike disabling tracking globally, this still allows nested reactive internals\n * (e.g., computed recomputation) to track their own dependencies.\n * @internal\n */\nexport const withoutCurrentObserver = <T>(fn: () => T): T => {\n // Push undefined to temporarily \"hide\" the current observer\n // This way, Signal.value reads won't link to the previous observer,\n // but nested track() calls (e.g., computed recompute) still work normally.\n observerStack.push(undefined as unknown as Observer);\n try {\n return fn();\n } finally {\n observerStack.pop();\n }\n};\n\nexport const scheduleObserver = (observer: Observer): void => {\n if (batchDepth > 0) {\n pendingObservers.add(observer);\n return;\n }\n observer();\n};\n\nconst flushObservers = (): void => {\n for (const observer of Array.from(pendingObservers)) {\n pendingObservers.delete(observer);\n observer();\n }\n};\n\nexport const beginBatch = (): void => {\n batchDepth += 1;\n};\n\nexport const endBatch = (): void => {\n batchDepth -= 1;\n if (batchDepth === 0) {\n flushObservers();\n }\n};\n\n/**\n * Registers a dependency between an observer and a reactive source.\n * @internal\n */\nexport const registerDependency = (observer: Observer, source: ReactiveSource): void => {\n let deps = observerDependencies.get(observer);\n if (!deps) {\n deps = new Set();\n observerDependencies.set(observer, deps);\n }\n deps.add(source);\n};\n\n/**\n * Clears all dependencies for an observer, unsubscribing from all sources.\n * @internal\n */\nexport const clearDependencies = (observer: Observer): void => {\n const deps = observerDependencies.get(observer);\n if (deps) {\n for (const source of deps) {\n source.unsubscribe(observer);\n }\n deps.clear();\n }\n};\n","/**\n * Computed reactive values.\n */\n\nimport {\n clearDependencies,\n getCurrentObserver,\n registerDependency,\n scheduleObserver,\n track,\n type ReactiveSource,\n} from './internals';\n\n/**\n * A computed value that derives from other reactive sources.\n *\n * Computed values are lazily evaluated and cached. They only\n * recompute when their dependencies change.\n *\n * @template T - The type of the computed value\n */\nexport class Computed<T> implements ReactiveSource {\n private cachedValue!: T;\n private dirty = true;\n private subscribers = new Set<() => void>();\n private readonly markDirty = () => {\n this.dirty = true;\n // Create snapshot to avoid issues with subscribers modifying the set during iteration\n const subscribersSnapshot = Array.from(this.subscribers);\n for (const subscriber of subscribersSnapshot) {\n scheduleObserver(subscriber);\n }\n };\n\n /**\n * Creates a new computed value.\n * @param compute - Function that computes the value\n */\n constructor(private readonly compute: () => T) {}\n\n /**\n * Gets the computed value, recomputing if dependencies changed.\n * During untrack calls, getCurrentObserver returns undefined, preventing dependency tracking.\n */\n get value(): T {\n const current = getCurrentObserver();\n if (current) {\n this.subscribers.add(current);\n registerDependency(current, this);\n }\n if (this.dirty) {\n this.dirty = false;\n // Clear old dependencies before recomputing\n clearDependencies(this.markDirty);\n this.cachedValue = track(this.markDirty, this.compute);\n }\n return this.cachedValue;\n }\n\n /**\n * Reads the current computed value without tracking.\n * Useful when you need the value but don't want to create a dependency.\n *\n * @returns The current cached value (recomputes if dirty)\n */\n peek(): T {\n if (this.dirty) {\n this.dirty = false;\n // Clear old dependencies before recomputing\n clearDependencies(this.markDirty);\n this.cachedValue = track(this.markDirty, this.compute);\n }\n return this.cachedValue;\n }\n\n /**\n * Removes an observer from this computed's subscriber set.\n * @internal\n */\n unsubscribe(observer: () => void): void {\n this.subscribers.delete(observer);\n }\n}\n\n/**\n * Creates a new computed value.\n *\n * @template T - The type of the computed value\n * @param fn - Function that computes the value from reactive sources\n * @returns A new Computed instance\n */\nexport const computed = <T>(fn: () => T): Computed<T> => new Computed(fn);\n","/**\n * Core reactive signals.\n */\n\nimport {\n getCurrentObserver,\n registerDependency,\n scheduleObserver,\n type ReactiveSource,\n} from './internals';\n\n/**\n * A reactive value container that notifies subscribers on change.\n *\n * Signals are the foundational primitive of the reactive system.\n * Reading a signal's value inside an effect or computed automatically\n * establishes a reactive dependency.\n *\n * @template T - The type of the stored value\n */\nexport class Signal<T> implements ReactiveSource {\n private subscribers = new Set<() => void>();\n\n /**\n * Creates a new signal with an initial value.\n * @param _value - The initial value\n */\n constructor(private _value: T) {}\n\n /**\n * Gets the current value and tracks the read if inside an observer.\n * During untrack calls, getCurrentObserver returns undefined, preventing dependency tracking.\n */\n get value(): T {\n const current = getCurrentObserver();\n if (current) {\n this.subscribers.add(current);\n registerDependency(current, this);\n }\n return this._value;\n }\n\n /**\n * Sets a new value and notifies all subscribers if the value changed.\n * Uses Object.is for equality comparison.\n */\n set value(next: T) {\n if (Object.is(this._value, next)) return;\n this._value = next;\n // Create snapshot to avoid issues with subscribers modifying the set during iteration\n const subscribersSnapshot = Array.from(this.subscribers);\n for (const subscriber of subscribersSnapshot) {\n scheduleObserver(subscriber);\n }\n }\n\n /**\n * Reads the current value without tracking.\n * Useful when you need the value but don't want to create a dependency.\n *\n * @returns The current value\n */\n peek(): T {\n return this._value;\n }\n\n /**\n * Updates the value using a function.\n * Useful for updates based on the current value.\n *\n * @param updater - Function that receives current value and returns new value\n */\n update(updater: (current: T) => T): void {\n this.value = updater(this._value);\n }\n\n /**\n * Removes an observer from this signal's subscriber set.\n * @internal\n */\n unsubscribe(observer: () => void): void {\n this.subscribers.delete(observer);\n }\n}\n\n/**\n * Creates a new reactive signal.\n *\n * @template T - The type of the signal value\n * @param value - The initial value\n * @returns A new Signal instance\n */\nexport const signal = <T>(value: T): Signal<T> => new Signal(value);\n"],"names":["observerStack","batchDepth","pendingObservers","observerDependencies","track","observer","fn","getCurrentObserver","withoutCurrentObserver","scheduleObserver","flushObservers","beginBatch","endBatch","registerDependency","source","deps","clearDependencies","Computed","compute","subscribersSnapshot","subscriber","current","computed","Signal","_value","next","updater","signal","value"],"mappings":"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"}
|