From 40c295638117c093728fef1980b01e81bbe41a9d Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 23 Jan 2024 20:21:38 +0100 Subject: [PATCH] chore: cleanup proxy files (#10268) - merge `readonly.js` into `proxy.js` and get rid of sub folder - extract types into `d.ts` file and properly document the properties - type tweaks --- packages/svelte/src/internal/client/each.js | 2 +- packages/svelte/src/internal/client/loop.js | 6 +- .../svelte/src/internal/client/private.d.ts | 8 -- .../src/internal/client/{proxy => }/proxy.js | 102 ++++++++++++++---- .../src/internal/client/proxy/readonly.js | 62 ----------- packages/svelte/src/internal/client/render.js | 2 +- .../svelte/src/internal/client/runtime.js | 9 +- .../svelte/src/internal/client/types.d.ts | 31 ++++++ packages/svelte/src/internal/index.js | 2 +- packages/svelte/src/motion/spring.js | 4 +- packages/svelte/src/motion/tweened.js | 4 +- 11 files changed, 127 insertions(+), 105 deletions(-) delete mode 100644 packages/svelte/src/internal/client/private.d.ts rename packages/svelte/src/internal/client/{proxy => }/proxy.js (73%) delete mode 100644 packages/svelte/src/internal/client/proxy/readonly.js diff --git a/packages/svelte/src/internal/client/each.js b/packages/svelte/src/internal/client/each.js index d098d9d38b..3974557ba7 100644 --- a/packages/svelte/src/internal/client/each.js +++ b/packages/svelte/src/internal/client/each.js @@ -14,7 +14,7 @@ import { set_current_hydration_fragment } from './hydration.js'; import { clear_text_content, map_get, map_set } from './operations.js'; -import { STATE_SYMBOL } from './proxy/proxy.js'; +import { STATE_SYMBOL } from './proxy.js'; import { insert, remove } from './reconciler.js'; import { empty } from './render.js'; import { diff --git a/packages/svelte/src/internal/client/loop.js b/packages/svelte/src/internal/client/loop.js index 8bae0a6198..b9511298c4 100644 --- a/packages/svelte/src/internal/client/loop.js +++ b/packages/svelte/src/internal/client/loop.js @@ -27,11 +27,11 @@ export function clear_loops() { /** * Creates a new task that runs on each raf frame * until it returns a falsy value or is aborted - * @param {import('./private.js').TaskCallback} callback - * @returns {import('./private.js').Task} + * @param {import('./types.js').TaskCallback} callback + * @returns {import('./types.js').Task} */ export function loop(callback) { - /** @type {import('./private.js').TaskEntry} */ + /** @type {import('./types.js').TaskEntry} */ let task; if (tasks.size === 0) raf.tick(run_tasks); return { diff --git a/packages/svelte/src/internal/client/private.d.ts b/packages/svelte/src/internal/client/private.d.ts deleted file mode 100644 index f678324286..0000000000 --- a/packages/svelte/src/internal/client/private.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type TaskCallback = (now: number) => boolean | void; - -export type TaskEntry = { c: TaskCallback; f: () => void }; - -export interface Task { - abort(): void; - promise: Promise; -} diff --git a/packages/svelte/src/internal/client/proxy/proxy.js b/packages/svelte/src/internal/client/proxy.js similarity index 73% rename from packages/svelte/src/internal/client/proxy/proxy.js rename to packages/svelte/src/internal/client/proxy.js index f55663d5fe..19806a8727 100644 --- a/packages/svelte/src/internal/client/proxy/proxy.js +++ b/packages/svelte/src/internal/client/proxy.js @@ -9,7 +9,7 @@ import { UNINITIALIZED, mutable_source, batch_inspect -} from '../runtime.js'; +} from './runtime.js'; import { array_prototype, define_property, @@ -20,36 +20,40 @@ import { is_frozen, object_keys, object_prototype -} from '../utils.js'; - -/** @typedef {{ s: Map>; v: import('../types.js').SourceSignal; a: boolean, i: boolean, p: StateObject }} Metadata */ -/** @typedef {Record & { [STATE_SYMBOL]: Metadata }} StateObject */ +} from './utils.js'; export const STATE_SYMBOL = Symbol('$state'); export const READONLY_SYMBOL = Symbol('readonly'); + /** - * @template {StateObject} T + * @template T * @param {T} value * @param {boolean} [immutable] - * @returns {T} + * @returns {import('./types.js').ProxyStateObject | T} */ export function proxy(value, immutable = true) { if (typeof value === 'object' && value != null && !is_frozen(value)) { if (STATE_SYMBOL in value) { - return /** @type {T} */ (value[STATE_SYMBOL].p); + return /** @type {import('./types.js').ProxyMetadata} */ (value[STATE_SYMBOL]).p; } const prototype = get_prototype_of(value); // TODO handle Map and Set as well if (prototype === object_prototype || prototype === array_prototype) { - const proxy = new Proxy(value, handler); + const proxy = new Proxy( + value, + /** @type {ProxyHandler>} */ (state_proxy_handler) + ); define_property(value, STATE_SYMBOL, { - value: init(value, proxy, immutable), + value: init( + /** @type {import('./types.js').ProxyStateObject} */ (value), + /** @type {import('./types.js').ProxyStateObject} */ (proxy), + immutable + ), writable: false }); - // @ts-expect-error not sure how to fix this return proxy; } } @@ -58,7 +62,7 @@ export function proxy(value, immutable = true) { } /** - * @template {StateObject} T + * @template {import('./types.js').ProxyStateObject} T * @param {T} value * @param {Map>} already_unwrapped * @returns {Record} @@ -104,14 +108,14 @@ function unwrap(value, already_unwrapped = new Map()) { * @returns {T} */ export function unstate(value) { - return /** @type {T} */ (unwrap(/** @type {StateObject} */ (value))); + return /** @type {T} */ (unwrap(/** @type {import('./types.js').ProxyStateObject} */ (value))); } /** - * @param {StateObject} value - * @param {StateObject} proxy + * @param {import('./types.js').ProxyStateObject} value + * @param {import('./types.js').ProxyStateObject} proxy * @param {boolean} immutable - * @returns {Metadata} + * @returns {import('./types.js').ProxyMetadata} */ function init(value, proxy, immutable) { return { @@ -123,8 +127,8 @@ function init(value, proxy, immutable) { }; } -/** @type {ProxyHandler} */ -const handler = { +/** @type {ProxyHandler} */ +const state_proxy_handler = { defineProperty(target, prop, descriptor) { if (descriptor.value) { const metadata = target[STATE_SYMBOL]; @@ -290,9 +294,67 @@ export function observe(object) { } if (DEV) { - handler.setPrototypeOf = () => { + state_proxy_handler.setPrototypeOf = () => { throw new Error('Cannot set prototype of $state object'); }; } -export { readonly } from './readonly.js'; +/** + * Expects a value that was wrapped with `proxy` and makes it readonly. + * + * @template {Record} T + * @template {import('./types.js').ProxyReadonlyObject | T} U + * @param {U} value + * @returns {Proxy | U} + */ +export function readonly(value) { + const proxy = value && value[READONLY_SYMBOL]; + if (proxy) return proxy; + + if ( + typeof value === 'object' && + value != null && + !is_frozen(value) && + STATE_SYMBOL in value && // TODO handle Map and Set as well + !(READONLY_SYMBOL in value) + ) { + const proxy = new Proxy( + value, + /** @type {ProxyHandler>} */ ( + readonly_proxy_handler + ) + ); + define_property(value, READONLY_SYMBOL, { value: proxy, writable: false }); + return proxy; + } + + return value; +} + +/** + * @param {any} _ + * @param {string} prop + * @returns {never} + */ +const readonly_error = (_, prop) => { + throw new Error( + `Non-bound props cannot be mutated — to make the \`${prop}\` settable, ensure the object it is used within is bound as a prop \`bind:={...}\`. Fallback values can never be mutated.` + ); +}; + +/** @type {ProxyHandler} */ +const readonly_proxy_handler = { + defineProperty: readonly_error, + deleteProperty: readonly_error, + set: readonly_error, + + get(target, prop, receiver) { + const value = Reflect.get(target, prop, receiver); + + if (!(prop in target)) { + return readonly(value); + } + + return value; + } +}; diff --git a/packages/svelte/src/internal/client/proxy/readonly.js b/packages/svelte/src/internal/client/proxy/readonly.js deleted file mode 100644 index c2123a9cce..0000000000 --- a/packages/svelte/src/internal/client/proxy/readonly.js +++ /dev/null @@ -1,62 +0,0 @@ -import { define_property, is_frozen } from '../utils.js'; -import { READONLY_SYMBOL, STATE_SYMBOL } from './proxy.js'; - -/** - * @template {Record} T - * @typedef {T & { [READONLY_SYMBOL]: Proxy }} StateObject - */ - -/** - * Expects a value that was wrapped with `proxy` and makes it readonly. - * - * @template {Record} T - * @template {StateObject} U - * @param {U} value - * @returns {Proxy | U} - */ -export function readonly(value) { - const proxy = value && value[READONLY_SYMBOL]; - if (proxy) return proxy; - - if ( - typeof value === 'object' && - value != null && - !is_frozen(value) && - STATE_SYMBOL in value && // TODO handle Map and Set as well - !(READONLY_SYMBOL in value) - ) { - const proxy = new Proxy(value, handler); - define_property(value, READONLY_SYMBOL, { value: proxy, writable: false }); - return proxy; - } - - return value; -} - -/** - * @param {any} _ - * @param {string} prop - * @returns {never} - */ -const readonly_error = (_, prop) => { - throw new Error( - `Non-bound props cannot be mutated — to make the \`${prop}\` settable, ensure the object it is used within is bound as a prop \`bind:={...}\`. Fallback values can never be mutated.` - ); -}; - -/** @type {ProxyHandler>} */ -const handler = { - defineProperty: readonly_error, - deleteProperty: readonly_error, - set: readonly_error, - - get(target, prop, receiver) { - const value = Reflect.get(target, prop, receiver); - - if (!(prop in target)) { - return readonly(value); - } - - return value; - } -}; diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 2d504b3ffe..d6ce2adc42 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -57,7 +57,7 @@ import { } from './utils.js'; import { is_promise } from '../common.js'; import { bind_transition, trigger_transitions } from './transitions.js'; -import { proxy } from './proxy/proxy.js'; +import { proxy } from './proxy.js'; /** @type {Set} */ const all_registerd_events = new Set(); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 61f0cecf78..9bc049ecbb 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -17,8 +17,7 @@ import { PROPS_IS_RUNES, PROPS_IS_UPDATED } from '../../constants.js'; -import { readonly } from './proxy/readonly.js'; -import { READONLY_SYMBOL, STATE_SYMBOL, proxy, unstate } from './proxy/proxy.js'; +import { READONLY_SYMBOL, STATE_SYMBOL, proxy, readonly, unstate } from './proxy.js'; import { EACH_BLOCK, IF_BLOCK } from './block.js'; export const SOURCE = 1; @@ -127,7 +126,7 @@ function is_runes(context) { } /** - * @param {import("./proxy/proxy.js").StateObject} target + * @param {import('./types.js').ProxyStateObject} target * @param {string | symbol} prop * @param {any} receiver */ @@ -2120,9 +2119,9 @@ if (DEV) { /** * Expects a value that was wrapped with `freeze` and makes it frozen. - * @template {import('./proxy/proxy.js').StateObject} T + * @template T * @param {T} value - * @returns {Readonly>} + * @returns {Readonly} */ export function freeze(value) { if (typeof value === 'object' && value != null && !is_frozen(value)) { diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index b81ed7c3be..38f76ed7c1 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -10,6 +10,7 @@ import { DYNAMIC_ELEMENT_BLOCK, SNIPPET_BLOCK } from './block.js'; +import type { READONLY_SYMBOL, STATE_SYMBOL } from './proxy.js'; import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT, LAZY_PROPERTY } from './runtime.js'; // Put all internal types in this file. Once we convert to JSDoc, we can make this a d.ts file @@ -388,3 +389,33 @@ export type Raf = { tick: (callback: (time: DOMHighResTimeStamp) => void) => any; now: () => number; }; + +export interface Task { + abort(): void; + promise: Promise; +} + +export type TaskCallback = (now: number) => boolean | void; + +export type TaskEntry = { c: TaskCallback; f: () => void }; + +export interface ProxyMetadata> { + /** A map of signals associated to the properties that are reactive */ + s: Map>; + /** A version counter, used within the proxy to signal changes in places where there's no other way to signal an update */ + v: SourceSignal; + /** `true` if the proxified object is an array */ + a: boolean; + /** Immutable: Whether to use a source or mutable source under the hood */ + i: boolean; + /** The associated proxy */ + p: ProxyStateObject | ProxyReadonlyObject; +} + +export type ProxyStateObject> = T & { + [STATE_SYMBOL]: ProxyMetadata; +}; + +export type ProxyReadonlyObject> = ProxyStateObject & { + [READONLY_SYMBOL]: ProxyMetadata; +}; diff --git a/packages/svelte/src/internal/index.js b/packages/svelte/src/internal/index.js index 3d6fd591cc..10ab1ef432 100644 --- a/packages/svelte/src/internal/index.js +++ b/packages/svelte/src/internal/index.js @@ -45,7 +45,7 @@ export * from './client/each.js'; export * from './client/render.js'; export * from './client/validate.js'; export { raf } from './client/timing.js'; -export { proxy, readonly, unstate } from './client/proxy/proxy.js'; +export { proxy, readonly, unstate } from './client/proxy.js'; export { create_custom_element } from './client/custom-element.js'; diff --git a/packages/svelte/src/motion/spring.js b/packages/svelte/src/motion/spring.js index 09d11402d5..ec56d35410 100644 --- a/packages/svelte/src/motion/spring.js +++ b/packages/svelte/src/motion/spring.js @@ -61,7 +61,7 @@ export function spring(value, opts = {}) { const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts; /** @type {number} */ let last_time; - /** @type {import('../internal/client/private').Task | null} */ + /** @type {import('../internal/client/types').Task | null} */ let task; /** @type {object} */ let current_token; @@ -120,7 +120,7 @@ export function spring(value, opts = {}) { }); } return new Promise((fulfil) => { - /** @type {import('../internal/client/private').Task} */ (task).promise.then(() => { + /** @type {import('../internal/client/types').Task} */ (task).promise.then(() => { if (token === current_token) fulfil(); }); }); diff --git a/packages/svelte/src/motion/tweened.js b/packages/svelte/src/motion/tweened.js index 74dca522f2..35ae4cbfa6 100644 --- a/packages/svelte/src/motion/tweened.js +++ b/packages/svelte/src/motion/tweened.js @@ -81,7 +81,7 @@ function get_interpolator(a, b) { */ export function tweened(value, defaults = {}) { const store = writable(value); - /** @type {import('../internal/client/private').Task} */ + /** @type {import('../internal/client/types').Task} */ let task; let target_value = value; /** @@ -95,7 +95,7 @@ export function tweened(value, defaults = {}) { } target_value = new_value; - /** @type {import('../internal/client/private').Task | null} */ + /** @type {import('../internal/client/types').Task | null} */ let previous_task = task; let started = false;