-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor strategy configuration & runtime-state
- use zod schemas for validation - simplify runtime state loading - merge `url` into StrategyRuntimeState (simplifies data loading) - fixes #655
- Loading branch information
Showing
28 changed files
with
149 additions
and
202 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,27 @@ | ||
import { strategyConfig } from '$lib/config'; | ||
import { z } from 'zod'; | ||
|
||
/** | ||
* TypeScript helper for having frontend side configuration for strategies. | ||
*/ | ||
export interface StrategyConfiguration { | ||
/** Strategy id - used internally in the state files, etc. */ | ||
id: string; | ||
|
||
/** Name displayed until we have loaded data from the server-side */ | ||
name: string; | ||
export const strategyConfigurationSchema = z.object({ | ||
id: z.string(), | ||
name: z.string(), | ||
url: z.string().url() | ||
}); | ||
export type StrategyConfiguration = z.infer<typeof strategyConfigurationSchema>; | ||
|
||
/** Webhook server URL */ | ||
url: string; | ||
} | ||
export type ConfiguredStrategies = Map<string, StrategyConfiguration>; | ||
|
||
/** | ||
* Get list of configured strategies. | ||
* | ||
* Typedefs JSON load from the config. | ||
* export all configured strategies as a Map for easy iteration and lookup | ||
*/ | ||
export function getConfiguredStrategies(): StrategyConfiguration[] { | ||
if (!!strategyConfig) { | ||
return strategyConfig; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
export function getConfiguredStrategyById(id: string): StrategyConfiguration | null { | ||
const strats = getConfiguredStrategies(); | ||
for (let strat of strats) { | ||
if (strat.id == id) { | ||
return strat; | ||
export const configuredStrategies: ConfiguredStrategies = strategyConfig.reduce( | ||
(acc: ConfiguredStrategies, strat: any) => { | ||
try { | ||
acc.set(strat.id, strategyConfigurationSchema.parse(strat)); | ||
} catch (e) { | ||
const message = e instanceof Error ? e.message : String(e); | ||
console.warn('Failed to parse strategy config', strat, message); | ||
} | ||
} | ||
return null; | ||
} | ||
return acc; | ||
}, | ||
new Map() | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,51 @@ | ||
/** | ||
* Strategy runtime state fetching. | ||
*/ | ||
|
||
import { getConfiguredStrategies } from './configuration'; | ||
import type { StrategyConfiguration } from './configuration'; | ||
// https://github.com/fram-x/assert-ts/issues/23 | ||
import { assert } from 'assert-ts'; | ||
import loadError from '../assets/load-error.jpg'; | ||
import { type StrategyConfiguration, configuredStrategies } from './configuration'; | ||
import { type StrategySummary, strategySummarySchema } from './summary'; | ||
import loadError from '../assets/load-error.jpg'; | ||
|
||
// use 5 second timeout when fetching strategy metadata | ||
const clientTimeout = 5000; | ||
const CLIENT_TIMEOUT = 5000; | ||
|
||
export type ConnectedRuntimeState = StrategySummary & { | ||
connected: true; | ||
id: string; | ||
}; | ||
export type ConnectedStrategyRuntimeState = StrategyConfiguration & | ||
StrategySummary & { | ||
connected: true; | ||
}; | ||
|
||
export type DisconnectedRuntimeState = { | ||
export type DisconnectedStrategyRuntimeState = StrategyConfiguration & { | ||
connected: false; | ||
id: string; | ||
name: string; | ||
icon_url: string; | ||
error: string; | ||
}; | ||
|
||
export type StrategyRuntimeState = ConnectedRuntimeState | DisconnectedRuntimeState; | ||
|
||
export async function getStrategiesWithRuntimeState( | ||
strats: StrategyConfiguration[], | ||
fetch: Fetch | ||
): Promise<StrategyRuntimeState[]> { | ||
// Load runtime state for all strategies parallel | ||
return await Promise.all( | ||
strats.map(async ({ id, name, url }) => { | ||
assert(url, `StrategyConfig URL missing: ${id}`); | ||
|
||
const endpoint = `${url}/metadata`; | ||
let resp: Partial<Response>; | ||
let error: string; | ||
|
||
try { | ||
resp = await fetch(endpoint, { signal: AbortSignal.timeout(clientTimeout) }); | ||
} catch (e) { | ||
resp = { ok: false, statusText: e.message }; | ||
} | ||
|
||
if (resp.ok) { | ||
try { | ||
const payload = await resp.json!(); | ||
|
||
const safe = strategySummarySchema.safeParse(payload); | ||
if (!safe.success) { | ||
console.error(safe.error.issues); | ||
} | ||
|
||
const summary = strategySummarySchema.parse(payload); | ||
return { connected: true, id, ...summary }; | ||
} catch (e) { | ||
error = (e as Error).message ?? `Error parsing response from ${endpoint}`; | ||
} | ||
} else { | ||
error = resp.statusText ?? `Error fetching ${endpoint}`; | ||
} | ||
|
||
return { | ||
connected: false, | ||
id, | ||
name, | ||
icon_url: loadError, | ||
error | ||
}; | ||
}) | ||
); | ||
export type StrategyRuntimeState = ConnectedStrategyRuntimeState | DisconnectedStrategyRuntimeState; | ||
|
||
export async function getStrategyRuntimeState(fetch: Fetch, id: string): Promise<StrategyRuntimeState | undefined> { | ||
const strategy = configuredStrategies.get(id); | ||
if (!strategy) return; | ||
|
||
try { | ||
const resp = await fetch(`${strategy.url}/metadata`, { signal: AbortSignal.timeout(CLIENT_TIMEOUT) }); | ||
if (!resp.ok) throw new Error(resp.statusText); | ||
const summary = strategySummarySchema.parse(await resp.json()); | ||
return { connected: true, ...strategy, ...summary }; | ||
} catch (e) { | ||
return { | ||
connected: false, | ||
...strategy, | ||
icon_url: loadError, | ||
error: e instanceof Error ? e.message : String(e) | ||
}; | ||
} | ||
} | ||
|
||
/** | ||
* Get list of configured strategies and pings server for the latest runtime state. | ||
* | ||
* Typedefs JSON load from the config. | ||
*/ | ||
export async function getConfiguredStrategiesWithRuntimeState(fetch: Fetch) { | ||
const strats = getConfiguredStrategies(); | ||
return getStrategiesWithRuntimeState(strats, fetch); | ||
} | ||
|
||
/** | ||
* Get runtime state for a single strategy | ||
* | ||
*/ | ||
export async function getStrategyRuntimeState(strategyConfig: StrategyConfiguration, fetch: Fetch) { | ||
const arr = await getStrategiesWithRuntimeState([strategyConfig], fetch); | ||
return arr[0]; | ||
export async function getStrategiesWithRuntimeState(fetch: Fetch) { | ||
// prettier-ignore | ||
return Promise.all( | ||
Array.from( | ||
configuredStrategies, | ||
async ([id]) => getStrategyRuntimeState(fetch, id) as Promise<StrategyRuntimeState> | ||
) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.