From d4791f3d357634daf506fb8f91cc5332a794c421 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Fri, 20 Jun 2025 14:53:38 -0700 Subject: Move to nodejs --- u/deno.json | 5 - u/fn/callable.ts | 8 +- u/fn/either.ts | 180 ++++++++++++++---------------- u/fn/index.ts | 2 + u/fn/mod.ts | 2 - u/index.ts | 6 + u/leftpadesque/debug.ts | 15 +-- u/leftpadesque/index.ts | 4 + u/leftpadesque/memoize.ts | 22 ++-- u/leftpadesque/mod.ts | 4 - u/leftpadesque/object.ts | 3 +- u/leftpadesque/prepend.ts | 8 +- u/mod.ts | 6 - u/package.json | 47 ++++++++ u/process/argv.ts | 77 +++++++------ u/process/env.ts | 53 ++++----- u/process/index.ts | 4 + u/process/mod.ts | 4 - u/process/run.ts | 102 +++++++---------- u/process/validate_identifier.ts | 23 ++-- u/server/activity/fourohfour.ts | 37 +++---- u/server/activity/health.ts | 108 +++++++++--------- u/server/activity/index.ts | 8 ++ u/server/activity/mod.ts | 13 --- u/server/filter/index.ts | 34 ++++++ u/server/filter/json.ts | 92 ++++++++-------- u/server/filter/method.ts | 67 +++++------- u/server/filter/mod.ts | 35 ------ u/server/index.ts | 7 ++ u/server/mod.ts | 7 -- u/server/request.ts | 74 ++++++------- u/server/response.ts | 139 ++++++++++++------------ u/trace/index.ts | 5 + u/trace/itrace.ts | 120 +++++++++----------- u/trace/logger.ts | 169 +++++++++++++---------------- u/trace/metrics.ts | 229 +++++++++++++++++++-------------------- u/trace/mod.ts | 5 - u/trace/trace.ts | 117 +++++++++----------- u/trace/util.ts | 76 +++++++------ u/tsconfig.json | 15 +++ 40 files changed, 915 insertions(+), 1017 deletions(-) delete mode 100644 u/deno.json create mode 100644 u/fn/index.ts delete mode 100644 u/fn/mod.ts create mode 100644 u/index.ts create mode 100644 u/leftpadesque/index.ts delete mode 100644 u/leftpadesque/mod.ts delete mode 100644 u/mod.ts create mode 100644 u/package.json create mode 100644 u/process/index.ts delete mode 100644 u/process/mod.ts create mode 100644 u/server/activity/index.ts delete mode 100644 u/server/activity/mod.ts create mode 100644 u/server/filter/index.ts delete mode 100644 u/server/filter/mod.ts create mode 100644 u/server/index.ts delete mode 100644 u/server/mod.ts create mode 100644 u/trace/index.ts delete mode 100644 u/trace/mod.ts create mode 100644 u/tsconfig.json (limited to 'u') diff --git a/u/deno.json b/u/deno.json deleted file mode 100644 index b277873..0000000 --- a/u/deno.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "@emprespresso/pengueno", - "version": "0.1.0", - "exports": "./mod.ts" -} diff --git a/u/fn/callable.ts b/u/fn/callable.ts index cfb7d00..8a61057 100644 --- a/u/fn/callable.ts +++ b/u/fn/callable.ts @@ -1,18 +1,18 @@ // deno-lint-ignore no-explicit-any export interface Callable { - (...args: Array): T; + (...args: Array): T; } export interface Supplier extends Callable { - (): T; + (): T; } export interface Mapper extends Callable { - (t: T): U; + (t: T): U; } export interface BiMapper extends Callable { - (t: T, u: U): R; + (t: T, u: U): R; } export interface SideEffect extends Mapper {} diff --git a/u/fn/either.ts b/u/fn/either.ts index ffe8033..8c47b64 100644 --- a/u/fn/either.ts +++ b/u/fn/either.ts @@ -1,114 +1,100 @@ -import { type Mapper, type Supplier, isObject } from "@emprespresso/pengueno"; +import { type Mapper, type Supplier, isObject } from '@emprespresso/pengueno'; -type IEitherTag = "IEither"; -const iEitherTag: IEitherTag = "IEither"; +type IEitherTag = 'IEither'; +const iEitherTag: IEitherTag = 'IEither'; export interface _Either { - readonly isLeft: LeftT; - readonly isRight: RightT; - readonly value: T; + readonly isLeft: LeftT; + readonly isRight: RightT; + readonly value: T; } export type Left = _Either; export type Right = _Either; export interface IEither { - readonly _tag: IEitherTag; - - mapBoth: <_E, _T>( - errBranch: Mapper, - okBranch: Mapper, - ) => IEither<_E, _T>; - fold: <_T>(folder: Mapper | Right, _T>) => _T; - moveRight: <_T>(t: _T) => IEither; - mapRight: <_T>(mapper: Mapper) => IEither; - mapLeft: <_E>(mapper: Mapper) => IEither<_E, T>; - flatMap: <_T>(mapper: Mapper>) => IEither; - flatMapAsync: <_T>( - mapper: Mapper>>, - ) => Promise>; + readonly _tag: IEitherTag; + + mapBoth: <_E, _T>(errBranch: Mapper, okBranch: Mapper) => IEither<_E, _T>; + fold: <_T>(folder: Mapper | Right, _T>) => _T; + moveRight: <_T>(t: _T) => IEither; + mapRight: <_T>(mapper: Mapper) => IEither; + mapLeft: <_E>(mapper: Mapper) => IEither<_E, T>; + flatMap: <_T>(mapper: Mapper>) => IEither; + flatMapAsync: <_T>(mapper: Mapper>>) => Promise>; } export class Either implements IEither { - private readonly self: Left | Right; - - private constructor( - init: { err?: E; ok?: T }, - public readonly _tag: IEitherTag = iEitherTag, - ) { - this.self = | Right>{ - isLeft: "err" in init, - isRight: "ok" in init, - value: init.err ?? init.ok!, - }; - } - - public moveRight<_T>(t: _T) { - return this.mapRight(() => t); - } - - public fold<_T>(folder: Mapper | Right, _T>): _T { - return folder(this.self); - } - - public mapBoth<_E, _T>( - errBranch: Mapper, - okBranch: Mapper, - ): IEither<_E, _T> { - if (this.self.isLeft) return Either.left(errBranch(this.self.value)); - return Either.right(okBranch(this.self.value)); - } - - public flatMap<_T>(mapper: Mapper>): IEither { - if (this.self.isRight) return mapper(this.self.value); - return Either.left(this.self.value); - } - - public mapRight<_T>(mapper: Mapper): IEither { - if (this.self.isRight) return Either.right(mapper(this.self.value)); - return Either.left(this.self.value); - } - - public mapLeft<_E>(mapper: Mapper): IEither<_E, T> { - if (this.self.isLeft) return Either.left<_E, T>(mapper(this.self.value)); - return Either.right<_E, T>(this.self.value); - } - - public async flatMapAsync<_T>( - mapper: Mapper>>, - ): Promise> { - if (this.self.isLeft) { - return Promise.resolve(Either.left(this.self.value)); + private readonly self: Left | Right; + + private constructor( + init: { err?: E; ok?: T }, + public readonly _tag: IEitherTag = iEitherTag, + ) { + this.self = | Right>{ + isLeft: 'err' in init, + isRight: 'ok' in init, + value: init.err ?? init.ok!, + }; } - return await mapper(this.self.value).catch((err) => - Either.left(err), - ); - } - - static left(e: E): IEither { - return new Either({ err: e }); - } - - static right(t: T): IEither { - return new Either({ ok: t }); - } - - static fromFailable(s: Supplier): IEither { - try { - return Either.right(s()); - } catch (e) { - return Either.left(e as E); + + public moveRight<_T>(t: _T) { + return this.mapRight(() => t); + } + + public fold<_T>(folder: Mapper | Right, _T>): _T { + return folder(this.self); + } + + public mapBoth<_E, _T>(errBranch: Mapper, okBranch: Mapper): IEither<_E, _T> { + if (this.self.isLeft) return Either.left(errBranch(this.self.value)); + return Either.right(okBranch(this.self.value)); + } + + public flatMap<_T>(mapper: Mapper>): IEither { + if (this.self.isRight) return mapper(this.self.value); + return Either.left(this.self.value); + } + + public mapRight<_T>(mapper: Mapper): IEither { + if (this.self.isRight) return Either.right(mapper(this.self.value)); + return Either.left(this.self.value); + } + + public mapLeft<_E>(mapper: Mapper): IEither<_E, T> { + if (this.self.isLeft) return Either.left<_E, T>(mapper(this.self.value)); + return Either.right<_E, T>(this.self.value); + } + + public async flatMapAsync<_T>(mapper: Mapper>>): Promise> { + if (this.self.isLeft) { + return Promise.resolve(Either.left(this.self.value)); + } + return await mapper(this.self.value).catch((err) => Either.left(err)); + } + + static left(e: E): IEither { + return new Either({ err: e }); + } + + static right(t: T): IEither { + return new Either({ ok: t }); + } + + static fromFailable(s: Supplier): IEither { + try { + return Either.right(s()); + } catch (e) { + return Either.left(e as E); + } + } + + static async fromFailableAsync(s: Supplier> | Promise): Promise> { + return await (typeof s === 'function' ? s() : s) + .then((t: T) => Either.right(t)) + .catch((e: E) => Either.left(e)); } - } - - static async fromFailableAsync( - s: Supplier>, - ): Promise> { - return await s() - .then((t: T) => Either.right(t)) - .catch((e: E) => Either.left(e)); - } } export const isEither = (o: unknown): o is IEither => { - return isObject(o) && "_tag" in o && o._tag === "IEither"; + return isObject(o) && '_tag' in o && o._tag === 'IEither'; }; diff --git a/u/fn/index.ts b/u/fn/index.ts new file mode 100644 index 0000000..1ec71aa --- /dev/null +++ b/u/fn/index.ts @@ -0,0 +1,2 @@ +export * from './callable.js'; +export * from './either.js'; diff --git a/u/fn/mod.ts b/u/fn/mod.ts deleted file mode 100644 index f0fbe88..0000000 --- a/u/fn/mod.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./callable.ts"; -export * from "./either.ts"; diff --git a/u/index.ts b/u/index.ts new file mode 100644 index 0000000..0c8c760 --- /dev/null +++ b/u/index.ts @@ -0,0 +1,6 @@ +export * from './fn/index.js'; +export * from './leftpadesque/index.js'; +export * from './process/index.js'; +export * from './trace/index.js'; +export * from './server/index.js'; +export * from './history.js'; diff --git a/u/leftpadesque/debug.ts b/u/leftpadesque/debug.ts index e50b2e0..074e567 100644 --- a/u/leftpadesque/debug.ts +++ b/u/leftpadesque/debug.ts @@ -1,13 +1,8 @@ -const _hasEnv = !Deno.permissions.querySync({ name: "env" }); +const _hasEnv = true; // Node.js always has access to environment variables -const _env: "development" | "production" = - _hasEnv && (Deno.env.get("ENVIRONMENT") ?? "").toLowerCase().includes("prod") - ? "production" - : "development"; -export const isProd = () => _env === "production"; +const _env: 'development' | 'production' = + _hasEnv && (process.env.ENVIRONMENT ?? '').toLowerCase().includes('prod') ? 'production' : 'development'; +export const isProd = () => _env === 'production'; -const _debug = - !isProd() || - (_hasEnv && - ["y", "t"].some((Deno.env.get("DEBUG") ?? "").toLowerCase().startsWith)); +const _debug = !isProd() || (_hasEnv && ['y', 't'].some((process.env.DEBUG ?? '').toLowerCase().startsWith)); export const isDebug = () => _debug; diff --git a/u/leftpadesque/index.ts b/u/leftpadesque/index.ts new file mode 100644 index 0000000..6403e4a --- /dev/null +++ b/u/leftpadesque/index.ts @@ -0,0 +1,4 @@ +export * from './object.js'; +export * from './prepend.js'; +export * from './debug.js'; +export * from './memoize.js'; diff --git a/u/leftpadesque/memoize.ts b/u/leftpadesque/memoize.ts index 95e6019..541bd20 100644 --- a/u/leftpadesque/memoize.ts +++ b/u/leftpadesque/memoize.ts @@ -1,14 +1,14 @@ -import type { Callable } from "@emprespresso/pengueno"; +import type { Callable } from '@emprespresso/pengueno'; export const memoize = >(fn: F): F => { - const cache = new Map(); - 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; + const cache = new Map(); + 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 deleted file mode 100644 index 63d8d7a..0000000 --- a/u/leftpadesque/mod.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./object.ts"; -export * from "./prepend.ts"; -export * from "./debug.ts"; -export * from "./memoize.ts"; diff --git a/u/leftpadesque/object.ts b/u/leftpadesque/object.ts index 73f7f80..fe97999 100644 --- a/u/leftpadesque/object.ts +++ b/u/leftpadesque/object.ts @@ -1,2 +1 @@ -export const isObject = (o: unknown): o is object => - typeof o === "object" && !Array.isArray(o) && !!o; +export const isObject = (o: unknown): o is object => typeof o === 'object' && !Array.isArray(o) && !!o; diff --git a/u/leftpadesque/prepend.ts b/u/leftpadesque/prepend.ts index 0f1ce30..d80f6b6 100644 --- a/u/leftpadesque/prepend.ts +++ b/u/leftpadesque/prepend.ts @@ -1,5 +1,5 @@ export const prependWith = (arr: string[], prep: string) => - Array(arr.length * 2) - .fill(0) - .map((_, i) => i % 2 === 0) - .map((isPrep, i) => (isPrep ? prep : arr[i])); + Array(arr.length * 2) + .fill(0) + .map((_, i) => i % 2 === 0) + .map((isPrep, i) => (isPrep ? prep : arr[i]!)); diff --git a/u/mod.ts b/u/mod.ts deleted file mode 100644 index 2ab8f68..0000000 --- a/u/mod.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from "./fn/mod.ts"; -export * from "./leftpadesque/mod.ts"; -export * from "./process/mod.ts"; -export * from "./trace/mod.ts"; -export * from "./server/mod.ts"; -export * from "./history.ts"; diff --git a/u/package.json b/u/package.json new file mode 100644 index 0000000..d38ac4c --- /dev/null +++ b/u/package.json @@ -0,0 +1,47 @@ +{ + "name": "@emprespresso/pengueno", + "version": "0.1.0", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./fn": { + "types": "./dist/fn/index.d.ts", + "import": "./dist/fn/index.js" + }, + "./leftpadesque": { + "types": "./dist/leftpadesque/index.d.ts", + "import": "./dist/leftpadesque/index.js" + }, + "./process": { + "types": "./dist/process/index.d.ts", + "import": "./dist/process/index.js" + }, + "./trace": { + "types": "./dist/trace/index.d.ts", + "import": "./dist/trace/index.js" + }, + "./server": { + "types": "./dist/server/index.d.ts", + "import": "./dist/server/index.js" + } + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "hono": "^4.8.0" + }, + "files": [ + "dist/**/*", + "package.json", + "README.md" + ] +} diff --git a/u/process/argv.ts b/u/process/argv.ts index 45f8ff0..dcdba85 100644 --- a/u/process/argv.ts +++ b/u/process/argv.ts @@ -1,7 +1,6 @@ -import { Either, type Mapper, type IEither } from "@emprespresso/pengueno"; +import { Either, type Mapper, type IEither } from '@emprespresso/pengueno'; -export const isArgKey = (k: string): k is K => - k.startsWith("--"); +export const isArgKey = (k: string): k is K => k.startsWith('--'); interface ArgHandler { absent?: V; @@ -10,42 +9,42 @@ interface ArgHandler { } export const getArg = ( - arg: K, - argv: Array, - whenValue: ArgHandler, + arg: K, + argv: Array, + whenValue: ArgHandler, ): IEither => { - const value = argv.filter((_argv) => isArgKey(_argv) && _argv.split("=")[0] === arg).map((_argv, i) => { - const next = _argv.includes("=") ? _argv.split("=")[1] : argv.at(i + 1); - if (next) { - if (isArgKey(next)) return whenValue.unspecified; - return whenValue.present(next); - } - return whenValue.unspecified; - }).find(x => x) ?? whenValue.absent; + const value = + argv + .filter((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg) + .map((_argv, i) => { + const next = _argv.includes('=') ? _argv.split('=')[1] : argv.at(i + 1); + if (next) { + if (isArgKey(next)) return whenValue.unspecified; + return whenValue.present(next); + } + return whenValue.unspecified; + }) + .find((x) => x) ?? whenValue.absent; if (value === undefined) { - return Either.left(new Error("no value specified for " + arg)); + return Either.left(new Error('no value specified for ' + arg)); } return Either.right(value); }; type MappedArgs< - Args extends ReadonlyArray, - Handlers extends Partial>> + Args extends ReadonlyArray, + Handlers extends Partial>>, > = { - [K in Args[number]]: K extends keyof Handlers - ? Handlers[K] extends ArgHandler - ? T - : string - : string; + [K in Args[number]]: K extends keyof Handlers ? (Handlers[K] extends ArgHandler ? T : string) : string; }; export const argv = < - const Args extends ReadonlyArray, - const Handlers extends Partial>> + const Args extends ReadonlyArray, + const Handlers extends Partial>>, >( args: Args, handlers?: Handlers, - argv = Deno.args, + argv = process.argv.slice(2), ): IEither> => { type Result = MappedArgs; @@ -53,20 +52,20 @@ export const argv = < const processArg = (arg: Args[number]): IEither => { const handler = handlers?.[arg] ?? defaultHandler; - return getArg(arg, argv, handler).mapRight(value => [arg, value] as const); + return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const); }; - const argResults = args.map(processArg); - - return argResults.reduce( - (acc: IEither>, current: IEither) => { - return acc.flatMap(accValue => - current.mapRight(([key, value]) => ({ - ...accValue, - [key]: value - })) - ); - }, - Either.right({} as Partial) - ).mapRight(result => result as Result); + return args + .map(processArg) + .reduce( + (acc: IEither>, current: IEither) => + acc.flatMap((accValue) => + current.mapRight(([key, value]) => ({ + ...accValue, + [key]: value, + })), + ), + Either.right(>{}), + ) + .mapRight((result) => result); }; diff --git a/u/process/env.ts b/u/process/env.ts index 5ba4189..1e4fd32 100644 --- a/u/process/env.ts +++ b/u/process/env.ts @@ -1,37 +1,30 @@ -import { Either, type IEither } from "@emprespresso/pengueno"; +import { Either, type IEither } from '@emprespresso/pengueno'; export const getRequiredEnv = (name: V): IEither => - Either.fromFailable( - () => Deno.env.get(name) as V | undefined, - ) // could throw when no permission. - .flatMap( - (v) => - (v && Either.right(v)) || - Either.left(new Error(`environment variable "${name}" is required D:`)), - ); + Either.fromFailable(() => process.env[name] as V | undefined) // could throw when no permission. + .flatMap( + (v) => (v && Either.right(v)) || Either.left(new Error(`environment variable "${name}" is required D:`)), + ); type ObjectFromList, V = string> = { - [K in T extends ReadonlyArray ? U : never]: V; + [K in T extends ReadonlyArray ? U : never]: V; }; export const getRequiredEnvVars = (vars: ReadonlyArray) => - vars - .map((envVar) => [envVar, getRequiredEnv(envVar)] as [V, IEither]) - .reduce( - ( - acc: IEither>, - x: [V, IEither], - ) => { - const [envVar, eitherVal] = x; - return acc.flatMap((args) => { - return eitherVal.mapRight( - (envValue) => - ({ - ...args, - [envVar]: envValue, - }) as ObjectFromList, - ); - }); - }, - Either.right({} as ObjectFromList), - ); + vars + .map((envVar) => [envVar, getRequiredEnv(envVar)] as [V, IEither]) + .reduce( + (acc: IEither>, x: [V, IEither]) => { + const [envVar, eitherVal] = x; + return acc.flatMap((args) => { + return eitherVal.mapRight( + (envValue) => + ({ + ...args, + [envVar]: envValue, + }) as ObjectFromList, + ); + }); + }, + Either.right({} as ObjectFromList), + ); diff --git a/u/process/index.ts b/u/process/index.ts new file mode 100644 index 0000000..4ffbf2a --- /dev/null +++ b/u/process/index.ts @@ -0,0 +1,4 @@ +export * from './env.js'; +export * from './run.js'; +export * from './validate_identifier.js'; +export * from './argv.js'; diff --git a/u/process/mod.ts b/u/process/mod.ts deleted file mode 100644 index 211e9a7..0000000 --- a/u/process/mod.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./env.ts"; -export * from "./run.ts"; -export * from "./validate_identifier.ts"; -export * from "./argv.ts"; diff --git a/u/process/run.ts b/u/process/run.ts index abe143c..e3c4c3d 100644 --- a/u/process/run.ts +++ b/u/process/run.ts @@ -1,68 +1,46 @@ import { - Either, - type IEither, - type ITraceable, - LogLevel, - type LogTraceSupplier, - TraceUtil, -} from "@emprespresso/pengueno"; + Either, + type IEither, + type ITraceable, + LogLevel, + type LogTraceSupplier, + TraceUtil, +} from '@emprespresso/pengueno'; +import { promisify } from 'node:util'; +import { exec as execCallback } from 'node:child_process'; +const exec = promisify(execCallback); export type Command = string[] | string; -type CommandOutputDecoded = { - code: number; - stdoutText: string; - stderrText: string; -}; +export type StdStreams = { stdout: string; stderr: string }; export const getStdout = ( - c: ITraceable, - options: Deno.CommandOptions = {}, + c: ITraceable, + options: { env?: Record; clearEnv?: boolean } = {}, ): Promise> => - c - .bimap(TraceUtil.withFunctionTrace(getStdout)) - .map((tCmd) => { - const cmd = tCmd.get(); - tCmd.trace.trace(`Command = ${cmd} :> im gonna run this command! `); - const [exec, ...args] = typeof cmd === "string" ? cmd.split(" ") : cmd; - return new Deno.Command(exec, { - args, - stdout: "piped", - stderr: "piped", - ...options, - }); - }) - .map((tCmd) => - Either.fromFailableAsync(() => - tCmd.get().output(), - ), - ) - .map( - TraceUtil.promiseify((tEitherOut) => - tEitherOut.get().flatMap(({ code, stderr, stdout }) => - Either.fromFailable(() => { - const stdoutText = new TextDecoder().decode(stdout); - const stderrText = new TextDecoder().decode(stderr); - return { code, stdoutText, stderrText }; - }) - .mapLeft((e) => { - tEitherOut.trace.addTrace(LogLevel.ERROR).trace(e); - return e; - }) - .flatMap((decodedOutput): IEither => { - const { code, stdoutText, stderrText } = decodedOutput; - if (stderrText) { - tEitherOut.trace - .addTrace(LogLevel.DEBUG) - .trace(`stderr: ${stderrText}`); - } - if (code !== 0) { - const msg = `i weceived an exit code of ${code} i wanna zewoooo :<`; - tEitherOut.trace.addTrace(LogLevel.ERROR).trace(msg); - return Either.left(new Error(msg)); - } - return Either.right(stdoutText); - }), - ), - ), - ) - .get(); + c + .bimap(TraceUtil.withFunctionTrace(getStdout)) + .bimap((tCmd) => { + const cmd = tCmd.get(); + tCmd.trace.trace(`Command = ${cmd} :> im gonna run this command! `); + + const _exec = typeof cmd === 'string' ? cmd : cmd.join(' '); + const env = options.clearEnv ? options.env : { ...process.env, ...options.env }; + + const p: Promise> = Either.fromFailableAsync(exec(_exec, { env })); + return [p, `Command = ${_exec}`]; + }) + .map( + TraceUtil.promiseify( + (tEitherProcess): IEither => + tEitherProcess.get().fold(({ isLeft, value }) => { + if (isLeft) { + return Either.left(value); + } + if (value.stderr) { + tEitherProcess.trace.addTrace(LogLevel.DEBUG).trace(`StdErr = ${value.stderr}`); + } + return Either.right(value.stdout); + }), + ), + ) + .get(); diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts index 4e93728..1ff3791 100644 --- a/u/process/validate_identifier.ts +++ b/u/process/validate_identifier.ts @@ -1,23 +1,18 @@ -import { Either, type IEither } from "@emprespresso/pengueno"; +import { Either, type IEither } from '@emprespresso/pengueno'; export const validateIdentifier = (token: string) => { - return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes(".."); + return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes('..'); }; // ensure {@param obj} is a Record with stuff that won't // have the potential for shell injection, just to be super safe. type InvalidEntry = [K, T]; -export const validateExecutionEntries = < - T, - K extends symbol | number | string = symbol | number | string, ->( - obj: Record, +export const validateExecutionEntries = ( + obj: Record, ): IEither>, Record> => { - const invalidEntries = >>( - Object.entries(obj).filter( - (e) => !e.every((x) => typeof x === "string" && validateIdentifier(x)), - ) - ); - if (invalidEntries.length > 0) return Either.left(invalidEntries); - return Either.right(>obj); + const invalidEntries = >>( + Object.entries(obj).filter((e) => !e.every((x) => typeof x === 'string' && validateIdentifier(x))) + ); + if (invalidEntries.length > 0) return Either.left(invalidEntries); + return Either.right(>obj); }; diff --git a/u/server/activity/fourohfour.ts b/u/server/activity/fourohfour.ts index 33cfe5f..cd90ba0 100644 --- a/u/server/activity/fourohfour.ts +++ b/u/server/activity/fourohfour.ts @@ -1,29 +1,28 @@ import { - type IActivity, - type ITraceable, - JsonResponse, - type PenguenoRequest, - type ServerTrace, -} from "@emprespresso/pengueno"; + type IActivity, + type ITraceable, + JsonResponse, + type PenguenoRequest, + type ServerTrace, +} from '@emprespresso/pengueno'; const messages = [ - "D: meow-t found! your api call ran away!", - "404-bidden! but like...in a cute way >:3 !", - ":< your data went on a paw-sible vacation!", - "uwu~ not found, but found our hearts instead!", + 'D: meow-t found! your api call ran away!', + '404-bidden! but like...in a cute way >:3 !', + ':< your data went on a paw-sible vacation!', + 'uwu~ not found, but found our hearts instead!', ]; -const randomFourOhFour = () => - messages[Math.floor(Math.random() * messages.length)]; +const randomFourOhFour = () => messages[Math.floor(Math.random() * messages.length)]!; export interface IFourOhFourActivity { - fourOhFour: IActivity; + fourOhFour: IActivity; } export class FourOhFourActivityImpl implements IFourOhFourActivity { - public fourOhFour(req: ITraceable) { - return req - .move(new JsonResponse(req, randomFourOhFour(), { status: 404 })) - .map((resp) => Promise.resolve(resp.get())) - .get(); - } + public fourOhFour(req: ITraceable) { + 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 95dfa97..b3ae559 100644 --- a/u/server/activity/health.ts +++ b/u/server/activity/health.ts @@ -1,71 +1,67 @@ import { - type IActivity, - type IEither, - type ITraceable, - JsonResponse, - LogLevel, - type Mapper, - Metric, - type PenguenoRequest, - type ServerTrace, - TraceUtil, -} from "@emprespresso/pengueno"; + type IActivity, + type IEither, + IMetric, + type ITraceable, + JsonResponse, + LogLevel, + type Mapper, + Metric, + type PenguenoRequest, + type ServerTrace, + TraceUtil, +} from '@emprespresso/pengueno'; export enum HealthCheckInput { - CHECK, + CHECK, } export enum HealthCheckOutput { - YAASSSLAYQUEEN, + YAASSSLAYQUEEN, } export interface IHealthCheckActivity { - checkHealth: IActivity; + checkHealth: IActivity; } -const healthCheckMetric = Metric.fromName("Health"); +const healthCheckMetric: IMetric = Metric.fromName('Health'); export interface HealthChecker - extends Mapper< - ITraceable, - Promise> - > {} + extends Mapper, Promise>> {} export class HealthCheckActivityImpl implements IHealthCheckActivity { - constructor(private readonly check: HealthChecker) {} + constructor(private readonly check: HealthChecker) {} - public checkHealth(req: ITraceable) { - return req - .bimap(TraceUtil.withFunctionTrace(this.checkHealth)) - .bimap(TraceUtil.withMetricTrace(healthCheckMetric)) - .flatMap((r) => - r.move(HealthCheckInput.CHECK).map((input) => this.check(input)), - ) - .peek( - TraceUtil.promiseify((h) => - h.get().fold(({ isLeft, value }) => { - if (!isLeft) { - h.trace.trace(healthCheckMetric.success); - return; - } - h.trace.trace(healthCheckMetric.failure); - h.trace.addTrace(LogLevel.ERROR).trace(value); - }), - ), - ) - .map( - TraceUtil.promiseify((h) => - h - .get() - .mapBoth( - () => "oh no, i need to eat more vegetables (。•́︿•̀。)...", - () => "think im healthy!! (✿˘◡˘) ready to do work~", + public checkHealth(req: ITraceable) { + return req + .bimap(TraceUtil.withFunctionTrace(this.checkHealth)) + .bimap(TraceUtil.withMetricTrace(healthCheckMetric)) + .flatMap((r) => r.move(HealthCheckInput.CHECK).map((input) => this.check(input))) + .peek( + TraceUtil.promiseify((h) => + h.get().fold(({ isLeft, value }) => { + if (!isLeft) { + h.trace.trace(healthCheckMetric.success); + return; + } + h.trace.trace(healthCheckMetric.failure); + h.trace.addTrace(LogLevel.ERROR).trace(value); + }), + ), ) - .fold( - ({ isLeft, value: message }) => - new JsonResponse(req, message, { - status: isLeft ? 500 : 200, - }), - ), - ), - ) - .get(); - } + .map( + TraceUtil.promiseify((h) => + h + .get() + .mapBoth( + () => 'oh no, i need to eat more vegetables (。•́︿•̀。)...', + () => 'think im healthy!! (✿˘◡˘) ready to do work~', + ) + .fold( + ({ isLeft, value: message }) => + new JsonResponse(req, message, { + status: isLeft ? 500 : 200, + }), + ), + ), + ) + .get(); + } } diff --git a/u/server/activity/index.ts b/u/server/activity/index.ts new file mode 100644 index 0000000..fa0a6b2 --- /dev/null +++ b/u/server/activity/index.ts @@ -0,0 +1,8 @@ +import type { ITraceable, PenguenoRequest, PenguenoResponse, ServerTrace } from '@emprespresso/pengueno'; + +export interface IActivity { + (req: ITraceable): Promise; +} + +export * from './health.js'; +export * from './fourohfour.js'; diff --git a/u/server/activity/mod.ts b/u/server/activity/mod.ts deleted file mode 100644 index 82d8ec4..0000000 --- a/u/server/activity/mod.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { - ITraceable, - PenguenoRequest, - PenguenoResponse, - ServerTrace, -} from "@emprespresso/pengueno"; - -export interface IActivity { - (req: ITraceable): Promise; -} - -export * from "./health.ts"; -export * from "./fourohfour.ts"; diff --git a/u/server/filter/index.ts b/u/server/filter/index.ts new file mode 100644 index 0000000..62a584d --- /dev/null +++ b/u/server/filter/index.ts @@ -0,0 +1,34 @@ +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( + override readonly message: string, + public readonly status: number, + ) { + super(message); + this.source = Math.floor(status / 100) === 4 ? ErrorSource.USER : ErrorSource.SYSTEM; + } +} + +export interface RequestFilter< + T, + Err extends PenguenoError = PenguenoError, + RIn = ITraceable, +> { + (req: RIn): Promise>; +} + +export * from './method.js'; +export * from './json.js'; diff --git a/u/server/filter/json.ts b/u/server/filter/json.ts index 145d1be..527d483 100644 --- a/u/server/filter/json.ts +++ b/u/server/filter/json.ts @@ -1,54 +1,50 @@ import { - Either, - type IEither, - type ITraceable, - LogLevel, - Metric, - PenguenoError, - type PenguenoRequest, - type RequestFilter, - type ServerTrace, - TraceUtil, -} from "@emprespresso/pengueno"; + Either, + type IEither, + type ITraceable, + LogLevel, + Metric, + PenguenoError, + type PenguenoRequest, + type RequestFilter, + type ServerTrace, + TraceUtil, +} from '@emprespresso/pengueno'; export interface JsonTransformer { - (json: ITraceable): IEither; + (json: ITraceable): IEither; } -const ParseJsonMetric = Metric.fromName("JsonParse"); +const ParseJsonMetric = Metric.fromName('JsonParse'); export const jsonModel = - ( - jsonTransformer: JsonTransformer, - ): RequestFilter => - (r: ITraceable) => - r - .bimap(TraceUtil.withFunctionTrace(jsonModel)) - .bimap(TraceUtil.withMetricTrace(ParseJsonMetric)) - .map((j) => - Either.fromFailableAsync(() => j.get().json()).then( - (either) => - either.mapLeft((errReason) => { - j.trace.addTrace(LogLevel.WARN).trace(errReason); - return new PenguenoError( - "seems to be invalid JSON (>//<) can you fix?", - 400, - ); - }), - ), - ) - .peek( - TraceUtil.promiseify((traceableEither) => - traceableEither.get().fold(({ isLeft }) => - traceableEither.trace.trace(ParseJsonMetric[isLeft ? "failure" : "success"]) - ), - ), - ) - .map( - TraceUtil.promiseify((traceableEitherJson) => - traceableEitherJson - .get() - .mapRight((j) => traceableEitherJson.move(j)) - .flatMap(jsonTransformer), - ), - ) - .get(); + (jsonTransformer: JsonTransformer): RequestFilter => + (r: ITraceable) => + r + .bimap(TraceUtil.withFunctionTrace(jsonModel)) + .bimap(TraceUtil.withMetricTrace(ParseJsonMetric)) + .map((j) => + Either.fromFailableAsync(>j.get().json()).then((either) => + either.mapLeft((errReason) => { + j.trace.addTrace(LogLevel.WARN).trace(errReason); + return new PenguenoError('seems to be invalid JSON (>//<) can you fix?', 400); + }), + ), + ) + .peek( + TraceUtil.promiseify((traceableEither) => + traceableEither + .get() + .fold(({ isLeft }) => + traceableEither.trace.trace(ParseJsonMetric[isLeft ? 'failure' : 'success']), + ), + ), + ) + .map( + TraceUtil.promiseify((traceableEitherJson) => + traceableEitherJson + .get() + .mapRight((j) => traceableEitherJson.move(j)) + .flatMap(jsonTransformer), + ), + ) + .get(); diff --git a/u/server/filter/method.ts b/u/server/filter/method.ts index 9901c6f..5ca5716 100644 --- a/u/server/filter/method.ts +++ b/u/server/filter/method.ts @@ -1,43 +1,32 @@ import { - Either, - type ITraceable, - LogLevel, - PenguenoError, - type PenguenoRequest, - type RequestFilter, - type ServerTrace, - TraceUtil, -} from "@emprespresso/pengueno"; + Either, + type ITraceable, + LogLevel, + PenguenoError, + type PenguenoRequest, + type RequestFilter, + type ServerTrace, + TraceUtil, +} from '@emprespresso/pengueno'; -type HttpMethod = - | "POST" - | "GET" - | "HEAD" - | "PUT" - | "DELETE" - | "CONNECT" - | "OPTIONS" - | "TRACE" - | "PATCH"; +type HttpMethod = 'POST' | 'GET' | 'HEAD' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'; export const requireMethod = - (methods: Array): RequestFilter => - (req: ITraceable) => - req - .bimap(TraceUtil.withFunctionTrace(requireMethod)) - .move(Promise.resolve(req.get())) - .map( - TraceUtil.promiseify((t) => { - const { method: _method } = t.get(); - const method = _method; - if (!methods.includes(method)) { - const msg = "that's not how you pet me (⋟﹏⋞)~"; - t.trace.addTrace(LogLevel.WARN).trace(msg); - return Either.left( - new PenguenoError(msg, 405), - ); - } - return Either.right(method); - }), - ) - .get(); + (methods: Array): RequestFilter => + (req: ITraceable) => + req + .bimap(TraceUtil.withFunctionTrace(requireMethod)) + .move(Promise.resolve(req.get())) + .map( + TraceUtil.promiseify((t) => { + const { method: _method } = t.get(); + const method = _method; + if (!methods.includes(method)) { + const msg = "that's not how you pet me (⋟﹏⋞)~"; + t.trace.addTrace(LogLevel.WARN).trace(msg); + return Either.left(new PenguenoError(msg, 405)); + } + return Either.right(method); + }), + ) + .get(); diff --git a/u/server/filter/mod.ts b/u/server/filter/mod.ts deleted file mode 100644 index 0e0a4cb..0000000 --- a/u/server/filter/mod.ts +++ /dev/null @@ -1,35 +0,0 @@ -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( - override readonly message: string, - public readonly status: number, - ) { - super(message); - this.source = - Math.floor(status / 100) === 4 ? ErrorSource.USER : ErrorSource.SYSTEM; - } -} - -export interface RequestFilter< - T, - Err extends PenguenoError = PenguenoError, - RIn = ITraceable, -> { - (req: RIn): Promise>; -} - -export * from "./method.ts"; -export * from "./json.ts"; diff --git a/u/server/index.ts b/u/server/index.ts new file mode 100644 index 0000000..17cbbdf --- /dev/null +++ b/u/server/index.ts @@ -0,0 +1,7 @@ +import type { LogMetricTraceSupplier } from '@emprespresso/pengueno'; +export type ServerTrace = LogMetricTraceSupplier; + +export * from './activity/index.js'; +export * from './filter/index.js'; +export * from './response.js'; +export * from './request.js'; diff --git a/u/server/mod.ts b/u/server/mod.ts deleted file mode 100644 index 866b5f9..0000000 --- a/u/server/mod.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { LogMetricTraceSupplier } from "@emprespresso/pengueno"; -export type ServerTrace = LogMetricTraceSupplier; - -export * from "./activity/mod.ts"; -export * from "./filter/mod.ts"; -export * from "./response.ts"; -export * from "./request.ts"; diff --git a/u/server/request.ts b/u/server/request.ts index 72e812a..10610f1 100644 --- a/u/server/request.ts +++ b/u/server/request.ts @@ -1,49 +1,39 @@ -import { LogMetricTraceable, LogTraceable } from "@emprespresso/pengueno"; -import { TraceUtil } from "../trace/util.ts"; +import { TraceUtil, LogMetricTraceable, LogTraceable } from '@emprespresso/pengueno'; -const greetings = [ - "hewwo :D", - "hiya cutie", - "boop!", - "sending virtual hugs!", - "stay pawsitive", -]; -const penguenoGreeting = () => - greetings[Math.floor(Math.random() * greetings.length)]; +const greetings = ['hewwo :D', 'hiya cutie', 'boop!', 'sending virtual hugs!', 'stay pawsitive']; +const penguenoGreeting = () => greetings[Math.floor(Math.random() * greetings.length)]; export class PenguenoRequest extends Request { - private constructor( - _input: Request, - public readonly id: string, - public readonly at: Date, - ) { - super(_input); - } + private constructor( + _input: Request, + public readonly id: string, + public readonly at: Date, + ) { + super(_input); + } - public baseResponseHeaders(): Record { - const ServerRequestTime = this.at.getTime(); - const ServerResponseTime = Date.now(); - const DeltaTime = ServerResponseTime - ServerRequestTime; - const RequestId = this.id; + public baseResponseHeaders(): Record { + const ServerRequestTime = this.at.getTime(); + const ServerResponseTime = Date.now(); + const DeltaTime = ServerResponseTime - ServerRequestTime; + const RequestId = this.id; - return Object.entries({ - RequestId, - ServerRequestTime, - ServerResponseTime, - DeltaTime, - Hai: penguenoGreeting(), - }).reduce((acc, [key, val]) => ({ ...acc, [key]: val.toString() }), {}); - } + return Object.entries({ + RequestId, + ServerRequestTime, + ServerResponseTime, + DeltaTime, + Hai: penguenoGreeting(), + }).reduce((acc, [key, val]) => ({ ...acc, [key]: val!.toString() }), {}); + } - public static from(request: Request): LogMetricTraceable { - const id = crypto.randomUUID(); - const url = new URL(request.url); - const { pathname } = url; - const logTraceable = LogTraceable.of( - new PenguenoRequest(request, id, new Date()), - ).bimap( - TraceUtil.withTrace(`RequestId = ${id}, Method = ${request.method}, Path = ${pathname}`), - ); - return LogMetricTraceable.ofLogTraceable(logTraceable); - } + public static from(request: Request): LogMetricTraceable { + const id = crypto.randomUUID(); + const url = new URL(request.url); + const { pathname } = url; + const logTraceable = LogTraceable.of(new PenguenoRequest(request, id, new Date())).bimap( + TraceUtil.withTrace(`RequestId = ${id}, Method = ${request.method}, Path = ${pathname}`), + ); + return LogMetricTraceable.ofLogTraceable(logTraceable); + } } diff --git a/u/server/response.ts b/u/server/response.ts index 629dbb5..18d70b5 100644 --- a/u/server/response.ts +++ b/u/server/response.ts @@ -1,86 +1,83 @@ import { - type IEither, - isEither, - type ITraceable, - Metric, - type PenguenoRequest, - type ServerTrace, -} from "@emprespresso/pengueno"; + type IEither, + isEither, + type ITraceable, + Metric, + type PenguenoRequest, + type ServerTrace, +} from '@emprespresso/pengueno'; +export type BodyInit = + | ArrayBuffer + | AsyncIterable + | Blob + | FormData + | Iterable + | NodeJS.ArrayBufferView + | URLSearchParams + | null + | string; export type ResponseBody = object | string; -export type TResponseInit = ResponseInit & { - status: number; - headers?: Record; +export type TResponseInit = Omit & { + status: number; + headers?: Record; }; -const getResponse = ( - req: PenguenoRequest, - opts: TResponseInit, -): TResponseInit => { - return { - ...opts, - headers: { - ...req.baseResponseHeaders(), - ...opts?.headers, - "Content-Type": - (opts?.headers?.["Content-Type"] ?? "text/plain") + "; charset=utf-8", - }, - }; +const getResponse = (req: PenguenoRequest, opts: TResponseInit): ResponseInit => { + const baseHeaders = req.baseResponseHeaders(); + const optHeaders = opts.headers || {}; + + return { + ...opts, + headers: { + ...baseHeaders, + ...optHeaders, + 'Content-Type': (optHeaders['Content-Type'] ?? 'text/plain') + '; charset=utf-8', + } as Record, + }; }; -const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => - Metric.fromName(`response.${x}xx`), -); -export const getResponseMetric = (status: number) => { - const index = Math.floor(status / 100); - return ResponseCodeMetrics[index] ?? ResponseCodeMetrics[5]; +const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => Metric.fromName(`response.${x}xx`)); +export const getResponseMetrics = (status: number) => { + const index = Math.floor(status / 100); + return ResponseCodeMetrics.map((metric, i) => metric.count.withValue(i === index ? 1.0 : 0.0)); }; export class PenguenoResponse extends Response { - constructor( - req: ITraceable, - msg: BodyInit, - opts: TResponseInit, - ) { - const responseOpts = getResponse(req.get(), opts); - const resMetric = getResponseMetric(opts.status); - req.trace.trace(resMetric.count.withValue(1.0)); - responseOpts.headers; - super(msg, responseOpts); - } + constructor(req: ITraceable, msg: BodyInit, opts: TResponseInit) { + const responseOpts = getResponse(req.get(), opts); + for (const metric of getResponseMetrics(opts.status)) { + req.trace.trace(metric); + } + super(msg, responseOpts); + } } export class JsonResponse extends PenguenoResponse { - constructor( - req: ITraceable, - e: BodyInit | IEither, - opts: TResponseInit, - ) { - const optsWithJsonContentType = { - ...opts, - headers: { - ...opts?.headers, - "Content-Type": "application/json", - }, - }; - if (isEither(e)) { - super( - req, - JSON.stringify( - e.fold(({ isLeft, value }) => - isLeft ? { error: value } : { ok: value }, - ), - ), - optsWithJsonContentType, - ); - return; + constructor( + req: ITraceable, + e: BodyInit | IEither, + opts: TResponseInit, + ) { + const optsWithJsonContentType: TResponseInit = { + ...opts, + headers: { + ...opts.headers, + 'Content-Type': 'application/json', + }, + }; + if (isEither(e)) { + super( + req, + JSON.stringify(e.fold(({ isLeft, value }) => (isLeft ? { error: value } : { ok: value }))), + optsWithJsonContentType, + ); + return; + } + super( + req, + JSON.stringify(Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e }), + optsWithJsonContentType, + ); } - super( - req, - JSON.stringify( - Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e }, - ), - optsWithJsonContentType, - ); - } } diff --git a/u/trace/index.ts b/u/trace/index.ts new file mode 100644 index 0000000..18da87a --- /dev/null +++ b/u/trace/index.ts @@ -0,0 +1,5 @@ +export * from './itrace.js'; +export * from './util.js'; +export * from './logger.js'; +export * from './metrics.js'; +export * from './trace.js'; diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts index fcfbe32..8cf123a 100644 --- a/u/trace/itrace.ts +++ b/u/trace/itrace.ts @@ -1,90 +1,72 @@ -import type { Mapper, SideEffect, Supplier } from "@emprespresso/pengueno"; +import type { Mapper, SideEffect, Supplier } from '@emprespresso/pengueno'; // the "thing" every Trace writer must "trace()" type BaseTraceWith = string; export type ITraceWith = BaseTraceWith | T; export interface ITrace { - addTrace: Mapper, ITrace>; - trace: SideEffect>; + addTrace: Mapper, ITrace>; + trace: SideEffect>; } export type ITraceableTuple = [T, BaseTraceWith | TraceWith]; -export type ITraceableMapper> = ( - w: W, -) => _T; +export type ITraceableMapper> = (w: W) => _T; export interface ITraceable { - readonly trace: ITrace; - get: Supplier; - move: <_T>(t: _T) => ITraceable<_T, Trace>; - map: <_T>(mapper: ITraceableMapper) => ITraceable<_T, Trace>; - bimap: <_T>( - mapper: ITraceableMapper< - T, - ITraceableTuple<_T, Array | Trace>, - Trace - >, - ) => ITraceable<_T, Trace>; - peek: (peek: ITraceableMapper) => ITraceable; - flatMap: <_T>( - mapper: ITraceableMapper, Trace>, - ) => ITraceable<_T, Trace>; - flatMapAsync<_T>( - mapper: ITraceableMapper>, Trace>, - ): ITraceable, Trace>; + readonly trace: ITrace; + get: Supplier; + move: <_T>(t: _T) => ITraceable<_T, Trace>; + map: <_T>(mapper: ITraceableMapper) => ITraceable<_T, Trace>; + bimap: <_T>(mapper: ITraceableMapper | Trace>, Trace>) => ITraceable<_T, Trace>; + peek: (peek: ITraceableMapper) => ITraceable; + flatMap: <_T>(mapper: ITraceableMapper, Trace>) => ITraceable<_T, Trace>; + flatMapAsync<_T>( + mapper: ITraceableMapper>, Trace>, + ): ITraceable, Trace>; } export class TraceableImpl implements ITraceable { - protected constructor( - private readonly item: T, - public readonly trace: ITrace, - ) {} + protected constructor( + private readonly item: T, + public readonly trace: ITrace, + ) {} - public map<_T>(mapper: ITraceableMapper) { - const result = mapper(this); - return new TraceableImpl(result, this.trace); - } + public map<_T>(mapper: ITraceableMapper) { + const result = mapper(this); + return new TraceableImpl(result, this.trace); + } - public flatMap<_T>( - mapper: ITraceableMapper, TraceWith>, - ): ITraceable<_T, TraceWith> { - return mapper(this); - } + public flatMap<_T>(mapper: ITraceableMapper, TraceWith>): ITraceable<_T, TraceWith> { + return mapper(this); + } - public flatMapAsync<_T>( - mapper: ITraceableMapper>, TraceWith>, - ): ITraceable, TraceWith> { - return new TraceableImpl( - mapper(this).then((t) => t.get()), - this.trace, - ); - } + public flatMapAsync<_T>( + mapper: ITraceableMapper>, TraceWith>, + ): ITraceable, TraceWith> { + return new TraceableImpl( + mapper(this).then((t) => t.get()), + this.trace, + ); + } - public peek(peek: ITraceableMapper) { - peek(this); - return this; - } + public peek(peek: ITraceableMapper) { + peek(this); + return this; + } - public move<_T>(t: _T): ITraceable<_T, TraceWith> { - return this.map(() => t); - } + public move<_T>(t: _T): ITraceable<_T, TraceWith> { + return this.map(() => t); + } - public bimap<_T>( - mapper: ITraceableMapper< - T, - ITraceableTuple<_T, Array | TraceWith>, - TraceWith - >, - ) { - const [item, trace] = mapper(this); - const traces = Array.isArray(trace) ? trace : [trace]; - return new TraceableImpl( - item, - traces.reduce((trace, _trace) => trace.addTrace(_trace), this.trace), - ); - } + public bimap<_T>(mapper: ITraceableMapper | TraceWith>, TraceWith>) { + const [item, trace] = mapper(this); + const traces = Array.isArray(trace) ? trace : [trace]; + return new TraceableImpl( + item, + traces.reduce((trace, _trace) => trace.addTrace(_trace), this.trace), + ); + } - public get() { - return this.item; - } + public get() { + return this.item; + } } diff --git a/u/trace/logger.ts b/u/trace/logger.ts index d8392eb..91432fe 100644 --- a/u/trace/logger.ts +++ b/u/trace/logger.ts @@ -1,112 +1,95 @@ -import { - isDebug, - type ITrace, - type ITraceWith, - type Supplier, -} from "@emprespresso/pengueno"; +import { isDebug, type ITrace, type ITraceWith, type Supplier } from '@emprespresso/pengueno'; export type LogTraceSupplier = ITraceWith | Error>; const defaultTrace = () => `TimeStamp = ${new Date().toISOString()}`; export class LogTrace implements ITrace { - constructor( - private readonly logger: ILogger = new LoggerImpl(), - private readonly traces: Array = [defaultTrace], - private readonly defaultLevel: LogLevel = LogLevel.INFO, - private readonly allowedLevels: Supplier< - Array - > = defaultAllowedLevels, - ) {} + constructor( + private readonly logger: ILogger = new LoggerImpl(), + private readonly traces: Array = [defaultTrace], + private readonly defaultLevel: LogLevel = LogLevel.INFO, + private readonly allowedLevels: Supplier> = defaultAllowedLevels, + ) {} - public addTrace(trace: LogTraceSupplier): ITrace { - return new LogTrace( - this.logger, - this.traces.concat(trace), - this.defaultLevel, - this.allowedLevels, - ); - } + public addTrace(trace: LogTraceSupplier): ITrace { + return new LogTrace(this.logger, this.traces.concat(trace), this.defaultLevel, this.allowedLevels); + } - public trace(trace: LogTraceSupplier) { - const { traces, level: _level } = this.foldTraces( - this.traces.concat(trace), - ); - if (!this.allowedLevels().includes(_level)) return; + public trace(trace: LogTraceSupplier) { + const { traces, level: _level } = this.foldTraces(this.traces.concat(trace)); + if (!this.allowedLevels().includes(_level)) return; - const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level; - this.logger.log(level, ...traces); - } + const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level; + this.logger.log(level, ...traces); + } - private foldTraces(_traces: Array) { - const _logTraces = _traces.map((trace) => - typeof trace === "function" ? trace() : trace, - ); - const _level = _logTraces - .filter((trace) => isLogLevel(trace)) - .reduce((acc, level) => Math.max(logLevelOrder.indexOf(level), acc), -1); - const level = logLevelOrder[_level] ?? LogLevel.UNKNOWN; + private foldTraces(_traces: Array) { + const _logTraces = _traces.map((trace) => (typeof trace === 'function' ? trace() : trace)); + const _level = _logTraces + .filter((trace) => isLogLevel(trace)) + .reduce((acc, level) => Math.max(logLevelOrder.indexOf(level), acc), -1); + const level = logLevelOrder[_level] ?? LogLevel.UNKNOWN; - const traces = _logTraces.filter((trace) => !isLogLevel(trace)).map((trace) => { - if (typeof trace === 'object') { - return `TracedException.Name = ${trace.name}, TracedException.Message = ${trace.message}, TracedException.Stack = ${trace.stack}` - } - return trace; - }); - return { - level, - traces, - }; - } + const traces = _logTraces + .filter((trace) => !isLogLevel(trace)) + .map((trace) => { + if (typeof trace === 'object') { + return `TracedException.Name = ${trace.name}, TracedException.Message = ${trace.message}, TracedException.Stack = ${trace.stack}`; + } + return trace; + }); + return { + level, + traces, + }; + } } export enum LogLevel { - UNKNOWN = "UNKNOWN", - INFO = "INFO", - WARN = "WARN", - DEBUG = "DEBUG", - ERROR = "ERROR", - SYS = "SYS", + UNKNOWN = 'UNKNOWN', + INFO = 'INFO', + WARN = 'WARN', + DEBUG = 'DEBUG', + ERROR = 'ERROR', + SYS = 'SYS', } -const logLevelOrder: Array = [ - LogLevel.DEBUG, - LogLevel.INFO, - LogLevel.WARN, - LogLevel.ERROR, - LogLevel.SYS, -]; +const logLevelOrder: Array = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.SYS]; export const isLogLevel = (l: unknown): l is LogLevel => - typeof l === "string" && logLevelOrder.some((level) => level === l); - + typeof l === 'string' && logLevelOrder.some((level) => level === l); const defaultAllowedLevels = () => - [ - LogLevel.UNKNOWN, - ...(isDebug() ? [LogLevel.DEBUG] : []), - LogLevel.INFO, - LogLevel.WARN, - LogLevel.ERROR, - LogLevel.SYS, - ] as Array; + [ + LogLevel.UNKNOWN, + ...(isDebug() ? [LogLevel.DEBUG] : []), + LogLevel.INFO, + LogLevel.WARN, + LogLevel.ERROR, + LogLevel.SYS, + ] as Array; export interface ILogger { - readonly log: (level: LogLevel, ...args: string[]) => void; + readonly log: (level: LogLevel, ...args: string[]) => void; } class LoggerImpl implements ILogger { private readonly textEncoder = new TextEncoder(); public log(level: LogLevel, ...trace: string[]) { - const message = JSON.stringify({ - level, - trace, - }, null, 4); + const message = JSON.stringify( + { + level, + trace, + }, + null, + 4, + ); const styled = `${this.getStyle(level)}${message}${ANSI.RESET}\n`; - this.getStream(level).writeSync(this.textEncoder.encode(styled)); + this.getStream(level)(this.textEncoder.encode(styled)); } private getStream(level: LogLevel) { if (level === LogLevel.ERROR) { - return Deno.stderr; + return console.error; } - return Deno.stdout; + return console.log; } private getStyle(level: LogLevel) { @@ -127,17 +110,17 @@ class LoggerImpl implements ILogger { } export const ANSI = { - RESET: "\x1b[0m", - BOLD: "\x1b[1m", - DIM: "\x1b[2m", - RED: "\x1b[31m", - GREEN: "\x1b[32m", - YELLOW: "\x1b[33m", - BLUE: "\x1b[34m", - MAGENTA: "\x1b[35m", - CYAN: "\x1b[36m", - WHITE: "\x1b[37m", - BRIGHT_RED: "\x1b[91m", - BRIGHT_YELLOW: "\x1b[93m", - GRAY: "\x1b[90m", + RESET: '\x1b[0m', + BOLD: '\x1b[1m', + DIM: '\x1b[2m', + RED: '\x1b[31m', + GREEN: '\x1b[32m', + YELLOW: '\x1b[33m', + BLUE: '\x1b[34m', + MAGENTA: '\x1b[35m', + CYAN: '\x1b[36m', + WHITE: '\x1b[37m', + BRIGHT_RED: '\x1b[91m', + BRIGHT_YELLOW: '\x1b[93m', + GRAY: '\x1b[90m', }; diff --git a/u/trace/metrics.ts b/u/trace/metrics.ts index 822fc38..2301afd 100644 --- a/u/trace/metrics.ts +++ b/u/trace/metrics.ts @@ -1,151 +1,140 @@ import { - isObject, - type ITrace, - type ITraceWith, - type Mapper, - type SideEffect, - type Supplier, -} from "@emprespresso/pengueno"; + isObject, + type ITrace, + type ITraceWith, + type Mapper, + type SideEffect, + type Supplier, +} from '@emprespresso/pengueno'; export enum Unit { - COUNT = "COUNT", - MILLISECONDS = "MILLISECONDS", + COUNT = 'COUNT', + MILLISECONDS = 'MILLISECONDS', } export interface IMetric { - readonly count: IEmittableMetric; - readonly time: IEmittableMetric; - readonly failure?: IMetric; - readonly success?: IMetric; - readonly warn?: IMetric; - readonly children: Supplier>; - - readonly _tag: "IMetric"; + readonly count: IEmittableMetric; + readonly time: IEmittableMetric; + readonly failure: undefined | IMetric; + readonly success: undefined | IMetric; + readonly warn: undefined | IMetric; + readonly children: Supplier>; + + readonly _tag: 'IMetric'; } -export const isIMetric = (t: unknown): t is IMetric => - isObject(t) && "_tag" in t && t._tag === "IMetric"; +export const isIMetric = (t: unknown): t is IMetric => isObject(t) && '_tag' in t && t._tag === 'IMetric'; export interface IEmittableMetric { - readonly name: string; - readonly unit: Unit; - withValue: Mapper; + readonly name: string; + readonly unit: Unit; + withValue: Mapper; } export class EmittableMetric implements IEmittableMetric { - constructor( - public readonly name: string, - public readonly unit: Unit, - ) {} - - public withValue(value: number): MetricValue { - return { - name: this.name, - unit: this.unit, - emissionTimestamp: Date.now(), - value, - _tag: "MetricValue", - }; - } + constructor( + public readonly name: string, + public readonly unit: Unit, + ) {} + + public withValue(value: number): MetricValue { + return { + name: this.name, + unit: this.unit, + emissionTimestamp: Date.now(), + value, + _tag: 'MetricValue', + }; + } } export class Metric implements IMetric { - constructor( - public readonly count: IEmittableMetric, - public readonly time: IEmittableMetric, - public readonly failure?: Metric, - public readonly success?: Metric, - public readonly warn?: Metric, - public readonly _tag: "IMetric" = "IMetric", - ) {} - - public children() { - return [this.failure, this.success, this.warn].filter( - (x) => x, - ) as IMetric[]; - } - - static fromName(name: string, addChildren = true): Metric { - return new Metric( - new EmittableMetric(`${name}.count`, Unit.COUNT), - new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS), - addChildren ? Metric.fromName(`${name}.failure`, false) : undefined, - addChildren ? Metric.fromName(`${name}.success`, false) : undefined, - addChildren ? Metric.fromName(`${name}.warn`, false) : undefined, - ); - } + constructor( + public readonly count: IEmittableMetric, + public readonly time: IEmittableMetric, + public readonly failure: undefined | Metric = undefined, + public readonly success: undefined | Metric = undefined, + public readonly warn: undefined | Metric = undefined, + public readonly _tag: 'IMetric' = 'IMetric', + ) {} + + public children() { + return [this.failure, this.success, this.warn].filter((x) => x) as IMetric[]; + } + + static fromName(name: string, addChildren = true): Metric { + return new Metric( + new EmittableMetric(`${name}.count`, Unit.COUNT), + new EmittableMetric(`${name}.elapsed`, Unit.MILLISECONDS), + addChildren ? Metric.fromName(`${name}.failure`, false) : undefined, + addChildren ? Metric.fromName(`${name}.success`, false) : undefined, + addChildren ? Metric.fromName(`${name}.warn`, false) : undefined, + ); + } } export interface MetricValue { - readonly name: string; - readonly unit: Unit; - readonly value: number; - readonly emissionTimestamp: number; - readonly _tag: "MetricValue"; + readonly name: string; + readonly unit: Unit; + readonly value: number; + readonly emissionTimestamp: number; + readonly _tag: 'MetricValue'; } -export const isMetricValue = (t: unknown): t is MetricValue => - isObject(t) && "_tag" in t && t._tag === "MetricValue"; +export const isMetricValue = (t: unknown): t is MetricValue => isObject(t) && '_tag' in t && t._tag === 'MetricValue'; -export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => - isMetricValue(t) || isIMetric(t); +export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => isMetricValue(t) || isIMetric(t); -export type MetricsTraceSupplier = ITraceWith< - IMetric | MetricValue | undefined ->; +export type MetricsTraceSupplier = ITraceWith; type MetricTracingTuple = [IMetric, Date]; export class MetricsTrace implements ITrace { - constructor( - private readonly metricConsumer: SideEffect>, - private readonly tracing: Array = [], - private readonly flushed: Set = new Set(), - ) {} - - public addTrace(trace: MetricsTraceSupplier) { - if (!isIMetric(trace)) return this; - return new MetricsTrace(this.metricConsumer)._nowTracing(trace); - } - - public trace(metric: MetricsTraceSupplier) { - if (typeof metric === "undefined" || typeof metric === "string") - return this; - if (isMetricValue(metric)) { - this.metricConsumer([metric]); - return this; + constructor( + private readonly metricConsumer: SideEffect>, + private readonly tracing: Array = [], + private readonly flushed: Set = new Set(), + ) {} + + public addTrace(trace: MetricsTraceSupplier) { + if (!isIMetric(trace)) return this; + return new MetricsTrace(this.metricConsumer)._nowTracing(trace); } - const foundMetricValues = this.tracing - .flatMap(([tracing, startedTracing]) => - [tracing, ...tracing.children()] - .filter((_tracing) => metric === _tracing) - .flatMap((metric) => [ - this.addMetric(metric, startedTracing), - this.addMetric(tracing, startedTracing), - ]), - ) - .flatMap((values) => values); - - if (foundMetricValues.length === 0) { - return this._nowTracing(metric); + public trace(metric: MetricsTraceSupplier) { + if (typeof metric === 'undefined' || typeof metric === 'string') return this; + if (isMetricValue(metric)) { + this.metricConsumer([metric]); + return this; + } + + const foundMetricValues = this.tracing + .flatMap(([tracing, startedTracing]) => + [tracing, ...tracing.children()] + .filter((_tracing) => metric === _tracing) + .flatMap((metric) => [ + this.addMetric(metric, startedTracing), + this.addMetric(tracing, startedTracing), + ]), + ) + .flatMap((values) => values); + + if (foundMetricValues.length === 0) { + return this._nowTracing(metric); + } + + this.metricConsumer(foundMetricValues); + return this; } - this.metricConsumer(foundMetricValues); - return this; - } + private addMetric(metric: IMetric, startedTracing: Date): Array { + if (this.flushed.has(metric)) { + return []; + } - private addMetric(metric: IMetric, startedTracing: Date): Array { - if (this.flushed.has(metric)) { - return []; + this.flushed.add(metric); + return [metric.count.withValue(1.0), metric.time.withValue(Date.now() - startedTracing.getTime())]; } - this.flushed.add(metric); - return [ - metric.count.withValue(1.0), - metric.time.withValue(Date.now() - startedTracing.getTime()), - ]; - } - - private _nowTracing(metric?: IMetric): MetricsTrace { - if (!metric) return this; - this.tracing.push([metric, new Date()]); - return this; - } + private _nowTracing(metric?: IMetric): MetricsTrace { + if (!metric) return this; + this.tracing.push([metric, new Date()]); + return this; + } } diff --git a/u/trace/mod.ts b/u/trace/mod.ts deleted file mode 100644 index 0f9b61b..0000000 --- a/u/trace/mod.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./itrace.ts"; -export * from "./util.ts"; -export * from "./logger.ts"; -export * from "./metrics.ts"; -export * from "./trace.ts"; diff --git a/u/trace/trace.ts b/u/trace/trace.ts index 5629c28..acc116f 100644 --- a/u/trace/trace.ts +++ b/u/trace/trace.ts @@ -1,86 +1,69 @@ import { - isMetricsTraceSupplier, - type ITrace, - type ITraceable, - type ITraceWith, - LogLevel, - LogTrace, - type LogTraceSupplier, - MetricsTrace, - type MetricsTraceSupplier, - type MetricValue, - TraceableImpl, -} from "@emprespresso/pengueno"; + isMetricsTraceSupplier, + type ITrace, + type ITraceable, + type ITraceWith, + LogLevel, + LogTrace, + type LogTraceSupplier, + MetricsTrace, + type MetricsTraceSupplier, + type MetricValue, + TraceableImpl, +} from '@emprespresso/pengueno'; export class LogTraceable extends TraceableImpl { - public static LogTrace = new LogTrace(); - static of(t: T) { - return new LogTraceable(t, LogTraceable.LogTrace); - } + public static LogTrace = new LogTrace(); + static of(t: T) { + return new LogTraceable(t, LogTraceable.LogTrace); + } } -const getEmbeddedMetricConsumer = - (logTrace: ITrace) => - (metrics: Array) => +const getEmbeddedMetricConsumer = (logTrace: ITrace) => (metrics: Array) => logTrace.addTrace(LogLevel.SYS).trace(`Metrics = ${JSON.stringify(metrics)}`); -export class EmbeddedMetricsTraceable extends TraceableImpl< - T, - MetricsTraceSupplier -> { - public static MetricsTrace = new MetricsTrace( - getEmbeddedMetricConsumer(LogTraceable.LogTrace), - ); +export class EmbeddedMetricsTraceable extends TraceableImpl { + public static MetricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(LogTraceable.LogTrace)); - static of(t: T, metricsTrace = EmbeddedMetricsTraceable.MetricsTrace) { - return new EmbeddedMetricsTraceable(t, metricsTrace); - } + static of(t: T, metricsTrace = EmbeddedMetricsTraceable.MetricsTrace) { + return new EmbeddedMetricsTraceable(t, metricsTrace); + } } -export type LogMetricTraceSupplier = ITraceWith< - LogTraceSupplier | MetricsTraceSupplier ->; +export type LogMetricTraceSupplier = ITraceWith; export class LogMetricTrace implements ITrace { - constructor( - private logTrace: ITrace, - private metricsTrace: ITrace, - ) {} + constructor( + private logTrace: ITrace, + private metricsTrace: ITrace, + ) {} - public addTrace( - trace: LogTraceSupplier | MetricsTraceSupplier, - ): LogMetricTrace { - if (isMetricsTraceSupplier(trace)) { - this.metricsTrace = this.metricsTrace.addTrace(trace); - return this; + public addTrace(trace: LogTraceSupplier | MetricsTraceSupplier): LogMetricTrace { + if (isMetricsTraceSupplier(trace)) { + this.metricsTrace = this.metricsTrace.addTrace(trace); + return this; + } + this.logTrace = this.logTrace.addTrace(trace); + return this; } - this.logTrace = this.logTrace.addTrace(trace); - return this; - } - public trace(trace: LogTraceSupplier | MetricsTraceSupplier) { - if (isMetricsTraceSupplier(trace)) { - this.metricsTrace.trace(trace); - return this; + public trace(trace: LogTraceSupplier | MetricsTraceSupplier) { + if (isMetricsTraceSupplier(trace)) { + this.metricsTrace.trace(trace); + return this; + } + this.logTrace.trace(trace); + return this; } - this.logTrace.trace(trace); - return this; - } } -export class LogMetricTraceable extends TraceableImpl< - T, - MetricsTraceSupplier | LogTraceSupplier -> { - static ofLogTraceable(t: ITraceable) { - const metricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(t.trace)); - return new LogMetricTraceable( - t.get(), - new LogMetricTrace(t.trace, metricsTrace), - ); - } +export class LogMetricTraceable extends TraceableImpl { + static ofLogTraceable(t: ITraceable) { + const metricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(t.trace)); + return new LogMetricTraceable(t.get(), new LogMetricTrace(t.trace, metricsTrace)); + } - static of(t: T) { - const logTrace = LogTraceable.of(t); - return LogMetricTraceable.ofLogTraceable(logTrace); - } + static of(t: T) { + const logTrace = LogTraceable.of(t); + return LogMetricTraceable.ofLogTraceable(logTrace); + } } diff --git a/u/trace/util.ts b/u/trace/util.ts index e2200b9..db1db63 100644 --- a/u/trace/util.ts +++ b/u/trace/util.ts @@ -1,47 +1,45 @@ import { -ANSI, - type Callable, - type IMetric, - type ITraceableMapper, - type ITraceableTuple, - type MetricsTraceSupplier, -} from "@emprespresso/pengueno"; + ANSI, + type Callable, + type IMetric, + type ITraceableMapper, + type ITraceableTuple, + type MetricsTraceSupplier, +} from '@emprespresso/pengueno'; export class TraceUtil { - static withTrace( - trace: string, - ansi?: Array - ): ITraceableMapper>, Trace> { - if (ansi) { - return (t) => [t.get(), `${ansi.join("")}${trace}${ANSI.RESET}`]; - } - return (t) => [t.get(), trace]; - } + static withTrace( + trace: string, + ansi?: Array, + ): ITraceableMapper>, Trace> { + if (ansi) { + return (t) => [t.get(), `${ansi.join('')}${trace}${ANSI.RESET}`]; + } + return (t) => [t.get(), trace]; + } - static withMetricTrace( - metric: IMetric, - ): ITraceableMapper>, Trace> { - return (t) => [t.get(), metric as Trace]; - } + static withMetricTrace( + metric: IMetric, + ): ITraceableMapper>, Trace> { + return (t) => [t.get(), metric as Trace]; + } - static withFunctionTrace( - f: F, - ): ITraceableMapper>, Trace> { - return TraceUtil.withTrace(`fn.${f.name}`); - } + static withFunctionTrace( + f: F, + ): ITraceableMapper>, Trace> { + return TraceUtil.withTrace(`fn.${f.name}`); + } - static withClassTrace( - c: C, - ): ITraceableMapper>, Trace> { - return TraceUtil.withTrace(`class.${c.constructor.name}`); - } + static withClassTrace( + c: C, + ): ITraceableMapper>, Trace> { + return TraceUtil.withTrace(`class.${c.constructor.name}`); + } - static promiseify( - mapper: ITraceableMapper, - ): ITraceableMapper, Promise, Trace> { - return (traceablePromise) => - traceablePromise - .flatMapAsync(async (t) => t.move(await t.get()).map(mapper)) - .get(); - } + static promiseify( + mapper: ITraceableMapper, + ): ITraceableMapper, Promise, Trace> { + return (traceablePromise) => + traceablePromise.flatMapAsync(async (t) => t.move(await t.get()).map(mapper)).get(); + } } diff --git a/u/tsconfig.json b/u/tsconfig.json new file mode 100644 index 0000000..80e5ff4 --- /dev/null +++ b/u/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./", + "composite": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noEmit": false, + "moduleResolution": "node" + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] +} -- cgit v1.2.3-70-g09d2