Skip to content

Commit

Permalink
Adjust ReadOnlySignal so we cant widen the type (#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock authored Aug 3, 2024
1 parent 7494fb2 commit cd9efbb
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 14 deletions.
16 changes: 16 additions & 0 deletions .changeset/seven-seahorses-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@preact/signals-core": minor
---

Adjust the `ReadOnlySignal` type to not inherit from `Signal`
this way the type can't be widened without noticing, i.e. when
we'd have

```js
const sig: Signal = useComputed(() => x);
```

We would have widened the type to be mutable again, which for
a computed is not allowed. We want to provide the tools to our
users to avoid these footguns hence we are correcting this type
in a minor version.
9 changes: 8 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,15 @@ Object.defineProperty(Computed.prototype, "value", {
/**
* An interface for read-only signals.
*/
interface ReadonlySignal<T = any> extends Signal<T> {
interface ReadonlySignal<T = any> {
readonly value: T;
peek(): T;

subscribe(fn: (value: T) => void): () => void;
valueOf(): T;
toString(): string;
toJSON(): T;
brand: typeof BRAND_SYMBOL;
}

/**
Expand Down
23 changes: 12 additions & 11 deletions packages/core/test/signal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
batch,
Signal,
untracked,
ReadonlySignal,
} from "@preact/signals-core";

describe("signal", () => {
Expand Down Expand Up @@ -962,15 +963,15 @@ describe("computed()", () => {
});

it("should detect simple dependency cycles", () => {
const a: Signal = computed(() => a.value);
const a: ReadonlySignal = computed(() => a.value);
expect(() => a.value).to.throw(/Cycle detected/);
});

it("should detect deep dependency cycles", () => {
const a: Signal = computed(() => b.value);
const b: Signal = computed(() => c.value);
const c: Signal = computed(() => d.value);
const d: Signal = computed(() => a.value);
const a: ReadonlySignal = computed(() => b.value);
const b: ReadonlySignal = computed(() => c.value);
const c: ReadonlySignal = computed(() => d.value);
const d: ReadonlySignal = computed(() => a.value);
expect(() => a.value).to.throw(/Cycle detected/);
});

Expand Down Expand Up @@ -1243,15 +1244,15 @@ describe("computed()", () => {
});

it("should detect simple dependency cycles", () => {
const a: Signal = computed(() => a.peek());
const a: ReadonlySignal = computed(() => a.peek());
expect(() => a.peek()).to.throw(/Cycle detected/);
});

it("should detect deep dependency cycles", () => {
const a: Signal = computed(() => b.value);
const b: Signal = computed(() => c.value);
const c: Signal = computed(() => d.value);
const d: Signal = computed(() => a.peek());
const a: ReadonlySignal = computed(() => b.value);
const b: ReadonlySignal = computed(() => c.value);
const c: ReadonlySignal = computed(() => d.value);
const d: ReadonlySignal = computed(() => a.peek());
expect(() => a.peek()).to.throw(/Cycle detected/);
});

Expand Down Expand Up @@ -1340,7 +1341,7 @@ describe("computed()", () => {
it("should be garbage collectable after it has lost all of its listeners", async () => {
const s = signal(0);

let ref: WeakRef<Signal>;
let ref: WeakRef<ReadonlySignal>;
let dispose: () => void;
(function () {
const c = computed(() => s.value);
Expand Down
2 changes: 1 addition & 1 deletion packages/preact/test/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ describe("@preact/signals", () => {
const childSpy = sinon.spy();
const parentSpy = sinon.spy();

function Child({ num }: { num: Signal<number> }) {
function Child({ num }: { num: ReadonlySignal<number> }) {
childSpy();
return <p>{num.value}</p>;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,7 @@ declare module "@preact/signals-core" {
// @ts-ignore internal Signal is viewed as function
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface Signal extends ReactElement {}
// @ts-ignore internal Signal is viewed as function
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface ReadonlySignal extends ReactElement {}
}
2 changes: 1 addition & 1 deletion packages/react/test/shared/updates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ export function updateSignalsTests(usingTransform = false) {
const childSpy = sinon.spy();
const parentSpy = sinon.spy();

function Child({ num }: { num: Signal<number> }) {
function Child({ num }: { num: ReadonlySignal<number> }) {
childSpy();
return <p>{num.value}</p>;
}
Expand Down

0 comments on commit cd9efbb

Please sign in to comment.