fix alpaca news auth and native worker wiring

This commit is contained in:
dirtydishes 2026-05-19 19:57:56 -04:00
parent e9739f5dc9
commit 7d25608b35
21 changed files with 285 additions and 80 deletions

View file

@ -1,3 +1,8 @@
import {
buildAlpacaAuthHeaders,
buildAlpacaWebSocketAuthMessage,
type AlpacaCredentials
} from "@islandflow/config";
import { createLogger } from "@islandflow/observability";
import type { EquityPrint, EquityQuote } from "@islandflow/types";
import type { EquityIngestAdapter, EquityIngestHandlers } from "./types";
@ -6,7 +11,7 @@ import WebSocket from "ws";
export type AlpacaEquitiesFeed = "iex" | "sip";
export type AlpacaEquitiesAdapterConfig = {
apiKey: string;
credentials: AlpacaCredentials;
restUrl: string;
wsBaseUrl: string;
feed: AlpacaEquitiesFeed;
@ -62,12 +67,6 @@ const normalizeSymbols = (symbols: string[]): string[] => {
return result;
};
const buildHeaders = (config: AlpacaEquitiesAdapterConfig): Record<string, string> => {
return {
Authorization: `Bearer ${config.apiKey}`
};
};
const parseTimestamp = (value: string): number => {
const parsed = Date.parse(value);
if (Number.isFinite(parsed)) {
@ -157,7 +156,7 @@ const fetchExchangeMeta = async (config: AlpacaEquitiesAdapterConfig): Promise<M
try {
const response = await fetch(url.toString(), {
headers: buildHeaders(config)
headers: buildAlpacaAuthHeaders(config.credentials)
});
if (!response.ok) {
@ -184,8 +183,8 @@ export const createAlpacaEquitiesAdapter = (
return {
name: "alpaca",
start: async (handlers: EquityIngestHandlers) => {
if (!config.apiKey) {
throw new Error("Alpaca equities adapter requires ALPACA_API_KEY.");
if (!config.credentials.keyId) {
throw new Error("Alpaca equities adapter requires Alpaca credentials.");
}
const symbols = normalizeSymbols(config.symbols);
@ -196,7 +195,7 @@ export const createAlpacaEquitiesAdapter = (
const exchangeNameMap = await fetchExchangeMeta(config);
const wsUrl = buildWsUrl(config.wsBaseUrl, config.feed);
const ws = new WebSocket(wsUrl, {
headers: buildHeaders(config)
headers: buildAlpacaAuthHeaders(config.credentials)
});
let seq = 0;
@ -204,13 +203,7 @@ export const createAlpacaEquitiesAdapter = (
let authenticated = false;
ws.on("open", () => {
ws.send(
JSON.stringify({
action: "auth",
key: config.apiKey,
secret: ""
})
);
ws.send(JSON.stringify(buildAlpacaWebSocketAuthMessage(config.credentials)));
});
const subscribe = () => {

View file

@ -1,4 +1,4 @@
import { readEnv } from "@islandflow/config";
import { hasAlpacaCredentials, readEnv, resolveAlpacaCredentials } from "@islandflow/config";
import { createLogger } from "@islandflow/observability";
import {
SUBJECT_EQUITY_PRINTS,
@ -47,6 +47,10 @@ const envSchema = z.object({
// Alpaca (equities)
ALPACA_API_KEY: z.string().default(""),
ALPACA_API_KEY_ID: z.string().default(""),
ALPACA_KEY_ID: z.string().default(""),
ALPACA_API_SECRET_KEY: z.string().default(""),
ALPACA_SECRET_KEY: z.string().default(""),
ALPACA_REST_URL: z.string().default("https://data.alpaca.markets"),
ALPACA_WS_BASE_URL: z.string().default("wss://stream.data.alpaca.markets"),
ALPACA_UNDERLYINGS: z.string().default("SPY,NVDA,AAPL"),
@ -70,6 +74,7 @@ const envSchema = z.object({
});
const env = readEnv(envSchema);
const alpacaCredentials = resolveAlpacaCredentials(env);
const syntheticModes = resolveSyntheticMarketModes({
syntheticMarketMode: env.SYNTHETIC_MARKET_MODE,
syntheticEquitiesMode: env.SYNTHETIC_EQUITIES_MODE
@ -175,13 +180,15 @@ const selectAdapter = (
}
if (name === "alpaca") {
if (!env.ALPACA_API_KEY) {
logger.warn("alpaca credentials missing; set ALPACA_API_KEY");
throw new Error("ALPACA_API_KEY is required for the alpaca adapter.");
if (!hasAlpacaCredentials(alpacaCredentials)) {
logger.warn("alpaca credentials missing; set ALPACA_API_KEY_ID and ALPACA_API_SECRET_KEY");
throw new Error(
"Alpaca equities adapter requires ALPACA_API_KEY_ID and ALPACA_API_SECRET_KEY (or legacy ALPACA_API_KEY)."
);
}
return createAlpacaEquitiesAdapter({
apiKey: env.ALPACA_API_KEY,
credentials: alpacaCredentials,
restUrl: env.ALPACA_REST_URL,
wsBaseUrl: env.ALPACA_WS_BASE_URL,
feed: env.ALPACA_EQUITIES_FEED,