Scratch space for learning atproto app development

tidy

+2 -2
src/env.ts
···
import dotenv from 'dotenv'
import { cleanEnv, port, str, testOnly } from 'envalid'
-
import { privateKeys } from '#/lib/envalid-private-keys'
dotenv.config()
···
PUBLIC_URL: str({}),
DB_PATH: str({ devDefault: ':memory:' }),
COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }),
-
PRIVATE_JWKS: privateKeys({ default: undefined }),
})
···
import dotenv from 'dotenv'
import { cleanEnv, port, str, testOnly } from 'envalid'
+
import { envalidJsonWebKeys } from '#/lib/jwk'
dotenv.config()
···
PUBLIC_URL: str({}),
DB_PATH: str({ devDefault: ':memory:' }),
COOKIE_SECRET: str({ devDefault: '00000000000000000000000000000000' }),
+
PRIVATE_JWKS: envalidJsonWebKeys({ default: undefined }),
})
-17
src/lib/envalid-private-keys.ts
···
-
import { Jwk, jwkValidator } from '@atproto/oauth-client-node'
-
import { makeValidator } from 'envalid'
-
import { z } from 'zod'
-
-
export type PrivateKey = Jwk & { kid: string }
-
-
const privateKeySchema = z.intersection(
-
jwkValidator,
-
z.object({ kid: z.string().nonempty() }),
-
) satisfies z.ZodType<PrivateKey>
-
-
const privateKeysSchema = z.array(privateKeySchema).nonempty()
-
-
export const privateKeys = makeValidator((input) => {
-
const value = JSON.parse(input)
-
return privateKeysSchema.parse(value)
-
})
···
+10 -2
src/lib/http.ts
···
Res extends ServerResponse<Req> = ServerResponse<Req>,
> = (req: Req, res: Res, next: NextFunction) => Promise<void>
-
// Helper function for defining routes
export function handler<
Req extends IncomingMessage = IncomingMessage,
Res extends ServerResponse<Req> = ServerResponse<Req>,
>(fn: Handler<Req, Res> | AsyncHandler<Req, Res>): Handler<Req, Res> {
return (req, res, next) => {
-
// NodeJS prefers objects over functions for garbage collection,
const nextSafe = nextOnce.bind({ next })
try {
const result = fn(req, res, nextSafe)
···
}
}
export async function startServer(
requestListener: RequestListener,
{
···
Res extends ServerResponse<Req> = ServerResponse<Req>,
> = (req: Req, res: Res, next: NextFunction) => Promise<void>
+
/**
+
* Wraps a request handler middleware to ensure that `next` is called if it
+
* throws or returns a promise that rejects.
+
*/
export function handler<
Req extends IncomingMessage = IncomingMessage,
Res extends ServerResponse<Req> = ServerResponse<Req>,
>(fn: Handler<Req, Res> | AsyncHandler<Req, Res>): Handler<Req, Res> {
return (req, res, next) => {
+
// Optimization: NodeJS prefers objects over functions for garbage collection
const nextSafe = nextOnce.bind({ next })
try {
const result = fn(req, res, nextSafe)
···
}
}
+
/**
+
* Create an HTTP server with the provided request listener, ensuring that it
+
* can bind the listening port, and returns a termination function that allows
+
* graceful termination of HTTP connections.
+
*/
export async function startServer(
requestListener: RequestListener,
{
+17
src/lib/jwk.ts
···
···
+
import { Jwk, jwkValidator } from '@atproto/oauth-client-node'
+
import { makeValidator } from 'envalid'
+
import { z } from 'zod'
+
+
export type JsonWebKey = Jwk & { kid: string }
+
+
const jsonWebKeySchema = z.intersection(
+
jwkValidator,
+
z.object({ kid: z.string().nonempty() }),
+
) satisfies z.ZodType<JsonWebKey>
+
+
const jsonWebKeysSchema = z.array(jsonWebKeySchema).nonempty()
+
+
export const envalidJsonWebKeys = makeValidator((input) => {
+
const value = JSON.parse(input)
+
return jsonWebKeysSchema.parse(value)
+
})
+5 -1
src/lib/process.ts
···
const SIGNALS = ['SIGINT', 'SIGTERM'] as const
export async function run<F extends (signal: AbortSignal) => unknown>(
-
fn: F
): Promise<Awaited<ReturnType<F>>> {
const killController = new AbortController()
···
const SIGNALS = ['SIGINT', 'SIGTERM'] as const
+
/**
+
* Runs a function with an abort signal that will be triggered when the process
+
* receives a termination signal.
+
*/
export async function run<F extends (signal: AbortSignal) => unknown>(
+
fn: F,
): Promise<Awaited<ReturnType<F>>> {
const killController = new AbortController()