diff options
author | Elizabeth Alexander Hunt <me@liz.coffee> | 2025-05-18 22:54:15 -0700 |
---|---|---|
committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2025-05-18 22:55:20 -0700 |
commit | d54e91c6582ed160cf2f2fcf977e48b4439d133b (patch) | |
tree | 5669367c4fa49bc0373b0c581ea3027218fd5e32 /u | |
parent | 9cf3fc0259730b7dcf47b3ab4a04369e39fb4614 (diff) | |
download | ci-theBigRefactor.tar.gz ci-theBigRefactor.zip |
snapshottheBigRefactor
Diffstat (limited to 'u')
-rw-r--r-- | u/fn/either.ts | 6 | ||||
-rw-r--r-- | u/fn/mod.ts | 1 | ||||
-rw-r--r-- | u/leftpadesque/memoize.ts | 14 | ||||
-rw-r--r-- | u/leftpadesque/mod.ts | 1 | ||||
-rw-r--r-- | u/process/env.ts | 15 | ||||
-rw-r--r-- | u/process/run.ts | 33 | ||||
-rw-r--r-- | u/server/activity/fourohfour.ts | 27 | ||||
-rw-r--r-- | u/server/activity/health.ts | 77 | ||||
-rw-r--r-- | u/server/activity/mod.ts | 20 | ||||
-rw-r--r-- | u/server/filter/json.ts | 43 | ||||
-rw-r--r-- | u/server/filter/method.ts | 10 | ||||
-rw-r--r-- | u/server/filter/mod.ts | 28 | ||||
-rw-r--r-- | u/trace/metrics.ts | 24 | ||||
-rw-r--r-- | u/trace/trace.ts | 8 | ||||
-rw-r--r-- | u/trace/util.ts | 14 |
15 files changed, 207 insertions, 114 deletions
diff --git a/u/fn/either.ts b/u/fn/either.ts index 9dc1027..8b233bf 100644 --- a/u/fn/either.ts +++ b/u/fn/either.ts @@ -10,7 +10,7 @@ export interface IEither<E, T> { errBranch: Mapper<E, Ee>, okBranch: Mapper<T, Tt>, ) => IEither<Ee, Tt>; - fold: <Tt>(folder: BiMapper<E | null, T | null, Tt>) => Tt; + fold: <Tt>(folder: BiMapper<E | undefined, T | undefined, Tt>) => Tt; moveRight: <Tt>(t: Tt) => IEither<E, Tt>; mapRight: <Tt>(mapper: Mapper<T, Tt>) => IEither<E, Tt>; mapLeft: <Ee>(mapper: Mapper<E, Ee>) => IEither<Ee, T>; @@ -31,8 +31,8 @@ export class Either<E, T> implements IEither<E, T> { return this.mapRight(() => t); } - public fold<R>(folder: BiMapper<E | null, T | null, R>): R { - return folder(this.err ?? null, this.ok ?? null); + public fold<R>(folder: BiMapper<E | undefined, T | undefined, R>): R { + return folder(this.err ?? undefined, this.ok ?? undefined); } public mapBoth<Ee, Tt>( diff --git a/u/fn/mod.ts b/u/fn/mod.ts index f0fbe88..265c6db 100644 --- a/u/fn/mod.ts +++ b/u/fn/mod.ts @@ -1,2 +1,3 @@ export * from "./callable.ts"; export * from "./either.ts"; +export * from "./memoize.ts"; diff --git a/u/leftpadesque/memoize.ts b/u/leftpadesque/memoize.ts new file mode 100644 index 0000000..95e6019 --- /dev/null +++ b/u/leftpadesque/memoize.ts @@ -0,0 +1,14 @@ +import type { Callable } from "@emprespresso/pengueno"; + +export const memoize = <R, F extends Callable<R>>(fn: F): F => { + const cache = new Map<string, R>(); + return ((...args: unknown[]): R => { + const key = JSON.stringify(args); + if (cache.has(key)) { + return cache.get(key)!; + } + const res = fn.apply(args); + cache.set(key, res); + return res; + }) as F; +}; diff --git a/u/leftpadesque/mod.ts b/u/leftpadesque/mod.ts index 801846a..63d8d7a 100644 --- a/u/leftpadesque/mod.ts +++ b/u/leftpadesque/mod.ts @@ -1,3 +1,4 @@ export * from "./object.ts"; export * from "./prepend.ts"; export * from "./debug.ts"; +export * from "./memoize.ts"; diff --git a/u/process/env.ts b/u/process/env.ts index 26e1158..65f4e63 100644 --- a/u/process/env.ts +++ b/u/process/env.ts @@ -10,3 +10,18 @@ export const getRequiredEnv = (name: string): IEither<Error, string> => new Error(`environment variable "${name}" is required D:`), ) ); + +export const getRequiredEnvVars = (vars: Array<string>) => + vars + .map((envVar) => + [envVar, getRequiredEnv(envVar)] as [string, IEither<Error, string>] + ) + .reduce((acc, x: [string, IEither<Error, string>]) => { + const [envVar, eitherVal] = x; + return acc.flatMap((args) => { + return eitherVal.mapRight((envValue) => ({ + ...args, + [envVar]: envValue, + })); + }); + }, Either.right<Error, Record<string, string>>({})); diff --git a/u/process/run.ts b/u/process/run.ts index cbf8c65..4954438 100644 --- a/u/process/run.ts +++ b/u/process/run.ts @@ -6,21 +6,21 @@ import { TraceUtil, } from "@emprespresso/pengueno"; -type Command = string[] | string; +export type Command = string[] | string; type CommandOutputDecoded = { code: number; stdoutText: string; stderrText: string; }; -export class ProcessError extends Error {} export const getStdout = <Trace>( c: ITraceable<Command, Trace>, options: Deno.CommandOptions = {}, -): Promise<IEither<ProcessError, string>> => +): Promise<IEither<Error, string>> => c.bimap(TraceUtil.withFunctionTrace(getStdout)) - .map(({ item: cmd, trace }) => { - trace.trace(`:> im gonna run this command! ${cmd}`); + .map((tCmd) => { + const cmd = tCmd.get(); + tCmd.trace.trace(`:> im gonna run this command! ${cmd}`); const [exec, ...args] = (typeof cmd === "string") ? cmd.split(" ") : cmd; return new Deno.Command(exec, { args, @@ -29,12 +29,12 @@ export const getStdout = <Trace>( ...options, }).output(); }) - .map(({ item: p }) => - Either.fromFailableAsync<Error, Deno.CommandOutput>(p) + .map((tOut) => + Either.fromFailableAsync<Error, Deno.CommandOutput>(tOut.get()) ) .map( - TraceUtil.promiseify(({ item: eitherOutput, trace }) => - eitherOutput.flatMap(({ code, stderr, stdout }) => + TraceUtil.promiseify((tEitherOut) => + tEitherOut.get().flatMap(({ code, stderr, stdout }) => Either .fromFailable<Error, CommandOutputDecoded>(() => { const stdoutText = new TextDecoder().decode(stdout); @@ -42,22 +42,23 @@ export const getStdout = <Trace>( return { code, stdoutText, stderrText }; }) .mapLeft((e) => { - trace.addTrace(LogLevel.ERROR).trace(`o.o wat ${e}`); - return new ProcessError(`${e}`); + tEitherOut.trace.addTrace(LogLevel.ERROR).trace(`o.o wat ${e}`); + return new Error(`${e}`); }) - .flatMap((decodedOutput): Either<ProcessError, string> => { + .flatMap((decodedOutput): Either<Error, string> => { const { code, stdoutText, stderrText } = decodedOutput; - trace.addTrace(LogLevel.DEBUG).trace( + tEitherOut.trace.addTrace(LogLevel.DEBUG).trace( `stderr hehehe ${stderrText}`, ); if (code !== 0) { const msg = `i weceived an exit code of ${code} i wanna zewoooo :<`; - trace.addTrace(LogLevel.ERROR).trace(msg); - return Either.left(new ProcessError(msg)); + tEitherOut.trace.addTrace(LogLevel.ERROR).trace(msg); + return Either.left(new Error(msg)); } return Either.right(stdoutText); }) ) ), - ).item; + ) + .get(); diff --git a/u/server/activity/fourohfour.ts b/u/server/activity/fourohfour.ts index 48740df..6449abd 100644 --- a/u/server/activity/fourohfour.ts +++ b/u/server/activity/fourohfour.ts @@ -1,4 +1,5 @@ import { + type IActivity, type ITraceable, JsonResponse, type PenguenoRequest, @@ -16,12 +17,20 @@ const messages = [ "ヽ(;▽;)ノ Eep! This route has ghosted you~", ]; const randomFourOhFour = () => messages[Math.random() * messages.length]; -export const FourOhFourActivity = ( - req: ITraceable<PenguenoRequest, ServerTrace>, -) => - req - .move( - new JsonResponse(req, randomFourOhFour(), { - status: 404, - }), - ); + +export interface IFourOhFourActivity { + fourOhFour: IActivity; +} + +export class FourOhFourActivityImpl implements IFourOhFourActivity { + public fourOhFour( + req: ITraceable<PenguenoRequest, ServerTrace>, + ) { + return req + .move( + new JsonResponse(req, randomFourOhFour(), { status: 404 }), + ) + .map((resp) => Promise.resolve(resp.get())) + .get(); + } +} diff --git a/u/server/activity/health.ts b/u/server/activity/health.ts index b9efa3a..0f54a99 100644 --- a/u/server/activity/health.ts +++ b/u/server/activity/health.ts @@ -1,4 +1,5 @@ import { + type IActivity, type IEither, type ITraceable, JsonResponse, @@ -14,39 +15,53 @@ export enum HealthCheckInput { CHECK, } export enum HealthCheckOutput { - YAASQUEEN, + YAASSSLAYQUEEN, +} + +export interface IHealthCheckActivity { + checkHealth: IActivity; } const healthCheckMetric = Metric.fromName("Health"); -export const HealthCheckActivity = ( - check: Mapper< +export interface HealthChecker extends + Mapper< ITraceable<HealthCheckInput, ServerTrace>, Promise<IEither<Error, HealthCheckOutput>> - >, -) => -(req: ITraceable<PenguenoRequest, ServerTrace>) => - req - .bimap(TraceUtil.withFunctionTrace(HealthCheckActivity)) - .bimap(TraceUtil.withMetricTrace(healthCheckMetric)) - .flatMap((r) => r.move(HealthCheckInput.CHECK).map(check)) - .map(TraceUtil.promiseify((h) => { - const health = h.get(); - health.mapBoth((e) => { - h.trace.trace(healthCheckMetric.failure); - h.trace.addTrace(LogLevel.ERROR).trace(`${e}`); - return new JsonResponse( - req, - "oh no, i need to eat more vegetables (。•́︿•̀。)...", - { status: 500 }, - ); - }, (_healthy) => { - h.trace.trace(healthCheckMetric.success); - const msg = `think im healthy!! (✿˘◡˘) ready to do work~`; - h.trace.trace(msg); - return new JsonResponse( - req, - msg, - { status: 200 }, - ); - }); - })); + > {} +export class HealthCheckActivityImpl implements IHealthCheckActivity { + constructor( + private readonly check: HealthChecker, + ) {} + + public checkHealth(req: ITraceable<PenguenoRequest, ServerTrace>) { + return req + .bimap(TraceUtil.withFunctionTrace(this.checkHealth)) + .bimap(TraceUtil.withMetricTrace(healthCheckMetric)) + .flatMap((r) => r.move(HealthCheckInput.CHECK).map(this.check)) + .peek(TraceUtil.promiseify((h) => + h.get().fold((err) => { + if (err) { + h.trace.trace(healthCheckMetric.failure); + h.trace.addTrace(LogLevel.ERROR).trace(`${err}`); + return; + } + h.trace.trace(healthCheckMetric.success); + }) + )) + .map(TraceUtil.promiseify((h) => + h.get() + .mapBoth( + () => "oh no, i need to eat more vegetables (。•́︿•̀。)...", + () => "think im healthy!! (✿˘◡˘) ready to do work~", + ) + .fold((errMsg, okMsg) => + new JsonResponse( + req, + errMsg ?? okMsg, + { status: errMsg ? 500 : 200 }, + ) + ) + )) + .get(); + } +} diff --git a/u/server/activity/mod.ts b/u/server/activity/mod.ts index 9bd512f..82d8ec4 100644 --- a/u/server/activity/mod.ts +++ b/u/server/activity/mod.ts @@ -1,15 +1,13 @@ -import type { PenguenoResponse, RequestFilter } from "@emprespresso/pengueno"; +import type { + ITraceable, + PenguenoRequest, + PenguenoResponse, + ServerTrace, +} from "@emprespresso/pengueno"; -export enum StatusOK { - FOLLOW = 300, - OK = 200, -} -export interface ActivityOk { - readonly status: StatusOK; -} - -export interface IActivity<Trace> - extends RequestFilter<ActivityOk, Trace, PenguenoResponse> { +export interface IActivity { + (req: ITraceable<PenguenoRequest, ServerTrace>): Promise<PenguenoResponse>; } export * from "./health.ts"; +export * from "./fourohfour.ts"; diff --git a/u/server/filter/json.ts b/u/server/filter/json.ts index c839707..4a2961e 100644 --- a/u/server/filter/json.ts +++ b/u/server/filter/json.ts @@ -3,46 +3,49 @@ import { type IEither, type ITraceable, LogLevel, + Metric, + PenguenoError, type PenguenoRequest, type RequestFilter, type ServerTrace, TraceUtil, } from "@emprespresso/pengueno"; -import { Metric } from "../../trace/mod.ts"; -type JsonTransformer<R, ParsedJson = unknown> = ( - json: ITraceable<ParsedJson, ServerTrace>, -) => IEither<Error, R>; +export interface JsonTransformer<R, ParsedJson = unknown> { + (json: ITraceable<ParsedJson, ServerTrace>): IEither<PenguenoError, R>; +} const ParseJsonMetric = Metric.fromName("JsonParse"); export const jsonModel = <MessageT>( jsonTransformer: JsonTransformer<MessageT>, -): RequestFilter<MessageT, Error> => +): RequestFilter<MessageT> => (r: ITraceable<PenguenoRequest, ServerTrace>) => - r - .bimap(TraceUtil.withMetricTrace(ParseJsonMetric)) + r.bimap(TraceUtil.withMetricTrace(ParseJsonMetric)) .map((j) => Either.fromFailableAsync<Error, MessageT>(j.get().json()) .then((either) => either.mapLeft((errReason) => { j.trace.addTrace(LogLevel.WARN).trace(`${errReason}`); - return new Error("seems to be invalid JSON (>//<) can you fix?"); + return new PenguenoError( + "seems to be invalid JSON (>//<) can you fix?", + 400, + ); }) ) ) - .flatMapAsync( - TraceUtil.promiseify((traceableEitherJson) => - traceableEitherJson.map((t) => - t.get().mapRight(traceableEitherJson.move).flatMap( - jsonTransformer, - ) + .peek( + TraceUtil.promiseify((traceableEither) => + traceableEither.get().mapBoth( + () => traceableEither.trace.trace(ParseJsonMetric.failure), + () => traceableEither.trace.trace(ParseJsonMetric.success), ) ), ) - .peek(TraceUtil.promiseify((traceableEither) => - traceableEither.get().mapBoth( - () => traceableEither.trace.trace(ParseJsonMetric.failure), - () => traceableEither.trace.trace(ParseJsonMetric.success), - ) - )) + .map( + TraceUtil.promiseify((traceableEitherJson) => + traceableEitherJson.get() + .mapRight(traceableEitherJson.move) + .flatMap(jsonTransformer) + ), + ) .get(); diff --git a/u/server/filter/method.ts b/u/server/filter/method.ts index 350f04c..6b0419d 100644 --- a/u/server/filter/method.ts +++ b/u/server/filter/method.ts @@ -1,8 +1,8 @@ import { Either, type ITraceable, - JsonResponse, LogLevel, + PenguenoError, type PenguenoRequest, type RequestFilter, type ServerTrace, @@ -22,7 +22,7 @@ type HttpMethod = export const requireMethod = ( methods: Array<HttpMethod>, -): RequestFilter<HttpMethod, JsonResponse> => +): RequestFilter<HttpMethod> => (req: ITraceable<PenguenoRequest, ServerTrace>) => req.bimap(TraceUtil.withFunctionTrace(requireMethod)) .move(Promise.resolve(req.get())) @@ -32,10 +32,10 @@ export const requireMethod = ( if (!methods.includes(method)) { const msg = "that's not how you pet me (⋟﹏⋞)~"; t.trace.addTrace(LogLevel.WARN).trace(msg); - return Either.left<JsonResponse, HttpMethod>( - new JsonResponse(req, msg, { status: 405 }), + return Either.left<PenguenoError, HttpMethod>( + new PenguenoError(msg, 405), ); } - return Either.right<JsonResponse, HttpMethod>(method); + return Either.right<PenguenoError, HttpMethod>(method); })) .get(); diff --git a/u/server/filter/mod.ts b/u/server/filter/mod.ts index 22ddad5..bbf37df 100644 --- a/u/server/filter/mod.ts +++ b/u/server/filter/mod.ts @@ -1,13 +1,29 @@ -import type { - IEither, - ITraceable, - PenguenoRequest, - ServerTrace, +import { + type IEither, + type ITraceable, + LogLevel, + type PenguenoRequest, + type ServerTrace, } from "@emprespresso/pengueno"; +export enum ErrorSource { + USER = LogLevel.WARN, + SYSTEM = LogLevel.ERROR, +} + +export class PenguenoError extends Error { + public readonly source: ErrorSource; + constructor(message: string, public readonly status: number) { + super(message); + this.source = Math.floor(status / 100) === 4 + ? ErrorSource.USER + : ErrorSource.SYSTEM; + } +} + export interface RequestFilter< T, - Err, + Err extends PenguenoError = PenguenoError, RIn = ITraceable<PenguenoRequest, ServerTrace>, > { (req: RIn): Promise<IEither<Err, T>>; diff --git a/u/trace/metrics.ts b/u/trace/metrics.ts index a26ee5d..4ddde06 100644 --- a/u/trace/metrics.ts +++ b/u/trace/metrics.ts @@ -4,6 +4,7 @@ import { type ITraceWith, type Mapper, type SideEffect, + type Supplier, } from "@emprespresso/pengueno"; export enum Unit { @@ -16,10 +17,13 @@ export interface IMetric { readonly time: IEmittableMetric; readonly failure: IMetric; readonly success: IMetric; - readonly _isIMetric: true; + readonly warn: IMetric; + readonly children: Supplier<Array<IMetric>>; + + readonly _tag: "IMetric"; } export const isIMetric = (t: unknown): t is IMetric => - isObject(t) && "_isIMetric" in t; + isObject(t) && "_tag" in t && t._tag === "IMetric"; export interface IEmittableMetric { readonly name: string; @@ -35,9 +39,9 @@ export class EmittableMetric implements IEmittableMetric { return { name: this.name, unit: this.unit, - _isMetricValue: true as true, emissionTimestamp: Date.now(), value, + _tag: "MetricValue", }; } } @@ -48,15 +52,21 @@ export class Metric implements IMetric { public readonly time: IEmittableMetric, public readonly failure: Metric, public readonly success: Metric, - public readonly _isIMetric: true = true, + public readonly warn: Metric, + public readonly _tag: "IMetric" = "IMetric", ) {} + public children() { + return [this.failure, this.success, this.warn]; + } + static fromName(name: string): Metric { return new Metric( new EmittableMetric(`${name}.count`, Unit.COUNT), new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS), Metric.fromName(`${name}.failure`), Metric.fromName(`${name}.success`), + Metric.fromName(`${name}.warn`), ); } } @@ -66,10 +76,10 @@ export interface MetricValue { readonly unit: Unit; readonly value: number; readonly emissionTimestamp: number; - readonly _isMetricValue: true; + readonly _tag: "MetricValue"; } export const isMetricValue = (t: unknown): t is MetricValue => - isObject(t) && "_isMetricValue" in t; + isObject(t) && "_tag" in t && t._tag === "MetricValue"; export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => isMetricValue(t) || isIMetric(t); @@ -98,7 +108,7 @@ export class MetricsTrace implements ITrace<MetricsTraceSupplier> { const foundMetricValues = this.tracing.flatMap(( [tracing, startedTracing], ) => - [tracing, tracing.success, tracing.failure] + [tracing, ...tracing.children()] .filter((_tracing) => metric === _tracing) .flatMap((metric) => [ this.addMetric(metric, startedTracing), diff --git a/u/trace/trace.ts b/u/trace/trace.ts index 72d4eef..e942066 100644 --- a/u/trace/trace.ts +++ b/u/trace/trace.ts @@ -41,18 +41,18 @@ export type LogMetricTraceSupplier = ITraceWith< >; export class LogMetricTrace implements ITrace<LogMetricTraceSupplier> { constructor( - private readonly logTrace: ITrace<LogTraceSupplier>, - private readonly metricsTrace: ITrace<MetricsTraceSupplier>, + private logTrace: ITrace<LogTraceSupplier>, + private metricsTrace: ITrace<MetricsTraceSupplier>, ) {} public addTrace( trace: LogTraceSupplier | MetricsTraceSupplier, ): LogMetricTrace { if (isMetricsTraceSupplier(trace)) { - this.metricsTrace.addTrace(trace); + this.metricsTrace = this.metricsTrace.addTrace(trace); return this; } - this.logTrace.addTrace(trace); + this.logTrace = this.logTrace.addTrace(trace); return this; } diff --git a/u/trace/util.ts b/u/trace/util.ts index dd8fb0d..302c8e4 100644 --- a/u/trace/util.ts +++ b/u/trace/util.ts @@ -7,6 +7,16 @@ import type { } from "@emprespresso/pengueno"; export class TraceUtil { + static withTrace<T, Trace>( + trace: string, + ): ITraceableMapper< + T, + ITraceableTuple<T, Trace | Array<Trace>>, + Trace + > { + return (t) => [t.get(), `[${trace}]`]; + } + static withMetricTrace<T, Trace extends MetricsTraceSupplier>( metric: IMetric, ): ITraceableMapper< @@ -24,7 +34,7 @@ export class TraceUtil { ITraceableTuple<T, Trace | Array<Trace>>, Trace > { - return (t) => [t.get(), `[${f.name}]`]; + return TraceUtil.withTrace(f.name); } static withClassTrace<C extends object, T, Trace>( @@ -34,7 +44,7 @@ export class TraceUtil { ITraceableTuple<T, Trace | Array<Trace>>, Trace > { - return (t) => [t.get(), `[${c.constructor.name}]`]; + return TraceUtil.withTrace(c.constructor.name); } static promiseify<T, U, Trace>( |