Scaffold monorepo dev setup

This commit is contained in:
dirtydishes 2025-12-27 18:45:26 -05:00
commit d2a09e095a
47 changed files with 1033 additions and 0 deletions

16
.gitignore vendored Normal file
View file

@ -0,0 +1,16 @@
.DS_Store
.node-version
.bun
bun.lockb
node_modules/
dist/
.env
.env.*
coverage/
logs/
.tmp/
AGENTS.md
PLAN.md
CODING_STYLE.md
RESEARCH.md
README.md

44
apps/web/app/globals.css Normal file
View file

@ -0,0 +1,44 @@
:root {
color-scheme: light;
font-family: "IBM Plex Mono", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
background: #f4f3ef;
color: #1b1b1b;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
}
.page {
display: grid;
place-items: center;
min-height: 100vh;
padding: 48px 24px;
background: radial-gradient(circle at top, #fef7e4, #f4f3ef 60%);
}
.panel {
max-width: 520px;
padding: 32px 36px;
border: 1px solid #dad2c2;
border-radius: 18px;
background: #fff9ee;
box-shadow: 0 20px 40px rgba(48, 32, 12, 0.12);
}
h1 {
margin: 0 0 12px;
font-size: 2.25rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
p {
margin: 8px 0;
line-height: 1.6;
}

19
apps/web/app/layout.tsx Normal file
View file

@ -0,0 +1,19 @@
import "./globals.css";
import type { ReactNode } from "react";
export const metadata = {
title: "Islandflow",
description: "Realtime options flow & off-exchange analysis"
};
type RootLayoutProps = {
children: ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

11
apps/web/app/page.tsx Normal file
View file

@ -0,0 +1,11 @@
export default function HomePage() {
return (
<main className="page">
<section className="panel">
<h1>Islandflow</h1>
<p>Realtime options flow + off-exchange analysis.</p>
<p>UI scaffold is up; live data wiring next.</p>
</section>
</main>
);
}

4
apps/web/next-env.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// Note: This file is normally generated by Next.js.

15
apps/web/package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "@islandflow/web",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev -p 3000",
"build": "next build",
"start": "next start -p 3000"
},
"dependencies": {
"next": "^14.2.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}

11
apps/web/tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "preserve",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"incremental": true,
"noEmit": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

164
bun.lock Normal file
View file

@ -0,0 +1,164 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "islandflow",
},
"apps/web": {
"name": "@islandflow/web",
"dependencies": {
"next": "^14.2.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
},
},
"packages/config": {
"name": "@islandflow/config",
"dependencies": {
"zod": "^3.23.8",
},
},
"packages/observability": {
"name": "@islandflow/observability",
},
"packages/types": {
"name": "@islandflow/types",
"dependencies": {
"zod": "^3.23.8",
},
},
"services/api": {
"name": "@islandflow/api",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
"services/candles": {
"name": "@islandflow/candles",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
"services/compute": {
"name": "@islandflow/compute",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
"services/eod-enricher": {
"name": "@islandflow/eod-enricher",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
"services/ingest-equities": {
"name": "@islandflow/ingest-equities",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
"services/ingest-options": {
"name": "@islandflow/ingest-options",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
"services/refdata": {
"name": "@islandflow/refdata",
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*",
},
},
},
"packages": {
"@islandflow/api": ["@islandflow/api@workspace:services/api"],
"@islandflow/candles": ["@islandflow/candles@workspace:services/candles"],
"@islandflow/compute": ["@islandflow/compute@workspace:services/compute"],
"@islandflow/config": ["@islandflow/config@workspace:packages/config"],
"@islandflow/eod-enricher": ["@islandflow/eod-enricher@workspace:services/eod-enricher"],
"@islandflow/ingest-equities": ["@islandflow/ingest-equities@workspace:services/ingest-equities"],
"@islandflow/ingest-options": ["@islandflow/ingest-options@workspace:services/ingest-options"],
"@islandflow/observability": ["@islandflow/observability@workspace:packages/observability"],
"@islandflow/refdata": ["@islandflow/refdata@workspace:services/refdata"],
"@islandflow/types": ["@islandflow/types@workspace:packages/types"],
"@islandflow/web": ["@islandflow/web@workspace:apps/web"],
"@next/env": ["@next/env@14.2.35", "", {}, "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.33", "", { "os": "linux", "cpu": "x64" }, "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ=="],
"@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.33", "", { "os": "win32", "cpu": "ia32" }, "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.33", "", { "os": "win32", "cpu": "x64" }, "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg=="],
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
"@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001761", "", {}, "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next": ["next@14.2.35", "", { "dependencies": { "@next/env": "14.2.35", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.33", "@next/swc-darwin-x64": "14.2.33", "@next/swc-linux-arm64-gnu": "14.2.33", "@next/swc-linux-arm64-musl": "14.2.33", "@next/swc-linux-x64-gnu": "14.2.33", "@next/swc-linux-x64-musl": "14.2.33", "@next/swc-win32-arm64-msvc": "14.2.33", "@next/swc-win32-ia32-msvc": "14.2.33", "@next/swc-win32-x64-msvc": "14.2.33" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
"styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
}
}

30
docker-compose.yml Normal file
View file

@ -0,0 +1,30 @@
services:
clickhouse:
image: clickhouse/clickhouse-server:23.8
ports:
- "8123:8123"
- "9000:9000"
volumes:
- clickhouse-data:/var/lib/clickhouse
ulimits:
nofile:
soft: 262144
hard: 262144
redis:
image: redis:7.2
ports:
- "6379:6379"
volumes:
- redis-data:/data
nats:
image: nats:2.10
command: ["-js"]
ports:
- "4222:4222"
- "8222:8222"
volumes:
- nats-data:/data
volumes:
clickhouse-data:
redis-data:
nats-data:

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "islandflow",
"private": true,
"type": "module",
"workspaces": [
"apps/*",
"services/*",
"packages/*"
],
"scripts": {
"dev": "bun run scripts/dev.ts",
"dev:infra": "docker compose up",
"dev:infra:down": "docker compose down",
"dev:web": "bun --cwd apps/web run dev",
"dev:services": "bun run scripts/dev-services.ts"
}
}

View file

@ -0,0 +1,11 @@
{
"name": "@islandflow/config",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"zod": "^3.23.8"
}
}

View file

@ -0,0 +1,34 @@
import { z } from "zod";
export class EnvError extends Error {
readonly issues: z.ZodIssue[];
constructor(message: string, issues: z.ZodIssue[]) {
super(message);
this.name = "EnvError";
this.issues = issues;
}
}
const formatIssues = (issues: z.ZodIssue[]): string => {
return issues
.map((issue) => {
const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
return `${path}: ${issue.message}`;
})
.join("; ");
};
export const readEnv = <T extends z.ZodTypeAny>(
schema: T,
env: Record<string, string | undefined> = Bun.env
): z.infer<T> => {
const result = schema.safeParse(env);
if (!result.success) {
const details = formatIssues(result.error.issues);
throw new EnvError(`Invalid environment: ${details}`, result.error.issues);
}
return result.data;
};

View file

@ -0,0 +1 @@
export * from "./env";

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,8 @@
{
"name": "@islandflow/observability",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
}
}

View file

@ -0,0 +1,2 @@
export * from "./logger";
export * from "./metrics";

View file

@ -0,0 +1,54 @@
export type LogLevel = "debug" | "info" | "warn" | "error";
export type LogContext = Record<string, unknown>;
export type LogRecord = LogContext & {
level: LogLevel;
service: string;
msg: string;
ts: string;
};
export type LoggerFn = (msg: string, context?: LogContext) => void;
export type Logger = {
debug: LoggerFn;
info: LoggerFn;
warn: LoggerFn;
error: LoggerFn;
};
export type LoggerOptions = {
service: string;
now?: () => string;
sink?: (record: LogRecord) => void;
};
const defaultSink = (record: LogRecord) => {
console.log(JSON.stringify(record));
};
export const createLogger = ({
service,
now = () => new Date().toISOString(),
sink = defaultSink
}: LoggerOptions): Logger => {
const write = (level: LogLevel, msg: string, context?: LogContext) => {
const record: LogRecord = {
level,
service,
msg,
ts: now(),
...(context ?? {})
};
sink(record);
};
return {
debug: (msg, context) => write("debug", msg, context),
info: (msg, context) => write("info", msg, context),
warn: (msg, context) => write("warn", msg, context),
error: (msg, context) => write("error", msg, context)
};
};

View file

@ -0,0 +1,51 @@
export type MetricType = "counter" | "gauge" | "timing";
export type MetricTags = Record<string, string>;
export type MetricRecord = {
name: string;
type: MetricType;
value: number;
ts: number;
service?: string;
tags?: MetricTags;
};
export type MetricsEmitter = (record: MetricRecord) => void;
export type MetricsOptions = {
service?: string;
emit?: MetricsEmitter;
now?: () => number;
};
export type Metrics = {
count: (name: string, value?: number, tags?: MetricTags) => void;
gauge: (name: string, value: number, tags?: MetricTags) => void;
timing: (name: string, value: number, tags?: MetricTags) => void;
};
const noopEmit: MetricsEmitter = () => {};
export const createMetrics = ({
service,
emit = noopEmit,
now = () => Date.now()
}: MetricsOptions = {}): Metrics => {
const write = (type: MetricType, name: string, value: number, tags?: MetricTags) => {
emit({
name,
type,
value,
tags,
service,
ts: now()
});
};
return {
count: (name, value = 1, tags) => write("counter", name, value, tags),
gauge: (name, value, tags) => write("gauge", name, value, tags),
timing: (name, value, tags) => write("timing", name, value, tags)
};
};

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,11 @@
{
"name": "@islandflow/types",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"zod": "^3.23.8"
}
}

View file

@ -0,0 +1,105 @@
import { z } from "zod";
export const EventMetaSchema = z.object({
source_ts: z.number().int().nonnegative(),
ingest_ts: z.number().int().nonnegative(),
seq: z.number().int().nonnegative(),
trace_id: z.string().min(1)
});
export type EventMeta = z.infer<typeof EventMetaSchema>;
export const OptionPrintSchema = EventMetaSchema.merge(
z.object({
ts: z.number().int().nonnegative(),
option_contract_id: z.string().min(1),
price: z.number().nonnegative(),
size: z.number().int().positive(),
exchange: z.string().min(1),
conditions: z.array(z.string().min(1)).optional()
})
);
export type OptionPrint = z.infer<typeof OptionPrintSchema>;
export const OptionNBBOSchema = EventMetaSchema.merge(
z.object({
ts: z.number().int().nonnegative(),
option_contract_id: z.string().min(1),
bid: z.number().nonnegative(),
ask: z.number().nonnegative(),
bidSize: z.number().int().nonnegative(),
askSize: z.number().int().nonnegative()
})
);
export type OptionNBBO = z.infer<typeof OptionNBBOSchema>;
export const EquityPrintSchema = EventMetaSchema.merge(
z.object({
ts: z.number().int().nonnegative(),
underlying_id: z.string().min(1),
price: z.number().nonnegative(),
size: z.number().int().positive(),
exchange: z.string().min(1),
offExchangeFlag: z.boolean()
})
);
export type EquityPrint = z.infer<typeof EquityPrintSchema>;
export const EquityQuoteSchema = EventMetaSchema.merge(
z.object({
ts: z.number().int().nonnegative(),
underlying_id: z.string().min(1),
bid: z.number().nonnegative(),
ask: z.number().nonnegative()
})
);
export type EquityQuote = z.infer<typeof EquityQuoteSchema>;
export const FlowPacketSchema = EventMetaSchema.merge(
z.object({
id: z.string().min(1),
members: z.array(z.string().min(1)),
features: z.record(z.union([z.string(), z.number(), z.boolean()])),
join_quality: z.record(z.number())
})
);
export type FlowPacket = z.infer<typeof FlowPacketSchema>;
export const ClassifierHitSchema = z.object({
classifier_id: z.string().min(1),
confidence: z.number().min(0).max(1),
direction: z.string().min(1),
explanations: z.array(z.string().min(1))
});
export type ClassifierHit = z.infer<typeof ClassifierHitSchema>;
export const ClassifierHitEventSchema = EventMetaSchema.merge(ClassifierHitSchema);
export type ClassifierHitEvent = z.infer<typeof ClassifierHitEventSchema>;
export const AlertEventSchema = EventMetaSchema.merge(
z.object({
score: z.number(),
severity: z.string().min(1),
hits: z.array(ClassifierHitSchema),
evidence_refs: z.array(z.string().min(1))
})
);
export type AlertEvent = z.infer<typeof AlertEventSchema>;
export const InferredDarkEventSchema = EventMetaSchema.merge(
z.object({
type: z.string().min(1),
confidence: z.number().min(0).max(1),
evidence_refs: z.array(z.string().min(1))
})
);
export type InferredDarkEvent = z.infer<typeof InferredDarkEventSchema>;

View file

@ -0,0 +1 @@
export * from "./events";

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

68
scripts/dev-services.ts Normal file
View file

@ -0,0 +1,68 @@
type ChildSpec = {
name: string;
cmd: string[];
cwd: string;
};
type Child = {
name: string;
process: Bun.Subprocess;
};
const children: Child[] = [];
let shuttingDown = false;
const spawnChild = ({ name, cmd, cwd }: ChildSpec): void => {
const proc = Bun.spawn(cmd, {
cwd,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit"
});
children.push({ name, process: proc });
proc.exited.then((code) => {
if (shuttingDown) {
return;
}
const exitCode = code ?? 0;
const statusLabel = exitCode === 0 ? "exited" : "failed";
console.error(`[dev-services] ${name} ${statusLabel} (${exitCode})`);
shutdown(exitCode);
});
};
const shutdown = (code: number): void => {
if (shuttingDown) {
return;
}
shuttingDown = true;
for (const child of children) {
child.process.kill();
}
process.exit(code);
};
process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));
const tasks: ChildSpec[] = [
{ name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" },
{ name: "ingest-equities", cmd: ["bun", "run", "dev"], cwd: "services/ingest-equities" },
{ name: "compute", cmd: ["bun", "run", "dev"], cwd: "services/compute" },
{ name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" },
{ name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" },
{ name: "eod-enricher", cmd: ["bun", "run", "dev"], cwd: "services/eod-enricher" },
{ name: "api", cmd: ["bun", "run", "dev"], cwd: "services/api" }
];
for (const task of tasks) {
spawnChild(task);
}
await new Promise(() => {});

70
scripts/dev.ts Normal file
View file

@ -0,0 +1,70 @@
type ChildSpec = {
name: string;
cmd: string[];
cwd?: string;
};
type Child = {
name: string;
process: Bun.Subprocess;
};
const children: Child[] = [];
let shuttingDown = false;
const spawnChild = ({ name, cmd, cwd }: ChildSpec): void => {
const proc = Bun.spawn(cmd, {
cwd,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit"
});
children.push({ name, process: proc });
proc.exited.then((code) => {
if (shuttingDown) {
return;
}
const exitCode = code ?? 0;
const statusLabel = exitCode === 0 ? "exited" : "failed";
console.error(`[dev] ${name} ${statusLabel} (${exitCode})`);
shutdown(exitCode);
});
};
const shutdown = (code: number): void => {
if (shuttingDown) {
return;
}
shuttingDown = true;
for (const child of children) {
child.process.kill();
}
process.exit(code);
};
process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));
const tasks: ChildSpec[] = [
{ name: "infra", cmd: ["docker", "compose", "up"] },
{ name: "web", cmd: ["bun", "run", "dev"], cwd: "apps/web" },
{ name: "ingest-options", cmd: ["bun", "run", "dev"], cwd: "services/ingest-options" },
{ name: "ingest-equities", cmd: ["bun", "run", "dev"], cwd: "services/ingest-equities" },
{ name: "compute", cmd: ["bun", "run", "dev"], cwd: "services/compute" },
{ name: "candles", cmd: ["bun", "run", "dev"], cwd: "services/candles" },
{ name: "refdata", cmd: ["bun", "run", "dev"], cwd: "services/refdata" },
{ name: "eod-enricher", cmd: ["bun", "run", "dev"], cwd: "services/eod-enricher" },
{ name: "api", cmd: ["bun", "run", "dev"], cwd: "services/api" }
];
for (const task of tasks) {
spawnChild(task);
}
await new Promise(() => {});

12
services/api/package.json Normal file
View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/api",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

17
services/api/src/index.ts Normal file
View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "api";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/candles",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "candles";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/compute",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "compute";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/eod-enricher",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "eod-enricher";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/ingest-equities",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "ingest-equities";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/ingest-options",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "ingest-options";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,12 @@
{
"name": "@islandflow/refdata",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts"
},
"dependencies": {
"@islandflow/config": "workspace:*",
"@islandflow/observability": "workspace:*"
}
}

View file

@ -0,0 +1,17 @@
import { createLogger } from "@islandflow/observability";
const service = "refdata";
const logger = createLogger({ service });
logger.info("service starting");
const shutdown = (signal: string) => {
logger.info("service stopping", { signal });
process.exit(0);
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
// Keep the process alive until real listeners are wired.
setInterval(() => {}, 60_000);

View file

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*.ts"]
}

13
tsconfig.base.json Normal file
View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022"],
"strict": true,
"isolatedModules": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"noEmit": true
}
}