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

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"]
}