Skip to content

Commit

Permalink
Merge pull request #35 from tildeio/peer-dep-not-required
Browse files Browse the repository at this point in the history
Remove the need to install @starbeam/peer as a peer
  • Loading branch information
wycats committed Jul 28, 2022
2 parents bd40215 + 33859f5 commit e2d0548
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 37 deletions.
10 changes: 7 additions & 3 deletions packages/peer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ This package provides an extremely stable API for getting:
- The current timestamp as a number
- The value of the `UNINITIALIZED` symbol

Apps shouldn't use the exports of this dependency directly. Instead, installing it as a peer
dependency allows two versions of Starbeam to coexist in the same process and **to share reactivity
between them**.
Apps shouldn't use the exports of this dependency directly. Instead, separating the most fundamental
parts of Starbeam's composition into a separate package allows two versions of Starbeam to coexist
in the same process and **to share reactivity between them**.

In other words, if you access a Cell from version 1 of Starbeam in the context of a formula
created in version 2 of Starbeam, updating the cell will invalidate the formula.

This package uses `Symbol.for` to ensure that only a single copy of the fundamental symbols and
constants exists in a single process. As a result, it is not necessary to install this package as a
peer dependency.
2 changes: 1 addition & 1 deletion packages/peer/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { REACTIVE, UNINITIALIZED } from "./src/constants.js";
export { NOW, REACTIVE, UNINITIALIZED } from "./src/constants.js";
export { bump, now } from "./src/now.js";
19 changes: 18 additions & 1 deletion packages/peer/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
/**
* The `UNINITIALIZED` symbol represents a special internal value that can be used to differentiate
* between any user-supplied value and the state of being uninitialized.
*
* You do not **need** to import `@starbeam/peer` to get this symbol, as it is specified using
* `Symbol.for`.
*/
const UNINITIALIZED = Symbol.for("starbeam.UNINITIALIZED");
type UNINITIALIZED = typeof UNINITIALIZED;

/**
* The `REACTIVE` symbol is the protocol entry point for reactive values. Implementations of
* the `ReactiveProtocol` interface specify their reactive behavior under this symbol.
*/
const REACTIVE: unique symbol = Symbol.for("starbeam.REACTIVE");
type REACTIVE = typeof REACTIVE;

export { REACTIVE, UNINITIALIZED };
/**
* The `NOW` symbol is the name on `globalThis` that is used to store the current timestamp.
*/
const NOW: unique symbol = Symbol.for("starbeam.NOW");
type NOW = typeof NOW;

export { NOW, REACTIVE, UNINITIALIZED };
11 changes: 11 additions & 0 deletions packages/peer/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { NOW } from "./constants.js";

export interface Clock {
timestamp: number;
}

export interface GlobalWithNow {
[NOW]: {
timestamp: number;
};
}
31 changes: 26 additions & 5 deletions packages/peer/src/now.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
const NOW = {
timestamp: 0,
};
import { NOW } from "./constants.js";
import type { GlobalWithNow } from "./env.js";

/**
* The `CLOCK` constant is a universal monotonically increasing clock. The `Timestamp` class is used
* in `@starbeam/timeline` and `@starbeam/core`, but `Timestamp` defers to this constant. This means
* that multiple copies of `@starbeam/timeline` will still see the same monotonically increasing clock.
*
* The term "timestamp" is used in this context to refer to a monotonically increasing number, where
* each number represents a different moment in time.
*/
let CLOCK = (globalThis as unknown as GlobalWithNow)[NOW];

if (!CLOCK) {
CLOCK = (globalThis as unknown as GlobalWithNow)[NOW] = {
timestamp: 0,
};
}

/**
* Get the current timestamp.
*/
export function now(): number {
return NOW.timestamp;
return CLOCK.timestamp;
}

/**
* Increment the current timestamp, and return the new one.
*/
export function bump(): number {
NOW.timestamp = NOW.timestamp + 1;
CLOCK.timestamp = CLOCK.timestamp + 1;
return now();
}
60 changes: 33 additions & 27 deletions packages/peer/tests/constants.spec.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,52 @@
import { UNINITIALIZED } from "@starbeam/peer";
import { NOW, REACTIVE, UNINITIALIZED } from "@starbeam/peer";
import { describe, expect, test } from "vitest";

describe("UNINITALIZED", () => {
testSymbol(UNINITIALIZED, "UNINITIALIZED");
testSymbol(REACTIVE, "REACTIVE");
testSymbol(NOW, "NOW");
});

function testSymbol(symbol: symbol, description: string) {
test("is a symbol", () => {
expect(typeof UNINITIALIZED).toBe("symbol");
expect(UNINITIALIZED.description).toBe("starbeam.UNINITIALIZED");
expect(typeof symbol).toBe("symbol");
expect(symbol.description).toBe(`starbeam.${description}`);
});

test("is the same value each time (i.e. not an export let)", () => {
expect(UNINITIALIZED).toBe(UNINITIALIZED);
expect(symbol).toBe(symbol);
});

test("is registered at Symbol.for('starbeam.UNINITIALIZED')", () => {
expect(Symbol.for("starbeam.UNINITIALIZED")).toBe(UNINITIALIZED);
test(`is registered at Symbol.for('starbeam.${description}')`, () => {
expect(Symbol.for(`starbeam.${description}`)).toBe(symbol);
});

test("isn't one of the builtin symbols", () => {
expect(UNINITIALIZED).not.toBe(Symbol.iterator);
expect(UNINITIALIZED).not.toBe(Symbol.toStringTag);
expect(UNINITIALIZED).not.toBe(Symbol.unscopables);
expect(UNINITIALIZED).not.toBe(Symbol.hasInstance);
expect(UNINITIALIZED).not.toBe(Symbol.isConcatSpreadable);
expect(UNINITIALIZED).not.toBe(Symbol.match);
expect(UNINITIALIZED).not.toBe(Symbol.replace);
expect(UNINITIALIZED).not.toBe(Symbol.search);
expect(UNINITIALIZED).not.toBe(Symbol.species);
expect(UNINITIALIZED).not.toBe(Symbol.split);
expect(UNINITIALIZED).not.toBe(Symbol.toPrimitive);
expect(symbol).not.toBe(Symbol.iterator);
expect(symbol).not.toBe(Symbol.toStringTag);
expect(symbol).not.toBe(Symbol.unscopables);
expect(symbol).not.toBe(Symbol.hasInstance);
expect(symbol).not.toBe(Symbol.isConcatSpreadable);
expect(symbol).not.toBe(Symbol.match);
expect(symbol).not.toBe(Symbol.replace);
expect(symbol).not.toBe(Symbol.search);
expect(symbol).not.toBe(Symbol.species);
expect(symbol).not.toBe(Symbol.split);
expect(symbol).not.toBe(Symbol.toPrimitive);

// it's not node's inspect symbol
expect(UNINITIALIZED).not.toBe(Symbol.for("nodejs.util.inspect.custom"));
expect(symbol).not.toBe(Symbol.for("nodejs.util.inspect.custom"));

// other react symbols
expect(UNINITIALIZED).not.toBe(Symbol.for("react.element"));
expect(UNINITIALIZED).not.toBe(Symbol.for("react.forward_ref"));
expect(UNINITIALIZED).not.toBe(Symbol.for("react.fragment"));
expect(UNINITIALIZED).not.toBe(Symbol.for("react.profiler"));
expect(UNINITIALIZED).not.toBe(Symbol.for("react.provider"));
expect(UNINITIALIZED).not.toBe(Symbol.for("react.context"));
expect(UNINITIALIZED).not.toBe(Symbol.for("react.concurrent_mode"));
expect(symbol).not.toBe(Symbol.for("react.element"));
expect(symbol).not.toBe(Symbol.for("react.forward_ref"));
expect(symbol).not.toBe(Symbol.for("react.fragment"));
expect(symbol).not.toBe(Symbol.for("react.profiler"));
expect(symbol).not.toBe(Symbol.for("react.provider"));
expect(symbol).not.toBe(Symbol.for("react.context"));
expect(symbol).not.toBe(Symbol.for("react.concurrent_mode"));

// observable symbol, casting Symbol to avoid TS error
expect(UNINITIALIZED).not.toBe(Symbol.for("rxjs.internal.observable"));
expect(symbol).not.toBe(Symbol.for("rxjs.internal.observable"));
});
});
}

0 comments on commit e2d0548

Please sign in to comment.