Skip to content

Commit

Permalink
Use scoping terminology in code to increase consistency
Browse files Browse the repository at this point in the history
  • Loading branch information
drborges committed Sep 13, 2024
1 parent f83c8af commit c9209b2
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 91 deletions.
2 changes: 1 addition & 1 deletion packages/arbor-react/tests/matchers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import "./toBeTrackedNode"
import "./toBeScopedNode"
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { expect } from "vitest"

expect.extend({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toBeTrackedNode(received: any) {
const isTracked = received.$tracked === true
toBeScopedNode(received: any) {
const isScoped = received.$scoped === true
return {
pass: isTracked,
pass: isScoped,
actual: received,
message: () =>
`Received value is ${isTracked ? "" : "not"} a tracked Arbor node`,
`Received value is ${isScoped ? "" : "not"} a tracked Arbor node`,
}
},
})
8 changes: 4 additions & 4 deletions packages/arbor-react/tests/useArbor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ describe("useArbor", () => {

const { result } = renderHook(() => useArbor(store))

expect(result.current).toBeTrackedNode()
expect(result.current[0]).toBeTrackedNode()
expect(result.current[1]).toBeTrackedNode()
expect(result.current[2]).toBeTrackedNode()
expect(result.current).toBeScopedNode()
expect(result.current[0]).toBeScopedNode()
expect(result.current[1]).toBeScopedNode()
expect(result.current[2]).toBeScopedNode()
})

it("returns the current state of the store", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/arbor-react/tests/vitest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ declare module "vitest" {
interface Assertion<T> {
toBeDetached: () => T
toBeArborNode: () => T
toBeTrackedNode: () => T
toBeScopedNode: () => T
toBeProxiedExactlyOnce: () => T
toBeNodeOf: (expected: unknown) => T
toHaveNodeFor: (expected: unknown) => T
Expand Down
11 changes: 11 additions & 0 deletions packages/arbor-store/src/handlers/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ export class DefaultHandler<T extends object = object>
return true
}

// TODO: expose seed value via a getter so user-defined data can have access to it.
// Example:
// get $seed() { return Seed.from(this) }
//
// User-defined classes could then:
//
// @proxiable
// class MyNode {
// $seed: number
// }

$getChildNode<C extends object>(link: Link): Node<C> {
return this[link]
}
Expand Down
40 changes: 20 additions & 20 deletions packages/arbor-store/src/scoping/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,36 @@ import { Seed } from "../path"
import { ArborNode, Link, MutationEvent, Node } from "../types"
import { isGetter, recursivelyUnwrap } from "../utilities"

export type Tracked<T extends object = object> = T & {
$tracked?: boolean
export type Scoped<T extends object = object> = T & {
$scoped?: boolean
}

export class Scope<T extends object> {
private readonly bindings = new WeakMap<object, WeakMap<object, object>>()
private readonly cache = new WeakMap<ArborNode, Tracked>()
private tracking = new WeakMap<Seed, Set<string>>()
private readonly cache = new WeakMap<ArborNode, Scoped>()
private scopes = new WeakMap<Seed, Set<string>>()

getOrCache(node: ArborNode): Tracked {
getOrCache(node: ArborNode): Scoped {
if (!this.cache.has(node)) {
this.cache.set(node, this.wrap(node))
}

return this.cache.get(node)
}

isTracking(node: ArborNode, prop: string) {
toBeScoping(node: ArborNode, prop: string) {
const seed = Seed.from(node)
const tracked = this.tracking.get(seed)
const scoped = this.scopes.get(seed)

if (!tracked) {
if (!scoped) {
return false
}

return tracked.has(prop)
return scoped.has(prop)
}

reset() {
this.tracking = new WeakMap<Seed, Set<string>>()
this.scopes = new WeakMap<Seed, Set<string>>()
}

// NOTE: Something to consider in the future as new node handler types
Expand Down Expand Up @@ -84,28 +84,28 @@ export class Scope<T extends object> {

const rootSeed = Seed.from(event.state)
const targetSeed = event.mutationPath.seeds.at(-1)
const tracked = this.tracking.get(targetSeed || rootSeed)
const scoped = this.scopes.get(targetSeed || rootSeed)

// If the affected node is not being tracked, then no need to notify
// If the affected node is not being scoped, then no need to notify
// any subscribers.
if (!tracked) {
if (!scoped) {
return false
}

// Lastly, subscribers will be notified if any of the mutated props are
// being tracked.
return event.metadata.props.some((prop) => tracked.has(prop as string))
// being scoped.
return event.metadata.props.some((prop) => scoped.has(prop as string))
}

track(value: object, prop?: string) {
const seed = Seed.from(value)

if (!this.tracking.has(seed)) {
this.tracking.set(seed, new Set())
if (!this.scopes.has(seed)) {
this.scopes.set(seed, new Set())
}

if (prop != null) {
this.tracking.get(seed).add(prop)
this.scopes.get(seed).add(prop)
}
}

Expand All @@ -116,8 +116,8 @@ export class Scope<T extends object> {

return new Proxy(node, {
get(target: Node, prop, proxy) {
// TODO: Rename $tracked to Symbol.for("ArborScoped")
if (prop === "$tracked") {
// TODO: Rename $scoped to Symbol.for("ArborScoped")
if (prop === "$scoped") {
return true
}

Expand Down
4 changes: 2 additions & 2 deletions packages/arbor-store/tests/matchers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import "./toBeDetached"
import "./toBeNodeOf"
import "./toBeProxiedExactlyOnce"
import "./toBeSeeded"
import "./toBeTrackedNode"
import "./toBeTracking"
import "./toBeScopedNode"
import "./toBeScoping"
import "./toHaveLink"
import "./toHaveLinkFor"
import "./toHaveNodeFor"
15 changes: 15 additions & 0 deletions packages/arbor-store/tests/matchers/toBeScopedNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect } from "vitest"

import { Scoped } from "../../src/scoping/scope"

expect.extend({
toBeScopedNode(received) {
const isScoped = (received as Scoped)?.$scoped === true
return {
pass: isScoped,
actual: received,
message: () =>
`Received value is ${isScoped ? "" : "not"} a scoped Arbor node`,
}
},
})
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect } from "vitest"

import { ArborError, isNode, NotAnArborNodeError, ScopedStore } from "../../src"
import { Tracked } from "../../src/scoping/scope"
import { Scoped } from "../../src/scoping/scope"

expect.extend({
toBeTracking(scopedStore, node, prop) {
toBeScoping(scopedStore, node, prop) {
if (!(scopedStore instanceof ScopedStore)) {
throw new ArborError("received value is not an instance of ScopedStore")
}
Expand All @@ -13,17 +13,17 @@ expect.extend({
throw new NotAnArborNodeError()
}

const isTrackedNode = (node as Tracked)?.$tracked === true
const isTrackingNodeProp =
isTrackedNode && scopedStore.scope.isTracking(node, prop)
const isScopedNode = (node as Scoped)?.$scoped === true
const toBeScopingNodeProp =
isScopedNode && scopedStore.scope.toBeScoping(node, prop)

return {
pass: isTrackingNodeProp,
pass: toBeScopingNodeProp,
actual: scopedStore,
message: () =>
`ScopedStore is ${
isTrackingNodeProp ? "" : "not"
} tracking prop ${prop} for node ${node}`,
toBeScopingNodeProp ? "" : "not"
} scoping updates to ${prop}`,
}
},
})
15 changes: 0 additions & 15 deletions packages/arbor-store/tests/matchers/toBeTrackedNode.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/arbor-store/tests/scoping/array.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ describe("Array", () => {
delete scoped.state.todos[1]
delete scoped.state.todos[1]

expect(scoped).not.toBeTracking(scoped.state.todos, 1)
expect(scoped).not.toBeScoping(scoped.state.todos, 1)

expect(subscriber).toHaveBeenCalledTimes(2)
})
Expand Down
8 changes: 4 additions & 4 deletions packages/arbor-store/tests/scoping/map.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ describe("map", () => {

expect(unwrap(scopedNode1)).toBeNodeOf(alice)
expect(unwrap(scopedNode2)).toBeNodeOf(bob)
expect(scopedNode1).toBeTrackedNode()
expect(scopedNode2).toBeTrackedNode()
expect(scopedNode1).toBeScopedNode()
expect(scopedNode2).toBeScopedNode()
})
})

Expand Down Expand Up @@ -62,8 +62,8 @@ describe("map", () => {

expect(unwrap(list[0][1])).toBeNodeOf(alice)
expect(unwrap(list[1][1])).toBeNodeOf(bob)
expect(list[0][1]).toBeTrackedNode()
expect(list[1][1]).toBeTrackedNode()
expect(list[0][1]).toBeScopedNode()
expect(list[1][1]).toBeScopedNode()
})
})
})
Loading

0 comments on commit c9209b2

Please sign in to comment.