mirror of
https://github.com/re-rxjs/react-rxjs.git
synced 2025-12-08 18:01:51 +00:00
subinstance proposal
This commit is contained in:
parent
e57de5dc3a
commit
e719931523
@ -15,11 +15,11 @@ export function createRoot<KeyValue, KeyName extends string>(
|
||||
export function createRoot<KeyValue = never, KeyName extends string = "">(
|
||||
keyName?: KeyName,
|
||||
): RootNode<KeyValue, KeyName> {
|
||||
const internalNode = createStateNode<
|
||||
null,
|
||||
RootNodeKey<KeyName, KeyValue>,
|
||||
null
|
||||
>(keyName ? [keyName] : [], [], () => of(null))
|
||||
const internalNode = createStateNode<RootNodeKey<KeyName, KeyValue>, null>(
|
||||
keyName ? [keyName] : [],
|
||||
[],
|
||||
() => of(null),
|
||||
)
|
||||
|
||||
internalNode.public.getState$ = () => {
|
||||
throw new Error("RootNode doesn't have value")
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export * from "./create-root"
|
||||
export * from "./route-state"
|
||||
export * from "./subinstance"
|
||||
export * from "./substate"
|
||||
export * from "./types"
|
||||
export { StatePromise } from "./internal/promises"
|
||||
|
||||
@ -47,9 +47,9 @@ interface GetObservableFn<K> {
|
||||
): Observable<T>
|
||||
}
|
||||
|
||||
export function createStateNode<T, K extends KeysBaseType, R>(
|
||||
export function createStateNode<K extends KeysBaseType, R>(
|
||||
keysOrder: Array<keyof K>,
|
||||
parents: Array<InternalStateNode<T, K>>,
|
||||
parents: Array<InternalStateNode<unknown, any>>,
|
||||
instanceCreator: (
|
||||
getContext: <R>(node: InternalStateNode<R, K>) => R,
|
||||
getObservable: GetObservableFn<K>,
|
||||
@ -67,7 +67,7 @@ export function createStateNode<T, K extends KeysBaseType, R>(
|
||||
}
|
||||
|
||||
const getContext = <TC>(
|
||||
otherNode: InternalStateNode<TC, K>,
|
||||
otherNode: InternalStateNode<TC, any>,
|
||||
key: K,
|
||||
visited = new Set<InternalStateNode<any, any>>(),
|
||||
) => {
|
||||
|
||||
55
packages/context-state2/src/subinstance.test.ts
Normal file
55
packages/context-state2/src/subinstance.test.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Subject, filter, map, startWith } from "rxjs"
|
||||
import { InstanceUpdate, createRoot, subinstance } from "./"
|
||||
|
||||
describe("subinstance", () => {
|
||||
it("works", () => {
|
||||
const root = createRoot()
|
||||
const instance$ = new Subject<InstanceUpdate<string>>()
|
||||
const updates$ = new Subject<{ key: string; value: string }>()
|
||||
const instanceNode = subinstance(
|
||||
root,
|
||||
"keyName",
|
||||
() => instance$,
|
||||
(id) =>
|
||||
updates$.pipe(
|
||||
filter((v) => v.key === id),
|
||||
map((v) => v.value),
|
||||
startWith(id),
|
||||
),
|
||||
)
|
||||
root.run()
|
||||
|
||||
instance$.next({
|
||||
type: "add",
|
||||
key: "a",
|
||||
})
|
||||
expect(instanceNode.getValue({ keyName: "a" })).toEqual("a")
|
||||
|
||||
instance$.next({
|
||||
type: "add",
|
||||
key: "b",
|
||||
})
|
||||
expect(instanceNode.getValue({ keyName: "a" })).toEqual("a")
|
||||
expect(instanceNode.getValue({ keyName: "b" })).toEqual("b")
|
||||
|
||||
updates$.next({
|
||||
key: "a",
|
||||
value: "new A value",
|
||||
})
|
||||
expect(instanceNode.getValue({ keyName: "a" })).toEqual("new A value")
|
||||
expect(instanceNode.getValue({ keyName: "b" })).toEqual("b")
|
||||
|
||||
instance$.next({
|
||||
type: "remove",
|
||||
key: "a",
|
||||
})
|
||||
expect(() => instanceNode.getValue({ keyName: "a" })).toThrow()
|
||||
expect(instanceNode.getValue({ keyName: "b" })).toEqual("b")
|
||||
|
||||
instance$.next({
|
||||
type: "remove",
|
||||
key: "b",
|
||||
})
|
||||
expect(() => instanceNode.getValue({ keyName: "b" })).toThrow()
|
||||
})
|
||||
})
|
||||
149
packages/context-state2/src/subinstance.ts
Normal file
149
packages/context-state2/src/subinstance.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { Observable, map, scan, startWith } from "rxjs"
|
||||
import { NestedMap, createStateNode, getInternals } from "./internal"
|
||||
import { substate } from "./substate"
|
||||
import {
|
||||
CtxFn,
|
||||
GetObservableFn,
|
||||
GetValueFn,
|
||||
KeysBaseType,
|
||||
StateNode,
|
||||
isSignal,
|
||||
} from "./types"
|
||||
|
||||
export interface InstanceUpdate<K> {
|
||||
type: "add" | "remove"
|
||||
key: K
|
||||
}
|
||||
|
||||
type MergeKey<K extends KeysBaseType, KN extends string, KV> = {
|
||||
[key in keyof K | KN]: (K & Record<KN, KV>)[key]
|
||||
}
|
||||
|
||||
export type InstanceCtxFn<T, K extends KeysBaseType, Id> = (
|
||||
id: Id,
|
||||
ctxValue: GetValueFn,
|
||||
ctxObservable: GetObservableFn<K>,
|
||||
key: K,
|
||||
) => Observable<T>
|
||||
|
||||
export function subinstance<
|
||||
T,
|
||||
K extends KeysBaseType,
|
||||
KN extends string,
|
||||
KV,
|
||||
R,
|
||||
>(
|
||||
parent: StateNode<T, K>,
|
||||
keyName: KN,
|
||||
keySelector: CtxFn<InstanceUpdate<KV>, K>,
|
||||
instanceObs: InstanceCtxFn<R, MergeKey<K, KN, KV>, KV>,
|
||||
): StateNode<R, MergeKey<K, KN, KV>> {
|
||||
const instanceKeys = substate(
|
||||
parent,
|
||||
(ctx, getObs, key) => {
|
||||
const keys = Object.assign(new Set<KV>(), {
|
||||
lastUpdate: null,
|
||||
} as {
|
||||
lastUpdate: InstanceUpdate<KV> | null
|
||||
})
|
||||
return keySelector(ctx, getObs, key).pipe(
|
||||
scan((acc, change) => {
|
||||
acc.lastUpdate = change
|
||||
if (change.type === "add") {
|
||||
acc.add(change.key)
|
||||
} else {
|
||||
acc.delete(change.key)
|
||||
}
|
||||
return acc
|
||||
}, keys),
|
||||
startWith(keys),
|
||||
)
|
||||
},
|
||||
() => false,
|
||||
)
|
||||
|
||||
const parentInternals = getInternals(parent)
|
||||
const result = createStateNode<MergeKey<K, KN, KV>, R>(
|
||||
[...parentInternals.keysOrder, keyName],
|
||||
[parentInternals],
|
||||
(ctx, obs, key) =>
|
||||
// TODO common pattern, mapping the CtxFn from internal to external
|
||||
instanceObs(
|
||||
key[keyName],
|
||||
(node) => ctx(getInternals(node)),
|
||||
((node, keys) =>
|
||||
obs(
|
||||
isSignal(node) ? node : getInternals(node),
|
||||
keys,
|
||||
)) as GetObservableFn<MergeKey<K, KN, KV>>,
|
||||
key,
|
||||
),
|
||||
)
|
||||
|
||||
const parentInstanceWatches = new NestedMap<K[keyof K], () => void>()
|
||||
function watchParentInstance(key: K) {
|
||||
const sub = instanceKeys
|
||||
.getState$(key)
|
||||
.pipe(map((v, i) => [v, i] as const))
|
||||
.subscribe(([v, i]) => {
|
||||
if (i === 0) {
|
||||
for (let instanceKey of v) {
|
||||
result.addInstance({
|
||||
...key,
|
||||
[keyName]: instanceKey,
|
||||
})
|
||||
}
|
||||
for (let instanceKey of v) {
|
||||
result.activateInstance({
|
||||
...key,
|
||||
[keyName]: instanceKey,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (v.lastUpdate?.type === "add") {
|
||||
result.addInstance({
|
||||
...key,
|
||||
[keyName]: v.lastUpdate.key,
|
||||
})
|
||||
result.activateInstance({
|
||||
...key,
|
||||
[keyName]: v.lastUpdate.key,
|
||||
})
|
||||
} else if (v.lastUpdate?.type === "remove") {
|
||||
result.removeInstance({
|
||||
...key,
|
||||
[keyName]: v.lastUpdate.key,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
parentInstanceWatches.set(
|
||||
parentInternals.keysOrder.map((k) => key[k]),
|
||||
() => sub.unsubscribe(),
|
||||
)
|
||||
}
|
||||
function stopWatchParentInstance(key: K) {
|
||||
const orderedKey = parentInternals.keysOrder.map((k) => key[k])
|
||||
const teardown = parentInstanceWatches.get(orderedKey)
|
||||
parentInstanceWatches.delete(orderedKey)
|
||||
teardown?.()
|
||||
}
|
||||
|
||||
// TODO this pattern is common on all operators... maybe it can be abstracted away? watch parent instances, do something on them, tear down their subscriptions.
|
||||
for (let instance of parentInternals.getInstances()) {
|
||||
watchParentInstance(instance.key)
|
||||
}
|
||||
|
||||
parentInternals.instanceChange$.subscribe((change) => {
|
||||
if (change.type === "added") {
|
||||
watchParentInstance(change.key)
|
||||
} else if (change.type === "removed") {
|
||||
stopWatchParentInstance(change.key)
|
||||
}
|
||||
})
|
||||
|
||||
return Object.assign(result.public, {
|
||||
instanceKeys,
|
||||
})
|
||||
}
|
||||
@ -27,7 +27,7 @@ export function isSignal<T, CK extends KeysBaseType>(
|
||||
)
|
||||
}
|
||||
|
||||
interface GetObservableFn<K> {
|
||||
export interface GetObservableFn<K> {
|
||||
<T, CK extends KeysBaseType>(
|
||||
other: K extends CK ? StateNode<T, CK> | Signal<T, CK> : never,
|
||||
): Observable<T>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user