mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 16:02:11 +00:00
189 lines
4.1 KiB
TypeScript
189 lines
4.1 KiB
TypeScript
import type { InputConnectionEval } from './inputType'
|
|
import { writable, type Writable } from 'svelte/store'
|
|
import { deepEqual } from 'fast-equals'
|
|
import sum from 'hash-sum'
|
|
export interface Subscriber<T> {
|
|
id?: string
|
|
next(v: T): void
|
|
}
|
|
|
|
export interface Observable<T> {
|
|
subscribe(x: Subscriber<T>, previousValue: T): void
|
|
}
|
|
export interface Output<T> extends Observable<T> {
|
|
set(x: T, force?: boolean): void
|
|
peak(): T | any | undefined
|
|
}
|
|
|
|
export interface Input<T> extends Subscriber<T> {
|
|
peak(): T | any | undefined
|
|
}
|
|
|
|
export type World = {
|
|
outputsById: Record<string, Record<string, Output<any>>>
|
|
connect: <T>(
|
|
connection: InputConnectionEval,
|
|
next: (x: T) => void,
|
|
id: string,
|
|
previousValue: any
|
|
) => Input<T>
|
|
stateId: Writable<number>
|
|
newOutput: <T>(id: string, name: string, previousValue: T) => Output<T>
|
|
}
|
|
|
|
export function buildWorld(context: Record<string, any>): Writable<World> {
|
|
const newWorld = buildObservableWorld()
|
|
const stateId = writable(0)
|
|
let writableWorld: Writable<World> | undefined = undefined
|
|
const outputsById: Record<string, Record<string, Output<any>>> = {
|
|
ctx: Object.fromEntries(
|
|
Object.entries(context).map(([k, v]) => {
|
|
return [k, newWorld.newOutput('ctx', k, stateId, v)]
|
|
})
|
|
),
|
|
state: {}
|
|
}
|
|
|
|
function newOutput<T>(id: string, name: string, previousValue: T) {
|
|
if (outputsById[id]?.[name]) {
|
|
return outputsById[id][name]
|
|
}
|
|
let o = newWorld.newOutput(id, name, stateId, previousValue)
|
|
if (!outputsById[id]) {
|
|
outputsById[id] = {}
|
|
}
|
|
outputsById[id][name] = o
|
|
writableWorld?.update((x) => x)
|
|
return o
|
|
}
|
|
stateId.update((x) => x + 1)
|
|
writableWorld = writable({
|
|
outputsById,
|
|
connect: newWorld.connect,
|
|
stateId,
|
|
newOutput
|
|
})
|
|
return writableWorld
|
|
}
|
|
|
|
export function buildObservableWorld() {
|
|
const observables: Record<string, Output<any>> = {}
|
|
|
|
function connect<T>(
|
|
connection: InputConnectionEval,
|
|
next: (x: T) => void,
|
|
id: string,
|
|
previousValue: T
|
|
): Input<T> {
|
|
if (!connection) {
|
|
return {
|
|
id,
|
|
peak: () => undefined,
|
|
next: () => {}
|
|
}
|
|
}
|
|
|
|
const input = cachedInput(next, id)
|
|
|
|
const { componentId, id: idc } = connection
|
|
|
|
let obs = observables[`${componentId}.${idc}`]
|
|
|
|
if (!obs) {
|
|
if (componentId != 'row' && componentId != 'iter') {
|
|
console.warn('Observable at ' + componentId + '.' + idc + ' not found')
|
|
}
|
|
return {
|
|
peak: () => undefined,
|
|
next: () => {}
|
|
}
|
|
}
|
|
|
|
obs.subscribe(input, previousValue)
|
|
return input
|
|
}
|
|
|
|
function newOutput<T>(
|
|
id: string,
|
|
name: string,
|
|
state: Writable<number>,
|
|
previousValue: T
|
|
): Output<T> {
|
|
const output = settableOutput<T>(state, previousValue)
|
|
observables[`${id}.${name}`] = output
|
|
return output
|
|
}
|
|
|
|
return {
|
|
connect,
|
|
newOutput
|
|
}
|
|
}
|
|
export function cachedInput<T>(nextParan: (x: T) => void, id?: string): Input<T> {
|
|
let value: T | undefined = undefined
|
|
|
|
function peak(): T | undefined {
|
|
return value
|
|
}
|
|
|
|
function next(x: T): void {
|
|
value = x
|
|
nextParan(x)
|
|
}
|
|
|
|
return {
|
|
id,
|
|
peak,
|
|
next
|
|
}
|
|
}
|
|
|
|
export function settableOutput<T>(state: Writable<number>, previousValue: T): Output<T> {
|
|
let value: T | undefined = previousValue
|
|
const subscribers: Subscriber<T>[] = []
|
|
|
|
function subscribe(x: Subscriber<T>, npreviousValue: any) {
|
|
let currentSubscriber = subscribers.findIndex((y) => y === x || (y.id && y.id === x.id))
|
|
if (currentSubscriber == -1) {
|
|
subscribers.push(x)
|
|
} else {
|
|
subscribers[currentSubscriber] = x
|
|
}
|
|
|
|
// Send the current value to the new subscriber if it already exists
|
|
if (value !== undefined && !deepEqual(value, npreviousValue)) {
|
|
x.next(value)
|
|
}
|
|
}
|
|
|
|
let lastHash: any = undefined
|
|
function set(x: T, force: boolean = false) {
|
|
let newHash = typeof x === 'object' ? sum(x) : x
|
|
if (lastHash != newHash || force) {
|
|
lastHash = newHash
|
|
state.update((x) => x + 1)
|
|
|
|
if (typeof x === 'object') {
|
|
if (Array.isArray(x)) {
|
|
value = [...x] as any
|
|
} else {
|
|
value = Object.assign({}, x)
|
|
}
|
|
} else {
|
|
value = x
|
|
}
|
|
subscribers.forEach((x) => x.next(value!))
|
|
}
|
|
}
|
|
|
|
function peak(): T | undefined {
|
|
return value
|
|
}
|
|
|
|
return {
|
|
subscribe,
|
|
set,
|
|
peak
|
|
}
|
|
}
|