Add hosted synthetic control plane
This commit is contained in:
parent
af04875107
commit
8dcbcd2201
21 changed files with 3695 additions and 772 deletions
|
|
@ -6,6 +6,7 @@
|
|||
".": "./src/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@islandflow/types": "workspace:*",
|
||||
"nats": "^2.24.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export * from "./jetstream";
|
||||
export * from "./streams";
|
||||
export * from "./subjects";
|
||||
export * from "./synthetic-control";
|
||||
|
|
|
|||
100
packages/bus/src/synthetic-control.ts
Normal file
100
packages/bus/src/synthetic-control.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
DEFAULT_SYNTHETIC_CONTROL_STATE,
|
||||
SyntheticControlStateSchema,
|
||||
normalizeSyntheticControlState,
|
||||
type SyntheticControlState
|
||||
} from "@islandflow/types";
|
||||
import { JSONCodec, type JetStreamClient, type KV, type KvEntry } from "nats";
|
||||
|
||||
export const SYNTHETIC_CONTROL_BUCKET = "synthetic_control";
|
||||
export const SYNTHETIC_CONTROL_GLOBAL_KEY = "global";
|
||||
|
||||
const codec = JSONCodec<SyntheticControlState>();
|
||||
|
||||
const decodeSyntheticControlEntry = (
|
||||
entry: KvEntry | null | undefined
|
||||
): SyntheticControlState => {
|
||||
if (!entry || entry.operation !== "PUT") {
|
||||
return DEFAULT_SYNTHETIC_CONTROL_STATE;
|
||||
}
|
||||
return SyntheticControlStateSchema.parse(entry.json());
|
||||
};
|
||||
|
||||
export const openSyntheticControlKv = async (
|
||||
js: JetStreamClient
|
||||
): Promise<KV> => {
|
||||
return js.views.kv(SYNTHETIC_CONTROL_BUCKET, {
|
||||
description: "Hosted synthetic market internal control state",
|
||||
history: 8
|
||||
});
|
||||
};
|
||||
|
||||
export const readSyntheticControlState = async (
|
||||
kv: KV
|
||||
): Promise<SyntheticControlState> => {
|
||||
return decodeSyntheticControlEntry(
|
||||
await kv.get(SYNTHETIC_CONTROL_GLOBAL_KEY)
|
||||
);
|
||||
};
|
||||
|
||||
export const ensureSyntheticControlState = async (
|
||||
kv: KV
|
||||
): Promise<SyntheticControlState> => {
|
||||
const current = await kv.get(SYNTHETIC_CONTROL_GLOBAL_KEY);
|
||||
if (current && current.operation === "PUT") {
|
||||
return SyntheticControlStateSchema.parse(current.json());
|
||||
}
|
||||
|
||||
await kv.put(
|
||||
SYNTHETIC_CONTROL_GLOBAL_KEY,
|
||||
codec.encode(DEFAULT_SYNTHETIC_CONTROL_STATE)
|
||||
);
|
||||
return DEFAULT_SYNTHETIC_CONTROL_STATE;
|
||||
};
|
||||
|
||||
export const writeSyntheticControlState = async (
|
||||
kv: KV,
|
||||
control: Partial<SyntheticControlState>
|
||||
): Promise<SyntheticControlState> => {
|
||||
const normalized = normalizeSyntheticControlState(control);
|
||||
await kv.put(
|
||||
SYNTHETIC_CONTROL_GLOBAL_KEY,
|
||||
codec.encode(normalized)
|
||||
);
|
||||
return normalized;
|
||||
};
|
||||
|
||||
export const watchSyntheticControlState = async (
|
||||
kv: KV,
|
||||
onUpdate: (control: SyntheticControlState) => void,
|
||||
onError?: (error: unknown) => void
|
||||
): Promise<() => Promise<void>> => {
|
||||
const iterator = await kv.watch({
|
||||
key: SYNTHETIC_CONTROL_GLOBAL_KEY,
|
||||
ignoreDeletes: true
|
||||
});
|
||||
let stopped = false;
|
||||
const task = (async () => {
|
||||
try {
|
||||
for await (const entry of iterator) {
|
||||
if (stopped || entry.operation !== "PUT") {
|
||||
continue;
|
||||
}
|
||||
onUpdate(SyntheticControlStateSchema.parse(entry.json()));
|
||||
}
|
||||
} catch (error) {
|
||||
if (!stopped) {
|
||||
onError?.(error);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return async () => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
stopped = true;
|
||||
iterator.stop();
|
||||
await task;
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue