@nerdalytics/beacon 1000.3.2 → 1000.3.3

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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![license:mit](https://flat.badgen.net/static/license/MIT/blue)](https://github.com/nerdalytics/beacon/blob/trunk/LICENSE)
6
6
  [![registry:npm:version](https://img.shields.io/npm/v/@nerdalytics/beacon.svg)](https://www.npmjs.com/package/@nerdalytics/beacon)
7
- [![Socket Badge](https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.0)](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.0)
7
+ [![Socket Badge](https://badge.socket.dev/npm/package/@nerdalytics/beacon/1000.3.3)](https://socket.dev/npm/package/@nerdalytics/beacon/overview/1000.3.3)
8
8
 
9
9
  [![tech:nodejs](https://img.shields.io/badge/Node%20js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://nodejs.org/)
10
10
  [![language:typescript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
@@ -4,10 +4,7 @@ interface WriteableState<T> {
4
4
  set(value: T): void;
5
5
  update(fn: (value: T) => T): void;
6
6
  }
7
- declare const STATE_ID: unique symbol;
8
- type State<T> = ReadOnlyState<T> & WriteableState<T> & {
9
- [STATE_ID]?: symbol;
10
- };
7
+ type State<T> = ReadOnlyState<T> & WriteableState<T>;
11
8
  declare const createState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => State<T>;
12
9
  declare const createEffect: (fn: () => void) => Unsubscribe;
13
10
  declare const executeBatch: <T>(fn: () => T) => T;
@@ -1 +1 @@
1
- let r=Symbol("STATE_ID"),f=null,o=new Set,s=!1,u=0,a=[],l=new Set,d=new WeakMap,c=new WeakMap,v=new WeakMap,i=new WeakMap,p=new Set(["__proto__","constructor","prototype"]),y=(e,t,r)=>{let n=e.get(t);return n||(n=r(),e.set(t,n)),n},w=()=>{if(!s){s=!0;try{for(;0<o.size;){var e,t=o;o=new Set;for(e of t)e()}}finally{s=!1}}},g=e=>{o.delete(e);var t=c.get(e);if(t){for(var r of t)r.delete(e);t.clear(),c.delete(e)}},h=e=>{g(e),l.delete(e),d.delete(e);var t=v.get(e),t=(t&&(t=i.get(t))&&t.delete(e),v.delete(e),i.get(e));if(t){for(var r of t)h(r);t.clear(),i.delete(e)}},S=(e,n=Object.is)=>{let a=e,l=new Set,i=Symbol(),t=()=>{var e=f;return e&&(l.add(e),y(c,e,()=>new Set).add(l),y(d,e,()=>new Set).add(i)),a};return t.set=e=>{if(!n(a,e)){var t=f;if(t)if(d.get(t)?.has(i)&&!v.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(a=e,0!==l.size){for(var r of l)o.add(r);0!==u||s||w()}}},t.update=e=>{t.set(e(a))},t[r]=i,t},b=r=>{let n=()=>{if(!l.has(n)){l.add(n);var e=f;try{g(n),f=n;var t=d.get(n);t?t.clear():d.set(n,new Set),e&&(v.set(n,e),y(i,e,()=>new Set).add(n)),r()}finally{f=e,l.delete(n)}}};var e;return 0===u?n():(f&&(e=f,v.set(n,e),y(i,e,()=>new Set).add(n)),a.push(n)),()=>{h(n)}};var e=e=>{u++;try{return e()}catch(e){throw 1===u&&(o.clear(),a.length=0),e}finally{if(0===--u){if(0<a.length){var t,e=a;a=[];for(t of e)t()}0<o.size&&!s&&w()}}},t=t=>{let r=void 0,n=!1,a=S(void 0);return b(function(){var e=t();n&&Object.is(r,e)||(r=e,a.set(e)),n=!0}),function(){return n||(r=t(),n=!0,a.set(r)),a()}},n=(t,r,n=Object.is)=>{let a=!1,l,i,f=S(void 0);return b(function(){var e=t();a&&Object.is(i,e)||(i=e,e=r(e),a&&void 0!==l&&n(l,e))||(l=e,f.set(e),a=!0)}),function(){return a||(i=t(),l=r(i),f.set(l),a=!0),f()}};let m=(e,t,r)=>{e=[...e];return e[t]=r,e},j=(e,t,r)=>{e={...e};return e[t]=r,e},N=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},O=(n,a,l,i)=>{if(l>=a.length)return i;if(null==n)return O({},a,l,i);var f=a[l];if(void 0===f)return n;if(Array.isArray(n)){var o=Number(f);if(l===a.length-1)return m(n,o,i);var s=[...n];let e=l+1,t=a[e],r=n[o];return null==r&&(r=void 0===t?{}:N(t)),s[o]=O(r,a,e,i),s}o=n;if(l===a.length-1)return j(o,f,i);let e=l+1,t=a[e],r=o[f];null==r&&(r=void 0===t?{}:N(t));s={...o};return s[f]=O(r,a,e,i),s};var _=(e,t)=>{let r=!1;let n=(()=>{let r=[],n=!1,a=new Proxy({},{get:(e,t)=>(n||"string"!=typeof t&&"number"!=typeof t||(p.has(String(t))?n=!0:r.push(t)),a)});try{t(a)}catch{}return n?[]:r})(),a=S(t(e())),l=a.set;return b(function(){if(!r){r=!0;try{a.set(t(e()))}finally{r=!1}}}),a.set=function(t){if(!r&&0!==n.length){r=!0;try{l(t),e.update(e=>O(e,n,0,t))}finally{r=!1}}},a.update=function(e){a.set(e(a()))},a};let k=e=>()=>e();var M=(e,t=Object.is)=>{let r=S(e,t);return[k(r),{set:e=>r.set(e),update:e=>r.update(e)}]};export{t as derive,b as effect,_ as lens,M as protectedState,k as readonlyState,n as select,S as state,e as batch};
1
+ let o=null,r=new Set,a=new Set,s=a,u=!1,c=0,n=[],f=new Set,i=new WeakMap,v=new WeakMap,d=new WeakMap,l=new WeakMap,w=new Set(["__proto__","constructor","prototype"]),p=(e,t,r)=>{let a=e.get(t);return a||(a=r(),e.set(t,a)),a},y=()=>{if(!u){u=!0;try{for(;0<s.size;){var e,t=s;s=t===a?r:a;for(e of t)e();t.clear()}}finally{u=!1}}},S=e=>{s.delete(e);var t=v.get(e);if(t){for(var r of t)r.delete(e);t.clear(),v.delete(e)}},g=e=>{S(e),f.delete(e),i.delete(e);var t=d.get(e),t=(t&&(t=l.get(t))&&t.delete(e),d.delete(e),l.get(e));if(t){for(var r of t)g(r);t.clear(),l.delete(e)}},h=(e,a=Object.is)=>{let n=e,f=new Set,l=Symbol(),t=()=>{var e=o;return e&&(f.add(e),p(v,e,()=>new Set).add(f),p(i,e,()=>new Set).add(l)),n};return t.set=e=>{if(!a(n,e)){var t=o;if(t)if(i.get(t)?.has(l)&&!d.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(n=e,0!==f.size){for(var r of f)s.add(r);0!==c||u||y()}}},t.update=e=>{t.set(e(n))},t},b=r=>{let a=()=>{if(!f.has(a)){f.add(a);var e=o;try{S(a),o=a;var t=i.get(a);t?t.clear():i.set(a,new Set),e&&(d.set(a,e),p(l,e,()=>new Set).add(a)),r()}finally{o=e,f.delete(a)}}};var e;return 0===c?a():(o&&(e=o,d.set(a,e),p(l,e,()=>new Set).add(a)),n.push(a)),()=>{g(a)}};var e=e=>{c++;try{return e()}catch(e){throw 1===c&&(s.clear(),n.length=0),e}finally{if(0===--c){if(0<n.length){var t,e=n;n=[];for(t of e)t()}0<s.size&&!u&&y()}}},t=r=>{let a=void 0,n=!1,f=new Set;return b(function(){var e=r();if(!n||!Object.is(a,e)){a=e;for(var t of f)s.add(t);0!==c||u||y()}n=!0}),function(){var e=o;return e&&(f.add(e),p(v,e,()=>new Set).add(f)),n||(a=r(),n=!0),a}},j=(r,a,n=Object.is)=>{let f=!1,l,i,d=new Set;return b(function(){var e=r();if(!f||!Object.is(i,e)){i=e;e=a(e);if(!f||void 0===l||!n(l,e)){l=e,f=!0;for(var t of d)s.add(t);0!==c||u||y()}}}),function(){var e=o;return e&&(d.add(e),p(v,e,()=>new Set).add(d)),f||(i=r(),l=a(i),f=!0),l}};let N=(e,t)=>null!=e?e:void 0===t||Number.isNaN(Number(t))?{}:[],O=(t,e,r,a)=>{if(r>=e.length)return a;if(null==t)return O({},e,r,a);var n=e[r];if(void 0===n)return t;var f=Array.isArray(t),n=f?Number(n):n;if(r===e.length-1){if(f)return(l=[...t])[n]=a,l;let e={...t};return e[n]=a,e}var l=r+1,r=e[l],i=t[n],i=N(i,r);if(f)return(r=[...t])[n]=O(i,e,l,a),r;let d={...t};return d[n]=O(i,e,l,a),d};var k=(e,t)=>{let r=!1;let a=(()=>{let r=[],a=!1,n=new Proxy({},{get:(e,t)=>(a||"string"!=typeof t||(w.has(String(t))?a=!0:r.push(t)),n)});try{t(n)}catch{}return a?[]:r})(),n=h(t(e())),f=n.set;return b(function(){if(!r){r=!0;try{n.set(t(e()))}finally{r=!1}}}),n.set=function(t){if(!r&&0!==a.length){r=!0;try{f(t),e.update(e=>O(e,a,0,t))}finally{r=!1}}},n.update=function(e){n.set(e(n()))},n};let m=e=>()=>e();var M=(e,t=Object.is)=>{let r=h(e,t);return[m(r),{set:e=>r.set(e),update:e=>r.update(e)}]};export{t as derive,b as effect,k as lens,M as protectedState,m as readonlyState,j as select,h as state,e as batch};
package/package.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "author": "Denny Trebbin (nerdalytics)",
3
3
  "description": "Reactive dependency graph runtime for Node.js backends. Tracks dependencies between signals and propagates updates automatically.",
4
4
  "devDependencies": {
5
- "@biomejs/biome": "2.3.14",
6
- "@types/node": "25.2.2",
7
- "npm-check-updates": "19.3.2",
8
- "typescript": "5.9.3",
5
+ "@biomejs/biome": "2.4.11",
6
+ "@types/node": "25.5.2",
7
+ "npm-check-updates": "20.0.0",
8
+ "typescript": "6.0.2",
9
9
  "uglify-js": "3.19.3"
10
10
  },
11
11
  "engines": {
@@ -32,32 +32,25 @@
32
32
  ],
33
33
  "keywords": [
34
34
  "backend",
35
- "batching",
36
- "computed-values",
37
- "dependency-tracking",
38
- "effects",
39
- "fine-grained",
40
- "lightweight",
41
- "memory-management",
35
+ "batch",
36
+ "dependency-graph",
37
+ "effect",
42
38
  "nodejs",
43
- "performance",
44
39
  "reactive",
45
- "server-side",
46
40
  "signals",
47
- "state-management",
48
- "typescript"
41
+ "state"
49
42
  ],
50
43
  "license": "MIT",
51
44
  "main": "dist/src/index.min.js",
52
45
  "name": "@nerdalytics/beacon",
53
- "packageManager": "npm@11.9.0",
46
+ "packageManager": "npm@11.12.1",
54
47
  "repository": {
55
48
  "type": "git",
56
49
  "url": "git+https://github.com/nerdalytics/beacon.git"
57
50
  },
58
51
  "scripts": {
59
52
  "benchmark": "node --expose-gc scripts/benchmark.ts -R 3",
60
- "benchmark:naiv": "node scripts/naiv-benchmark.ts",
53
+
61
54
  "build": "npm run build:lts",
62
55
  "build:lts": "tsc -p tsconfig.lts.json",
63
56
  "check": "npx @biomejs/biome check",
@@ -71,28 +64,13 @@
71
64
  "prepublishOnly": "npm run build:lts",
72
65
  "pretest:lts": "node scripts/run-lts-tests.js",
73
66
  "test": "node --test --test-skip-pattern=\"COMPONENT NAME\" tests/**/*.ts",
74
- "test:behavior": "node --test tests/infinite-loop.test.ts tests/cyclic-dependency.test.ts tests/cleanup.test.ts",
75
- "test:core": "node --test tests/*-core.test.ts",
76
67
  "test:coverage": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info tests/**/*.test.ts",
77
- "test:integration": "node --test tests/state-*.test.ts tests/batch-integration.test.ts",
78
68
  "test:lts:20": "node --test dist/tests/**.js",
79
69
  "test:lts:22": "node --test --test-skip-pattern=\"COMPONENT NAME\" dist/tests/**/*.js",
80
- "test:unit:batch": "node --test tests/batch.test.ts",
81
- "test:unit:cleanup": "node --test tests/cleanup.test.ts",
82
- "test:unit:custom-equality": "node --test tests/custom-equality.test.ts",
83
- "test:unit:cyclic-dependency": "node --test tests/cyclic-dependency.test.ts",
84
- "test:unit:deep-chain": "node --test tests/deep-chain.test.ts",
85
- "test:unit:derive": "node --test tests/derive.test.ts",
86
- "test:unit:effect": "node --test tests/effect.test.ts",
87
- "test:unit:infinite-loop": "node --test tests/infinite-loop.test.ts",
88
- "test:unit:lens": "node --test tests/lens.test.ts",
89
- "test:unit:select": "node --test tests/select.test.ts",
90
- "test:unit:state": "node --test tests/state.test.ts",
91
- "update-dependencies": "npx npm-check-updates --interactive --upgrade --removeRange",
92
- "update-performance-docs": "node --experimental-config-file=node.config.json scripts/update-performance-docs.ts"
70
+ "update-dependencies": "npx npm-check-updates --interactive --upgrade --removeRange"
93
71
  },
94
72
  "sideEffects": false,
95
73
  "type": "module",
96
74
  "types": "dist/src/index.d.ts",
97
- "version": "1000.3.2"
75
+ "version": "1000.3.3"
98
76
  }
package/src/index.ts CHANGED
@@ -7,17 +7,13 @@ interface WriteableState<T> {
7
7
  update(fn: (value: T) => T): void
8
8
  }
9
9
 
10
- // Special symbol used for internal tracking
11
- const STATE_ID: unique symbol = Symbol('STATE_ID')
12
-
13
- type State<T> = ReadOnlyState<T> &
14
- WriteableState<T> & {
15
- [STATE_ID]?: symbol
16
- }
10
+ type State<T> = ReadOnlyState<T> & WriteableState<T>
17
11
 
18
12
  // Module-level reactive state
19
13
  let currentSubscriber: Subscriber | null = null
20
- let pendingSubscribers: Set<Subscriber> = new Set<Subscriber>()
14
+ const flushing: Set<Subscriber> = new Set<Subscriber>()
15
+ const queued: Set<Subscriber> = new Set<Subscriber>()
16
+ let pendingSubscribers: Set<Subscriber> = queued
21
17
  let isNotifying = false
22
18
  let batchDepth = 0
23
19
  let deferredEffectCreations: Subscriber[] = []
@@ -54,11 +50,12 @@ const notifySubscribers = (): void => {
54
50
  try {
55
51
  while (pendingSubscribers.size > 0) {
56
52
  const subscribers = pendingSubscribers
57
- pendingSubscribers = new Set()
53
+ pendingSubscribers = subscribers === queued ? flushing : queued
58
54
 
59
55
  for (const effect of subscribers) {
60
56
  effect()
61
57
  }
58
+ subscribers.clear()
62
59
  }
63
60
  } finally {
64
61
  isNotifying = false
@@ -151,7 +148,6 @@ const createState = <T>(initialValue: T, equalityFn: (a: T, b: T) => boolean = O
151
148
  get.set(fn(value))
152
149
  }
153
150
 
154
- get[STATE_ID] = stateId
155
151
  return get as State<T>
156
152
  }
157
153
 
@@ -236,26 +232,37 @@ const executeBatch = <T>(fn: () => T): T => {
236
232
  const createDerive = <T>(computeFn: () => T): ReadOnlyState<T> => {
237
233
  let cachedValue: T = undefined as unknown as T
238
234
  let initialized = false
239
- const valueState = createState<T | undefined>(undefined)
235
+ const subscribers = new Set<Subscriber>()
240
236
 
241
237
  createEffect(function deriveEffect(): void {
242
238
  const newValue = computeFn()
243
239
 
244
240
  if (!(initialized && Object.is(cachedValue, newValue))) {
245
241
  cachedValue = newValue
246
- valueState.set(newValue)
242
+
243
+ for (const sub of subscribers) {
244
+ pendingSubscribers.add(sub)
245
+ }
246
+ if (batchDepth === 0 && !isNotifying) {
247
+ notifySubscribers()
248
+ }
247
249
  }
248
250
 
249
251
  initialized = true
250
252
  })
251
253
 
252
254
  return function deriveGetter(): T {
255
+ const currentEffect = currentSubscriber
256
+ if (currentEffect) {
257
+ subscribers.add(currentEffect)
258
+ getOrCreate(subscriberDependencies, currentEffect, () => new Set()).add(subscribers)
259
+ }
260
+
253
261
  if (!initialized) {
254
262
  cachedValue = computeFn()
255
263
  initialized = true
256
- valueState.set(cachedValue)
257
264
  }
258
- return valueState() as T
265
+ return cachedValue
259
266
  }
260
267
  }
261
268
 
@@ -267,7 +274,7 @@ const createSelect = <T, R>(
267
274
  let initialized = false
268
275
  let lastSelectedValue: R | undefined
269
276
  let lastSourceValue: T | undefined
270
- const valueState = createState<R | undefined>(undefined)
277
+ const subscribers = new Set<Subscriber>()
271
278
 
272
279
  createEffect(function selectEffect(): void {
273
280
  const sourceValue = source()
@@ -284,55 +291,45 @@ const createSelect = <T, R>(
284
291
  }
285
292
 
286
293
  lastSelectedValue = newSelectedValue
287
- valueState.set(newSelectedValue)
288
294
  initialized = true
295
+
296
+ for (const sub of subscribers) {
297
+ pendingSubscribers.add(sub)
298
+ }
299
+ if (batchDepth === 0 && !isNotifying) {
300
+ notifySubscribers()
301
+ }
289
302
  })
290
303
 
291
304
  return function selectGetter(): R {
305
+ const currentEffect = currentSubscriber
306
+ if (currentEffect) {
307
+ subscribers.add(currentEffect)
308
+ getOrCreate(subscriberDependencies, currentEffect, () => new Set()).add(subscribers)
309
+ }
310
+
292
311
  if (!initialized) {
293
312
  lastSourceValue = source()
294
313
  lastSelectedValue = selectorFn(lastSourceValue)
295
- valueState.set(lastSelectedValue)
296
314
  initialized = true
297
315
  }
298
- return valueState() as R
299
- }
300
- }
301
-
302
- // Helper for array updates
303
- const updateArrayItem = <V>(arr: unknown[], index: number, value: V): unknown[] => {
304
- const copy = [
305
- ...arr,
306
- ]
307
- copy[index] = value
308
- return copy
309
- }
310
-
311
- // Helper for single-level updates (optimization)
312
- const updateShallowProperty = <V>(
313
- obj: Record<string | number, unknown>,
314
- key: string | number,
315
- value: V
316
- ): Record<string | number, unknown> => {
317
- const result = {
318
- ...obj,
316
+ return lastSelectedValue as R
319
317
  }
320
- result[key] = value
321
- return result
322
318
  }
323
319
 
324
- // Helper to create the appropriate container type
325
- const createContainer = (key: string | number): Record<string | number, unknown> | unknown[] => {
326
- const isArrayKey = typeof key === 'number' || !Number.isNaN(Number(key))
327
- return isArrayKey ? [] : {}
320
+ // Returns the value if non-nullish, otherwise creates the appropriate container type
321
+ const ensureContainer = (value: unknown, nextKey: string | undefined): unknown => {
322
+ if (value != null) return value
323
+ if (nextKey !== undefined && !Number.isNaN(Number(nextKey))) return []
324
+ return {}
328
325
  }
329
326
 
330
- const setValueAtPath = <V, O>(obj: O, pathSegments: (string | number)[], depth: number, value: V): O => {
327
+ const setValueAtPath = <V, O>(obj: O, pathSegments: string[], depth: number, value: V): O => {
331
328
  if (depth >= pathSegments.length) {
332
329
  return value as unknown as O
333
330
  }
334
331
 
335
- if (obj === undefined || obj === null) {
332
+ if (obj == null) {
336
333
  return setValueAtPath({} as O, pathSegments, depth, value)
337
334
  }
338
335
 
@@ -341,60 +338,56 @@ const setValueAtPath = <V, O>(obj: O, pathSegments: (string | number)[], depth:
341
338
  return obj
342
339
  }
343
340
 
344
- if (Array.isArray(obj)) {
345
- const index = Number(currentKey)
341
+ const isArray = Array.isArray(obj)
342
+ const key = isArray ? Number(currentKey) : currentKey
346
343
 
347
- if (depth === pathSegments.length - 1) {
348
- return updateArrayItem(obj, index, value) as unknown as O
344
+ if (depth === pathSegments.length - 1) {
345
+ if (isArray) {
346
+ const copy = [
347
+ ...(obj as unknown[]),
348
+ ]
349
+ copy[key as number] = value
350
+ return copy as unknown as O
349
351
  }
350
-
351
- const copy = [
352
- ...obj,
353
- ]
354
- const nextDepth = depth + 1
355
- const nextKey = pathSegments[nextDepth]
356
-
357
- let nextValue = obj[index]
358
- if (nextValue === undefined || nextValue === null) {
359
- nextValue = nextKey === undefined ? {} : createContainer(nextKey)
352
+ const result = {
353
+ ...(obj as Record<string, unknown>),
360
354
  }
361
-
362
- copy[index] = setValueAtPath(nextValue, pathSegments, nextDepth, value)
363
- return copy as unknown as O
364
- }
365
-
366
- const record = obj as Record<string | number, unknown>
367
-
368
- if (depth === pathSegments.length - 1) {
369
- return updateShallowProperty(record, currentKey, value) as unknown as O
355
+ result[key] = value
356
+ return result as unknown as O
370
357
  }
371
358
 
372
359
  const nextDepth = depth + 1
373
360
  const nextKey = pathSegments[nextDepth]
361
+ const source = isArray ? (obj as unknown[])[key as number] : (obj as Record<string | number, unknown>)[key]
362
+
363
+ const nextValue = ensureContainer(source, nextKey)
374
364
 
375
- let currentValue = record[currentKey]
376
- if (currentValue === undefined || currentValue === null) {
377
- currentValue = nextKey === undefined ? {} : createContainer(nextKey)
365
+ if (isArray) {
366
+ const copy = [
367
+ ...(obj as unknown[]),
368
+ ]
369
+ copy[key as number] = setValueAtPath(nextValue, pathSegments, nextDepth, value)
370
+ return copy as unknown as O
378
371
  }
379
372
 
380
373
  const result = {
381
- ...record,
374
+ ...(obj as Record<string | number, unknown>),
382
375
  }
383
- result[currentKey] = setValueAtPath(currentValue, pathSegments, nextDepth, value)
376
+ result[key] = setValueAtPath(nextValue, pathSegments, nextDepth, value)
384
377
  return result as unknown as O
385
378
  }
386
379
 
387
380
  const createLens = <T, K>(source: State<T>, accessor: (state: T) => K): State<K> => {
388
381
  let isUpdating = false
389
382
 
390
- const extractPath = (): (string | number)[] => {
391
- const pathCollector: (string | number)[] = []
383
+ const extractPath = (): string[] => {
384
+ const pathCollector: string[] = []
392
385
  let tainted = false
393
386
  const proxy = new Proxy(
394
387
  {},
395
388
  {
396
389
  get: (_: object, prop: string | symbol): unknown => {
397
- if (!tainted && (typeof prop === 'string' || typeof prop === 'number')) {
390
+ if (!tainted && typeof prop === 'string') {
398
391
  if (DANGEROUS_KEYS.has(String(prop))) {
399
392
  tainted = true
400
393
  } else {