Skip to content

Commit

Permalink
small adjustments (#21)
Browse files Browse the repository at this point in the history
* small adjustments

* changeset
  • Loading branch information
TGlide authored Apr 26, 2024
1 parent d9e24d2 commit a5d2f7f
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-pans-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"runed": patch
---

box.readonly perfomance adjustment
38 changes: 20 additions & 18 deletions packages/runed/src/lib/functions/box/box.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ function boxWith<T>(getter: () => T, setter?: (v: T) => void) {

export type BoxFrom<T> =
T extends WritableBox<infer U>
? WritableBox<U>
: T extends ReadableBox<infer U>
? ReadableBox<U>
: T extends Getter<infer U>
? ReadableBox<U>
: WritableBox<T>;
? WritableBox<U>
: T extends ReadableBox<infer U>
? ReadableBox<U>
: T extends Getter<infer U>
? ReadableBox<U>
: WritableBox<T>;

/**
* Creates a box from either a static value, a box, or a getter function.
Expand All @@ -131,16 +131,16 @@ type BoxFlatten<R extends Record<string, unknown>> = Expand<
},
never
> &
RemoveValues<
{
readonly [K in keyof R]: R[K] extends WritableBox<infer _>
? never
: R[K] extends ReadableBox<infer T>
? T
: never;
},
never
>
RemoveValues<
{
readonly [K in keyof R]: R[K] extends WritableBox<infer _>
? never
: R[K] extends ReadableBox<infer T>
? T
: never;
},
never
>
> &
RemoveValues<
{
Expand Down Expand Up @@ -192,11 +192,13 @@ function boxFlatten<R extends Record<string, unknown>>(boxes: R): BoxFlatten<R>
* const count = box(0) // WritableBox<number>
* const countReadonly = box.readonly(count) // ReadableBox<number>
*/
function toReadonlyBox<T>(box: ReadableBox<T>): ReadableBox<T> {
function toReadonlyBox<T>(b: ReadableBox<T>): ReadableBox<T> {
if (!box.isWritableBox(b)) return b

return {
[BoxSymbol]: true,
get value() {
return box.value;
return b.value;
},
};
}
Expand Down
52 changes: 27 additions & 25 deletions packages/runed/src/lib/functions/box/box.test.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,33 @@ describe("box.flatten", () => {
});
});

describe("box.readonly", () => {
test("box.readonly returns a non-settable box", () => {
const count = box(0);
const readonlyCount = box.readonly(count);

function setReadOnlyCount() {
// eslint-disable-next-line ts/no-explicit-any
(readonlyCount as any).value = 1;
}

expect(setReadOnlyCount).toThrow();
});

test("box.readonly returned box should update with original box", () => {
const count = box(0);
const readonlyCount = box.readonly(count);

expect(readonlyCount.value).toBe(0);
count.value = 1;
expect(readonlyCount.value).toBe(1);

count.value = 2;
expect(readonlyCount.value).toBe(2);
});
});


describe("box types", () => {
test("box without initial value", () => {
const count = box<number>();
Expand Down Expand Up @@ -163,28 +190,3 @@ describe("box types", () => {
});
});

describe("box.readonly", () => {
test("box.readonly returns a non-settable box", () => {
const count = box(0);
const readonlyCount = box.readonly(count);

function setReadOnlyCount() {
// eslint-disable-next-line ts/no-explicit-any
(readonlyCount as any).value = 1;
}

expect(setReadOnlyCount).toThrow();
});

test("box.readonly returned box should update with original box", () => {
const count = box(0);
const readonlyCount = box.readonly(count);

expect(readonlyCount.value).toBe(0);
count.value = 1;
expect(readonlyCount.value).toBe(1);

count.value = 2;
expect(readonlyCount.value).toBe(2);
});
});
114 changes: 114 additions & 0 deletions packages/runed/src/lib/functions/box/new-box.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/* eslint-disable ts/consistent-type-definitions */

import type { Getter, Setter } from "$lib/internal/types.js";

const BoxSymbol = Symbol('box');

interface Box<T> {
[BoxSymbol]: true
value: T,
}

interface ReadonlyBox<T> extends Box<T> {
readonly value: T,
}

export function box<T>(): Box<T | undefined>
export function box<T>(initial: T): Box<T>
export function box<T>(initial?: T) {
let value = $state(initial)

return {
get value() {
return value
},
set value(v) {
value = v;
},
[BoxSymbol]: true
}
}

box.isBox = function isBox<T>(value: unknown): value is Box<T> {
return typeof value === 'object' && value !== null && (BoxSymbol in value);
}

function isWritable<T extends {}>(obj: T, key: keyof T) {
const desc = Object.getOwnPropertyDescriptor(obj, key) || {}
return Boolean(desc.writable)
}

box.isReadonly = function boxIsReadonly<T>(value: unknown): value is ReadonlyBox<T> {
return box.isBox(value) && !isWritable(value, 'value')
}

box.isWritable = function boxIsWritable<T>(value: unknown): value is Box<T> {
return box.isBox(value) && isWritable(value, 'value')
}

function boxFrom<T>(value: T): T extends ReadonlyBox<infer _> ? T : Box<T> {
// eslint-disable-next-line ts/no-explicit-any -- I'm fucking something up here
if (box.isBox(value)) return value as any;
// eslint-disable-next-line ts/no-explicit-any
return box(value) as any;
}
box.from = boxFrom

function boxWith<T>(get: Getter<T>): ReadonlyBox<T>
function boxWith<T>(get: Getter<T>, set: Setter<T>): Box<T>
function boxWith<T>(get: Getter<T>, set?: Setter<T>) {
const value = $derived.by(get)

if (set) {
return {
get value() {
return value;
},
set value(v) {
set(v)
},
[BoxSymbol]: true
}
} else {
return {
get value() {
return value;
},
[BoxSymbol]: true
}
}
}
box.with = boxWith

// Usage examples
function acceptsReadonly(disabled: boolean | ReadonlyBox<boolean>) {
const disabledBox = box.from(disabled);
// @ts-expect-error -- testing
disabledBox.value = false
if (box.isWritable(disabledBox)) {
disabledBox.value = true
}
}

function acceptsMutable(disabled: boolean | Box<boolean>) {
const disabledBox = box.from(disabled);
disabledBox.value = false
}

let disabled = $state(false);

acceptsReadonly(
box.with(() => disabled)
);

acceptsReadonly(
box(false)
);

acceptsMutable(
box.with(() => disabled, (v) => disabled = v)
)

acceptsMutable(
box(false)
)

0 comments on commit a5d2f7f

Please sign in to comment.