Skip to content

Commit 40c2956

Browse files
authored
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
1 parent 14bf4b4 commit 40c2956

File tree

11 files changed

+127
-105
lines changed

11 files changed

+127
-105
lines changed

packages/svelte/src/internal/client/each.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
set_current_hydration_fragment
1515
} from './hydration.js';
1616
import { clear_text_content, map_get, map_set } from './operations.js';
17-
import { STATE_SYMBOL } from './proxy/proxy.js';
17+
import { STATE_SYMBOL } from './proxy.js';
1818
import { insert, remove } from './reconciler.js';
1919
import { empty } from './render.js';
2020
import {

packages/svelte/src/internal/client/loop.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ export function clear_loops() {
2727
/**
2828
* Creates a new task that runs on each raf frame
2929
* until it returns a falsy value or is aborted
30-
* @param {import('./private.js').TaskCallback} callback
31-
* @returns {import('./private.js').Task}
30+
* @param {import('./types.js').TaskCallback} callback
31+
* @returns {import('./types.js').Task}
3232
*/
3333
export function loop(callback) {
34-
/** @type {import('./private.js').TaskEntry} */
34+
/** @type {import('./types.js').TaskEntry} */
3535
let task;
3636
if (tasks.size === 0) raf.tick(run_tasks);
3737
return {

packages/svelte/src/internal/client/private.d.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

packages/svelte/src/internal/client/proxy/proxy.js renamed to packages/svelte/src/internal/client/proxy.js

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
UNINITIALIZED,
1010
mutable_source,
1111
batch_inspect
12-
} from '../runtime.js';
12+
} from './runtime.js';
1313
import {
1414
array_prototype,
1515
define_property,
@@ -20,36 +20,40 @@ import {
2020
is_frozen,
2121
object_keys,
2222
object_prototype
23-
} from '../utils.js';
24-
25-
/** @typedef {{ s: Map<string | symbol, import('../types.js').SourceSignal<any>>; v: import('../types.js').SourceSignal<number>; a: boolean, i: boolean, p: StateObject }} Metadata */
26-
/** @typedef {Record<string | symbol, any> & { [STATE_SYMBOL]: Metadata }} StateObject */
23+
} from './utils.js';
2724

2825
export const STATE_SYMBOL = Symbol('$state');
2926
export const READONLY_SYMBOL = Symbol('readonly');
27+
3028
/**
31-
* @template {StateObject} T
29+
* @template T
3230
* @param {T} value
3331
* @param {boolean} [immutable]
34-
* @returns {T}
32+
* @returns {import('./types.js').ProxyStateObject<T> | T}
3533
*/
3634
export function proxy(value, immutable = true) {
3735
if (typeof value === 'object' && value != null && !is_frozen(value)) {
3836
if (STATE_SYMBOL in value) {
39-
return /** @type {T} */ (value[STATE_SYMBOL].p);
37+
return /** @type {import('./types.js').ProxyMetadata<T>} */ (value[STATE_SYMBOL]).p;
4038
}
4139

4240
const prototype = get_prototype_of(value);
4341

4442
// TODO handle Map and Set as well
4543
if (prototype === object_prototype || prototype === array_prototype) {
46-
const proxy = new Proxy(value, handler);
44+
const proxy = new Proxy(
45+
value,
46+
/** @type {ProxyHandler<import('./types.js').ProxyStateObject<T>>} */ (state_proxy_handler)
47+
);
4748
define_property(value, STATE_SYMBOL, {
48-
value: init(value, proxy, immutable),
49+
value: init(
50+
/** @type {import('./types.js').ProxyStateObject<T>} */ (value),
51+
/** @type {import('./types.js').ProxyStateObject<T>} */ (proxy),
52+
immutable
53+
),
4954
writable: false
5055
});
5156

52-
// @ts-expect-error not sure how to fix this
5357
return proxy;
5458
}
5559
}
@@ -58,7 +62,7 @@ export function proxy(value, immutable = true) {
5862
}
5963

6064
/**
61-
* @template {StateObject} T
65+
* @template {import('./types.js').ProxyStateObject} T
6266
* @param {T} value
6367
* @param {Map<T, Record<string | symbol, any>>} already_unwrapped
6468
* @returns {Record<string | symbol, any>}
@@ -104,14 +108,14 @@ function unwrap(value, already_unwrapped = new Map()) {
104108
* @returns {T}
105109
*/
106110
export function unstate(value) {
107-
return /** @type {T} */ (unwrap(/** @type {StateObject} */ (value)));
111+
return /** @type {T} */ (unwrap(/** @type {import('./types.js').ProxyStateObject} */ (value)));
108112
}
109113

110114
/**
111-
* @param {StateObject} value
112-
* @param {StateObject} proxy
115+
* @param {import('./types.js').ProxyStateObject} value
116+
* @param {import('./types.js').ProxyStateObject} proxy
113117
* @param {boolean} immutable
114-
* @returns {Metadata}
118+
* @returns {import('./types.js').ProxyMetadata}
115119
*/
116120
function init(value, proxy, immutable) {
117121
return {
@@ -123,8 +127,8 @@ function init(value, proxy, immutable) {
123127
};
124128
}
125129

126-
/** @type {ProxyHandler<StateObject>} */
127-
const handler = {
130+
/** @type {ProxyHandler<import('./types.js').ProxyStateObject>} */
131+
const state_proxy_handler = {
128132
defineProperty(target, prop, descriptor) {
129133
if (descriptor.value) {
130134
const metadata = target[STATE_SYMBOL];
@@ -290,9 +294,67 @@ export function observe(object) {
290294
}
291295

292296
if (DEV) {
293-
handler.setPrototypeOf = () => {
297+
state_proxy_handler.setPrototypeOf = () => {
294298
throw new Error('Cannot set prototype of $state object');
295299
};
296300
}
297301

298-
export { readonly } from './readonly.js';
302+
/**
303+
* Expects a value that was wrapped with `proxy` and makes it readonly.
304+
*
305+
* @template {Record<string | symbol, any>} T
306+
* @template {import('./types.js').ProxyReadonlyObject<T> | T} U
307+
* @param {U} value
308+
* @returns {Proxy<U> | U}
309+
*/
310+
export function readonly(value) {
311+
const proxy = value && value[READONLY_SYMBOL];
312+
if (proxy) return proxy;
313+
314+
if (
315+
typeof value === 'object' &&
316+
value != null &&
317+
!is_frozen(value) &&
318+
STATE_SYMBOL in value && // TODO handle Map and Set as well
319+
!(READONLY_SYMBOL in value)
320+
) {
321+
const proxy = new Proxy(
322+
value,
323+
/** @type {ProxyHandler<import('./types.js').ProxyReadonlyObject<U>>} */ (
324+
readonly_proxy_handler
325+
)
326+
);
327+
define_property(value, READONLY_SYMBOL, { value: proxy, writable: false });
328+
return proxy;
329+
}
330+
331+
return value;
332+
}
333+
334+
/**
335+
* @param {any} _
336+
* @param {string} prop
337+
* @returns {never}
338+
*/
339+
const readonly_error = (_, prop) => {
340+
throw new Error(
341+
`Non-bound props cannot be mutated — to make the \`${prop}\` settable, ensure the object it is used within is bound as a prop \`bind:<prop>={...}\`. Fallback values can never be mutated.`
342+
);
343+
};
344+
345+
/** @type {ProxyHandler<import('./types.js').ProxyReadonlyObject>} */
346+
const readonly_proxy_handler = {
347+
defineProperty: readonly_error,
348+
deleteProperty: readonly_error,
349+
set: readonly_error,
350+
351+
get(target, prop, receiver) {
352+
const value = Reflect.get(target, prop, receiver);
353+
354+
if (!(prop in target)) {
355+
return readonly(value);
356+
}
357+
358+
return value;
359+
}
360+
};

packages/svelte/src/internal/client/proxy/readonly.js

Lines changed: 0 additions & 62 deletions
This file was deleted.

packages/svelte/src/internal/client/render.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import {
5757
} from './utils.js';
5858
import { is_promise } from '../common.js';
5959
import { bind_transition, trigger_transitions } from './transitions.js';
60-
import { proxy } from './proxy/proxy.js';
60+
import { proxy } from './proxy.js';
6161

6262
/** @type {Set<string>} */
6363
const all_registerd_events = new Set();

packages/svelte/src/internal/client/runtime.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import {
1717
PROPS_IS_RUNES,
1818
PROPS_IS_UPDATED
1919
} from '../../constants.js';
20-
import { readonly } from './proxy/readonly.js';
21-
import { READONLY_SYMBOL, STATE_SYMBOL, proxy, unstate } from './proxy/proxy.js';
20+
import { READONLY_SYMBOL, STATE_SYMBOL, proxy, readonly, unstate } from './proxy.js';
2221
import { EACH_BLOCK, IF_BLOCK } from './block.js';
2322

2423
export const SOURCE = 1;
@@ -127,7 +126,7 @@ function is_runes(context) {
127126
}
128127

129128
/**
130-
* @param {import("./proxy/proxy.js").StateObject} target
129+
* @param {import('./types.js').ProxyStateObject} target
131130
* @param {string | symbol} prop
132131
* @param {any} receiver
133132
*/
@@ -2120,9 +2119,9 @@ if (DEV) {
21202119

21212120
/**
21222121
* Expects a value that was wrapped with `freeze` and makes it frozen.
2123-
* @template {import('./proxy/proxy.js').StateObject} T
2122+
* @template T
21242123
* @param {T} value
2125-
* @returns {Readonly<Record<string | symbol, any>>}
2124+
* @returns {Readonly<T>}
21262125
*/
21272126
export function freeze(value) {
21282127
if (typeof value === 'object' && value != null && !is_frozen(value)) {

packages/svelte/src/internal/client/types.d.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
DYNAMIC_ELEMENT_BLOCK,
1111
SNIPPET_BLOCK
1212
} from './block.js';
13+
import type { READONLY_SYMBOL, STATE_SYMBOL } from './proxy.js';
1314
import { DERIVED, EFFECT, RENDER_EFFECT, SOURCE, PRE_EFFECT, LAZY_PROPERTY } from './runtime.js';
1415

1516
// 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 = {
388389
tick: (callback: (time: DOMHighResTimeStamp) => void) => any;
389390
now: () => number;
390391
};
392+
393+
export interface Task {
394+
abort(): void;
395+
promise: Promise<void>;
396+
}
397+
398+
export type TaskCallback = (now: number) => boolean | void;
399+
400+
export type TaskEntry = { c: TaskCallback; f: () => void };
401+
402+
export interface ProxyMetadata<T = Record<string | symbol, any>> {
403+
/** A map of signals associated to the properties that are reactive */
404+
s: Map<string | symbol, SourceSignal<any>>;
405+
/** A version counter, used within the proxy to signal changes in places where there's no other way to signal an update */
406+
v: SourceSignal<number>;
407+
/** `true` if the proxified object is an array */
408+
a: boolean;
409+
/** Immutable: Whether to use a source or mutable source under the hood */
410+
i: boolean;
411+
/** The associated proxy */
412+
p: ProxyStateObject<T> | ProxyReadonlyObject<T>;
413+
}
414+
415+
export type ProxyStateObject<T = Record<string | symbol, any>> = T & {
416+
[STATE_SYMBOL]: ProxyMetadata;
417+
};
418+
419+
export type ProxyReadonlyObject<T = Record<string | symbol, any>> = ProxyStateObject<T> & {
420+
[READONLY_SYMBOL]: ProxyMetadata;
421+
};

packages/svelte/src/internal/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export * from './client/each.js';
4545
export * from './client/render.js';
4646
export * from './client/validate.js';
4747
export { raf } from './client/timing.js';
48-
export { proxy, readonly, unstate } from './client/proxy/proxy.js';
48+
export { proxy, readonly, unstate } from './client/proxy.js';
4949

5050
export { create_custom_element } from './client/custom-element.js';
5151

packages/svelte/src/motion/spring.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function spring(value, opts = {}) {
6161
const { stiffness = 0.15, damping = 0.8, precision = 0.01 } = opts;
6262
/** @type {number} */
6363
let last_time;
64-
/** @type {import('../internal/client/private').Task | null} */
64+
/** @type {import('../internal/client/types').Task | null} */
6565
let task;
6666
/** @type {object} */
6767
let current_token;
@@ -120,7 +120,7 @@ export function spring(value, opts = {}) {
120120
});
121121
}
122122
return new Promise((fulfil) => {
123-
/** @type {import('../internal/client/private').Task} */ (task).promise.then(() => {
123+
/** @type {import('../internal/client/types').Task} */ (task).promise.then(() => {
124124
if (token === current_token) fulfil();
125125
});
126126
});

0 commit comments

Comments
 (0)