diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-07-27 18:50:33 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-07-27 19:31:06 -0700 |
commit | 7aa11b7a8abacf81dec20fff21216df35d333756 (patch) | |
tree | 40f6a76c37412cf1c5a67f99a4ee30e3aae863c9 | |
parent | e4df72cd446270cf867ec308995a05e21b3aa601 (diff) | |
download | ci-7aa11b7a8abacf81dec20fff21216df35d333756.tar.gz ci-7aa11b7a8abacf81dec20fff21216df35d333756.zip |
Pulls in pengueno from npm
64 files changed, 34 insertions, 1784 deletions
diff --git a/model/package.json b/model/package.json index 23cfd52..5d5cec4 100644 --- a/model/package.json +++ b/model/package.json @@ -17,7 +17,7 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@emprespresso/pengueno": "*" + "@emprespresso/pengueno": "^0.0.5" }, "files": [ "dist/**/*", diff --git a/model/tsconfig.json b/model/tsconfig.json index 7ad21ad..98e457c 100644 --- a/model/tsconfig.json +++ b/model/tsconfig.json @@ -10,6 +10,5 @@ "noEmit": false }, "include": ["**/*.ts"], - "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], - "references": [{ "path": "../u" }] + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"] } diff --git a/package-lock.json b/package-lock.json index a04d81d..8522d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,6 @@ "name": "@emprespresso/ci", "version": "0.1.0", "workspaces": [ - "u", "model", "server", "worker" @@ -41,7 +40,7 @@ "name": "@emprespresso/ci_model", "version": "0.1.0", "dependencies": { - "@emprespresso/pengueno": "*" + "@emprespresso/pengueno": "^0.0.5" } }, "node_modules/@emprespresso/ci_model": { @@ -57,8 +56,17 @@ "link": true }, "node_modules/@emprespresso/pengueno": { - "resolved": "u", - "link": true + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@emprespresso/pengueno/-/pengueno-0.0.5.tgz", + "integrity": "sha512-UvkcchpQfD6EeIaoyeMtBC1WfjF21O+y1WFIM2Nft5R4vRcPefa2xtGHZep7JSE9Fi98sp02LKhY9YAZHxXhCw==", + "license": "MIT", + "dependencies": { + "module-alias": "^2.2.3" + }, + "engines": { + "node": ">=22.16.0", + "npm": ">=10.0.0" + } }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", @@ -1311,9 +1319,9 @@ } }, "node_modules/hono": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.8.1.tgz", - "integrity": "sha512-ErA2ifywnSmcnB5XDuFqGDfXJ9xuAJR2C/8cZAk6vDaOCzofB8eNlha/wZWIiamREzWk94S9Z7wHsnKQHn7Niw==", + "version": "4.8.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.8.9.tgz", + "integrity": "sha512-ERIxkXMRhUxGV7nS/Af52+j2KL60B1eg+k6cPtgzrGughS+espS9KQ7QO0SMnevtmRlBfAcN0mf1jKtO6j/doA==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -1576,6 +1584,12 @@ "node": ">=10" } }, + "node_modules/module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2268,16 +2282,9 @@ "version": "0.1.0", "dependencies": { "@emprespresso/ci_model": "*", - "@emprespresso/pengueno": "*", + "@emprespresso/pengueno": "^0.0.5", "@hono/node-server": "^1.14.0", - "hono": "^4.8.0" - } - }, - "u": { - "name": "@emprespresso/pengueno", - "version": "0.1.0", - "dependencies": { - "hono": "^4.8.0" + "hono": "^4.8.9" } }, "worker": { @@ -2285,7 +2292,7 @@ "version": "0.1.0", "dependencies": { "@emprespresso/ci_model": "*", - "@emprespresso/pengueno": "*" + "@emprespresso/pengueno": "^0.0.5" }, "devDependencies": { "copyfiles": "2.4.1" diff --git a/package.json b/package.json index cd56ad8..c14f900 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "private": true, "type": "module", "workspaces": [ - "u", "model", "server", "worker" diff --git a/server/package.json b/server/package.json index e5e808e..f29d460 100644 --- a/server/package.json +++ b/server/package.json @@ -20,10 +20,10 @@ "format:check": "prettier --check ." }, "dependencies": { - "@emprespresso/pengueno": "*", + "@emprespresso/pengueno": "^0.0.5", "@emprespresso/ci_model": "*", - "hono": "^4.8.0", - "@hono/node-server": "^1.14.0" + "@hono/node-server": "^1.14.0", + "hono": "^4.8.9" }, "files": [ "dist/**/*", diff --git a/server/tsconfig.json b/server/tsconfig.json index 58e9147..3858ff4 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -11,5 +11,5 @@ }, "include": ["**/*.ts"], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], - "references": [{ "path": "../u" }, { "path": "../model" }] + "references": [{ "path": "../model" }] } diff --git a/tsconfig.json b/tsconfig.json index a28cdaf..83c85af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,6 @@ "noFallthroughCasesInSwitch": true, "lib": ["ES2022"], "paths": { - "@emprespresso/pengueno": ["./u/index.ts"], "@emprespresso/ci_model": ["./model/index.ts"], "@emprespresso/ci_server": ["./server/index.ts"], "@emprespresso/ci_worker": ["./worker/index.ts"] @@ -25,5 +24,5 @@ }, "include": ["**/*.ts", "**/*.js"], "exclude": ["node_modules", "dist", "**/*.d.ts"], - "references": [{ "path": "./u" }, { "path": "./model" }, { "path": "./server" }, { "path": "./worker" }] + "references": [{ "path": "./model" }, { "path": "./server" }, { "path": "./worker" }] } diff --git a/u/index.ts b/u/index.ts deleted file mode 100644 index e2e0768..0000000 --- a/u/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './leftpadesque/index.js'; -export * from './trace/index.js'; -export * from './process/index.js'; -export * from './server/index.js'; -export * from './types/index.js'; diff --git a/u/leftpadesque/debug.ts b/u/leftpadesque/debug.ts deleted file mode 100644 index 074e567..0000000 --- a/u/leftpadesque/debug.ts +++ /dev/null @@ -1,8 +0,0 @@ -const _hasEnv = true; // Node.js always has access to environment variables - -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((process.env.DEBUG ?? '').toLowerCase().startsWith)); -export const isDebug = () => _debug; diff --git a/u/leftpadesque/index.ts b/u/leftpadesque/index.ts deleted file mode 100644 index 09a0bd1..0000000 --- a/u/leftpadesque/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './prepend.js'; -export * from './debug.js'; -export * from './memoize.js'; diff --git a/u/leftpadesque/memoize.ts b/u/leftpadesque/memoize.ts deleted file mode 100644 index 541bd20..0000000 --- a/u/leftpadesque/memoize.ts +++ /dev/null @@ -1,14 +0,0 @@ -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/prepend.ts b/u/leftpadesque/prepend.ts deleted file mode 100644 index 1819536..0000000 --- a/u/leftpadesque/prepend.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const prependWith = (arr: string[], prep: string) => - Array(arr.length * 2) - .fill(0) - .map((_, i) => i % 2 === 0) - .map((isPrep, i) => (isPrep ? prep : arr[Math.floor(i / 2)]!)); diff --git a/u/package.json b/u/package.json deleted file mode 100644 index d38ac4c..0000000 --- a/u/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100644 index 396fa96..0000000 --- a/u/process/argv.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Either, type Mapper, type IEither, Optional } from '@emprespresso/pengueno'; - -export const isArgKey = <K extends string>(k: string): k is K => k.startsWith('--'); - -interface ArgHandler<V> { - absent?: V; - unspecified?: V; - present: Mapper<string, V>; -} - -export const getArg = <K extends string, V>( - arg: K, - argv: Array<string>, - whenValue: ArgHandler<V>, -): IEither<Error, V> => { - const argIndex = Optional.from(argv.findIndex((_argv) => isArgKey(_argv) && _argv.split('=')[0] === arg)).filter( - (index) => index >= 0 && index < argv.length, - ); - if (!argIndex.present()) { - return Optional.from(whenValue.absent) - .map((v) => Either.right<Error, V>(v)) - .orSome(() => - Either.left( - new Error(`arg ${arg} is not present in arguments list and does not have an 'absent' value`), - ), - ) - .get(); - } - - return argIndex - .flatMap((idx) => - Optional.from(argv.at(idx)).map((_argv) => (_argv.includes('=') ? _argv.split('=')[1] : argv.at(idx + 1))), - ) - .filter((next) => !isArgKey(next)) - .map((next) => whenValue.present(next)) - .orSome(() => whenValue.unspecified) - .map((v) => Either.right<Error, V>(<V>v)) - .get(); -}; - -type MappedArgs< - Args extends ReadonlyArray<string>, - Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>, -> = { - [K in Args[number]]: K extends keyof Handlers ? (Handlers[K] extends ArgHandler<infer T> ? T : string) : string; -}; - -export const argv = < - const Args extends ReadonlyArray<string>, - const Handlers extends Partial<Record<Args[number], ArgHandler<unknown>>>, ->( - args: Args, - handlers?: Handlers, - argv = process.argv.slice(2), -): IEither<Error, MappedArgs<Args, Handlers>> => { - type Result = MappedArgs<Args, Handlers>; - - const defaultHandler: ArgHandler<string> = { present: (value: string) => value }; - - const processArg = (arg: Args[number]): IEither<Error, [Args[number], unknown]> => { - const handler = handlers?.[arg] ?? defaultHandler; - return getArg(arg, argv, handler).mapRight((value) => [arg, value] as const); - }; - - const res = args - .map(processArg) - .reduce( - (acc: IEither<Error, Partial<Result>>, current: IEither<Error, [Args[number], unknown]>) => - acc.flatMap((accValue) => - current.mapRight(([key, value]) => ({ - ...accValue, - [key]: value, - })), - ), - Either.right(<Partial<Result>>{}), - ) - .mapRight((result) => <Result>result); - return res; -}; diff --git a/u/process/env.ts b/u/process/env.ts deleted file mode 100644 index f59fadf..0000000 --- a/u/process/env.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { IOptional, Either, Optional, type IEither, type ObjectFromList } from '@emprespresso/pengueno'; - -// type safe environment variables - -export const getEnv = (name: string): IOptional<string> => Optional.from(process.env[name]); - -export const getRequiredEnv = <V extends string>(name: V): IEither<Error, string> => - Either.fromFailable(() => getEnv(name).get()).mapLeft( - () => new Error(`environment variable "${name}" is required D:`), - ); - -export const getRequiredEnvVars = <V extends string>(vars: Array<V>): IEither<Error, ObjectFromList<typeof vars>> => { - type Environment = ObjectFromList<typeof vars>; - const emptyEnvironment = Either.right<Error, Environment>(<Environment>{}); - const addTo = (env: Environment, key: V, val: string) => - <Environment>{ - ...env, - [key]: val, - }; - return vars.reduce( - (environment, key) => - environment.joinRight(getRequiredEnv(key), (value, environment) => addTo(environment, key, value)), - emptyEnvironment, - ); -}; diff --git a/u/process/exec.ts b/u/process/exec.ts deleted file mode 100644 index 46b31c4..0000000 --- a/u/process/exec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - Either, - IEither, - type ITraceable, - LogLevel, - LogMetricTraceSupplier, - Metric, - TraceUtil, -} from '@emprespresso/pengueno'; -import { exec } from 'node:child_process'; - -export type Command = string[] | string; -export type StdStreams = { stdout: string; stderr: string }; - -export const CmdMetric = Metric.fromName('Exec').asResult(); -type Environment = Record<string, string>; -type Options = { streamTraceable?: Array<'stdout' | 'stderr'>; env?: Environment; clearEnv?: boolean }; -export const getStdout = ( - cmd: ITraceable<Command, LogMetricTraceSupplier>, - options: Options = { streamTraceable: [] }, -): Promise<IEither<Error, string>> => - cmd - .flatMap(TraceUtil.withFunctionTrace(getStdout)) - .flatMap((tCmd) => tCmd.traceScope(() => `Command = ${tCmd.get()}`)) - .map((tCmd) => { - const cmd = tCmd.get(); - const _exec = typeof cmd === 'string' ? cmd : cmd.join(' '); - const env = options.clearEnv ? options.env : { ...process.env, ...options.env }; - return Either.fromFailableAsync<Error, StdStreams>( - new Promise<StdStreams>((res, rej) => { - const proc = exec(_exec, { env }); - let stdout = ''; - proc.stdout?.on('data', (d) => { - const s = d.toString(); - stdout += s; - if (options.streamTraceable?.includes('stdout')) { - tCmd.trace.trace(s); - } - }); - let stderr = ''; - proc.stderr?.on('data', (d) => { - const s = d.toString(); - stdout += s; - if (options.streamTraceable?.includes('stderr')) { - tCmd.trace.trace(s); - } - }); - - proc.on('exit', (code) => { - const streams = { stdout, stderr }; - if (code === 0) { - res(streams); - } else { - rej(new Error(`exited with non-zero code: ${code}. ${stderr}`)); - } - }); - }), - ); - }) - .map( - TraceUtil.promiseify((tEitherStdStreams) => - tEitherStdStreams.get().mapRight(({ stderr, stdout }) => { - if (stderr) tEitherStdStreams.trace.traceScope(LogLevel.DEBUG).trace(`StdErr = ${stderr}`); - return stdout; - }), - ), - ) - .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(CmdMetric))) - .get(); - -export const getStdoutMany = ( - cmds: ITraceable<Array<Command>, LogMetricTraceSupplier>, - options: Options = { streamTraceable: [] }, -): Promise<IEither<Error, Array<string>>> => - cmds - .coExtend((t) => t.get()) - .reduce( - async (_result, tCmd) => { - const result = await _result; - return result.joinRightAsync( - () => tCmd.map((cmd) => getStdout(cmd, options)).get(), - (stdout, pre) => pre.concat(stdout), - ); - }, - Promise.resolve(Either.right<Error, Array<string>>([])), - ); diff --git a/u/process/index.ts b/u/process/index.ts deleted file mode 100644 index 2d74a5f..0000000 --- a/u/process/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './exec.js'; -export * from './env.js'; -export * from './validate_identifier.js'; -export * from './argv.js'; -export * from './signals.js'; diff --git a/u/process/signals.ts b/u/process/signals.ts deleted file mode 100644 index c4feb7a..0000000 --- a/u/process/signals.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - Either, - IEither, - IMetric, - ITraceable, - LogMetricTrace, - LogMetricTraceSupplier, - Mapper, - Metric, - Optional, - ResultMetric, - SideEffect, - TraceUtil, -} from '@emprespresso/pengueno'; - -export const SigIntMetric = Metric.fromName('SigInt').asResult(); -export const SigTermMetric = Metric.fromName('SigTerm').asResult(); - -export interface Closeable<TFailure> { - readonly close: SideEffect<SideEffect<TFailure | undefined>>; -} - -export class Signals { - public static async awaitClose<E extends Error>( - t: ITraceable<Closeable<E>, LogMetricTraceSupplier>, - ): Promise<IEither<Error, void>> { - const success: IEither<Error, void> = Either.right(<void>undefined); - return new Promise<IEither<Error, void>>((res) => { - const metricizedInterruptHandler = (metric: ResultMetric) => (err: Error | undefined) => - t - .flatMap(TraceUtil.withMetricTrace(metric)) - .peek((_t) => _t.trace.trace('closing')) - .move( - Optional.from(err) - .map((e) => Either.left<Error, void>(e)) - .orSome(() => success) - .get(), - ) - .flatMap(TraceUtil.traceResultingEither(metric)) - .map((e) => res(e.get())) - .peek((_t) => _t.trace.trace('finished')) - .get(); - const sigintCloser = metricizedInterruptHandler(SigIntMetric); - const sigtermCloser = metricizedInterruptHandler(SigTermMetric); - process.on('SIGINT', () => t.flatMap(TraceUtil.withTrace('SIGINT')).get().close(sigintCloser)); - process.on('SIGTERM', () => t.flatMap(TraceUtil.withTrace('SIGTERM')).get().close(sigtermCloser)); - }); - } -} diff --git a/u/process/validate_identifier.ts b/u/process/validate_identifier.ts deleted file mode 100644 index 1ff3791..0000000 --- a/u/process/validate_identifier.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Either, type IEither } from '@emprespresso/pengueno'; - -export const validateIdentifier = (token: string) => { - return /^[a-zA-Z0-9_\-:. \/]+$/.test(token) && !token.includes('..'); -}; - -// ensure {@param obj} is a Record<string, string> with stuff that won't -// have the potential for shell injection, just to be super safe. -type InvalidEntry<K, T> = [K, T]; -export const validateExecutionEntries = <T, K extends symbol | number | string = symbol | number | string>( - obj: Record<K, T>, -): IEither<Array<InvalidEntry<K, T>>, Record<string, string>> => { - const invalidEntries = <Array<InvalidEntry<K, T>>>( - Object.entries(obj).filter((e) => !e.every((x) => typeof x === 'string' && validateIdentifier(x))) - ); - if (invalidEntries.length > 0) return Either.left(invalidEntries); - return Either.right(<Record<string, string>>obj); -}; diff --git a/u/server/activity/fourohfour.ts b/u/server/activity/fourohfour.ts deleted file mode 100644 index cd90ba0..0000000 --- a/u/server/activity/fourohfour.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - 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!', -]; -const randomFourOhFour = () => messages[Math.floor(Math.random() * messages.length)]!; - -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 deleted file mode 100644 index 9396490..0000000 --- a/u/server/activity/health.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - type IActivity, - type IEither, - IMetric, - type ITraceable, - JsonResponse, - LogLevel, - type Mapper, - Metric, - type PenguenoRequest, - type ServerTrace, - TraceUtil, -} from '@emprespresso/pengueno'; - -export enum HealthCheckInput { - CHECK, -} -export enum HealthCheckOutput { - YAASSSLAYQUEEN, -} - -export interface IHealthCheckActivity { - checkHealth: IActivity; -} - -const healthCheckMetric = Metric.fromName('Health').asResult(); -export interface HealthChecker - extends Mapper<ITraceable<HealthCheckInput, ServerTrace>, Promise<IEither<Error, HealthCheckOutput>>> {} -export class HealthCheckActivityImpl implements IHealthCheckActivity { - constructor(private readonly check: HealthChecker) {} - - public checkHealth(req: ITraceable<PenguenoRequest, ServerTrace>) { - return req - .flatMap(TraceUtil.withFunctionTrace(this.checkHealth)) - .flatMap(TraceUtil.withMetricTrace(healthCheckMetric)) - .flatMap((r) => r.move(HealthCheckInput.CHECK).map((input) => this.check(input))) - .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(healthCheckMetric))) - .map( - TraceUtil.promiseify((h) => { - const { status, message } = h.get().fold( - () => ({ status: 500, message: 'err' }), - () => ({ status: 200, message: 'ok' }), - ); - return new JsonResponse(req, message, { status }); - }), - ) - .get(); - } -} diff --git a/u/server/activity/index.ts b/u/server/activity/index.ts deleted file mode 100644 index fa0a6b2..0000000 --- a/u/server/activity/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ITraceable, PenguenoRequest, PenguenoResponse, ServerTrace } from '@emprespresso/pengueno'; - -export interface IActivity { - (req: ITraceable<PenguenoRequest, ServerTrace>): Promise<PenguenoResponse>; -} - -export * from './health.js'; -export * from './fourohfour.js'; diff --git a/u/server/filter/index.ts b/u/server/filter/index.ts deleted file mode 100644 index 75168c7..0000000 --- a/u/server/filter/index.ts +++ /dev/null @@ -1,34 +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<PenguenoRequest, ServerTrace>, -> { - (req: RIn): IEither<Err, T> | Promise<IEither<Err, T>>; -} - -export * from './method.js'; -export * from './json.js'; diff --git a/u/server/filter/json.ts b/u/server/filter/json.ts deleted file mode 100644 index bc53d47..0000000 --- a/u/server/filter/json.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - Either, - type IEither, - type ITraceable, - LogLevel, - Metric, - PenguenoError, - type PenguenoRequest, - type RequestFilter, - type ServerTrace, - TraceUtil, -} from '@emprespresso/pengueno'; - -export interface JsonTransformer<R, ParsedJson = unknown> { - (json: ITraceable<ParsedJson, ServerTrace>): IEither<PenguenoError, R>; -} - -const ParseJsonMetric = Metric.fromName('JsonParse').asResult(); -export const jsonModel = - <MessageT>(jsonTransformer: JsonTransformer<MessageT>): RequestFilter<MessageT> => - (r: ITraceable<PenguenoRequest, ServerTrace>) => - r - .flatMap(TraceUtil.withFunctionTrace(jsonModel)) - .flatMap(TraceUtil.withMetricTrace(ParseJsonMetric)) - .map((j) => - Either.fromFailableAsync<Error, MessageT>(<Promise<MessageT>>j.get().req.json()).then((either) => - either.mapLeft((errReason) => { - j.trace.traceScope(LogLevel.WARN).trace(errReason); - return new PenguenoError('seems to be invalid JSON (>//<) can you fix?', 400); - }), - ), - ) - .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(ParseJsonMetric))) - .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 deleted file mode 100644 index 7d6aa76..0000000 --- a/u/server/filter/method.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - Either, - HttpMethod, - IEither, - type ITraceable, - LogLevel, - PenguenoError, - type PenguenoRequest, - type RequestFilter, - type ServerTrace, - TraceUtil, -} from '@emprespresso/pengueno'; - -export const requireMethod = - (methods: Array<HttpMethod>): RequestFilter<HttpMethod> => - (req: ITraceable<PenguenoRequest, ServerTrace>) => - req - .flatMap(TraceUtil.withFunctionTrace(requireMethod)) - .map((t): IEither<PenguenoError, HttpMethod> => { - const { - req: { method }, - } = t.get(); - if (!methods.includes(method)) { - const msg = "that's not how you pet me (⋟﹏⋞)~"; - t.trace.traceScope(LogLevel.WARN).trace(msg); - return Either.left(new PenguenoError(msg, 405)); - } - return Either.right(method); - }) - .get(); diff --git a/u/server/http/body.ts b/u/server/http/body.ts deleted file mode 100644 index 5fc4caa..0000000 --- a/u/server/http/body.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type Body = - | ArrayBuffer - | AsyncIterable<Uint8Array> - | Blob - | FormData - | Iterable<Uint8Array> - | NodeJS.ArrayBufferView - | URLSearchParams - | null - | string; diff --git a/u/server/http/index.ts b/u/server/http/index.ts deleted file mode 100644 index ef1c039..0000000 --- a/u/server/http/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './body.js'; -export * from './status.js'; -export * from './method.js'; diff --git a/u/server/http/method.ts b/u/server/http/method.ts deleted file mode 100644 index 172d77a..0000000 --- a/u/server/http/method.ts +++ /dev/null @@ -1 +0,0 @@ -export type HttpMethod = 'POST' | 'GET' | 'HEAD' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'; diff --git a/u/server/http/status.ts b/u/server/http/status.ts deleted file mode 100644 index 15cb30c..0000000 --- a/u/server/http/status.ts +++ /dev/null @@ -1,71 +0,0 @@ -export const HttpStatusCodes: Record<number, string> = { - 100: 'Continue', - 101: 'Switching Protocols', - 102: 'Processing (WebDAV)', - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-Authoritative Information', - 204: 'No Content', - 205: 'Reset Content', - 206: 'Partial Content', - 207: 'Multi-Status (WebDAV)', - 208: 'Already Reported (WebDAV)', - 226: 'IM Used', - 300: 'Multiple Choices', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 306: '(Unused)', - 307: 'Temporary Redirect', - 308: 'Permanent Redirect (experimental)', - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Request Entity Too Large', - 414: 'Request-URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Requested Range Not Satisfiable', - 417: 'Expectation Failed', - 418: "I'm a teapot (RFC 2324)", - 420: 'Enhance Your Calm (Twitter)', - 422: 'Unprocessable Entity (WebDAV)', - 423: 'Locked (WebDAV)', - 424: 'Failed Dependency (WebDAV)', - 425: 'Reserved for WebDAV', - 426: 'Upgrade Required', - 428: 'Precondition Required', - 429: 'Too Many Requests', - 431: 'Request Header Fields Too Large', - 444: 'No Response (Nginx)', - 449: 'Retry With (Microsoft)', - 450: 'Blocked by Windows Parental Controls (Microsoft)', - 451: 'Unavailable For Legal Reasons', - 499: 'Client Closed Request (Nginx)', - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', - 506: 'Variant Also Negotiates (Experimental)', - 507: 'Insufficient Storage (WebDAV)', - 508: 'Loop Detected (WebDAV)', - 509: 'Bandwidth Limit Exceeded (Apache)', - 510: 'Not Extended', - 511: 'Network Authentication Required', - 598: 'Network read timeout error', - 599: 'Network connect timeout error', -}; diff --git a/u/server/index.ts b/u/server/index.ts deleted file mode 100644 index 1cefb71..0000000 --- a/u/server/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ITraceable, LogMetricTraceSupplier, Mapper } from '@emprespresso/pengueno'; -export type ServerTrace = LogMetricTraceSupplier; - -export * from './http/index.js'; -export * from './response/index.js'; -export * from './request/index.js'; -export * from './activity/index.js'; -export * from './filter/index.js'; - -import { PenguenoRequest, PenguenoResponse } from '@emprespresso/pengueno'; -export interface Server { - readonly serve: Mapper<ITraceable<PenguenoRequest, ServerTrace>, Promise<PenguenoResponse>>; -} diff --git a/u/server/request/index.ts b/u/server/request/index.ts deleted file mode 100644 index 41d59b7..0000000 --- a/u/server/request/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HttpMethod } from '@emprespresso/pengueno'; - -export interface BaseRequest { - url: string; - method: HttpMethod; - - header(): Record<string, string>; - - formData(): Promise<FormData>; - json(): Promise<unknown>; - text(): Promise<string>; - - param(key: string): string | undefined; - query(): Record<string, string>; - queries(): Record<string, string[]>; -} - -export * from './pengueno.js'; diff --git a/u/server/request/pengueno.ts b/u/server/request/pengueno.ts deleted file mode 100644 index 31563e9..0000000 --- a/u/server/request/pengueno.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { BaseRequest, ITraceable, ServerTrace } from '@emprespresso/pengueno'; - -const greetings = ['hewwo :D', 'hiya cutie', 'boop!', 'sending virtual hugs!', 'stay pawsitive']; -const penguenoGreeting = () => greetings[Math.floor(Math.random() * greetings.length)]; - -export class PenguenoRequest { - private constructor( - public readonly req: BaseRequest, - private readonly id: string, - private readonly at: Date, - ) {} - - public elapsedTimeMs(after = () => Date.now()): number { - return after() - this.at.getTime(); - } - - public getResponseHeaders(): Record<string, string> { - const RequestId = this.id; - const RequestReceivedUnix = this.at.getTime(); - const RequestHandleUnix = Date.now(); - const DeltaUnix = this.elapsedTimeMs(() => RequestHandleUnix); - const Hai = penguenoGreeting(); - - return Object.entries({ - RequestId, - RequestReceivedUnix, - RequestHandleUnix, - DeltaUnix, - Hai, - }).reduce((acc, [key, val]) => ({ ...acc, [key]: val!.toString() }), {}); - } - - public static from(request: ITraceable<BaseRequest, ServerTrace>): ITraceable<PenguenoRequest, ServerTrace> { - const id = crypto.randomUUID(); - return request.bimap((tRequest) => { - const request = tRequest.get(); - const url = new URL(request.url); - const { pathname } = url; - const trace = `RequestId = ${id}, Method = ${request.method}, Path = ${pathname}`; - - return { item: new PenguenoRequest(request, id, new Date()), trace }; - }); - } -} diff --git a/u/server/response/index.ts b/u/server/response/index.ts deleted file mode 100644 index 17a2d97..0000000 --- a/u/server/response/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Body } from '@emprespresso/pengueno'; - -export interface BaseResponse { - status: number; - statusText: string; - headers: Record<string, string>; - - body(): Body; -} - -export interface ResponseOpts { - status: number; - statusText?: string; - headers?: Record<string, string>; -} - -export * from './pengueno.js'; -export * from './json_pengueno.js'; diff --git a/u/server/response/json_pengueno.ts b/u/server/response/json_pengueno.ts deleted file mode 100644 index d0b74a8..0000000 --- a/u/server/response/json_pengueno.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - isEither, - ITraceable, - PenguenoRequest, - PenguenoResponse, - ResponseOpts, - ServerTrace, -} from '@emprespresso/pengueno'; - -type Jsonable = any; -export class JsonResponse extends PenguenoResponse { - constructor(req: ITraceable<PenguenoRequest, ServerTrace>, e: Jsonable, _opts: ResponseOpts) { - const opts = { ..._opts, headers: { ..._opts.headers, 'Content-Type': 'application/json' } }; - if (isEither<Jsonable, Jsonable>(e)) { - super( - req, - JSON.stringify( - e.fold( - (error) => ({ error, ok: undefined }), - (ok) => ({ ok }), - ), - ), - opts, - ); - return; - } - super(req, JSON.stringify(Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e }), opts); - } -} diff --git a/u/server/response/pengueno.ts b/u/server/response/pengueno.ts deleted file mode 100644 index 5a953db..0000000 --- a/u/server/response/pengueno.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - BaseResponse, - Body, - HttpStatusCodes, - ITraceable, - Metric, - Optional, - PenguenoRequest, - ResponseOpts, - ServerTrace, -} from '@emprespresso/pengueno'; - -const getHeaders = (req: PenguenoRequest, extraHeaders: Record<string, string>) => { - const optHeaders = { - ...req.getResponseHeaders(), - ...extraHeaders, - }; - optHeaders['Content-Type'] = (optHeaders['Content-Type'] ?? 'text/plain') + '; charset=utf-8'; - return optHeaders; -}; - -const ResponseCodeMetrics = [0, 1, 2, 3, 4, 5].map((x) => Metric.fromName(`response.${x}xx`).asResult()); -export const getResponseMetrics = (status: number, elapsedMs?: number) => { - const index = Math.floor(status / 100); - return ResponseCodeMetrics.flatMap((metric, i) => - Optional.from(i) - .filter((i) => i === index) - .map(() => [metric.count.withValue(1.0)]) - .flatMap((metricValues) => - Optional.from(elapsedMs) - .map((ms) => metricValues.concat(metric.time.withValue(ms))) - .orSome(() => metricValues), - ) - .orSome(() => [metric.count.withValue(0.0)]) - .get(), - ); -}; - -export class PenguenoResponse implements BaseResponse { - public readonly statusText: string; - public readonly status: number; - public readonly headers: Record<string, string>; - - constructor( - req: ITraceable<PenguenoRequest, ServerTrace>, - private readonly _body: Body, - opts: ResponseOpts, - ) { - this.headers = getHeaders(req.get(), opts?.headers ?? {}); - this.status = opts.status; - this.statusText = opts.statusText ?? HttpStatusCodes[this.status]!; - - req.trace.trace(getResponseMetrics(opts.status, req.get().elapsedTimeMs())); - } - - public body() { - return this._body; - } -} diff --git a/u/trace/index.ts b/u/trace/index.ts deleted file mode 100644 index 332fb52..0000000 --- a/u/trace/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './itrace.js'; -export * from './metric/index.js'; -export * from './log/index.js'; -export * from './trace.js'; -export * from './util.js'; diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts deleted file mode 100644 index e2019fa..0000000 --- a/u/trace/itrace.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { Mapper, SideEffect, Supplier } from '@emprespresso/pengueno'; - -/** - * the "thing" every Trace writer must "trace()". - */ -type BaseTraceWith = string; -export type ITraceWith<T> = BaseTraceWith | T; -export interface ITrace<TraceWith> { - /** - * creates a new trace scope which inherits from this trace. - */ - traceScope: Mapper<ITraceWith<TraceWith>, ITrace<TraceWith>>; - - /** - * does the tracing. - */ - trace: SideEffect<ITraceWith<TraceWith>>; -} - -export type ITraceableTuple<T, TraceWith> = { item: T; trace: BaseTraceWith | TraceWith }; -export type ITraceableMapper<T, _T, TraceWith> = (w: ITraceable<T, TraceWith>) => _T; - -export interface ITraceable<T, Trace = BaseTraceWith> { - readonly trace: ITrace<Trace>; - readonly get: Supplier<T>; - - readonly move: <_T>(t: _T) => ITraceable<_T, Trace>; - readonly map: <_T>(mapper: ITraceableMapper<T, _T, Trace>) => ITraceable<_T, Trace>; - readonly bimap: <_T>(mapper: ITraceableMapper<T, ITraceableTuple<_T, Trace>, Trace>) => ITraceable<_T, Trace>; - readonly coExtend: <_T>(mapper: ITraceableMapper<T, Array<_T>, Trace>) => Array<ITraceable<_T, Trace>>; - readonly peek: (peek: ITraceableMapper<T, void, Trace>) => ITraceable<T, Trace>; - - readonly traceScope: (mapper: ITraceableMapper<T, Trace, Trace>) => ITraceable<T, Trace>; - - readonly flatMap: <_T>(mapper: ITraceableMapper<T, ITraceable<_T, Trace>, Trace>) => ITraceable<_T, Trace>; - readonly flatMapAsync: <_T>( - mapper: ITraceableMapper<T, Promise<ITraceable<_T, Trace>>, Trace>, - ) => ITraceable<Promise<_T>, Trace>; -} - -export class TraceableImpl<T, Trace> implements ITraceable<T, Trace> { - protected constructor( - private readonly item: T, - public readonly trace: ITrace<Trace>, - ) {} - - public map<_T>(mapper: ITraceableMapper<T, _T, Trace>) { - const result = mapper(this); - return new TraceableImpl(result, this.trace); - } - - public coExtend<_T>(mapper: ITraceableMapper<T, Array<_T>, Trace>): Array<ITraceable<_T, Trace>> { - const results = mapper(this); - return Array.from(results).map((result) => this.move(result)); - } - - public flatMap<_T>(mapper: ITraceableMapper<T, ITraceable<_T, Trace>, Trace>): ITraceable<_T, Trace> { - return mapper(this); - } - - public flatMapAsync<_T>( - mapper: ITraceableMapper<T, Promise<ITraceable<_T, Trace>>, Trace>, - ): ITraceable<Promise<_T>, Trace> { - return new TraceableImpl( - mapper(this).then((t) => t.get()), - this.trace, - ); - } - - public traceScope(mapper: ITraceableMapper<T, Trace, Trace>): ITraceable<T, Trace> { - return new TraceableImpl(this.get(), this.trace.traceScope(mapper(this))); - } - - public peek(peek: ITraceableMapper<T, void, Trace>) { - peek(this); - return this; - } - - public move<_T>(t: _T): ITraceable<_T, Trace> { - return this.map(() => t); - } - - public bimap<_T>(mapper: ITraceableMapper<T, ITraceableTuple<_T, Trace>, Trace>) { - const { item, trace: _trace } = mapper(this); - return this.move(item).traceScope(() => <Trace>_trace); - } - - public get() { - return this.item; - } -} diff --git a/u/trace/log/ansi.ts b/u/trace/log/ansi.ts deleted file mode 100644 index 7ff16a3..0000000 --- a/u/trace/log/ansi.ts +++ /dev/null @@ -1,15 +0,0 @@ -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', -}; diff --git a/u/trace/log/index.ts b/u/trace/log/index.ts deleted file mode 100644 index 670e333..0000000 --- a/u/trace/log/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './ansi.js'; -export * from './level.js'; -export * from './logger.js'; -export * from './pretty_json_console.js'; -export * from './trace.js'; diff --git a/u/trace/log/level.ts b/u/trace/log/level.ts deleted file mode 100644 index 027dd71..0000000 --- a/u/trace/log/level.ts +++ /dev/null @@ -1,19 +0,0 @@ -export enum LogLevel { - UNKNOWN = 'UNKNOWN', - INFO = 'INFO', - WARN = 'WARN', - DEBUG = 'DEBUG', - ERROR = 'ERROR', - SYS = 'SYS', -} - -export const logLevelOrder: Array<LogLevel> = [ - 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); diff --git a/u/trace/log/logger.ts b/u/trace/log/logger.ts deleted file mode 100644 index 3ced60a..0000000 --- a/u/trace/log/logger.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { LogLevel } from './level.js'; - -export interface ILogger { - readonly log: (level: LogLevel, ...args: string[]) => void; -} diff --git a/u/trace/log/pretty_json_console.ts b/u/trace/log/pretty_json_console.ts deleted file mode 100644 index 758af51..0000000 --- a/u/trace/log/pretty_json_console.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ANSI, LogLevel, ILogger } from './index.js'; - -export class PrettyJsonConsoleLogger implements ILogger { - public log(level: LogLevel, ...trace: string[]) { - const message = JSON.stringify( - { - level, - trace, - }, - null, - 4, - ); - const styled = `${this.getStyle(level)}${message}${ANSI.RESET}\n`; - this.getStream(level)(styled); - } - - private getStream(level: LogLevel) { - if (level === LogLevel.ERROR) { - return console.error; - } - return console.log; - } - - private getStyle(level: LogLevel) { - switch (level) { - case LogLevel.UNKNOWN: - case LogLevel.INFO: - return `${ANSI.MAGENTA}`; - case LogLevel.DEBUG: - return `${ANSI.CYAN}`; - case LogLevel.WARN: - return `${ANSI.BRIGHT_YELLOW}`; - case LogLevel.ERROR: - return `${ANSI.BRIGHT_RED}`; - case LogLevel.SYS: - return `${ANSI.DIM}${ANSI.BLUE}`; - } - } -} diff --git a/u/trace/log/trace.ts b/u/trace/log/trace.ts deleted file mode 100644 index 3f9f1b2..0000000 --- a/u/trace/log/trace.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { isDebug, ITrace, ITraceWith, memoize, Supplier } from '@emprespresso/pengueno'; -import { ILogger, isLogLevel, LogLevel, logLevelOrder, PrettyJsonConsoleLogger } from './index.js'; - -export type LogTraceSupplier = ITraceWith<Supplier<string>> | ITraceWith<Error>; - -export class LogTrace implements ITrace<LogTraceSupplier> { - constructor( - private readonly logger: ILogger = new PrettyJsonConsoleLogger(), - private readonly traces: Array<LogTraceSupplier> = [defaultTrace], - private readonly defaultLevel: LogLevel = LogLevel.INFO, - private readonly allowedLevels: Supplier<Set<LogLevel>> = defaultAllowedLevelsSupplier, - ) {} - - public traceScope(trace: LogTraceSupplier): ITrace<LogTraceSupplier> { - 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().has(_level)) return; - - const level = _level === LogLevel.UNKNOWN ? this.defaultLevel : _level; - this.logger.log(level, ...traces); - } - - private foldTraces(_traces: Array<LogTraceSupplier>) { - 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 defaultTrace = () => `TimeStamp = ${new Date().toISOString()}`; -const defaultAllowedLevels = memoize( - (isDebug: boolean) => - new Set([ - LogLevel.UNKNOWN, - ...(isDebug ? [LogLevel.DEBUG] : []), - LogLevel.INFO, - LogLevel.WARN, - LogLevel.ERROR, - LogLevel.SYS, - ]), -); -const defaultAllowedLevelsSupplier = () => defaultAllowedLevels(isDebug()); diff --git a/u/trace/metric/emittable.ts b/u/trace/metric/emittable.ts deleted file mode 100644 index f3441ec..0000000 --- a/u/trace/metric/emittable.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IEmittableMetric, MetricValue, MetricValueTag, Unit } from './index.js'; - -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: MetricValueTag, - }; - } -} diff --git a/u/trace/metric/index.ts b/u/trace/metric/index.ts deleted file mode 100644 index 72c37d2..0000000 --- a/u/trace/metric/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { isTagged, Tagged, type Mapper } from '@emprespresso/pengueno'; - -export enum Unit { - COUNT = 'COUNT', - MILLISECONDS = 'MILLISECONDS', -} - -export const MetricValueTag = 'MetricValue' as const; -export type MetricValueTag = typeof MetricValueTag; -export const isMetricValue = (t: unknown): t is MetricValue => isTagged(t, MetricValueTag); -export interface MetricValue extends Tagged<MetricValueTag> { - readonly name: string; - readonly unit: Unit; - readonly value: number; - readonly emissionTimestamp: number; -} - -export interface IEmittableMetric { - readonly name: string; - readonly unit: Unit; - readonly withValue: Mapper<number, MetricValue>; -} - -export const IMetricTag = 'IMetric' as const; -export type IMetricTag = typeof IMetricTag; -export const isIMetric = (t: unknown): t is IMetric => isTagged(t, IMetricTag); -export interface IMetric extends Tagged<IMetricTag> { - readonly count: IEmittableMetric; - readonly time: IEmittableMetric; - readonly parent: undefined | IMetric; -} - -export interface IResultMetric extends IMetric { - readonly failure: IMetric; - readonly success: IMetric; - readonly warn: IMetric; -} - -export * from './emittable.js'; -export * from './metric.js'; -export * from './trace.js'; diff --git a/u/trace/metric/metric.ts b/u/trace/metric/metric.ts deleted file mode 100644 index 8ef339f..0000000 --- a/u/trace/metric/metric.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { EmittableMetric, IMetric, IMetricTag, IResultMetric, Unit } from './index.js'; - -class _Tagged { - protected constructor(public readonly _tag = IMetricTag) {} -} - -export class Metric extends _Tagged implements IMetric { - private static DELIM = '.'; - - protected constructor( - public readonly name: string, - public readonly parent: undefined | IMetric = undefined, - public readonly count = new EmittableMetric(Metric.join(name, 'count'), Unit.COUNT), - public readonly time = new EmittableMetric(Metric.join(name, 'time'), Unit.MILLISECONDS), - ) { - super(); - } - - public child(_name: string): Metric { - const childName = Metric.join(this.name, _name); - return new Metric(childName, this); - } - - public asResult() { - return ResultMetric.from(this); - } - - static join(...name: Array<string>) { - return name.join(Metric.DELIM); - } - - static fromName(name: string): Metric { - return new Metric(name); - } -} - -export class ResultMetric extends Metric implements IResultMetric { - protected constructor( - public readonly name: string, - public readonly parent: undefined | IMetric = undefined, - public readonly failure: IMetric, - public readonly success: IMetric, - public readonly warn: IMetric, - ) { - super(name, parent); - } - - static from(metric: Metric) { - const failure = metric.child('failure'); - const success = metric.child('success'); - const warn = metric.child('warn'); - return new ResultMetric(metric.name, metric.parent, failure, success, warn); - } -} diff --git a/u/trace/metric/trace.ts b/u/trace/metric/trace.ts deleted file mode 100644 index 0c5fe37..0000000 --- a/u/trace/metric/trace.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { IMetric, isIMetric, isMetricValue, ITrace, ITraceWith, MetricValue, SideEffect } from '@emprespresso/pengueno'; - -export type MetricsTraceSupplier = - | ITraceWith<IMetric | MetricValue | undefined> - | Array<ITraceWith<IMetric | MetricValue | undefined>>; -export const isMetricsTraceSupplier = (t: unknown): t is MetricsTraceSupplier => - isMetricValue(t) || isIMetric(t) || (Array.isArray(t) && t.every((_m) => isMetricValue(_m) || isIMetric(_m))); - -export class MetricsTrace implements ITrace<MetricsTraceSupplier> { - constructor( - private readonly metricConsumer: SideEffect<Array<MetricValue>>, - private readonly activeTraces: ReadonlyMap<IMetric, number> = new Map(), - private readonly completedTraces: ReadonlySet<IMetric> = new Set(), - ) {} - - public traceScope(trace: MetricsTraceSupplier): MetricsTrace { - const now = Date.now(); - const metricsToTrace = (Array.isArray(trace) ? trace : [trace]).filter(isIMetric); - - const initialTraces = new Map(metricsToTrace.map((metric) => [metric, now])); - - return new MetricsTrace(this.metricConsumer, initialTraces); - } - - public trace(metrics: MetricsTraceSupplier): MetricsTrace { - if (!metrics || typeof metrics === 'string') { - return this; - } - - const now = Date.now(); - const allMetrics = Array.isArray(metrics) ? metrics : [metrics]; - - // partition the incoming metrics - const valuesToEmit = allMetrics.filter(isMetricValue); - const traceableMetrics = allMetrics.filter(isIMetric); - - const metricsToStart = traceableMetrics.filter((m) => !this.activeTraces.has(m)); - const metricsToEnd = traceableMetrics.filter((m) => this.activeTraces.has(m) && !this.completedTraces.has(m)); - - // the new metrics to emit based on traces ending *now* - const endedMetricValues = metricsToEnd.flatMap((metric) => [ - metric.count.withValue(1.0), - metric.time.withValue(now - this.activeTraces.get(metric)!), - ]); - - const allMetricsToEmit = [...valuesToEmit, ...endedMetricValues]; - if (allMetricsToEmit.length > 0) { - this.metricConsumer(allMetricsToEmit); - } - - // the next immutable state - const nextActiveTraces = new Map([ - ...this.activeTraces, - ...metricsToStart.map((m): [IMetric, number] => [m, now]), - ]); - const nextCompletedTraces = new Set([...this.completedTraces, ...metricsToEnd]); - return new MetricsTrace(this.metricConsumer, nextActiveTraces, nextCompletedTraces); - } -} diff --git a/u/trace/trace.ts b/u/trace/trace.ts deleted file mode 100644 index bde83a3..0000000 --- a/u/trace/trace.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { - isMetricsTraceSupplier, - type ITrace, - type ITraceable, - type ITraceWith, - LogLevel, - LogTrace, - type LogTraceSupplier, - MetricsTrace, - type MetricsTraceSupplier, - type MetricValue, - TraceableImpl, -} from './index.js'; - -export class LogTraceable<T> extends TraceableImpl<T, LogTraceSupplier> { - public static LogTrace = new LogTrace(); - static of<T>(t: T) { - return new LogTraceable(t, LogTraceable.LogTrace); - } -} - -const getEmbeddedMetricConsumer = (logTrace: ITrace<LogTraceSupplier>) => (metrics: Array<MetricValue>) => { - if (metrics.length === 0) return; - logTrace.traceScope(LogLevel.SYS).trace(`Metrics = <metrics>${JSON.stringify(metrics)}</metrics>`); -}; - -export class EmbeddedMetricsTraceable<T> extends TraceableImpl<T, MetricsTraceSupplier> { - public static MetricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(LogTraceable.LogTrace)); - - static of<T>(t: T, metricsTrace = EmbeddedMetricsTraceable.MetricsTrace) { - return new EmbeddedMetricsTraceable(t, metricsTrace); - } -} - -export type LogMetricTraceSupplier = ITraceWith<LogTraceSupplier | MetricsTraceSupplier>; -export class LogMetricTrace implements ITrace<LogMetricTraceSupplier> { - constructor( - private logTrace: ITrace<LogTraceSupplier>, - private metricsTrace: ITrace<MetricsTraceSupplier>, - ) {} - - // public traceScope(trace: LogTraceSupplier | MetricsTraceSupplier): LogMetricTrace { - // if (isMetricsTraceSupplier(trace)) { - // this.metricsTrace = this.metricsTrace.traceScope(trace); - // return this; - // } - // this.logTrace = this.logTrace.traceScope(trace); - // return this; - // } - public traceScope(trace: LogTraceSupplier | MetricsTraceSupplier): LogMetricTrace { - if (isMetricsTraceSupplier(trace)) { - return new LogMetricTrace(this.logTrace, this.metricsTrace.traceScope(trace)); - } - return new LogMetricTrace(this.logTrace.traceScope(trace), this.metricsTrace); - } - - public trace(trace: LogTraceSupplier | MetricsTraceSupplier) { - if (isMetricsTraceSupplier(trace)) { - this.metricsTrace.trace(trace); - return this; - } - this.logTrace.trace(trace); - return this; - } -} - -export class LogMetricTraceable<T> extends TraceableImpl<T, MetricsTraceSupplier | LogTraceSupplier> { - static ofLogTraceable<T>(t: ITraceable<T, LogTraceSupplier>) { - const metricsTrace = new MetricsTrace(getEmbeddedMetricConsumer(t.trace)); - return new LogMetricTraceable(t.get(), new LogMetricTrace(t.trace, metricsTrace)); - } - - static of<T>(t: T) { - const logTrace = LogTraceable.of(t); - return LogMetricTraceable.ofLogTraceable(logTrace); - } -} diff --git a/u/trace/util.ts b/u/trace/util.ts deleted file mode 100644 index ec67571..0000000 --- a/u/trace/util.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - IEither, - IMetric, - isEither, - ITraceable, - ITraceWith, - LogLevel, - ResultMetric, - type Callable, - type ITraceableMapper, -} from '@emprespresso/pengueno'; - -export class TraceUtil { - static promiseify<T, U, Trace>( - mapper: ITraceableMapper<T, U, Trace>, - ): ITraceableMapper<Promise<T>, Promise<U>, Trace> { - return (traceablePromise) => - traceablePromise.flatMapAsync(async (t) => t.move(await t.get()).map(mapper)).get(); - } - - static traceResultingEither<TErr, TOk, Trace>( - metric?: ResultMetric, - warnOnFailure = false, - ): ITraceableMapper<IEither<TErr, TOk>, ITraceable<IEither<TErr, TOk>, Trace>, Trace> { - return (t) => { - if (metric) - t.trace.trace( - t.get().fold( - (_err) => <Trace>(warnOnFailure ? metric.warn : metric.failure), - (_ok) => <Trace>metric.success, - ), - ); - return t.traceScope((_t) => - _t.get().fold( - (_err) => <Trace>(warnOnFailure ? LogLevel.WARN : LogLevel.ERROR), - (_ok) => <Trace>LogLevel.INFO, - ), - ); - }; - } - - static withTrace<T, Trace, _Trace extends ITraceWith<Trace>>( - trace: _Trace, - ): ITraceableMapper<T, ITraceable<T, Trace>, Trace> { - return (t) => t.traceScope(() => <Trace>trace); - } - - static withMetricTrace<T, Trace>(metric: IMetric): ITraceableMapper<T, ITraceable<T, Trace>, Trace> { - return TraceUtil.withTrace(<Trace>metric); - } - - static withFunctionTrace<F extends Callable, T, Trace>(f: F): ITraceableMapper<T, ITraceable<T, Trace>, Trace> { - return TraceUtil.withTrace(<Trace>`fn.${f.name}`); - } - - static withClassTrace<C extends object, T, Trace>(c: C): ITraceableMapper<T, ITraceable<T, Trace>, Trace> { - return TraceUtil.withTrace(<Trace>`class.${c.constructor.name}`); - } -} diff --git a/u/tsconfig.json b/u/tsconfig.json deleted file mode 100644 index 80e5ff4..0000000 --- a/u/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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"] -} diff --git a/u/types/collections/cons.ts b/u/types/collections/cons.ts deleted file mode 100644 index 05dbe7c..0000000 --- a/u/types/collections/cons.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IOptional, Mapper, Optional } from '@emprespresso/pengueno'; - -export interface ICons<T> extends Iterable<T> { - readonly value: T; - readonly next: IOptional<ICons<T>>; - - readonly replace: Mapper<T, ICons<T>>; - readonly before: Mapper<IOptional<ICons<T>>, ICons<T>>; -} - -export class Cons<T> implements ICons<T> { - constructor( - public readonly value: T, - public readonly next: IOptional<ICons<T>> = Optional.none(), - ) {} - - public before(head: IOptional<ICons<T>>): ICons<T> { - return new Cons<T>(this.value, head); - } - - public replace(_value: T): ICons<T> { - return new Cons<T>(_value, this.next); - } - - *[Symbol.iterator]() { - for (let cur = Optional.some<ICons<T>>(this); cur.present(); cur = cur.flatMap((cur) => cur.next)) { - yield cur.get().value; - } - } - - static addOnto<T>(items: Iterable<T>, tail: IOptional<ICons<T>>): IOptional<ICons<T>> { - return Array.from(items) - .reverse() - .reduce((cons, value) => Optional.from<ICons<T>>(new Cons<T>(value, cons)), tail); - } - - static from<T>(items: Iterable<T>): IOptional<ICons<T>> { - return Cons.addOnto(items, Optional.none()); - } -} diff --git a/u/types/collections/index.ts b/u/types/collections/index.ts deleted file mode 100644 index 69e5d0b..0000000 --- a/u/types/collections/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './cons.js'; -export * from './list_zipper.js'; diff --git a/u/types/collections/list_zipper.ts b/u/types/collections/list_zipper.ts deleted file mode 100644 index 3df15b5..0000000 --- a/u/types/collections/list_zipper.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Cons, ICons } from './cons.js'; -import { IOptional, Mapper, Optional, Supplier } from '@emprespresso/pengueno'; - -export interface IZipper<T> extends Iterable<T> { - readonly read: Supplier<IOptional<T>>; - readonly next: Supplier<IOptional<IZipper<T>>>; - readonly previous: Supplier<IOptional<IZipper<T>>>; - - readonly prependChunk: Mapper<Iterable<T>, IZipper<T>>; - readonly prepend: Mapper<T, IZipper<T>>; - readonly remove: Supplier<IZipper<T>>; - readonly replace: Mapper<T, IZipper<T>>; -} - -export class ListZipper<T> implements IZipper<T> { - private constructor( - private readonly reversedPathToHead: IOptional<ICons<T>>, - private readonly currentHead: IOptional<ICons<T>>, - ) {} - - public read(): IOptional<T> { - return this.currentHead.map(({ value }) => value); - } - - public next(): IOptional<IZipper<T>> { - return this.currentHead.map<IZipper<T>>( - (head) => new ListZipper<T>(Optional.some(head.before(this.reversedPathToHead)), head.next), - ); - } - - public previous(): IOptional<IZipper<T>> { - return this.reversedPathToHead.map<IZipper<T>>( - (lastVisited) => new ListZipper<T>(lastVisited.next, Optional.some(lastVisited.before(this.currentHead))), - ); - } - - public prependChunk(values: Iterable<T>): IZipper<T> { - return new ListZipper<T>(Cons.addOnto(Array.from(values).reverse(), this.reversedPathToHead), this.currentHead); - } - - public prepend(value: T): IZipper<T> { - return this.prependChunk([value]); - } - - public remove(): IZipper<T> { - const newHead = this.currentHead.flatMap((right) => right.next); - return new ListZipper<T>(this.reversedPathToHead, newHead); - } - - public replace(value: T): IZipper<T> { - const newHead = this.currentHead.map((right) => right.replace(value)); - return new ListZipper<T>(this.reversedPathToHead, newHead); - } - - *[Symbol.iterator]() { - let head: ListZipper<T> = this; - for (let prev = head.previous(); prev.present(); prev = prev.flatMap((p) => p.previous())) { - head = <ListZipper<T>>prev.get(); - } - if (head.currentHead.present()) yield* head.currentHead.get(); - } - - public collection() { - return Array.from(this); - } - - static from<T>(iterable: Iterable<T>): ListZipper<T> { - return new ListZipper(Optional.none(), Cons.from(iterable)); - } -} diff --git a/u/types/fn/callable.ts b/u/types/fn/callable.ts deleted file mode 100644 index 60d747b..0000000 --- a/u/types/fn/callable.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface Callable<T = any, ArgT = any> { - (...args: Array<ArgT>): T; -} - -export interface Supplier<T> extends Callable<T, undefined> { - (): T; -} - -export interface Mapper<T, U> extends Callable<U, T> { - (t: T): U; -} - -export interface Predicate<T> extends Mapper<T, boolean> {} - -export interface BiMapper<T, U, R> extends Callable { - (t: T, u: U): R; -} - -export interface SideEffect<T> extends Mapper<T, void> {} - -export interface BiSideEffect<T, U> extends BiMapper<T, U, void> {} diff --git a/u/types/fn/either.ts b/u/types/fn/either.ts deleted file mode 100644 index 0f65859..0000000 --- a/u/types/fn/either.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { - BiMapper, - IOptional, - type Mapper, - Optional, - Predicate, - type Supplier, - Tagged, - isTagged, -} from '@emprespresso/pengueno'; - -export const IEitherTag = 'IEither' as const; -export type IEitherTag = typeof IEitherTag; -export const isEither = <E, T>(o: unknown): o is IEither<E, T> => isTagged(o, IEitherTag); -export interface IEither<E, T> extends Tagged<IEitherTag> { - readonly left: Supplier<IOptional<E>>; - readonly right: Supplier<IOptional<T>>; - - readonly mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>; - readonly filter: (mapper: Predicate<T>) => IEither<E, T>; - readonly mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>; - readonly mapBoth: <_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>) => IEither<_E, _T>; - - readonly flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>; - readonly flatMapAsync: <_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>) => Promise<IEither<E, _T>>; - - readonly moveRight: <_T>(t: _T) => IEither<E, _T>; - readonly fold: <_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>) => _T; - readonly joinRight: <O, _T>(other: IEither<E, O>, mapper: (a: O, b: T) => _T) => IEither<E, _T>; - readonly joinRightAsync: <O, _T>( - other: (() => Promise<IEither<E, O>>) | Promise<IEither<E, O>>, - mapper: (a: O, b: T) => _T, - ) => Promise<IEither<E, _T>>; -} - -const ELeftTag = 'E.Left' as const; -type ELeftTag = typeof ELeftTag; -export const isLeft = <E>(o: unknown): o is Left<E> => isTagged(o, ELeftTag); -interface Left<E> extends Tagged<ELeftTag> { - err: E; -} - -const ERightTag = 'E.Right' as const; -type ERightTag = typeof ERightTag; -export const isRight = <T>(o: unknown): o is Right<T> => isTagged(o, ERightTag); -interface Right<T> extends Tagged<ERightTag> { - ok: T; -} - -class _Tagged implements Tagged<IEitherTag> { - protected constructor(public readonly _tag = IEitherTag) {} -} - -export class Either<E, T> extends _Tagged implements IEither<E, T> { - protected constructor(private readonly self: Left<E> | Right<T>) { - super(); - } - - public moveRight<_T>(t: _T) { - return this.mapRight(() => t); - } - - public mapBoth<_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>): IEither<_E, _T> { - if (isLeft(this.self)) return Either.left(errBranch(this.self.err)); - return Either.right(okBranch(this.self.ok)); - } - - public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> { - if (isRight(this.self)) return Either.right(mapper(this.self.ok)); - return Either.left(this.self.err); - } - - public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> { - if (isLeft(this.self)) return Either.left(mapper(this.self.err)); - return Either.right(this.self.ok); - } - - public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> { - if (isRight(this.self)) return mapper(this.self.ok); - return Either.left<E, _T>(this.self.err); - } - - public filter(mapper: Predicate<T>): IEither<E, T> { - if (isLeft(this.self)) return Either.left<E, T>(this.self.err); - return Either.fromFailable<E, T>(() => this.right().filter(mapper).get()); - } - - public async flatMapAsync<_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>): Promise<IEither<E, _T>> { - if (isLeft(this.self)) return Promise.resolve(Either.left(this.self.err)); - return await mapper(this.self.ok).catch((err) => Either.left(err)); - } - - public fold<_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>): _T { - if (isLeft(this.self)) return leftFolder(this.self.err); - return rightFolder(this.self.ok); - } - - public left(): IOptional<E> { - if (isLeft(this.self)) return Optional.from(this.self.err) as IOptional<E>; - return Optional.none(); - } - - public right(): IOptional<T> { - if (isRight(this.self)) return Optional.from(this.self.ok) as IOptional<T>; - return Optional.none(); - } - - public joinRight<O, _T>(other: IEither<E, O>, mapper: BiMapper<O, T, _T>) { - return this.flatMap((t) => other.mapRight((o) => mapper(o, t))); - } - - public joinRightAsync<O, _T>( - other: Supplier<Promise<IEither<E, O>>> | Promise<IEither<E, O>>, - mapper: BiMapper<O, T, _T>, - ) { - return this.flatMapAsync(async (t) => { - const o = typeof other === 'function' ? other() : other; - return await o.then((other) => other.mapRight((o) => mapper(o, t))); - }); - } - - static left<E, T>(e: E): IEither<E, T> { - return new Either({ err: e, _tag: ELeftTag }); - } - - static right<E, T>(t: T): IEither<E, T> { - return new Either({ ok: t, _tag: ERightTag }); - } - - static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> { - try { - return Either.right(s()); - } catch (e) { - return Either.left(e as E); - } - } - - static async fromFailableAsync<E, T>(s: Supplier<Promise<T>> | Promise<T>): Promise<IEither<E, T>> { - return await (typeof s === 'function' ? s() : s) - .then((t: T) => Either.right<E, T>(t)) - .catch((e: E) => Either.left<E, T>(e)); - } -} diff --git a/u/types/fn/index.ts b/u/types/fn/index.ts deleted file mode 100644 index 780c86c..0000000 --- a/u/types/fn/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './callable.js'; -export * from './either.js'; -export * from './optional.js'; diff --git a/u/types/fn/optional.ts b/u/types/fn/optional.ts deleted file mode 100644 index 504e496..0000000 --- a/u/types/fn/optional.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { type Mapper, Predicate, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno'; - -export type MaybeGiven<T> = T | undefined | null; - -export const IOptionalTag = 'IOptional' as const; -export type IOptionalTag = typeof IOptionalTag; -export const isOptional = <T>(o: unknown): o is IOptional<T> => isTagged(o, IOptionalTag); -export class IOptionalEmptyError extends Error {} -export interface IOptional<t, T extends NonNullable<t> = NonNullable<t>> extends Tagged<IOptionalTag>, Iterable<T> { - readonly move: <_T>(t: MaybeGiven<_T>) => IOptional<_T>; - readonly map: <_T>(mapper: Mapper<T, MaybeGiven<_T>>) => IOptional<_T>; - readonly filter: (mapper: Predicate<T>) => IOptional<T>; - readonly flatMap: <_T>(mapper: Mapper<T, MaybeGiven<IOptional<_T>>>) => IOptional<_T>; - readonly orSome: (supplier: Supplier<MaybeGiven<t>>) => IOptional<T>; - readonly get: Supplier<T>; - readonly present: Supplier<boolean>; -} - -type OSomeTag = typeof OSomeTag; -const OSomeTag = 'O.Some' as const; -interface Some<T> extends Tagged<OSomeTag> { - value: NonNullable<T>; -} - -const ONoneTag = 'O.None' as const; -type ONoneTag = typeof ONoneTag; -interface None extends Tagged<ONoneTag> {} - -const isNone = (o: unknown): o is None => isTagged(o, ONoneTag); -const isSome = <T>(o: unknown): o is Some<T> => isTagged(o, OSomeTag); - -class _Tagged implements Tagged<IOptionalTag> { - protected constructor(public readonly _tag = IOptionalTag) {} -} - -export class Optional<t, T extends NonNullable<t> = NonNullable<t>> extends _Tagged implements IOptional<T> { - private constructor(private readonly self: Some<T> | None) { - super(); - } - - public move<_T>(t: MaybeGiven<_T>): IOptional<_T> { - return this.map(() => t); - } - - public orSome(supplier: Supplier<MaybeGiven<t>>): IOptional<T> { - if (isNone(this.self)) return Optional.from(supplier()); - return this; - } - - public get(): T { - if (isNone(this.self)) throw new IOptionalEmptyError('called get() on None optional'); - return this.self.value; - } - - public filter(mapper: Predicate<T>): IOptional<T> { - if (isNone(this.self) || !mapper(this.self.value)) return Optional.none(); - return Optional.some(this.self.value); - } - - public map<_T>(mapper: Mapper<T, MaybeGiven<_T>>): IOptional<_T> { - if (isNone(this.self)) return Optional.none(); - return Optional.from(mapper(this.self.value)) as IOptional<_T>; - } - - public flatMap<_T>(mapper: Mapper<T, MaybeGiven<IOptional<_T>>>): IOptional<_T> { - if (isNone(this.self)) return Optional.none(); - return Optional.from(mapper(this.self.value)) - .orSome(() => Optional.none()) - .get(); - } - - public present() { - return isSome(this.self); - } - - *[Symbol.iterator]() { - if (isSome(this.self)) yield this.self.value; - } - - static some<t, T extends NonNullable<t> = NonNullable<t>>(value: T): IOptional<T> { - return new Optional({ value, _tag: OSomeTag }); - } - - private static readonly _none = new Optional({ _tag: ONoneTag }); - static none<T>(): IOptional<T> { - return this._none as unknown as IOptional<T>; - } - - static from<t, T extends NonNullable<t> = NonNullable<t>>(value: MaybeGiven<t>): IOptional<T> { - if (value === null || value === undefined) return Optional.none<T>(); - return Optional.some(<T>value); - } -} diff --git a/u/types/index.ts b/u/types/index.ts deleted file mode 100644 index fc1b15a..0000000 --- a/u/types/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './misc.js'; - -export * from './object.js'; -export * from './tagged.js'; - -export * from './fn/index.js'; -export * from './collections/index.js'; diff --git a/u/types/misc.ts b/u/types/misc.ts deleted file mode 100644 index 77833c4..0000000 --- a/u/types/misc.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type ObjectFromList<T extends ReadonlyArray<string | number | symbol>, V = string> = { - [K in T extends ReadonlyArray<infer U> ? U : never]: V; -}; diff --git a/u/types/object.ts b/u/types/object.ts deleted file mode 100644 index fe97999..0000000 --- a/u/types/object.ts +++ /dev/null @@ -1 +0,0 @@ -export const isObject = (o: unknown): o is object => typeof o === 'object' && !Array.isArray(o) && !!o; diff --git a/u/types/tagged.ts b/u/types/tagged.ts deleted file mode 100644 index 351e4c9..0000000 --- a/u/types/tagged.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { isObject } from './index.js'; - -export interface Tagged<TTag> { - _tag: TTag; -} - -export const isTagged = <TTag>(o: unknown, tag: TTag): o is Tagged<TTag> => - !!(isObject(o) && '_tag' in o && o._tag === tag); diff --git a/worker/package.json b/worker/package.json index a3cfcd6..4db8dba 100644 --- a/worker/package.json +++ b/worker/package.json @@ -17,8 +17,8 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@emprespresso/pengueno": "*", - "@emprespresso/ci_model": "*" + "@emprespresso/ci_model": "*", + "@emprespresso/pengueno": "^0.0.5" }, "devDependencies": { "copyfiles": "2.4.1" diff --git a/worker/scripts/npm_publish.ts b/worker/scripts/npm_publish.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/worker/scripts/npm_publish.ts diff --git a/worker/tsconfig.json b/worker/tsconfig.json index 58e9147..3858ff4 100644 --- a/worker/tsconfig.json +++ b/worker/tsconfig.json @@ -11,5 +11,5 @@ }, "include": ["**/*.ts"], "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"], - "references": [{ "path": "../u" }, { "path": "../model" }] + "references": [{ "path": "../model" }] } |