Initial OSS commit
This commit is contained in:
commit
4a7303f40a
391 changed files with 245738 additions and 0 deletions
19
packages/dataverse/src/utils/Emitter.test.ts
Normal file
19
packages/dataverse/src/utils/Emitter.test.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Emitter from './Emitter'
|
||||
|
||||
describe.only('DataVerse.Emitter', () => {
|
||||
it('should work', () => {
|
||||
const e: Emitter<string> = new Emitter()
|
||||
e.emit('no one will see this')
|
||||
e.emit('nor this')
|
||||
|
||||
const tappedEvents: string[] = []
|
||||
const untap = e.tappable.tap((payload) => {
|
||||
tappedEvents.push(payload)
|
||||
})
|
||||
e.emit('foo')
|
||||
e.emit('bar')
|
||||
untap()
|
||||
e.emit('baz')
|
||||
expect(tappedEvents).toMatchObject(['foo', 'bar'])
|
||||
})
|
||||
})
|
55
packages/dataverse/src/utils/Emitter.ts
Normal file
55
packages/dataverse/src/utils/Emitter.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import Tappable from './Tappable'
|
||||
|
||||
type Tapper<V> = (v: V) => void
|
||||
type Untap = () => void
|
||||
|
||||
export default class Emitter<V> {
|
||||
private _tappers: Map<any, (v: V) => void>
|
||||
private _lastTapperId: number
|
||||
readonly tappable: Tappable<V>
|
||||
private _onNumberOfTappersChangeListener: undefined | ((n: number) => void)
|
||||
|
||||
constructor() {
|
||||
this._lastTapperId = 0
|
||||
this._tappers = new Map()
|
||||
this.tappable = new Tappable({
|
||||
tapToSource: (cb: Tapper<V>) => {
|
||||
return this._tap(cb)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_tap(cb: Tapper<V>): Untap {
|
||||
const tapperId = this._lastTapperId++
|
||||
this._tappers.set(tapperId, cb)
|
||||
this._onNumberOfTappersChangeListener &&
|
||||
this._onNumberOfTappersChangeListener(this._tappers.size)
|
||||
return () => {
|
||||
this._removeTapperById(tapperId)
|
||||
}
|
||||
}
|
||||
|
||||
_removeTapperById(id: number) {
|
||||
const oldSize = this._tappers.size
|
||||
this._tappers.delete(id)
|
||||
const newSize = this._tappers.size
|
||||
if (oldSize !== newSize) {
|
||||
this._onNumberOfTappersChangeListener &&
|
||||
this._onNumberOfTappersChangeListener(this._tappers.size)
|
||||
}
|
||||
}
|
||||
|
||||
emit(payload: V) {
|
||||
this._tappers.forEach((cb) => {
|
||||
cb(payload)
|
||||
})
|
||||
}
|
||||
|
||||
hasTappers() {
|
||||
return this._tappers.size !== 0
|
||||
}
|
||||
|
||||
onNumberOfTappersChange(cb: (n: number) => void) {
|
||||
this._onNumberOfTappersChangeListener = cb
|
||||
}
|
||||
}
|
56
packages/dataverse/src/utils/EventEmitter.ts
Normal file
56
packages/dataverse/src/utils/EventEmitter.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
import forEach from 'lodash-es/forEach'
|
||||
import without from 'lodash-es/without'
|
||||
import type {$FixMe} from '../types'
|
||||
|
||||
type Listener = (v: $FixMe) => void
|
||||
|
||||
/**
|
||||
* A simple barebones event emitter
|
||||
*/
|
||||
export default class EventEmitter {
|
||||
_listenersByType: {[eventName: string]: Array<Listener>}
|
||||
constructor() {
|
||||
this._listenersByType = {}
|
||||
}
|
||||
|
||||
addEventListener(eventName: string, listener: Listener) {
|
||||
const listeners =
|
||||
this._listenersByType[eventName] ||
|
||||
(this._listenersByType[eventName] = [])
|
||||
|
||||
listeners.push(listener)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
removeEventListener(eventName: string, listener: Listener) {
|
||||
const listeners = this._listenersByType[eventName]
|
||||
if (listeners) {
|
||||
const newListeners = without(listeners, listener)
|
||||
if (newListeners.length === 0) {
|
||||
delete this._listenersByType[eventName]
|
||||
} else {
|
||||
this._listenersByType[eventName] = newListeners
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
emit(eventName: string, payload: unknown) {
|
||||
const listeners = this.getListenersFor(eventName)
|
||||
if (listeners) {
|
||||
forEach(listeners, (listener) => {
|
||||
listener(payload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getListenersFor(eventName: string) {
|
||||
return this._listenersByType[eventName]
|
||||
}
|
||||
|
||||
hasListenersFor(eventName: string) {
|
||||
return this.getListenersFor(eventName) ? true : false
|
||||
}
|
||||
}
|
133
packages/dataverse/src/utils/PathBasedReducer.ts
Normal file
133
packages/dataverse/src/utils/PathBasedReducer.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
export type PathBasedReducer<S, ReturnType> = {
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
A5 extends keyof S[A0][A1][A2][A3][A4],
|
||||
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
|
||||
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
|
||||
A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7],
|
||||
A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
|
||||
A10 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10],
|
||||
reducer: (
|
||||
d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10],
|
||||
) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9][A10],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
A5 extends keyof S[A0][A1][A2][A3][A4],
|
||||
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
|
||||
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
|
||||
A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7],
|
||||
A9 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8, A9],
|
||||
reducer: (
|
||||
d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9],
|
||||
) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8][A9],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
A5 extends keyof S[A0][A1][A2][A3][A4],
|
||||
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
|
||||
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
|
||||
A8 extends keyof S[A0][A1][A2][A3][A4][A5][A6][A7],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4, A5, A6, A7, A8],
|
||||
reducer: (
|
||||
d: S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
|
||||
) => S[A0][A1][A2][A3][A4][A5][A6][A7][A8],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
A5 extends keyof S[A0][A1][A2][A3][A4],
|
||||
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
|
||||
A7 extends keyof S[A0][A1][A2][A3][A4][A5][A6],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4, A5, A6, A7],
|
||||
reducer: (
|
||||
d: S[A0][A1][A2][A3][A4][A5][A6][A7],
|
||||
) => S[A0][A1][A2][A3][A4][A5][A6][A7],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
A5 extends keyof S[A0][A1][A2][A3][A4],
|
||||
A6 extends keyof S[A0][A1][A2][A3][A4][A5],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4, A5, A6],
|
||||
reducer: (
|
||||
d: S[A0][A1][A2][A3][A4][A5][A6],
|
||||
) => S[A0][A1][A2][A3][A4][A5][A6],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
A5 extends keyof S[A0][A1][A2][A3][A4],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4, A5],
|
||||
reducer: (d: S[A0][A1][A2][A3][A4][A5]) => S[A0][A1][A2][A3][A4][A5],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
A4 extends keyof S[A0][A1][A2][A3],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3, A4],
|
||||
reducer: (d: S[A0][A1][A2][A3][A4]) => S[A0][A1][A2][A3][A4],
|
||||
): ReturnType
|
||||
|
||||
<
|
||||
A0 extends keyof S,
|
||||
A1 extends keyof S[A0],
|
||||
A2 extends keyof S[A0][A1],
|
||||
A3 extends keyof S[A0][A1][A2],
|
||||
>(
|
||||
addr: [A0, A1, A2, A3],
|
||||
reducer: (d: S[A0][A1][A2][A3]) => S[A0][A1][A2][A3],
|
||||
): ReturnType
|
||||
|
||||
<A0 extends keyof S, A1 extends keyof S[A0], A2 extends keyof S[A0][A1]>(
|
||||
addr: [A0, A1, A2],
|
||||
reducer: (d: S[A0][A1][A2]) => S[A0][A1][A2],
|
||||
): ReturnType
|
||||
|
||||
<A0 extends keyof S, A1 extends keyof S[A0]>(
|
||||
addr: [A0, A1],
|
||||
reducer: (d: S[A0][A1]) => S[A0][A1],
|
||||
): ReturnType
|
||||
|
||||
<A0 extends keyof S>(addr: [A0], reducer: (d: S[A0]) => S[A0]): ReturnType
|
||||
|
||||
(addr: undefined[], reducer: (d: S) => S): ReturnType
|
||||
}
|
33
packages/dataverse/src/utils/Stack.ts
Normal file
33
packages/dataverse/src/utils/Stack.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
interface Node<Data> {
|
||||
next: undefined | Node<Data>
|
||||
data: Data
|
||||
}
|
||||
|
||||
/**
|
||||
* Just a simple LinkedList
|
||||
*/
|
||||
export default class Stack<Data> {
|
||||
_head: undefined | Node<Data>
|
||||
|
||||
constructor() {
|
||||
this._head = undefined
|
||||
}
|
||||
|
||||
peek() {
|
||||
return this._head && this._head.data
|
||||
}
|
||||
|
||||
pop() {
|
||||
const head = this._head
|
||||
if (!head) {
|
||||
return undefined
|
||||
}
|
||||
this._head = head.next
|
||||
return head.data
|
||||
}
|
||||
|
||||
push(data: Data) {
|
||||
const node = {next: this._head, data}
|
||||
this._head = node
|
||||
}
|
||||
}
|
91
packages/dataverse/src/utils/Tappable.ts
Normal file
91
packages/dataverse/src/utils/Tappable.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
type Untap = () => void
|
||||
type UntapFromSource = () => void
|
||||
|
||||
interface IProps<V> {
|
||||
tapToSource: (cb: (payload: V) => void) => UntapFromSource
|
||||
}
|
||||
|
||||
type Listener<V> = ((v: V) => void) | (() => void)
|
||||
|
||||
export default class Tappable<V> {
|
||||
private _props: IProps<V>
|
||||
private _tappers: Map<number, {bivarianceHack(v: V): void}['bivarianceHack']>
|
||||
private _untapFromSource: null | UntapFromSource
|
||||
private _lastTapperId: number
|
||||
private _untapFromSourceTimeout: null | NodeJS.Timer = null
|
||||
|
||||
constructor(props: IProps<V>) {
|
||||
this._lastTapperId = 0
|
||||
this._untapFromSource = null
|
||||
this._props = props
|
||||
this._tappers = new Map()
|
||||
}
|
||||
|
||||
private _check() {
|
||||
if (this._untapFromSource) {
|
||||
if (this._tappers.size === 0) {
|
||||
this._scheduleToUntapFromSource()
|
||||
/*
|
||||
* this._untapFromSource()
|
||||
* this._untapFromSource = null
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
if (this._tappers.size !== 0) {
|
||||
this._untapFromSource = this._props.tapToSource(this._cb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _scheduleToUntapFromSource() {
|
||||
if (this._untapFromSourceTimeout !== null) return
|
||||
this._untapFromSourceTimeout = setTimeout(() => {
|
||||
this._untapFromSourceTimeout = null
|
||||
if (this._tappers.size === 0) {
|
||||
this._untapFromSource!()
|
||||
|
||||
this._untapFromSource = null
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
private _cb: any = (arg: any): void => {
|
||||
this._tappers.forEach((cb) => {
|
||||
cb(arg)
|
||||
})
|
||||
}
|
||||
|
||||
tap(cb: Listener<V>): Untap {
|
||||
const tapperId = this._lastTapperId++
|
||||
this._tappers.set(tapperId, cb)
|
||||
this._check()
|
||||
return () => {
|
||||
this._removeTapperById(tapperId)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* tapImmediate(cb: Listener<V>): Untap {
|
||||
* const ret = this.tap(cb)
|
||||
* return ret
|
||||
* }
|
||||
*/
|
||||
|
||||
private _removeTapperById(id: number) {
|
||||
this._tappers.delete(id)
|
||||
this._check()
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @deprecated
|
||||
// */
|
||||
// map<T>(transform: {bivarianceHack(v: V): T}['bivarianceHack']): Tappable<T> {
|
||||
// return new Tappable({
|
||||
// tapToSource: (cb: (v: T) => void) => {
|
||||
// return this.tap((v: $IntentionalAny) => {
|
||||
// return cb(transform(v))
|
||||
// })
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
}
|
12
packages/dataverse/src/utils/typeTestUtils.ts
Normal file
12
packages/dataverse/src/utils/typeTestUtils.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import type {$IntentionalAny} from '../types'
|
||||
|
||||
/**
|
||||
* Useful in type tests, such as: const a: SomeType = _any
|
||||
*/
|
||||
export const _any: $IntentionalAny = null
|
||||
|
||||
/**
|
||||
* Useful in typeTests. If you want to ensure that value v follows type V,
|
||||
* just write `expectType<V>(v)`
|
||||
*/
|
||||
export const expectType = <T extends unknown>(v: T): T => v
|
42
packages/dataverse/src/utils/updateDeep.ts
Normal file
42
packages/dataverse/src/utils/updateDeep.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import type {$FixMe, $IntentionalAny} from '../types'
|
||||
|
||||
export default function updateDeep<S>(
|
||||
state: S,
|
||||
path: (string | number | undefined)[],
|
||||
reducer: (...args: $IntentionalAny[]) => $IntentionalAny,
|
||||
): S {
|
||||
if (path.length === 0) return reducer(state)
|
||||
return hoop(state, path as $IntentionalAny, reducer)
|
||||
}
|
||||
|
||||
const hoop = (
|
||||
s: $FixMe,
|
||||
path: (string | number)[],
|
||||
reducer: $FixMe,
|
||||
): $FixMe => {
|
||||
if (path.length === 0) {
|
||||
return reducer(s)
|
||||
}
|
||||
if (Array.isArray(s)) {
|
||||
let [index, ...restOfPath] = path
|
||||
index = parseInt(String(index), 10)
|
||||
if (isNaN(index)) index = 0
|
||||
const oldVal = s[index]
|
||||
const newVal = hoop(oldVal, restOfPath, reducer)
|
||||
if (oldVal === newVal) return s
|
||||
const newS = [...s]
|
||||
newS.splice(index, 1, newVal)
|
||||
return newS
|
||||
} else if (typeof s === 'object' && s !== null) {
|
||||
const [key, ...restOfPath] = path
|
||||
const oldVal = s[key]
|
||||
const newVal = hoop(oldVal, restOfPath, reducer)
|
||||
if (oldVal === newVal) return s
|
||||
const newS = {...s, [key]: newVal}
|
||||
return newS
|
||||
} else {
|
||||
const [key, ...restOfPath] = path
|
||||
|
||||
return {[key]: hoop(undefined, restOfPath, reducer)}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue