@solid-primitives/immutable
Advanced tools
Comparing version 1.0.12 to 1.1.0
@@ -1,10 +0,9 @@ | ||
import { Accessor } from 'solid-js'; | ||
import { ReconcileOptions } from 'solid-js/store'; | ||
type ImmutablePrimitive = string | number | boolean | null | undefined | object; | ||
type ImmutableObject = { | ||
import { Accessor } from "solid-js"; | ||
import { ReconcileOptions } from "solid-js/store"; | ||
export type ImmutablePrimitive = string | number | boolean | null | undefined | object; | ||
export type ImmutableObject = { | ||
[key: string]: ImmutableValue; | ||
}; | ||
type ImmutableArray = ImmutableValue[]; | ||
type ImmutableValue = ImmutablePrimitive | ImmutableObject | ImmutableArray; | ||
export type ImmutableArray = ImmutableValue[]; | ||
export type ImmutableValue = ImmutablePrimitive | ImmutableObject | ImmutableArray; | ||
/** | ||
@@ -29,4 +28,2 @@ * Creates a deeply nested reactive object derived from the given immutable source. The source can be any signal that is updated in an immutable fashion. | ||
*/ | ||
declare function createImmutable<T extends object>(source: Accessor<T>, options?: ReconcileOptions): T; | ||
export { type ImmutableArray, type ImmutableObject, type ImmutablePrimitive, type ImmutableValue, createImmutable }; | ||
export declare function createImmutable<T extends object>(source: Accessor<T>, options?: ReconcileOptions): T; |
@@ -1,162 +0,190 @@ | ||
import { createMemo, untrack, $PROXY, $TRACK, runWithOwner, getOwner, onCleanup, createRoot, getListener } from 'solid-js'; | ||
import { $RAW } from 'solid-js/store'; | ||
import { noop, arrayEquals, trueFn } from '@solid-primitives/utils'; | ||
import { keyArray } from '@solid-primitives/keyed'; | ||
// src/index.ts | ||
var $NO_KEY = Symbol("no-key"); | ||
var ARRAY_EQUALS_OPTIONS = { equals: arrayEquals }; | ||
var isWrappable = (v) => !!v && (v.constructor === Object || Array.isArray(v)); | ||
var CommonTraps = class { | ||
constructor(s, c) { | ||
this.s = s; | ||
this.c = c; | ||
} | ||
o = getOwner(); | ||
has(target, property) { | ||
if (property === $RAW || property === $PROXY || property === $TRACK || property === "__proto__") | ||
return true; | ||
this.ownKeys(); | ||
return property in untrack(this.s); | ||
} | ||
#trackKeys; | ||
ownKeys() { | ||
if (!this.#trackKeys && getListener()) | ||
runWithOwner( | ||
this.o, | ||
() => this.#trackKeys = createMemo(() => Reflect.ownKeys(this.s()), [], ARRAY_EQUALS_OPTIONS) | ||
); | ||
return this.#trackKeys ? this.#trackKeys() : Reflect.ownKeys(this.s()); | ||
} | ||
getOwnPropertyDescriptor(target, property) { | ||
let desc = Reflect.getOwnPropertyDescriptor(target, property); | ||
if (desc) { | ||
if (desc.get) { | ||
desc.get = this.get.bind(this, target, property, this); | ||
delete desc.writable; | ||
} else { | ||
desc.value = this.get(target, property, this); | ||
} | ||
} else { | ||
desc = this.has(target, property) ? { | ||
enumerable: true, | ||
configurable: true, | ||
get: this.get.bind(this, target, property, this) | ||
} : void 0; | ||
import { createRoot, createMemo, untrack, getOwner, runWithOwner, $TRACK, $PROXY, onCleanup, getListener, } from "solid-js"; | ||
import { $RAW } from "solid-js/store"; | ||
import { trueFn, arrayEquals, noop } from "@solid-primitives/utils"; | ||
import { keyArray } from "@solid-primitives/keyed"; | ||
const $NO_KEY = Symbol("no-key"); | ||
const ARRAY_EQUALS_OPTIONS = { equals: arrayEquals }; | ||
const isWrappable = (v) => !!v && (v.constructor === Object || Array.isArray(v)); | ||
class CommonTraps { | ||
s; | ||
c; | ||
o = getOwner(); | ||
constructor(s, c) { | ||
this.s = s; | ||
this.c = c; | ||
} | ||
return desc; | ||
} | ||
set = trueFn; | ||
deleteProperty = trueFn; | ||
}; | ||
var ObjectTraps = class extends CommonTraps { | ||
#cache = /* @__PURE__ */ new Map(); | ||
constructor(source, config) { | ||
super(source, config); | ||
} | ||
get(target, property, receiver) { | ||
if (property === $RAW) return untrack(this.s); | ||
if (property === $PROXY || property === $TRACK) return receiver; | ||
if (property === Symbol.iterator) return void 0; | ||
if (property === this.c.key) return untrack(this.s)[this.c.key]; | ||
let cached = this.#cache.get(property); | ||
if (cached) return cached.get(); | ||
let valueAccessor = () => { | ||
const source = this.s(); | ||
return source ? source[property] : void 0; | ||
}; | ||
let memo = false; | ||
this.#cache.set( | ||
property, | ||
cached = new PropertyWrapper( | ||
() => { | ||
const v = valueAccessor(); | ||
if (!memo && isWrappable(v)) { | ||
runWithOwner(this.o, () => valueAccessor = createMemo(valueAccessor)); | ||
memo = true; | ||
return valueAccessor(); | ||
} | ||
return v; | ||
}, | ||
this.o, | ||
this.c | ||
) | ||
); | ||
return cached.get(); | ||
} | ||
}; | ||
var ArrayTraps = class extends CommonTraps { | ||
#trackLength; | ||
#trackItems; | ||
constructor(source, config) { | ||
super(source, config); | ||
this.#trackItems = createMemo( | ||
keyArray( | ||
source, | ||
(item, index) => isWrappable(item) ? config.key in item ? item[config.key] : config.merge ? index : item : index, | ||
(item) => new PropertyWrapper(item, getOwner(), config) | ||
), | ||
[], | ||
ARRAY_EQUALS_OPTIONS | ||
); | ||
this.#trackLength = createMemo(() => this.#trackItems().length, 0); | ||
} | ||
get(_, property, receiver) { | ||
if (property === $RAW) return untrack(this.s); | ||
if (property === $PROXY) return receiver; | ||
if (property === $TRACK) return this.#trackItems(), receiver; | ||
if (property === Symbol.iterator) return this.#trackItems(), untrack(this.s)[property]; | ||
if (property === "length") return this.#trackLength(); | ||
if (typeof property === "symbol") return this.s()[property]; | ||
if (property in Array.prototype) return Array.prototype[property].bind(receiver); | ||
const num = typeof property === "string" ? parseInt(property) : property; | ||
if (!Number.isInteger(num) || num < 0) return this.s()[property]; | ||
if (num >= untrack(this.#trackLength)) return this.#trackLength(), this.s()[num]; | ||
return untrack(this.#trackItems)[num].get(); | ||
} | ||
}; | ||
var PropertyWrapper = class { | ||
constructor(s, o, c) { | ||
this.s = s; | ||
this.o = o; | ||
this.c = c; | ||
runWithOwner(o, () => onCleanup(() => this.#dispose())); | ||
} | ||
#lastId; | ||
#prev; | ||
#dispose = noop; | ||
#memo; | ||
#calc() { | ||
const v = this.s(); | ||
if (!isWrappable(v)) { | ||
this.#lastId = void 0; | ||
this.#dispose(); | ||
return this.#prev = v; | ||
has(target, property) { | ||
if (property === $RAW || property === $PROXY || property === $TRACK || property === "__proto__") | ||
return true; | ||
this.ownKeys(); | ||
return property in untrack(this.s); | ||
} | ||
const id = v[this.c.key]; | ||
if (id === this.#lastId && isWrappable(this.#prev)) return this.#prev; | ||
this.#lastId = id; | ||
this.#dispose(); | ||
return createRoot( | ||
(_dispose) => (this.#dispose = _dispose, this.#prev = wrap(v, this.s, this.c)), | ||
this.o | ||
); | ||
} | ||
get() { | ||
if (!this.#memo && getListener()) | ||
runWithOwner(this.o, () => this.#memo = createMemo(() => this.#calc())); | ||
return this.#memo ? this.#memo() : this.#calc(); | ||
} | ||
}; | ||
var wrap = (initialValue, source, config) => Array.isArray(initialValue) ? new Proxy([], new ArrayTraps(source, config)) : new Proxy({}, new ObjectTraps(source, config)); | ||
function createImmutable(source, options = {}) { | ||
const memo = createMemo(source); | ||
return untrack( | ||
() => wrap(memo(), memo, { | ||
key: options.key === null ? $NO_KEY : options.key ?? "id", | ||
merge: options.merge | ||
}) | ||
); | ||
#trackKeys; | ||
ownKeys() { | ||
if (!this.#trackKeys && getListener()) | ||
runWithOwner(this.o, () => (this.#trackKeys = createMemo(() => Reflect.ownKeys(this.s()), [], ARRAY_EQUALS_OPTIONS))); | ||
return this.#trackKeys ? this.#trackKeys() : Reflect.ownKeys(this.s()); | ||
} | ||
getOwnPropertyDescriptor(target, property) { | ||
let desc = Reflect.getOwnPropertyDescriptor(target, property); | ||
if (desc) { | ||
if (desc.get) { | ||
desc.get = this.get.bind(this, target, property, this); | ||
delete desc.writable; | ||
} | ||
else { | ||
desc.value = this.get(target, property, this); | ||
} | ||
} | ||
else { | ||
desc = this.has(target, property) | ||
? { | ||
enumerable: true, | ||
configurable: true, | ||
get: this.get.bind(this, target, property, this), | ||
} | ||
: undefined; | ||
} | ||
return desc; | ||
} | ||
set = trueFn; | ||
deleteProperty = trueFn; | ||
} | ||
export { createImmutable }; | ||
class ObjectTraps extends CommonTraps { | ||
#cache = new Map(); | ||
constructor(source, config) { | ||
super(source, config); | ||
} | ||
get(target, property, receiver) { | ||
if (property === $RAW) | ||
return untrack(this.s); | ||
if (property === $PROXY || property === $TRACK) | ||
return receiver; | ||
if (property === Symbol.iterator) | ||
return undefined; | ||
if (property === this.c.key) | ||
return untrack(this.s)[this.c.key]; | ||
let cached = this.#cache.get(property); | ||
if (cached) | ||
return cached.get(); | ||
let valueAccessor = () => { | ||
const source = this.s(); | ||
return source ? source[property] : undefined; | ||
}; | ||
let memo = false; | ||
this.#cache.set(property, (cached = new PropertyWrapper(() => { | ||
const v = valueAccessor(); | ||
// memoize property access if it is an object limit traversal to one level | ||
if (!memo && isWrappable(v)) { | ||
runWithOwner(this.o, () => (valueAccessor = createMemo(valueAccessor))); | ||
memo = true; | ||
return valueAccessor(); | ||
} | ||
return v; | ||
}, this.o, this.c))); | ||
return cached.get(); | ||
} | ||
} | ||
class ArrayTraps extends CommonTraps { | ||
#trackLength; | ||
#trackItems; | ||
constructor(source, config) { | ||
super(source, config); | ||
this.#trackItems = createMemo(keyArray(source, (item, index) => isWrappable(item) | ||
? config.key in item | ||
? item[config.key] | ||
: config.merge | ||
? index | ||
: item | ||
: index, item => new PropertyWrapper(item, getOwner(), config)), [], ARRAY_EQUALS_OPTIONS); | ||
this.#trackLength = createMemo(() => this.#trackItems().length, 0); | ||
} | ||
get(_, property, receiver) { | ||
if (property === $RAW) | ||
return untrack(this.s); | ||
if (property === $PROXY) | ||
return receiver; | ||
if (property === $TRACK) | ||
return this.#trackItems(), receiver; | ||
if (property === Symbol.iterator) | ||
return this.#trackItems(), untrack(this.s)[property]; | ||
if (property === "length") | ||
return this.#trackLength(); | ||
if (typeof property === "symbol") | ||
return this.s()[property]; | ||
if (property in Array.prototype) | ||
return Array.prototype[property].bind(receiver); | ||
const num = typeof property === "string" ? parseInt(property) : property; | ||
// invalid index - treat as obj property | ||
if (!Number.isInteger(num) || num < 0) | ||
return this.s()[property]; | ||
// out of bounds | ||
if (num >= untrack(this.#trackLength)) | ||
return this.#trackLength(), this.s()[num]; | ||
// valid index | ||
return untrack(this.#trackItems)[num].get(); | ||
} | ||
} | ||
class PropertyWrapper { | ||
s; | ||
o; | ||
c; | ||
constructor(s, o, c) { | ||
this.s = s; | ||
this.o = o; | ||
this.c = c; | ||
runWithOwner(o, () => onCleanup(() => this.#dispose())); | ||
} | ||
#lastId; | ||
#prev; | ||
#dispose = noop; | ||
#memo; | ||
#calc() { | ||
const v = this.s(); | ||
if (!isWrappable(v)) { | ||
this.#lastId = undefined; | ||
this.#dispose(); | ||
return (this.#prev = v); | ||
} | ||
const id = v[this.c.key]; | ||
if (id === this.#lastId && isWrappable(this.#prev)) | ||
return this.#prev; | ||
this.#lastId = id; | ||
this.#dispose(); | ||
return createRoot(_dispose => ((this.#dispose = _dispose), (this.#prev = wrap(v, this.s, this.c))), this.o); | ||
} | ||
get() { | ||
if (!this.#memo && getListener()) | ||
runWithOwner(this.o, () => (this.#memo = createMemo(() => this.#calc()))); | ||
return this.#memo ? this.#memo() : this.#calc(); | ||
} | ||
} | ||
const wrap = (initialValue, source, config) => Array.isArray(initialValue) | ||
? new Proxy([], new ArrayTraps(source, config)) | ||
: new Proxy({}, new ObjectTraps(source, config)); | ||
/** | ||
* Creates a deeply nested reactive object derived from the given immutable source. The source can be any signal that is updated in an immutable fashion. | ||
* @param source reactive function returning an immutable object | ||
* @param options optional configuration | ||
* - `key` property name to use as unique identifier for objects when their reference changes | ||
* - `merge` controls how objects witohut a unique identifier are identified when reconciling an array. If `true` the index is used, otherwise the object reference itself is used. | ||
* @returns a reactive object derived from the given source | ||
* @example | ||
* ```ts | ||
* const [data, setData] = createSignal({ a: 1, b: 2 }); | ||
* const state = createImmutable(data); | ||
* const a = () => state().a; | ||
* const b = () => state().b; | ||
* createEffect(() => console.log(a(), b())); | ||
* // logs 1 2 | ||
* setData({ a: 2, b: 3 }); | ||
* // logs 2 3 | ||
* ``` | ||
*/ | ||
export function createImmutable(source, options = {}) { | ||
const memo = createMemo(source); | ||
return untrack(() => wrap(memo(), memo, { | ||
key: options.key === null ? $NO_KEY : (options.key ?? "id"), | ||
merge: options.merge, | ||
})); | ||
} |
{ | ||
"name": "@solid-primitives/immutable", | ||
"version": "1.0.12", | ||
"version": "1.1.0", | ||
"description": "Primitive for rectifying immutable values and dealing with immutability in Solid.", | ||
@@ -36,3 +36,2 @@ "author": "Damian Tarnawski <gthetarnav@gmail.com>", | ||
"type": "module", | ||
"main": "./dist/index.cjs", | ||
"module": "./dist/index.js", | ||
@@ -46,6 +45,2 @@ "types": "./dist/index.d.ts", | ||
"default": "./dist/index.js" | ||
}, | ||
"require": { | ||
"types": "./dist/index.d.cts", | ||
"default": "./dist/index.cjs" | ||
} | ||
@@ -58,4 +53,4 @@ }, | ||
"dependencies": { | ||
"@solid-primitives/keyed": "^1.4.0", | ||
"@solid-primitives/utils": "^6.2.3" | ||
"@solid-primitives/keyed": "^1.5.0", | ||
"@solid-primitives/utils": "^6.3.0" | ||
}, | ||
@@ -62,0 +57,0 @@ "devDependencies": { |
@@ -7,3 +7,2 @@ <p> | ||
[![turborepo](https://img.shields.io/badge/built%20with-turborepo-cc00ff.svg?style=for-the-badge&logo=turborepo)](https://turborepo.org/) | ||
[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/immutable?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/immutable) | ||
@@ -10,0 +9,0 @@ [![version](https://img.shields.io/npm/v/@solid-primitives/immutable?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/immutable) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
17111
5
218
174
1