@youneed/ai-skill
Agent Skills that turn Claude into a youneed-framework expert: the `youneed` skill (components, server, organization, performance, server-optimizations/security/best-practices, middleware, realtime/pub-sub, plugins & infra, a11y, i18n, auth/login (OAuth2/OTP/JWT/webhooks), migration) plus focused skills for the CLI framework (cli), server-side rendering & static generation (ssr), devtools/ts-plugin (develop), logger/transports/config (logging), orm-sql/kv (orm), writing/running tests (testing), migrating an existing app onto the youneed stack — frontend/backend/data/tests + build switch + interop adapters (migration), authorization + secrets (security), feature flags (feature-flags), application server plugins graphql/grpc/mailer/storage/queue/otlp (server-plugins), the shad UI library + component providers (ui), typed API client / resilient fetch / runtime adapters (clients), and the shared @youneed/core primitives + build tooling (foundation).
README →
@youneed/api-client
Typed API client runtime + OpenAPI → TypeScript client codegen (consumes the OpenAPI @youneed/server generates). Dependency-free, uses fetch (or @youneed/http-client).
import { generateClient } from "@youneed/api-client/codegen";
const code = generateClient(openApiDoc, { className: "PetStore" }); // → .ts source string
README →
@youneed/cli
Type-safe, Commander-style CLI framework on the @youneed factory-class pattern.
import { Application, Command, Option, defaultOptions } from "@youneed/cli";
// A reusable, named option (its key + value type flow into `this.options`).
class FirstOption extends Option("--first", {
short: "f",
description: "display just the first substring",
}) {}
class SplitCommand extends Command({
name: "split <string>", // grammar: a word + positional args
description: "Split a string into substrings and display as an array",
options: [FirstOption, { name: "-s, --separator <char>", default: "," }, ...defaultOptions()],
}) {
execute(value: string) {
// `value` is typed from `<string>`; `this.options` from the options tuple.
const limit = this.options.first ? 1 : undefined;
console.log(value.split(this.options.separator, limit));
}
}
Application({
name: "string-util",
description: "CLI to some JavaScript string utilities",
version: "0.0.8",
commands: [SplitCommand],
options: [...defaultOptions()],
});
README →
@youneed/cli-middleware-cache
Disk cache middleware: this.cache.get/set/wrap, TTL, JSON on disk.
import { Command } from "@youneed/cli";
import { cache } from "@youneed/cli-middleware-cache";
class Build extends Command("build", { middleware: [cache()] }) {
async execute() {
// get-or-compute, expiring after 60s
const deps = await this.cache.wrap("deps", () => resolveDeps(), 60_000);
console.log(deps);
}
}
README →
@youneed/cli-middleware-childprocess
Child-process middleware for @youneed/cli: this.childprocess.spawn / .exec wrap node:child_process in a task — reactive (pending/output/exit), killed on shutdown or teardown.
import { Command } from "@youneed/cli";
import { childprocess } from "@youneed/cli-middleware-childprocess";
class Build extends Command("build", { middleware: [childprocess()] }) {
async execute() {
const tsc = this.childprocess.spawn("tsc", ["-p", "."]);
const { code } = (await tsc.exited) ?? {};
if (code !== 0) console.error(tsc.stderr);
// shell shorthand: run and await the collected output
const out = await this.childprocess.exec("git rev-parse HEAD");
console.log(out?.stdout.trim());
}
}
README →
@youneed/cli-middleware-clipboard
Clipboard middleware: this.clipboard.write/read via system clipboard.
import { Command } from "@youneed/cli";
import { clipboard } from "@youneed/cli-middleware-clipboard";
class Token extends Command("token", { middleware: [clipboard()] }) {
async execute() {
const t = generate();
await this.clipboard.write(t);
console.log("copied to clipboard");
}
}
README →
@youneed/cli-middleware-color
Terminal color & styling middleware for @youneed/cli: adds this.color with ANSI styles, honouring NO_COLOR / --no-color / TTY detection.
import { Command } from "@youneed/cli";
import { color } from "@youneed/cli-middleware-color";
class Build extends Command({ name: "build", middleware: [color()] }) {
execute() {
console.log(this.color.green("done"), this.color.bold(this.color.cyan("✓")));
if (this.color.enabled) console.log(this.color.background.magenta(" NEW "));
}
}
README →
@youneed/cli-middleware-env
Environment-variable middleware for @youneed/cli: adds a typed, validated this.env parsed from process.env via @youneed/schema, failing fast on bad config.
import { Command } from "@youneed/cli";
import { env, t } from "@youneed/cli-middleware-env";
class Serve extends Command({
name: "serve",
middleware: [env({ PORT: t.port().default(3000), NODE_ENV: t.enum(["dev", "prod"]) })],
}) {
execute() {
this.server.listen(this.env.PORT); // this.env.PORT: number
// this.env.NODE_ENV: "dev" | "prod"
}
}
README →
@youneed/cli-middleware-fs
Filesystem middleware: this.fs read/write + temp dirs auto-removed on teardown.
import { Command } from "@youneed/cli";
import { fs } from "@youneed/cli-middleware-fs";
class Gen extends Command("gen", { middleware: [fs()] }) {
async execute() {
const dir = this.fs.tempDir(); // removed automatically on teardown
this.fs.writeJson(`${dir}/out.json`, { ok: true });
if (this.fs.exists("config.json")) {
const cfg = this.fs.readJson<{ name: string }>("config.json");
this.fs.writeText("README.md", `# ${cfg.name}\n`);
}
}
}
README →
@youneed/cli-middleware-hotkeys
Hotkey middleware: this.keys.on(name, handler) over the raw terminal.
import { Application, Command, text } from "@youneed/cli";
import { hotkeys } from "@youneed/cli-middleware-hotkeys";
class Watch extends Command("watch", { middleware: [hotkeys()] }) {
#status = "watching — press r to rebuild, q to quit";
async execute() {
await new Promise<void>((done) => {
this.keys.on("r", () => {
this.#status = "rebuilding…";
this.requestUpdate();
rebuild().then(() => {
this.#status = "watching — press r to rebuild, q to quit";
this.requestUpdate();
});
});
this.keys.on("q", done); // q ends the command
this.keys.on("ctrl-c", done); // so does Ctrl-C
});
}
render() {
return text`${this.#status}`;
}
}
Application({ name: "dev", commands: [Watch] });
README →
@youneed/cli-middleware-i18n
i18n middleware for @youneed/cli: this.i18n (translate/locale) backed by @youneed/i18n.
import { Application, Command } from "@youneed/cli";
import { i18n } from "@youneed/cli-middleware-i18n";
const messages = {
en: { hi: "Hello {name}", bye: "Goodbye" },
ru: { hi: "Привет {name}", bye: "Пока" },
};
class Greet extends Command({
name: "greet <name>",
options: [{ name: "--locale <l>" }],
middleware: [i18n({ resources: messages, locale: "en" })],
}) {
execute(name: string) {
console.log(this.i18n.t("hi", { name })); // `greet Ada --locale ru` → "Привет Ada"
}
}
Application({ name: "app", commands: [Greet] });
README →
@youneed/cli-middleware-logger
Logger middleware for @youneed/cli: adds this.logger (a @youneed/logger instance) with level wired from --verbose/--quiet flags.
import { Application, Command } from "@youneed/cli";
import { logger } from "@youneed/cli-middleware-logger";
class Deploy extends Command({
name: "deploy <env>",
options: [{ name: "-v, --verbose" }, { name: "-q, --quiet" }],
middleware: [logger()],
}) {
execute(target: string) {
this.logger.info("deploying", { target });
this.logger.debug("resolved config", { region: "eu" }); // shown only with -v
}
}
Application({ name: "ops", commands: [Deploy] });
README →
@youneed/cli-middleware-markdown
Markdown middleware: this.markdown(md) renders Markdown to terminal.
import { Application, Command } from "@youneed/cli";
import { markdown } from "@youneed/cli-middleware-markdown";
class Readme extends Command("readme", { middleware: [markdown()] }) {
execute() {
console.log(
this.markdown("# Title\n\nSome **bold** text and a `code` span.\n\n- one\n- two"),
);
}
}
Application({ name: "docs", commands: [Readme] });
README →
@youneed/cli-middleware-music
Music transport for @youneed/cli: adds this.player (track metadata + elapsed/duration/play/pause clock), with an optional system-player backend for real audio.
import { Command, task, text } from "@youneed/cli";
import { formatTime, music, systemPlayer } from "@youneed/cli-middleware-music";
const TRACK = { title: "Midnight City", artist: "M83", duration: 30 };
export class Play extends Command("play", {
middleware: [music(TRACK, { autoplay: true, backend: systemPlayer("track.mp3") })],
}) {
// Keep the live region alive until the track ends.
#ended = task(this, () =>
new Promise<void>((resolve) => {
const poll = () =>
void (this.player?.ended || this.abortSignal.aborted ? resolve() : setTimeout(poll, 100));
setTimeout(poll, 100);
}),
);
constructor() {
super();
this.#ended.run();
// Advance the transport clock at 12fps; the runtime disposes the timer on exit.
this.scheduler.frame((dt) => this.player?.tick(dt), 12);
}
render() {
const p = this.player;
return text`${p.track.title} — ${formatTime(p.elapsed)} / ${formatTime(p.duration)}`;
}
}
README →
@youneed/cli-middleware-notification
Desktop notification middleware for @youneed/cli: adds this.notify (send/info/success/warn/error) backed by node-notifier, with a graceful fallback when it isn't installed.
import { Application, Command } from "@youneed/cli";
import { notifications } from "@youneed/cli-middleware-notification";
class Build extends Command("build", { middleware: [notifications()] }) {
async execute() {
await doWork();
await this.notify.success("Build complete"); // OS notification, no sound
// or, fully specified:
await this.notify.send({ message: "Deployed", subtitle: "prod", sound: true });
}
}
Application({ name: "ci", commands: [Build] });
README →
@youneed/cli-middleware-oscillator
A synthetic spectrum source + cava-style bar renderer for @youneed/cli: adds this.oscillator and spectrumBars() for terminal audio visualisers.
import { Application, Command, task, text } from "@youneed/cli";
import { oscillator, spectrumBars } from "@youneed/cli-middleware-oscillator";
class Vis extends Command("vis", { middleware: [oscillator({ bands: 32, speed: 1.5 })] }) {
t = 0;
execute() {
// animate: bump `t` on a timer and repaint via a task/render loop
task(this, async () => {
for (let i = 0; i < 300; i++) {
this.t = i / 30;
this.requestUpdate?.();
await new Promise((r) => setTimeout(r, 33));
}
}).run();
}
render() {
return text`${spectrumBars(this.oscillator.sample(this.t), { height: 10 })}`;
}
}
const app = Application({ name: "demo", commands: [Vis] });
app.run(["vis"]);
README →
@youneed/cli-middleware-pages
Pager middleware: this.pages.show(text) pages long output.
import { Application, Command } from "@youneed/cli";
import { pages } from "@youneed/cli-middleware-pages";
class Log extends Command("log", { middleware: [pages()] }) {
async execute() {
const bigLogString = await readSomeLongLog();
await this.pages.show(bigLogString); // takes over the screen until `q`
}
}
const app = Application({ name: "tool", commands: [Log] });
app.run(["log"]);
README →
@youneed/cli-middleware-progress
Progress-bar middleware: this.progress.bar() with percent/ETA.
import { Application, Command } from "@youneed/cli";
import { progress } from "@youneed/cli-middleware-progress";
class Download extends Command("download", { middleware: [progress()] }) {
async execute() {
const files = await listFiles();
const bar = this.progress.bar({ total: files.length, label: "downloading" });
for (const f of files) {
await fetchFile(f);
bar.tick(); // repaints the live region
}
bar.complete();
}
}
const app = Application({ name: "tool", commands: [Download] });
app.run(["download"]);
README →
@youneed/cli-middleware-prompt
Interactive prompts for @youneed/cli: adds this.prompt with ask/confirm/choice/list/alert — raw-key TUI primitives drawn via the core live renderer.
import { Application, Command } from "@youneed/cli";
import { prompts } from "@youneed/cli-middleware-prompt";
class Setup extends Command("setup", { middleware: [prompts()] }) {
async execute() {
const name = await this.prompt.ask("Project name?", { default: "app" });
const env = await this.prompt.choice("Environment", ["dev", "staging", "prod"]);
const feats = await this.prompt.list("Features", ["ts", "lint", "tests"]);
if (await this.prompt.confirm(`Create ${name}?`, { default: true })) {
await this.prompt.spinner("scaffolding", () => scaffold(name, env, feats));
await this.prompt.alert("Done!");
}
}
}
const app = Application({ name: "create", commands: [Setup] });
app.run(["setup"]);
README →
@youneed/cli-middleware-screen
Alternate-screen middleware: full-screen TUI buffer; restores on teardown.
import { Application, Command } from "@youneed/cli";
import { screen } from "@youneed/cli-middleware-screen";
class Top extends Command("top", { middleware: [screen()] }) {
async execute() {
const paint = () => this.screen.draw(renderDashboard(this.screen.columns, this.screen.rows));
paint();
this.screen.onResize(paint); // repaint on terminal resize
await untilQuit(); // your own quit signal
}
}
const app = Application({ name: "monitor", commands: [Top] });
app.run(["top"]);
README →
@youneed/cli-middleware-worker
Worker-thread middleware for @youneed/cli: this.worker.run / .spawn wrap node:worker_threads in a task — offload CPU work, reactive, terminated on shutdown or teardown.
import { Application, Command } from "@youneed/cli";
import { worker } from "@youneed/cli-middleware-worker";
class Hash extends Command("hash <file>", { middleware: [worker()] }) {
async execute(file: string) {
const job = this.worker.run((path, require) => {
const { readFileSync } = require("node:fs");
const { createHash } = require("node:crypto");
return createHash("sha256").update(readFileSync(path)).digest("hex");
}, file);
console.log(await job.exited); // resolves with the digest (undefined on error)
}
}
const app = Application({ name: "tool", commands: [Hash] });
app.run(["hash", "./package.json"]);
README →
@youneed/cli-plugin-completion
Shell-completion plugin for @youneed/cli: registers a `completion` command that emits bash/zsh/fish scripts generated from the command catalogue.
import { Application } from "@youneed/cli";
import { completion } from "@youneed/cli-plugin-completion";
Application({
name: "ops",
commands: [/* … */],
plugins: [completion()],
}).run();
// then, in the user's shell:
// ops completion >> ~/.bashrc # bash
// ops completion zsh >> ~/.zshrc # zsh
// eval "$(ops completion)" # current session
README →
@youneed/cli-plugin-config
Config-file plugin for @youneed/cli: loads a config file (rc / *.config.json / package.json field) and merges its values into option defaults app-wide.
import { Application } from "@youneed/cli";
import { config } from "@youneed/cli-plugin-config";
Application({
name: "ops",
commands: [/* … */],
plugins: [config()],
}).run();
README →
@youneed/cli-plugin-devtools
Devtools server for @youneed/cli: serves the unified <youneed-devtools> shell (same UI as the server devtools) — a shad command/option builder that lists commands, fills in an invocation from a form, and copies or runs it over the devtools protocol.
import { Application } from "@youneed/cli";
import { devtools } from "@youneed/cli-plugin-devtools";
Application({
name: "ops",
commands: [/* … */],
plugins: [devtools()],
}).run();
// ops devtools → http://127.0.0.1:7331
README →
@youneed/cli-plugin-error
Error-formatting plugin for @youneed/cli: pretty stderr output, hints, and optional stack traces via the onError lifecycle hook.
import { Application } from "@youneed/cli";
import { errorReporter, CliError } from "@youneed/cli-plugin-error";
Application({
name: "ops",
commands: [/* … */],
plugins: [errorReporter()],
}).run();
// In a command, throw a CliError for richer output:
throw new CliError("config not found", {
code: "ENOENT",
hint: "run `ops init` first",
});
// ✖ config not found [ENOENT]
// hint: run `ops init` first
README →
@youneed/cli-plugin-feature-flags
Feature-flag plugin for @youneed/cli: a `flags` command to list/inspect/override flags plus middleware that adds this.flags (isEnabled/variant/value/evaluate) so any command can gate behavior on a flag.
import { Application, Command } from "@youneed/cli";
import { createFlags } from "@youneed/feature-flags";
import { featureFlags, flagsMiddleware } from "@youneed/cli-plugin-feature-flags";
const flags = createFlags([
{ key: "beta", defaultValue: false, rollout: 20 },
{ key: "theme", defaultValue: "light", variants: { light: "light", dark: "dark" } },
]);
class Deploy extends Command({ name: "deploy", middleware: [flagsMiddleware(flags)] }) {
execute() {
if (this.flags.isEnabled("beta")) console.log("beta path");
}
}
Application({
name: "ops",
commands: [Deploy],
plugins: [featureFlags(flags)],
}).run();
README →
@youneed/cli-plugin-help
Enhanced help plugin: a richer help command with examples and grouped commands.
import { Application } from "@youneed/cli";
import { help } from "@youneed/cli-plugin-help";
Application({
name: "ops",
version: "1.0.0",
description: "Operations toolkit",
commands: [/* … */],
plugins: [
help({ examples: { split: ["ops split a,b,c --first"] } }),
],
}).run();
// ops help → grouped command list with examples
// ops help split → usage, options and examples for one command
README →
@youneed/cli-plugin-man
Man-page plugin: a man command emitting roff documentation from the catalogue.
import { Application } from "@youneed/cli";
import { man } from "@youneed/cli-plugin-man";
Application({
name: "ops",
version: "1.0.0",
description: "Operations toolkit",
commands: [/* … */],
plugins: [man()],
}).run();
// ops man > ops.1 → man ./ops.1
README →
@youneed/cli-plugin-otel
@youneed/cli plugin + middleware: real OpenTelemetry SDK — a span per command execution, cli.command metrics, OTLP/HTTP export flushed before exit.
import { Application, Command } from "@youneed/cli";
import { otelCommand, otelPlugin } from "@youneed/cli-plugin-otel";
class Deploy extends Command({
name: "deploy <env>",
middleware: [otelCommand()], // 1. per-command span + this.otel
}) {
async execute(env: string) {
const plan = await this.otel.spanAsync("plan", async () => buildPlan(env));
await this.otel.spanAsync("apply", async () => applyPlan(plan));
}
}
Application({
name: "ops",
version: "1.4.0",
commands: [Deploy],
plugins: [otelPlugin({ serviceName: "ops-cli" })], // 2. SDK lifecycle + metrics
});
README →
@youneed/cli-plugin-update-notifier
Update-notifier plugin: checks npm for a newer version, notifies after commands.
import { Application } from "@youneed/cli";
import { updateNotifier } from "@youneed/cli-plugin-update-notifier";
Application({
name: "ops",
version: "1.0.0",
commands: [/* … */],
plugins: [updateNotifier({ current: "1.0.0" })],
}).run();
// after a command, if a newer version is on npm:
// Update available: 1.0.0 → 1.2.0
// Run `npm i -g ops` to update.
README →
@youneed/core
Foundational primitives shared across @youneed packages: shared types, the class-metadata registry (TC39 addInitializer + WeakMap, esbuild/tsx-safe), and disposal helpers.
import { createRegistry, ctorOf, classChain } from "@youneed/core";
interface FieldMeta { name: string; prop: string; }
const FIELDS = createRegistry<FieldMeta[]>(() => []);
// A field decorator that records itself into the most-derived class's entry.
function field(name: string) {
return function (_v: unknown, ctx: ClassFieldDecoratorContext) {
ctx.addInitializer(function (this: object) {
FIELDS.for(ctorOf(this)).push({ name, prop: String(ctx.name) });
});
};
}
// The runtime reads it back, walking the inheritance chain most-derived first.
function fieldsOf(instance: object): FieldMeta[] {
const all: FieldMeta[] = [];
for (const c of classChain(ctorOf(instance))) all.push(...(FIELDS.read(c) ?? []));
return all;
}
README →
create-youneedpackage
Internal scaffolder for @youneed/* workspace packages.
README →
@youneed/devtools
Inspector panel (component tree, time-travel, schedulers) + Page devtools.
import { installDevtools, mountDevtoolsPanel } from "@youneed/devtools";
installDevtools(); // capture per-component state/props/events/styles
mountDevtoolsPanel(); // floating, dockable, interactive panel (state persists)
README →
@youneed/devtools-protocol
Universal, CDP-style devtools protocol for every youneed surface (frontend, server, ssr, cli): JSON-RPC 2.0 envelopes, targets, domains, commands + events, pluggable transports, and a domain-keyed UI extension registry (one protocol, per-surface UI).
README →
@youneed/dom
Reactive components on native Custom Elements + Shadow DOM.
import { Component, html, css } from "@youneed/dom";
@Component.define()
class Counter extends Component("x-counter") {
static styles = css`button { font: inherit }`;
@Component.prop() count = 0; // reactive: assigning re-renders
@Component.prop({ attribute: true }) label = "count"; // reflects <x-counter label="…">
@Component.event() inc() { this.count++; } // auto-bound for @click
render() {
return html`<button @click=${this.inc}>${this.label}: ${this.count}</button>`;
}
}
README →
@youneed/dom-adapter-angular
Use @youneed/dom components inside Angular and Angular components inside @youneed/dom — type-safe and refactor-friendly (pass the class, not a tag string).
import { toAngular } from "@youneed/dom-adapter-angular";
import { UserCard } from "./user-card";
// In an Angular component, append the element where you need it:
@Component({ selector: "app-profile", template: `<section #host></section>` })
class Profile {
@ViewChild("host") host!: ElementRef<HTMLElement>;
@Input() user!: User;
ngAfterViewInit() {
this.host.nativeElement.append(toAngular(UserCard, { user: this.user }));
}
}
README →
@youneed/dom-adapter-astro
Render @youneed/dom components to SSR HTML for Astro islands — Declarative Shadow DOM + client hydration, type-safe (pass the component, not a tag string).
import { registerDOM } from "@youneed/dom/register";
registerDOM();
README →
@youneed/dom-adapter-preact
Use @youneed/dom components inside Preact and Preact components inside @youneed/dom — type-safe and refactor-friendly (pass the component, not a tag string).
import { toPreact } from "@youneed/dom-adapter-preact";
import { UserCard } from "./user-card";
const PreactUserCard = toPreact(UserCard);
function Profile({ user }) {
return <PreactUserCard user={user} onSelect={e => console.log(e.detail)} />;
}
README →
@youneed/dom-adapter-react
Render @youneed/dom components inside React — type-safe and refactor-friendly (pass the class, not a tag string).
import { toReact } from "@youneed/dom-adapter-react";
import { UserCard } from "./user-card";
const ReactUserCard = toReact(UserCard); // ← a real React component
function Profile({ user }) {
return (
<section>
<ReactUserCard user={user} onSelect={(e) => console.log(e.detail)} />
</section>
);
}
README →
@youneed/dom-adapter-svelte
Use @youneed/dom components inside Svelte (a use: action) and Svelte components inside @youneed/dom — type-safe and refactor-friendly (pass the component, not a tag string).
import { fromSvelte } from "@youneed/dom-adapter-svelte";
import Chart from "./Chart.svelte";
const SvelteChart = fromSvelte(Chart, { events: ["select"] }); // ← a custom-element class
README →
@youneed/dom-adapter-vue
Use @youneed/dom components inside Vue and Vue components inside @youneed/dom — type-safe and refactor-friendly (pass the component, not a tag string).
import { fromVue } from "@youneed/dom-adapter-vue";
import Chart from "some-vue-charts";
const VueChart = fromVue(Chart); // ← a custom-element class
README →
@youneed/dom-provider-a11y
Accessibility helpers for @youneed/dom components — a composable provider for screen-reader announcements, focus trapping, and reduced-motion.
import { Component, html } from "@youneed/dom";
import { a11yProvider } from "@youneed/dom-provider-a11y";
class Dialog extends Component("x-dialog", { providers: [a11yProvider()] }) {
onMount() {
this.onCleanup(this.a11y.trapFocus()); // Tab stays inside; focus restored on close
this.a11y.announce("Dialog opened"); // spoken by screen readers
}
render() {
return html`<button>OK</button><button>Cancel</button>`;
}
}
README →
@youneed/dom-provider-color-scheme
Light/dark/auto color scheme for @youneed/dom — a composable provider that reflects CSS color-scheme globally or per component, toggled via this.setColorScheme.
import { Component, html } from "@youneed/dom";
import { colorSchemeProvider } from "@youneed/dom-provider-color-scheme";
class Card extends Component("x-card", { providers: [colorSchemeProvider("auto")] }) {
render() {
return html`
<button @click=${() => this.colorScheme.toggle()}>theme</button>
<p>scheme: ${this.colorScheme.value} (${this.colorScheme.resolved})</p>`;
// ^ this.colorScheme.{ value, resolved, set, toggle } are typed
}
}
README →
@youneed/dom-provider-direction
Per-component text direction (LTR/RTL) for @youneed/dom: a composable provider that reflects `dir` and toggles via this.setDirection.
import { Component, html } from "@youneed/dom";
import { directionProvider } from "@youneed/dom-provider-direction";
class Panel extends Component("x-panel", { providers: [directionProvider("ltr")] }) {
render() {
return html`
<button @click=${() => this.direction.toggle()}>flip</button>
<p>dir: ${this.direction.value}</p>`;
// ^ this.direction.{ value, set, toggle } are typed
}
}
README →
@youneed/dom-provider-env
Type-safe, fail-fast environment variables for @youneed/dom — coerce + validate import.meta.env (or any source) against a @youneed/schema spec, read it via this.env, inspect it in devtools.
import { Component, html } from "@youneed/dom";
import { defineEnvironmentVariables, envProvider, t } from "@youneed/dom-provider-env";
export const env = defineEnvironmentVariables(import.meta.env, {
schema: {
API_URL: t.url(),
FEATURE_X: t.boolean().default(false),
},
});
// ^ typed: { API_URL: string; FEATURE_X: boolean }
class Widget extends Component("x-widget", { providers: [envProvider(env)] }) {
render() {
return html`<a href=${this.env.API_URL}>open</a>`; // ← typed this.env
}
}
README →
@youneed/dom-provider-feature-flags
Use @youneed/feature-flags inside @youneed/dom components — evaluate flags in html templates with reactive re-render on flag change, plus SSR hydration.
import { Component, html, when } from "@youneed/dom";
import { createFlags } from "@youneed/feature-flags";
import { provideFlags, flags, flagged } from "@youneed/dom-provider-feature-flags";
provideFlags(createFlags([{ key: "new-dashboard", defaultValue: false }]));
class Dashboard extends Component() {
constructor() {
super();
flagged(this); // ← re-render on every flag change
}
render() {
return when(flags().isEnabled("new-dashboard"), () => html`<new-ui></new-ui>`);
}
}
README →
@youneed/dom-provider-i18n
Use @youneed/i18n translations inside @youneed/dom components — i18n() in html templates with reactive re-render on locale change.
import { Component, html } from "@youneed/dom";
import { createI18n } from "@youneed/i18n";
import { provideI18n, i18n, localized } from "@youneed/dom-provider-i18n";
provideI18n(createI18n({
resources: { en: { hello: "Hello {name}" }, de: { hello: "Hallo {name}" } },
locale: "en",
}));
class Greeting extends Component() {
constructor() {
super();
localized(this); // ← re-render on every setLocale(...)
}
render() {
return html`<div>${i18n("hello", { name: "Ada" })}</div>`;
}
}
README →
@youneed/dom-provider-logger
A scoped @youneed/logger child on every @youneed/dom component — this.logger, stamped with the component tag, via a composable provider.
import { Component, html } from "@youneed/dom";
import { loggerProvider } from "@youneed/dom-provider-logger";
class Cart extends Component("x-cart", { providers: [loggerProvider()] }) {
onMount() { this.logger.info("mounted"); } // → { component: "x-cart", message: "mounted", … }
checkout() { this.logger.warn("empty cart"); }
}
README →
@youneed/dom-provider-otel
@youneed/dom provider: real OpenTelemetry Web SDK — render spans per component, dom.render metrics, OTLP/HTTP export flushed on pagehide.
import { initDomOtel } from "@youneed/dom-provider-otel";
initDomOtel({ serviceName: "web-app", endpoint: "https://otel.example.com" });
README →
@youneed/dom-provider-rbac
Use @youneed/rbac inside @youneed/dom components — gate UI in html templates with `this.can(action, resource, instance?)`, re-rendering when the current subject changes.
import { Component, html, when } from "@youneed/dom";
import { createRBAC } from "@youneed/rbac";
import { provideRBAC, can, setSubject } from "@youneed/dom-provider-rbac";
provideRBAC(createRBAC((role) => {
role("editor").can(["create", "update"], "post");
}));
setSubject({ roles: ["editor"], id: "u1" }); // the logged-in user
class PostView extends Component() {
render() {
return when(can("update", "post", this.post), () => html`<button>Edit</button>`);
}
}
README →
@youneed/dom-provider-timers
Lifecycle-scoped timers for @youneed/dom — a composable provider contributing this.timers: setTimeout/setInterval/rAF/idle/delay + the Scheduler API (postTask, yield) + debounce/throttle, all auto-cancelled when the component disconnects.
import { Component, html } from "@youneed/dom";
import { timersProvider } from "@youneed/dom-provider-timers";
class Clock extends Component("x-clock", { providers: [timersProvider()] }) {
time = this.signal(new Date());
onMount() {
this.timers.setInterval(() => this.time.set(new Date()), 1_000);
// no teardown code — cancelled on disconnect
}
render() {
return html`<time>${this.time.get().toLocaleTimeString()}</time>`;
}
}
README →
@youneed/dom-provider-virtual
Composable @youneed/dom provider for IntersectionObserver-driven list virtualization (this.virtual) — only visible chunks render.
import { Component, html } from "@youneed/dom";
import { virtualProvider } from "@youneed/dom-provider-virtual";
class Feed extends Component("x-feed", { providers: [virtualProvider()] }) {
@Component.prop() rows: Row[] = [];
render() {
return html`${this.virtual({
items: this.rows,
render: (r) => html`<div class="row">${r.title}</div>`,
estimateHeight: 40,
})}`;
}
}
README →
@youneed/dom-provider-zustand
Bind a Zustand store to @youneed/dom components — a composable provider for reactive this.store with optional selector-gated re-renders.
import { createStore } from "zustand/vanilla";
import { Component, html } from "@youneed/dom";
import { zustandProvider } from "@youneed/dom-provider-zustand";
const cart = createStore((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
}));
class Cart extends Component("x-cart", { providers: [zustandProvider(cart)] }) {
render() {
return html`
<span>items: ${this.store.state.items.length}</span>
<button @click=${() => this.store.state.add(newItem)}>add</button>`;
}
}
README →
@youneed/dom-router
Tiny client-side SPA router (hash / history / query).
import { createRouter } from "@youneed/dom-router";
const router = createRouter({
outlet: document.getElementById("app")!,
mode: "history", // "hash" (default) · "history" · "query"
routes: [
{ path: "/", component: HomePage }, // a component CLASS…
{ path: "/users/:id", component: "user-page" }, // …or a tag string. params: { id }
{ path: "*", component: "not-found" }, // catch-all
],
});
router.navigate("/users/42"); // updates the URL + mounts <user-page>
router.current?.params; // { id: "42" }
README →
@youneed/dom-scheduler
Prioritized, batching render scheduler (DOM/Node-agnostic; rAF/idle with setTimeout fallback).
README →
@youneed/dom-ui-shad
shadcn-style component library on @youneed/dom (Custom Elements + Tailwind), with a copy-the-source CLI.
import { ShadButton, registerTailwind } from "@youneed/dom-ui-shad";
README →
@youneed/feature-flags
Tiny framework-agnostic feature-flag engine: boolean/variant/value flags, attribute targeting + deterministic percentage rollout, synchronous evaluation, SSR snapshot hydration.
import { createFlags } from "@youneed/feature-flags";
const flags = createFlags([
{ key: "new-dashboard", defaultValue: false, rollout: 20 }, // 20% of users
{
key: "checkout",
defaultValue: "control",
variants: { control: "control", fast: "fast" },
rules: [{ attributes: { plan: "pro" }, variant: "fast" }], // pro users → "fast"
},
]);
flags.isEnabled("new-dashboard", { targetingKey: user.id }); // stable 20% bucket
flags.variant("checkout", { targetingKey: user.id, attributes: { plan: user.plan } });
flags.value<string>("checkout", { attributes: { plan: "free" } }); // "control"
README →
@youneed/feature-flags-datadog
Framework-agnostic @youneed/feature-flags adapter: batch flag EXPOSURES to the Datadog Logs intake (no Datadog SDK, plain fetch) via flags.onEvaluation.
import { createFlags } from "@youneed/feature-flags";
import { attachDatadog } from "@youneed/feature-flags-datadog";
const flags = createFlags([
{ key: "new-dashboard", defaultValue: false, rollout: 20 },
]);
// one call wires flags.onEvaluation(...) up for you:
const exp = attachDatadog(flags, {
apiKey: process.env.DD_API_KEY!,
service: "web",
env: "production",
});
flags.isEnabled("new-dashboard", { targetingKey: user.id }); // → buffered exposure
// on shutdown, flush and stop the timer:
await exp.stop();
README →
@youneed/feature-flags-launchdarkly
LaunchDarkly provider adapter for @youneed/feature-flags (Node server SDK-backed remote evaluator).
import { FeatureFlags } from "@youneed/feature-flags";
import { launchDarklyProvider } from "@youneed/feature-flags-launchdarkly";
const flags = new FeatureFlags([], {
provider: launchDarklyProvider({ sdkKey: "sdk-…" }),
});
// authoritative (awaits the SDK)
const ev = await flags.evaluateAsync("new-dashboard", { targetingKey: user.id });
// synchronous (best-effort until the provider warms the per-context cache)
flags.isEnabled("new-dashboard", { targetingKey: user.id });
README →
@youneed/feature-flags-posthog
PostHog provider for @youneed/feature-flags — a framework-agnostic remote evaluator over PostHog's /decide HTTP API (no SDK, pure fetch).
import { FeatureFlags } from "@youneed/feature-flags";
import { posthogProvider } from "@youneed/feature-flags-posthog";
const flags = new FeatureFlags([], {
provider: posthogProvider({ apiKey: process.env.POSTHOG_KEY! }),
});
// Await the authoritative value from PostHog:
await flags.evaluateAsync("new-dashboard", { targetingKey: user.id });
await flags.evaluateAsync<string>("checkout", {
targetingKey: user.id,
attributes: { plan: user.plan },
}); // → { value: "fast", variant: "fast", reason: "TARGETING_MATCH" }
// Or pre-warm a context so the synchronous `evaluate`/`isEnabled` hit the cache:
await flags.warm({ targetingKey: user.id }, ["new-dashboard", "checkout"]);
flags.isEnabled("new-dashboard", { targetingKey: user.id });
README →
@youneed/feature-flags-vercel
Vercel Edge Config source for @youneed/feature-flags: pulls flag definitions from an Edge Config store (plain fetch, no SDK), with polling for live updates.
import { createFlags } from "@youneed/feature-flags";
import { vercelSource } from "@youneed/feature-flags-vercel";
const flags = createFlags(
vercelSource({
connectionString: process.env.EDGE_CONFIG, // https://edge-config.vercel.com/<id>?token=<token>
prefix: "flag:", // optional: only `flag:*` items, prefix stripped from the key
}),
);
await flags.load(); // async source → fill the snapshot from Edge Config
flags.isEnabled("new-dashboard", { targetingKey: user.id }); // 20% rollout
flags.value("checkout", { targetingKey: user.id, attributes: { plan: user.plan } });
README →
@youneed/http-client
Zero-dep resilient fetch: timeout, retry + backoff (honors Retry-After), and a circuit breaker.
import { createClient } from "@youneed/http-client";
const client = createClient({
timeout: 5_000, // per-attempt deadline (ms)
retries: 3, // up to 4 attempts
failureThreshold: 5, // trip the breaker after 5 consecutive failures
resetTimeout: 30_000, // stay OPEN this long before a half-open trial
});
// Callable like fetch …
const res = await client("https://api.example.com/users");
// … plus method helpers.
const created = await client.post("https://api.example.com/users", {
body: JSON.stringify({ name: "Ada" }),
headers: { "content-type": "application/json" },
});
// Introspect the breaker.
console.log(client.breaker.state); // "closed" | "open" | "half-open"
README →
@youneed/i18n
Tiny, fully-typed translation core: dotted-path keys with autocomplete, interpolation, locale switching, subscriptions.
import { createI18n } from "@youneed/i18n";
const i18n = createI18n({
resources: {
en: { greeting: "Hello {name}", nav: { home: "Home" } },
de: { greeting: "Hallo {name}", nav: { home: "Startseite" } },
},
locale: "en",
fallbackLocale: "en",
});
i18n("greeting", { name: "Ada" }); // "Hello Ada" ← autocompletes "greeting" | "nav.home"
i18n.setLocale("de");
i18n("nav.home"); // "Startseite"
README →
@youneed/kv
Back-compat alias for @youneed/server-plugin-store (the KV contract + MemoryKV).
// Both of these resolve to the same exports:
import { MemoryKV, namespaced, type KV } from "@youneed/kv";
import { MemoryKV, namespaced, type KV } from "@youneed/server-plugin-store";
README →
@youneed/kv-redis
Back-compat alias for @youneed/server-plugin-pubsub-redis (RedisKV + RedisPubSub).
import { RedisKV, redisKV } from "@youneed/kv-redis";
import { namespaced } from "@youneed/server-plugin-store";
const kv = new RedisKV({ host: "127.0.0.1", port: 6379, password: "…" });
// or from a URL: new RedisKV({ url: "redis://:pw@host:6379/0" })
// or the factory: const kv = redisKV({ url: process.env.REDIS_URL });
await kv.set("user:1", JSON.stringify({ name: "Ada" }), { ttl: 60 });
await kv.incr("hits", { ttl: 60 });
const sessions = namespaced(kv, "sess"); // share one Redis across consumers
README →
@youneed/logger
Zero-dep structured logger: levels, JSON lines, child bindings, secret redaction.
import { createLogger, format, ConsoleTransport } from "@youneed/logger";
const log = createLogger({
level: "info",
format: format.combine(format.timestamp(), format.redact(["ssn"]), format.json()),
defaultMeta: { service: "api" },
transports: [new ConsoleTransport()], // works in the browser and on the server
});
log.info("listening", { port: 3000 });
// {"level":"info","message":"listening","timestamp":"…","service":"api","port":3000}
const reqLog = log.child({ requestId: "r-42" }); // bindings on every record
reqLog.error("db down", { password: "hunter2" }); // password → "[REDACTED]"
README →
@youneed/logger-plugin-datadog
@youneed/logger plugin: stamp Datadog-standard default fields (ddsource/service/ddtags) on every record.
import { createLogger, format } from "@youneed/logger";
import { datadog } from "@youneed/logger-plugin-datadog";
const log = createLogger({
format: format.combine(format.timestamp(), format.json()),
plugins: [datadog({ service: "api", env: "prod", version: "1.4.0", tags: { team: "core" } })],
});
log.info("listening", { port: 3000 });
// {"level":"info","message":"listening","timestamp":"…","ddsource":"nodejs",
// "service":"api","ddtags":"env:prod,version:1.4.0,team:core","port":3000}
README →
@youneed/logger-plugin-exception
@youneed/logger plugin: log uncaughtException/unhandledRejection (Winston-style exception handlers).
import { createLogger } from "@youneed/logger";
import { exceptionHandler } from "@youneed/logger-plugin-exception";
const log = createLogger({ plugins: [exceptionHandler()] });
// later: throw new Error("boom") anywhere →
// { level: "error", message: "uncaughtException", exception: true,
// error: { name, message, stack } } then process exits 1
README →
@youneed/logger-plugin-i18n
@youneed/logger plugin: translate log message keys through an @youneed/i18n translator at format time.
import { createLogger, format } from "@youneed/logger";
import { createI18n } from "@youneed/i18n";
import { i18nPlugin } from "@youneed/logger-plugin-i18n";
const i18n = createI18n({
resources: { en: { "server.started": "Listening on :{port}" } },
locale: "en",
});
const log = createLogger({ format: format.combine(format.timestamp(), format.json()) });
log.use(i18nPlugin(i18n));
log.info("server.started", { port: 3000 }); // → message: "Listening on :3000"
README →
@youneed/logger-plugin-location
@youneed/logger plugin: stamp each record with the call site (file:line:column) it was logged from.
import { createLogger, format } from "@youneed/logger";
import { locationPlugin } from "@youneed/logger-plugin-location";
const log = createLogger({
format: format.combine(
format.timestamp(),
format.printf((i) => `[${i.timestamp}] ${i.location} ${String(i.message)}`),
),
plugins: [locationPlugin()],
});
log.info("starting up");
// [2026-06-22T15:36:00Z] app.ts:24:7 starting up
README →
@youneed/logger-plugin-otel
@youneed/logger plugin: stamp trace_id / span_id / trace_flags of the active OpenTelemetry span on every log record.
import { createLogger, format } from "@youneed/logger";
import { otel } from "@youneed/logger-plugin-otel";
import { withSpanAsync } from "@youneed/otel";
import { startNodeOtel } from "@youneed/otel/node";
const handle = startNodeOtel({ serviceName: "api" }); // once per process
const log = createLogger({
format: format.combine(format.timestamp(), format.json()),
plugins: [otel()], // or later: log.use(otel())
});
await withSpanAsync("GET /users", {}, async () => {
log.info("listening", { port: 3000 });
// {"level":"info","message":"listening","timestamp":"…","port":3000,
// "trace_id":"4bf92f3577b34da6a3ce929d0e0e4736",
// "span_id":"00f067aa0ba902b7","trace_flags":"01"}
});
README →
@youneed/logger-transport-file
@youneed/logger transport: append log lines to a file (sync or buffered stream).
import { createLogger, format } from "@youneed/logger";
import { FileTransport } from "@youneed/logger-transport-file";
const log = createLogger({
format: format.combine(format.timestamp(), format.json()),
transports: [new FileTransport({ filename: "app.log" })],
});
README →
@youneed/logger-transport-http
@youneed/logger transport: batch-ship log records to an HTTP endpoint via fetch/sendBeacon (universal).
import { createLogger, format } from "@youneed/logger";
import { HttpTransport } from "@youneed/logger-transport-http";
const log = createLogger({
format: format.combine(format.timestamp(), format.json()),
transports: [new HttpTransport({ url: "/_logs", batchSize: 50, flushInterval: 2000 })],
});
README →
@youneed/logger-transport-stdout
@youneed/logger transport: fast Node process.stdout/stderr writer for high-throughput servers.
import { createLogger, format } from "@youneed/logger";
import { StdoutTransport } from "@youneed/logger-transport-stdout";
const log = createLogger({
format: format.combine(format.timestamp(), format.json()),
transports: [new StdoutTransport()], // error/warn → stderr, rest → stdout
});
README →
@youneed/orm-adapter-mongo
MongoDB adapter for @youneed/orm-nosql (official mongodb driver).
import { Nosql, Collection, getCollectionRepository } from "@youneed/orm-nosql";
import { mongoAdapter } from "@youneed/orm-adapter-mongo";
class Note extends Collection("notes") {
@Collection.id() id!: string;
@Collection.field("string") title!: string;
}
const db = await Nosql({
adapter: mongoAdapter,
url: "mongodb://localhost:27017",
database: "app",
collections: [Note],
synchronize: true, // ensures collections + unique indexes
});
const notes = getCollectionRepository(Note);
await notes.insertOne({ title: "Hello" });
await notes.find({ title: { $regex: "^He" } });
README →
@youneed/orm-adapter-mysql
MySQL adapter for @youneed/orm-sql (mysql2-backed).
import { Orm, getRepository } from "@youneed/orm-sql";
import { mysqlAdapter } from "@youneed/orm-adapter-mysql";
await Orm({
adapter: mysqlAdapter,
host: "localhost",
port: 3306,
username: "root",
password: "root",
database: "test",
tables: [UsersTable],
synchronize: true,
});
const users = getRepository(UsersTable);
await users.insert({ userId: "u1", email: "ada@x.com" });
README →
@youneed/orm-adapter-postgres
PostgreSQL adapter for @youneed/orm-sql (node-postgres/`pg`-backed).
import { Orm, getRepository } from "@youneed/orm-sql";
import { postgresAdapter } from "@youneed/orm-adapter-postgres";
await Orm({
adapter: postgresAdapter,
host: "localhost",
port: 5432,
username: "postgres",
password: "postgres",
database: "test",
tables: [UsersTable],
synchronize: true,
});
const users = getRepository(UsersTable);
await users.insert({ userId: "u1", email: "ada@x.com" }); // generated id populated
README →
@youneed/orm-nosql
Tiny document/NoSQL ORM on standard TC39 decorators (Mongoose-style collections, Mongo-style query filters, pluggable document-store adapters; ships an in-memory store).
import { Collection, Nosql, getCollectionRepository } from "@youneed/orm-nosql";
class Users extends Collection("users") {
@Collection.id() id!: string; // store-assigned key
@Collection.field("string", { unique: true }) email!: string;
@Collection.field("string") name!: string;
@Collection.field("boolean", { default: true }) active!: boolean;
@Collection.index({ group: "by_name" }) sortName!: string;
}
await Nosql({ type: "memory", collections: [Users], synchronize: true });
const users = getCollectionRepository(Users);
const ada = await users.insertOne({ email: "ada@x.com", name: "Ada" }); // → Users instance w/ id
await users.findOne({ email: "ada@x.com" }); // Mongo-style filter
await users.find({ active: true }, { limit: 20, sort: { name: 1 } });
README →
@youneed/orm-sql
Tiny SQL ORM on standard TC39 decorators (TypeORM-style entities, pluggable DB adapters).
import { Table, Orm, getRepository } from "@youneed/orm-sql";
class UsersTable extends Table("users") {
@Table.primaryGeneratedColumn() id!: number;
@Table.field("string")
@Table.index({ group: "user_action" })
userId!: string;
@Table.field("string", { unique: true }) email!: string;
@Table.column({ type: "boolean", default: true }) isActive!: boolean;
@Table.oneToMany(() => Photo, (p) => p.user) photos!: Photo[];
}
await Orm({
type: "sqlite",
database: ":memory:",
tables: [UsersTable, Photo],
synchronize: true,
});
const users = getRepository(UsersTable);
const ada = await users.insert({ userId: "u1", email: "ada@x.com" });
await users.findOne({ email: "ada@x.com" }); // → UsersTable instance
README →
@youneed/otel
Shared OpenTelemetry setup for @youneed/* framework levels: real OTel SDK (node + web), OTLP/HTTP traces + metrics, W3C propagation helpers, instrumented fetch.
import { startNodeOtel } from "@youneed/otel/node";
const handle = startNodeOtel({ serviceName: "api", endpoint: "http://localhost:4318" });
// …app runs…
await handle.shutdown(); // force-flushes spans + metrics
README →
@youneed/rbac
Tiny framework-agnostic authorization engine: roles + permissions (action × resource), role inheritance, ownership/attribute conditions, deny-over-allow, synchronous can().
import { createRBAC, owns, attr } from "@youneed/rbac";
const rbac = createRBAC((role) => {
role("admin").can("*", "*"); // superuser
role("viewer").can("read", "post");
role("editor")
.inherits("viewer") // + everything viewer can
.can(["create", "update"], "post")
.can("delete", "post", owns("authorId")) // only own posts
.cannot("update", "post", attr("locked", true)); // never edit locked posts
});
rbac.can({ roles: ["editor"], id: "u1" }, "delete", "post", { authorId: "u1" }); // true
rbac.can({ roles: ["editor"], id: "u1" }, "delete", "post", { authorId: "u2" }); // false
rbac.can({ roles: ["viewer"] }, "delete", "post"); // false
rbac.check({ roles: ["editor"] }, "update", "post", { locked: true }); // { granted:false, reason:"DENY" }
README →
@youneed/schema
class-validator-style DTO validation on standard TC39 decorators (no reflect-metadata, works in JS).
import { IsEmail, IsNotEmpty, MinLength, IsOptional, IsInt, Min, validate } from "@youneed/schema";
class CreateUserDTO {
@IsEmail() email!: string;
@IsNotEmpty() @MinLength(8) password!: string;
@IsOptional() @IsInt() @Min(18) age?: number;
}
const errors = validate(CreateUserDTO, await req.json());
// [] when valid, else:
// [{ property: "email", value: "nope", constraints: { isEmail: "email must be an email" } }]
README →
@youneed/secrets
Tiny framework-agnostic secrets manager: one SecretsProvider contract, caching, `secret://` reference resolution, env/memory/file built-ins; Vault & AWS adapters ship separately.
import { createSecrets, EnvSecrets } from "@youneed/secrets";
const secrets = createSecrets(new EnvSecrets(), { cacheTtlMs: 60_000 });
const dbUrl = await secrets.require("DATABASE_URL"); // throws if unset
const cfg = await secrets.resolveAll({ // deep-resolve secret:// refs
db: { url: "secret://DATABASE_URL" },
stripe: "secret://STRIPE_KEY",
port: 3000,
});
README →
@youneed/secrets-aws
AWS Secrets Manager provider for @youneed/secrets (pure fetch + SigV4, no aws-sdk).
import { createSecrets } from "@youneed/secrets";
import { awsSecrets } from "@youneed/secrets-aws";
const secrets = createSecrets(
awsSecrets({
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
// sessionToken: process.env.AWS_SESSION_TOKEN, // STS temp creds
}),
{ cacheTtlMs: 60_000 },
);
const dbUrl = await secrets.require("prod/db/url"); // GetSecretValue
const cfg = await secrets.resolveAll({ db: "secret://prod/db/url" }); // deep-resolve
const names = await secrets.list(); // NAMES only (audit)
README →
@youneed/secrets-vault
HashiCorp Vault (KV v2) SecretsProvider for @youneed/secrets — pure fetch, no SDK.
import { createSecrets } from "@youneed/secrets";
import { vaultSecrets } from "@youneed/secrets-vault";
const secrets = createSecrets(
vaultSecrets({
address: "https://vault:8200",
token: process.env.VAULT_TOKEN!,
mount: "secret", // default
// namespace: "team-a", // Vault Enterprise → X-Vault-Namespace
}),
);
const dbUrl = await secrets.require("db"); // reads secret/data/db
const pw = await secrets.get("db#password"); // one field from that path
const names = await secrets.list(); // secret NAMES only
README →
@youneed/server
Tiny typed HTTP server (controllers, middleware, cache).
import { Application, Controller, t, HttpError } from "@youneed/server";
const Cat = t.object({ name: t.string(), age: t.number() });
class Cats extends Controller("/cats", { guards: [requireApiKey] }) {
@Controller.get("/:name", { params: t.object({ name: t.string() }), response: { 200: Cat } })
async byName(ctx: Context) {
const cat = lookup(ctx.params.name);
if (!cat) throw new HttpError(404, { error: "Not found" }); // throw any status
return cat; // validated against 200: Cat
}
@Controller.guard(isAdmin) // per-method guard (stacks with class guards)
@Controller.post({ body: Cat, response: { 201: Cat } })
async create(ctx: Context) {
return this.Response.json(ctx.body, { status: 201 }); // ctx.body is typed + validated
}
}
Application(Cats)
.use(requestLogger(), cors()) // global middleware (onion model)
.openapi({ title: "Cats", version: "1.0.0" }) // → GET /openapi.json
.listen(3000, (ctx) => console.log(`:${ctx.port}`));
README →
@youneed/server-adapter
Run a @youneed/server app on any runtime: a Web `fetch` bridge (Bun / Deno / edge / Workers) over the app's Node request listener, plus node/bun/deno serve adapters.
import { Application } from "@youneed/server";
import { serve, toFetchHandler } from "@youneed/server-adapter";
const app = Application().get("/hello", () => ({ hello: "world" }));
// 1) One-liner that works on Node / Bun / Deno (auto-detects the runtime):
const server = await serve(app, { port: 3000 });
console.log(server.runtime, server.url); // "node http://localhost:3000"
// 2) A raw Web fetch handler for edge / serverless:
export default { fetch: toFetchHandler(app) }; // Cloudflare Worker
// Bun: Bun.serve({ fetch: toFetchHandler(app) })
// Deno: Deno.serve(toFetchHandler(app))
README →
@youneed/server-middleware-accept-language
@youneed/server middleware: negotiate the request locale from the Accept-Language header (HTTP content negotiation).
import { Application, Response } from "@youneed/server";
import { acceptLanguage } from "@youneed/server-middleware-accept-language";
import { i18n } from "./i18n.ts"; // an @youneed/i18n instance
Application()
.use(acceptLanguage({ supported: ["en", "de", "fr"], default: "en", i18n }))
.get("/", (ctx) => Response.text(`locale: ${ctx.state.locale}`))
.listen(3000, () => {});
README →
@youneed/server-middleware-api-key
@youneed/server middleware: shared-secret API key auth (header/query/scheme), SHA-256 matched, principal mapping.
import { Application } from "@youneed/server";
import { apiKey } from "@youneed/server-middleware-api-key";
// Flat allowlist (key sent as `X-API-Key: <key>`):
app.use(apiKey({ keys: [process.env.PARTNER_KEY!] }));
// Keys mapped to a principal → ctx.state.apiClient:
app.use(apiKey({ keys: { k_live_abc: { name: "billing", scopes: ["read"] } } }));
// Store only HASHES at rest (never plaintext keys):
app.use(apiKey({ hashed: true, keys: [sha256hex(process.env.PARTNER_KEY!)] }));
// Dynamic lookup (DB / cache):
app.use(apiKey({ verify: async (key) => db.clientByKey(key) }));
README →
@youneed/server-middleware-authorization
@youneed/server middleware: generic Authorization-header auth with a pluggable signing algorithm (bring your own sign/verify/generatePair) + self-contained signed tokens.
import { sign, verify, generatePair } from "my-signing-algo";
import { authorization, createTokens } from "@youneed/server-middleware-authorization";
// 1. Wrap your algorithm (the package never inspects what it does):
const kuznyechik = { name: "Kuznyechik", sign, verify, generatePair };
const { publicKey, privateKey } = kuznyechik.generatePair();
// 2. Verify side — the middleware (any prefix you like):
app.use(authorization({ prefix: "Bearer", algorithm: kuznyechik, key: publicKey }));
// 3. Issue side — your login route:
const tokens = createTokens({ algorithm: kuznyechik, privateKey, publicKey });
const token = await tokens.sign({ sub: "u1", scope: "read" }, { expiresInSec: 3600 });
// 4. Handlers read the verified payload:
app.get("/me", (ctx) => ctx.state.user);
README →
@youneed/server-middleware-basic-auth
@youneed/server middleware: HTTP Basic auth + API-key auth.
import { Application, Response } from "@youneed/server";
import { basicAuth, apiKey } from "@youneed/server-middleware-basic-auth";
const app = Application()
// Static user → password map (passwords compared in constant time).
.use("/admin", basicAuth({ users: { alice: "s3cret" }, realm: "Admin" }))
// …or your own verifier, returning a principal (or false to reject).
.use("/api", apiKey({ verify: (key) => lookup(key), header: "x-api-key" }))
.get("/admin/me", (ctx) => Response.json({ you: ctx.state.user }))
.get("/api/data", (ctx) => Response.json({ key: ctx.state.apiKey }));
README →
@youneed/server-middleware-bearer
@youneed/server middleware: Bearer-token authentication.
import { Application } from "@youneed/server";
import { bearer } from "@youneed/server-middleware-bearer";
Application()
.use(bearer({
verify: async (token) => {
const user = await lookupSession(token);
return user ?? false; // false/null → 401
},
}))
.listen(3000, () => {});
README →
@youneed/server-middleware-body-limit
@youneed/server middleware: reject oversized request bodies.
import { Application } from "@youneed/server";
import { bodyLimit } from "@youneed/server-middleware-body-limit";
Application()
.use(bodyLimit("5mb"))
.listen(3000, () => {});
README →
@youneed/server-middleware-compression
@youneed/server middleware: gzip/brotli response compression.
import { Application } from "@youneed/server";
import { compression } from "@youneed/server-middleware-compression";
Application()
.use(compression({ threshold: 1024 }))
.listen(3000, () => {});
README →
@youneed/server-middleware-cors
@youneed/server middleware: CORS headers + preflight.
import { Application } from "@youneed/server";
import { cors } from "@youneed/server-middleware-cors";
Application()
.use(cors({ origin: "*" }))
.listen(3000, () => {});
README →
@youneed/server-middleware-csrf
@youneed/server middleware: stateless CSRF (double-submit cookie).
import { Application } from "@youneed/server";
import { csrf } from "@youneed/server-middleware-csrf";
Application()
.use(csrf()) // global, default options
.use("/admin", csrf({ cookieName: "admin_csrf" })) // scoped, custom cookie
.listen(3000, () => {});
README →
@youneed/server-middleware-etag
@youneed/server middleware: ETag + conditional GET (304).
import { Application } from "@youneed/server";
import { etag } from "@youneed/server-middleware-etag";
Application()
.use(etag()) // weak validators, global
.use("/assets", etag({ weak: false })) // strong, scoped
.listen(3000, () => {});
README →
@youneed/server-middleware-health
@youneed/server middleware: Kubernetes-style liveness/readiness probe endpoints.
import { Application, Response } from "@youneed/server";
import { health } from "@youneed/server-middleware-health";
const h = health({
checks: {
db: () => pool.totalCount > 0,
cache: async () => (await redis.ping()) === "PONG",
},
});
const app = Application()
.use(h) // register early
.get("/users", () => Response.json([/* … */]));
// GET /healthz → 200 { status: "ok" } (liveness)
// GET /readyz → 200 { status: "ready", checks: { … } } (readiness, all pass)
// → 503 { status: "not ready", checks: { … } } (any check failed)
app.listen(3000, (s) =>
s.gracefulShutdown({ onShutdown: () => h.setReady(false) }),
);
README →
@youneed/server-middleware-helmet
@youneed/server middleware: security response headers (helmet-style).
import { Application } from "@youneed/server";
import { helmet } from "@youneed/server-middleware-helmet";
Application()
.use(helmet()) // strict defaults, global
.use(helmet({ contentSecurityPolicy: false })) // opt out of CSP
.listen(3000, () => {});
README →
@youneed/server-middleware-http2-guard
@youneed/server middleware: HTTP/2 DoS protection (Rapid Reset, stream floods).
import { Application } from "@youneed/server";
import { http2Guard } from "@youneed/server-middleware-http2-guard";
Application()
.use(http2Guard()) // defaults, global
.use(http2Guard({ maxResetsPerWindow: 50, onAbuse: (i) => console.warn(i) }))
.listen(3000, () => {});
README →
@youneed/server-middleware-https-redirect
@youneed/server middleware: force HTTPS + canonical host/trailing-slash redirects.
import { Application, Response } from "@youneed/server";
import { httpsRedirect } from "@youneed/server-middleware-https-redirect";
const app = Application()
.use(httpsRedirect({ host: "example.com", trailingSlash: "never" }))
.get("/users", () => Response.json([/* … */]));
// http://www.example.com/users/ → 308 Location: https://example.com/users
README →
@youneed/server-middleware-idempotency
@youneed/server middleware: Idempotency-Key — safe retries of unsafe requests via a pluggable KV store.
import { Application, Response } from "@youneed/server";
import { idempotency } from "@youneed/server-middleware-idempotency";
const app = Application()
.use(idempotency({ ttl: 86400 })) // 24h replay window (in-process store)
.post("/charges", () => Response.json(charge())); // POST twice with one key → one charge
README →
@youneed/server-middleware-ip-filter
@youneed/server middleware: allow/deny requests by client IP (CIDR, IPv4/IPv6), proxy-aware.
import { Application } from "@youneed/server";
import { ipFilter } from "@youneed/server-middleware-ip-filter";
import { trustProxy } from "@youneed/server-middleware-trust-proxy";
// Allowlist — only these IPs/ranges may reach /admin (everything else 403):
app.use("/admin", ipFilter({ allow: ["10.0.0.0/8", "192.168.1.5", "2001:db8::/32"] }));
// Denylist — block known-bad ranges, everyone else passes:
app.use(ipFilter({ deny: ["203.0.113.0/24"] }));
README →
@youneed/server-middleware-jwt
@youneed/server middleware: JWT (JWS) authentication — verifies signature (HS/RS/PS/ES) + claims, with JWKS support. Zero dependencies.
import { Application } from "@youneed/server";
import { jwt } from "@youneed/server-middleware-jwt";
// Symmetric (HS256) — shared secret:
app.use(jwt({ secret: process.env.JWT_SECRET!, issuer: "auth.acme.dev", audience: "api" }));
// Asymmetric, single fixed key (RS256/ES256):
app.use(jwt({ publicKey: pemOrJwkOrKeyObject, algorithms: ["RS256"] }));
// Asymmetric via JWKS — keys rotate by `kid`, fetched and cached:
app.use(jwt({ jwks: "https://auth.acme.dev/.well-known/jwks.json", algorithms: ["RS256"] }));
// Handlers read the verified claims:
app.get("/me", (ctx) => ctx.state.user);
README →
@youneed/server-middleware-keep-alive
@youneed/server middleware: advertise a Keep-Alive header + drop connections programmatically.
import { Application, Response, HttpError } from "@youneed/server";
import { keepAlive, connection } from "@youneed/server-middleware-keep-alive";
const app = Application()
.use(keepAlive({ timeout: 10, max: 1000 })) // → Keep-Alive: timeout=10, max=1000
.use((ctx, next) => {
if (ctx.request.headers["x-malware"]) {
connection(ctx).destroy(); // sever the socket immediately
throw new HttpError(403, { error: "blocked" });
}
return next();
})
.get("/users", () => Response.json([/* … */]));
README →
@youneed/server-middleware-load-shed
@youneed/server middleware: load-shedding (global concurrency limit) — fast-fail surplus requests with 503 under overload.
import { Application, Response } from "@youneed/server";
import { loadShed } from "@youneed/server-middleware-load-shed";
const app = Application()
.use(loadShed({ maxConcurrent: 500, retryAfter: 2 }))
.get("/work", () => doExpensiveWork());
// once 500 requests are in flight, the 501st gets:
// 503 Service Unavailable
// Retry-After: 2
// { "error": "Service Unavailable" }
README →
@youneed/server-middleware-logger
@youneed/server middleware: attach a request-scoped @youneed/logger child (bound to requestId/traceId) — log(ctx).
import { Application, Response } from "@youneed/server";
import { createLogger } from "@youneed/logger";
import { logger, log } from "@youneed/server-middleware-logger";
const base = createLogger();
const app = Application()
.use(logger(base, { bindings: (ctx) => ({ method: ctx.request.method, url: ctx.request.url }) }))
.get("/users", (ctx) => {
log(ctx).info("listing users", { count: 3 });
// → {"level":"info","message":"listing users","count":3,"requestId":"…","method":"GET","url":"/users"}
return Response.json([/* … */]);
});
README →
@youneed/server-middleware-metrics
@youneed/server middleware: dependency-free Prometheus metrics + /metrics exposition.
import { Application, Response } from "@youneed/server";
import { metrics } from "@youneed/server-middleware-metrics";
const app = Application()
.use(metrics()) // GET /metrics → text exposition
.get("/users", () => Response.json([/* … */]));
// → http_requests_total{method="GET",status="200"} 1
// http_request_duration_seconds_bucket{method="GET",status="200",le="0.05"} 1
// http_request_duration_seconds_sum{method="GET",status="200"} 0.0012
// http_request_duration_seconds_count{method="GET",status="200"} 1
// http_requests_in_flight 0
README →
@youneed/server-middleware-rate-limit
@youneed/server middleware: rate limiting with pluggable strategies (fixed/sliding/token-bucket/leaky-bucket/exponential + KV).
import { Application } from "@youneed/server";
import { rateLimit, fixedWindow, tokenBucket } from "@youneed/server-middleware-rate-limit";
Application()
.use(rateLimit({ strategy: fixedWindow({ windowMs: 60_000, max: 100 }) })) // global
.use("/api", rateLimit({ strategy: tokenBucket({ capacity: 50, refillPerSec: 5 }) }))
.listen(3000, () => {});
README →
@youneed/server-middleware-request-id
@youneed/server middleware: per-request correlation id (trusted inbound X-Request-Id or generated), echoed + log-correlated.
import { requestId, getRequestId } from "@youneed/server-middleware-request-id";
import { requestLogger } from "@youneed/server-middleware-request-logger";
app.use(requestId()); // mount EARLY (before the logger)
app.use(requestLogger());
app.get("/x", (ctx) => ({ id: getRequestId(ctx) }));
README →
@youneed/server-middleware-request-logger
@youneed/server middleware: per-request access logging.
import { Application } from "@youneed/server";
import { requestLogger } from "@youneed/server-middleware-request-logger";
Application()
.use(requestLogger())
.listen(3000, () => {});
README →
@youneed/server-middleware-server-timing
@youneed/server middleware: emit a Server-Timing response header (DevTools timings).
import { Application, Response } from "@youneed/server";
import { serverTiming, timing } from "@youneed/server-middleware-server-timing";
const app = Application()
.use(serverTiming()) // register EARLY → accurate `total`
.get("/users", async (ctx) => {
const m = timing(ctx).metric("db"); // start a timer…
const rows = await db.query("…");
m.desc(`SQL · ${rows.length} rows`).stop(); // …set desc from the result, then stop
return Response.json(rows);
});
// Server-Timing: db;dur=12.3;desc="SQL · 42 rows", total;dur=14.1
README →
@youneed/server-middleware-session
@youneed/server middleware: signed-cookie sessions with a pluggable store.
import { Application, Response } from "@youneed/server";
import { session, getSession } from "@youneed/server-middleware-session";
const app = Application()
.use(session({ secret: process.env.SESSION_SECRET! }))
.get("/login", (ctx) => {
getSession(ctx)!.set("user", "ada"); // persisted on the way out
return Response.json({ ok: true });
})
.get("/me", (ctx) => Response.json({ user: getSession(ctx)?.get("user") }))
.get("/logout", (ctx) => {
getSession(ctx)!.destroy(); // clears store + cookie
return Response.json({ ok: true });
});
README →
@youneed/server-middleware-static
@youneed/server middleware: serve static files from disk with HTTP Range, ETag, and conditional-request support.
import { Application, Response } from "@youneed/server";
import { staticFiles } from "@youneed/server-middleware-static";
const app = Application()
.use(staticFiles("public", { cacheControl: "public, max-age=3600" }))
.get("/api/health", () => Response.json({ ok: true }));
README →
@youneed/server-middleware-timeout
@youneed/server middleware: fail requests that exceed a deadline (503).
import { Application } from "@youneed/server";
import { timeout } from "@youneed/server-middleware-timeout";
Application()
.use(timeout(5000)) // 5s, global
.use("/report", timeout(30_000, { status: 504 })) // scoped, custom status
.listen(3000, () => {});
README →
@youneed/server-middleware-trace
@youneed/server middleware: W3C traceparent distributed tracing (dependency-free, OpenTelemetry-compatible IDs).
import { Application, Response } from "@youneed/server";
import { tracing, span } from "@youneed/server-middleware-trace";
const app = Application()
.use(tracing({ onEnd: (s) => exporter.push(s) })) // → traceparent: 00-<traceId>-<spanId>-01
.get("/users", (ctx) => {
span(ctx).setAttribute("user.count", 3);
span(ctx).addEvent("queried-db");
return Response.json([/* … */]);
});
README →
@youneed/server-middleware-trust-proxy
@youneed/server middleware: resolve the real client IP/protocol/host from X-Forwarded-* headers behind a proxy/CDN.
import { Application, Response } from "@youneed/server";
import { trustProxy, clientInfo } from "@youneed/server-middleware-trust-proxy";
const app = Application()
.use(trustProxy({ hops: 1 })) // 1 trusted proxy in front
.get("/whoami", (ctx) => Response.json(clientInfo(ctx)));
// → { ip: "1.2.3.4", protocol: "https", host: "api.example.com" }
README →
@youneed/server-middleware-webhook-signature
@youneed/server middleware: verify inbound webhook HMAC signatures over the raw body. Pure verify function + generic builder + per-provider subpaths (stripe/github/shopify).
// 1. A pure validation FUNCTION — no Context. Reusable anywhere (queue consumers,
// tests, non-HTTP transports). Returns { valid, reason?, timestamp? }, never throws.
import { verifyWebhookSignature } from "@youneed/server-middleware-webhook-signature";
const result = await verifyWebhookSignature(
{ secret, header: "x-signature", prefix: "sha256=" },
{ rawBody, headers },
);
if (!result.valid) reject(result.reason);
// 2. The MIDDLEWARE wrapping it (reads the raw bytes via the core's memoized
// rawBody(ctx), so the handler still gets a parsed ctx.body).
import { webhookSignature } from "@youneed/server-middleware-webhook-signature";
app.use("/hooks", webhookSignature({ secret, header: "x-signature", prefix: "sha256=" }));
README →
@youneed/server-plugin-cluster
@youneed/server plugin: multi-core supervisor — fork workers across CPUs, restart crashes, graceful drain on SIGTERM.
import { Application, Response } from "@youneed/server";
import { cluster } from "@youneed/server-plugin-cluster";
Application()
.get("/", () => Response.text("ok"))
.plugin(cluster({ workers: 4 })) // default: os.availableParallelism() ?? CPU count
.listen(3000, (s) => s.gracefulShutdown());
README →
@youneed/server-plugin-devtools
@youneed/server plugin: dev-time topology, OWASP security audit, OpenAPI generation and microbenchmarks — mounts a devtools UI on a live app.
import { topology, externalServer, securityAudit, toOpenApi, microbench } from "@youneed/server-plugin-devtools";
const t = topology([
{ name: "api", middleware: ["cors", "helmet", "rate-limit"], routes: [/* … */] },
externalServer({ name: "billing", url: "https://billing.acme.dev" }), // not behind our API
]);
securityAudit(t.servers[0]); // OWASP API Top 10 findings
toOpenApi(t.servers[0]); // OpenAPI 3.1 document from route schemas
microbench(() => serialize(payload)); // ops/sec + p50/p99
README →
@youneed/server-plugin-docker
Wrap a @youneed/server or SSR app in Docker: generate a Dockerfile + .dockerignore + docker-compose.yml, with backing services (Mongo/MySQL/Postgres/Redis) inferred from the app's mounted plugins. Ships a devtools tab to view the artifacts.
README →
@youneed/server-plugin-env
Type-safe, fail-fast environment variables for @youneed/server — coerce + validate process.env against a @youneed/schema spec, with a devtools-visible ServerPlugin.
import { defineEnvironmentVariables, t } from "@youneed/server-plugin-env";
export const env = defineEnvironmentVariables(process.env, {
schema: {
PORT: t.port().default(3000),
DATABASE_URL: t.url().secret(),
NODE_ENV: t.enum(["development", "production", "test"] as const).default("development"),
},
});
// ^ typed: { PORT: number; DATABASE_URL: string; NODE_ENV: "development" | "production" | "test" }
README →
@youneed/server-plugin-feature-flags
@youneed/server plugin + controller provider for @youneed/feature-flags: request-scoped `this.flags`, a client-bootstrap snapshot route, dev override toggles, and a devtools Feature Flags tab.
import { Application, Controller } from "@youneed/server";
import { createFlags, featureFlags, flagsProvider } from "@youneed/server-plugin-feature-flags";
import type { Context } from "@youneed/server";
const flags = createFlags([
{ key: "new-checkout", defaultValue: false, rollout: 20 }, // stable 20% of users
{
key: "pricing",
defaultValue: "control",
variants: { control: "control", fast: "fast" },
rules: [{ attributes: { plan: "pro" }, variant: "fast" }],
},
]);
// Derive the per-request evaluation context (bucket by the authenticated user).
const ctxOf = (ctx: Context) => ({
targetingKey: (ctx.state.user as { id?: string } | undefined)?.id,
attributes: { plan: (ctx.state.user as { plan?: string } | undefined)?.plan },
});
class CheckoutController extends Controller("/checkout", {
providers: [flagsProvider(flags, { context: ctxOf })],
}) {
@Controller.get()
index() {
if (this.flags.isEnabled("new-checkout")) return { ui: "v2" };
return { ui: "v1", variant: this.flags.variant("pricing") };
}
}
const app = Application(CheckoutController).plugin(featureFlags(flags, { context: ctxOf }));
app.listen(3000);
README →
@youneed/server-plugin-graphql
@youneed/server plugin: a spec-compliant GraphQL HTTP endpoint over graphql-js (schema-first SDL or programmatic schema, GraphiQL, resolvers, per-request context), with a devtools tab (playground, SDL viewer, recent ops).
import { Application } from "@youneed/server";
import { graphql } from "@youneed/server-plugin-graphql";
const app = Application().plugin(
graphql({
schema: /* GraphQL */ `
type Query {
hello: String
add(a: Int, b: Int): Int
}
`,
rootValue: {
hello: () => "hi",
add: ({ a, b }: { a: number; b: number }) => a + b,
},
// context: (ctx) => ({ user: ctx.state.user }), // per-request contextValue
// path: "/graphql", // default
// graphiql: true, // GraphiQL on GET (browsers)
}),
);
app.listen(3000);
// POST /graphql { "query": "{ hello }" } → { "data": { "hello": "hi" } }
// GET /graphql (from a browser) → GraphiQL IDE
// GET /graphql?query={hello} → { "data": { "hello": "hi" } }
README →
@youneed/server-plugin-grpc
@youneed/server plugin: run a gRPC (HTTP/2) server alongside your @youneed/server app on its lifecycle, with introspection + a unary call-tester over HTTP and a devtools tab.
import { Application } from "@youneed/server";
import { grpc } from "@youneed/server-plugin-grpc";
const app = Application().plugin(
grpc({
protoPath: new URL("./greeter.proto", import.meta.url).pathname,
package: "greet",
port: 50051,
services: {
// serviceName → { method → handler } (grpc-js unary style)
Greeter: {
// (call, callback) — or return a value from an async handler
SayHello(call, callback) {
callback(null, { message: `Hello, ${call.request.name}!` });
},
},
},
}),
);
app.listen(3000); // starts the gRPC server on :50051 too, drains both on shutdown
README →
@youneed/server-plugin-jobs
@youneed/server plugin: job scheduler (cron, intervals, delays) wired to the server lifecycle, with an optional KV leader-lock for fleets.
import { Application } from "@youneed/server";
import { jobs } from "@youneed/server-plugin-jobs";
const cron = jobs({ jobs: [{ name: "cleanup", schedule: "0 */6 * * *", handler: purge }] });
app.plugin(cron).listen(3000, () => {}); // scheduler.start() on listen, stop() on drain
// still mutable after registration
cron.scheduler.add({ name: "heartbeat", schedule: { every: 30_000 }, handler: ping });
README →
@youneed/server-plugin-jsonrpc
JSON-RPC 2.0 for @youneed/server: class + TC39-decorator endpoints (@JsonRPC.method) with schema-validated params, served over a POST request or a Chrome-CDP-style WebSocket, plus a @youneed/server-plugin-devtools panel to inspect methods and debug calls.
import { JsonRPC, JsonRPCResponse, JsonRPCErrorResponse } from "@youneed/server-plugin-jsonrpc";
import { t } from "@youneed/schema";
import type { Context } from "@youneed/server";
class MathEndpoint extends JsonRPC({
providers: [loggerProvider(), anotherProvider()], // add private `this.<member>`
guards: [authRequired()], // run before every method
}) {
@JsonRPC.method("sum", { args: [t.number(), t.number()] })
sum(a: number, b: number, ctx?: Context) { // ctx optional, always last
if (a > 10) return JsonRPCResponse.error({ code: -32000, message: "something went wrong" });
// return JsonRPCResponse.error(JsonRPCErrorResponse.InternalError); // predefined map
return JsonRPCResponse.success({ result: a + b });
}
}
README →
@youneed/server-plugin-kv
Mount a KV store (from @youneed/server-plugin-store) as a ServerPlugin for @youneed/server: tracks reads/writes/hit-rate + surfaces a live key browser in @youneed/server-plugin-devtools (Infra card, header tab, flow node). Re-exports the KV contract.
import { Application } from "@youneed/server";
import { createKV, kv } from "@youneed/server-plugin-kv";
// re-exported for convenience: MemoryKV, namespaced, type KV …
const store = createKV(); // TrackedKV around an in-process MemoryKV
// const store = createKV(new RedisKV({ url: process.env.REDIS_URL }));
const app = Application().plugin(kv(store));
// Pass the SAME tracked instance to your consumers so all traffic is counted:
await store.set("user:1", JSON.stringify({ name: "Ada" }), { ttl: 60 });
await store.get("user:1"); // recorded as a hit
store.stats(); // → { gets, sets, deletes, incrs, hits, misses }
README →
@youneed/server-plugin-mailer
@youneed/server plugin: transactional email with pluggable transports — built-in dependency-free SMTP plus AWS SES, SendGrid and Postmark (fetch-based, no SDKs), with a devtools tab.
import { Application } from "@youneed/server";
import { mailer } from "@youneed/server-plugin-mailer";
import { smtpTransport } from "@youneed/server-plugin-mailer/smtp";
const mail = mailer(
smtpTransport({ host: "smtp.acme.dev", port: 587, auth: { user, pass }, from: "no-reply@acme.dev" }),
);
const app = Application().plugin(mail);
app.listen(3000);
// send from anywhere — use the plugin's tracked transport so devtools sees it
await mail.transport.send({
to: "ada@x.dev",
subject: "Welcome",
text: "Hello!",
html: "<b>Hello!</b>",
});
README →
@youneed/server-plugin-oauth2
OAuth2/OIDC login (Authorization Code + PKCE) for @youneed/server: a ServerPlugin core + universal, named providers (github/google/facebook/yandex/vk) you can extend, plus Telegram Login Widget.
import { Application } from "@youneed/server";
import { oauth2, redirect } from "@youneed/server-plugin-oauth2";
import { github } from "@youneed/server-plugin-oauth2/github";
import { google } from "@youneed/server-plugin-oauth2/google";
import { facebook } from "@youneed/server-plugin-oauth2/facebook";
import { yandex } from "@youneed/server-plugin-oauth2/yandex";
import { vk } from "@youneed/server-plugin-oauth2/vk";
const app = Application().plugin(
oauth2({
secret: process.env.OAUTH_SECRET!, // signs the state cookie
providers: {
github: github({ clientId: process.env.GH_ID!, clientSecret: process.env.GH_SECRET! }),
google: google({ clientId: process.env.G_ID!, clientSecret: process.env.G_SECRET!, offline: true }),
facebook: facebook({ clientId: process.env.FB_ID!, clientSecret: process.env.FB_SECRET! }),
yandex: yandex({ clientId: process.env.YA_ID!, clientSecret: process.env.YA_SECRET! }),
vk: vk({ clientId: process.env.VK_ID!, clientSecret: process.env.VK_SECRET! }),
},
async onLogin(ctx, { provider, profile, tokens }) {
const user = await db.upsert(provider, profile); // YOU decide what a session is
ctx.cookies.set("uid", user.id, { httpOnly: true });
return redirect("/");
},
}),
);
// Mounts: GET /auth/<provider> & /auth/<provider>/callback for each provider
README →
@youneed/server-plugin-otel
@youneed/server plugin + middleware: real OpenTelemetry SDK — SERVER spans per request with W3C propagation, http.server metrics, OTLP/HTTP export.
import { Application } from "@youneed/server";
import { otel } from "@youneed/server-plugin-otel";
const app = Application(MyController).plugin(
otel({
serviceName: "my-api",
endpoint: "http://localhost:4318", // OTLP/HTTP receiver (collector :4318, Tempo, …)
}),
);
app.listen(3000);
README →
@youneed/server-plugin-otlp
@youneed/server plugin: export per-request traces over OTLP/HTTP (JSON) to an OpenTelemetry collector / Jaeger / Tempo — no OTel SDK — with a devtools tab.
import { Application } from "@youneed/server";
import { otlp } from "@youneed/server-plugin-otlp";
const app = Application(MyController).plugin(
otlp({
endpoint: "http://localhost:4318", // OTLP/HTTP receiver (collector :4318, Tempo, …)
serviceName: "my-api",
headers: { "x-honeycomb-team": process.env.HONEYCOMB_KEY! }, // optional auth
batchSize: 100, // flush when buffered
flushMs: 5000, // …and on this interval
}),
);
app.listen(3000);
README →
@youneed/server-plugin-otp
One-time-password login (passwordless / 2FA) for @youneed/server: a ServerPlugin core + pluggable channels — email (built-in SMTP) and SMS.
import { Application } from "@youneed/server";
import { otp } from "@youneed/server-plugin-otp";
import { emailChannel } from "@youneed/server-plugin-otp/email";
import { smsChannel, twilioSms } from "@youneed/server-plugin-otp/sms";
const app = Application().plugin(
otp({
secret: process.env.OTP_SECRET!, // HMACs the stored code hash
channels: {
email: emailChannel({ host: "smtp.acme.dev", port: 587, auth: { user, pass }, from: "no-reply@acme.dev" }),
sms: smsChannel({ send: twilioSms({ accountSid, authToken, from: "+15550000000" }) }),
},
async onVerify(ctx, { channel, to }) { // identity proven → your session
const user = await db.upsertByContact(channel, to);
ctx.cookies.set("uid", user.id, { httpOnly: true });
return { ok: true };
},
}),
);
README →
@youneed/server-plugin-pubsub
Backend-agnostic publish/subscribe for @youneed/server: PubSub contract + MemoryPubSub + a ServerPlugin that surfaces channels in @youneed/server-plugin-devtools (flow graph, header tab, message sender). Re-exports the KV store.
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
// Wrap a backend (default = in-process MemoryPubSub) in a tracker for devtools.
const bus = createPubSub();
const app = Application()
.plugin(pubsub(bus)) // exposes /__pubsub/channels + /__pubsub/publish, and inspect()
.listen(3000, () => {});
// Subscribe — handler receives (message, channel). Messages are strings, so serialize.
const sub = await bus.subscribe("orders", (message, channel) => {
const order = JSON.parse(message);
console.log(`[${channel}]`, order.id);
});
// Publish from anywhere (a handler, a job, …).
await bus.publish("orders", JSON.stringify({ id: 42 }));
// Later: stop delivery.
await sub.close();
README →
@youneed/server-plugin-pubsub-deno
Deno KV adapter for @youneed/server-plugin-pubsub + -store: DenoKV and DenoPubSub (Deno KV queues). Runs on the Deno runtime / Deno Deploy.
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
import { denoPubSub } from "@youneed/server-plugin-pubsub-deno";
// Wire the adapter into the core pubsub plugin.
const bus = createPubSub(denoPubSub()); // uses Deno.openKv()
Application().plugin(pubsub(bus)).listen(3000, () => {});
await bus.subscribe("orders", (message, channel) => {
console.log(`[${channel}]`, JSON.parse(message));
});
await bus.publish("orders", JSON.stringify({ id: 42 }));
README →
@youneed/server-plugin-pubsub-kafka
Kafka adapter for @youneed/server-plugin-pubsub: KafkaPubSub over topics, via the official `kafkajs`. Pub/sub only (pair with a KV adapter for state).
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
import { kafkaPubSub } from "@youneed/server-plugin-pubsub-kafka";
// Wire the adapter into the core pubsub plugin.
const bus = createPubSub(
kafkaPubSub({ clientId: "orders-svc", brokers: ["localhost:9092"] }),
);
Application().plugin(pubsub(bus)).listen(3000, () => {});
// channel === Kafka topic. handler receives (message, topic).
await bus.subscribe("orders", (message, topic) => {
console.log(`[${topic}]`, JSON.parse(message));
});
await bus.publish("orders", JSON.stringify({ id: 42 }));
README →
@youneed/server-plugin-pubsub-nats
NATS adapter for @youneed/server-plugin-pubsub: NatsPubSub over subjects, via the official `nats` (nats.js). Pub/sub only (pair with a KV adapter for state).
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
import { natsPubSub } from "@youneed/server-plugin-pubsub-nats";
// Wire the adapter into the core pubsub plugin.
const bus = createPubSub(
natsPubSub({ servers: "localhost:4222" }),
);
Application().plugin(pubsub(bus)).listen(3000, () => {});
// channel === NATS subject. handler receives (message, subject).
await bus.subscribe("orders", (message, subject) => {
console.log(`[${subject}]`, JSON.parse(message));
});
await bus.publish("orders", JSON.stringify({ id: 42 }));
README →
@youneed/server-plugin-pubsub-postgres
Postgres adapter for @youneed/server-plugin-pubsub + -store: PostgresKV (table) and PostgresPubSub (LISTEN/NOTIFY), via the official `pg` driver.
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
import { postgresPubSub } from "@youneed/server-plugin-pubsub-postgres";
// Wire the adapter into the core pubsub plugin.
const bus = createPubSub(postgresPubSub({ connectionString: process.env.DATABASE_URL }));
Application().plugin(pubsub(bus)).listen(3000, () => {});
// Cross-instance: any process LISTENing on "orders" gets this NOTIFY.
await bus.subscribe("orders", (message) => console.log(JSON.parse(message)));
await bus.publish("orders", JSON.stringify({ id: 42 }));
README →
@youneed/server-plugin-pubsub-rabbitmq
RabbitMQ adapter for @youneed/server-plugin-pubsub: RabbitMQPubSub over an AMQP exchange, via the official `amqplib`. Pub/sub only (pair with a KV adapter for state).
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
import { rabbitmqPubSub } from "@youneed/server-plugin-pubsub-rabbitmq";
// Wire the adapter into the core pubsub plugin.
const bus = createPubSub(
rabbitmqPubSub({ url: "amqp://localhost", exchange: "orders-svc" }),
);
Application().plugin(pubsub(bus)).listen(3000, () => {});
// channel === routing key on the exchange. handler receives (message, channel).
await bus.subscribe("orders", (message, channel) => {
console.log(`[${channel}]`, JSON.parse(message));
});
await bus.publish("orders", JSON.stringify({ id: 42 }));
README →
@youneed/server-plugin-pubsub-redis
Redis/Valkey adapter — RedisKV (@youneed/server-plugin-store) + RedisPubSub (@youneed/server-plugin-pubsub) over a hand-rolled minimal RESP2 client (zero external deps).
import { redisKV } from "@youneed/kv-redis";
import { namespaced } from "@youneed/kv";
const kv = redisKV({ host: "127.0.0.1", port: 6379 });
// …or from a URL: redisKV({ url: "redis://:secret@cache.internal:6379/2" })
// Share one backend across consumers without key collisions:
const sessions = namespaced(kv, "sess");
const rate = namespaced(kv, "rl");
// Session store
await sessions.set(sid, JSON.stringify(data), { ttl: 1800 });
const raw = await sessions.get(sid);
// Rate limit — atomic, race-free counter; ttl applies only on creation:
const hits = await rate.incr(`ip:${ip}`, { by: 1, ttl: 60 });
if (hits > 100) throw new Error("rate limited");
README →
@youneed/server-plugin-pubsub-sqs
AWS SQS adapter for @youneed/server-plugin-pubsub: SqsPubSub over SQS queues (channel = queue), pure fetch + SigV4 (no aws-sdk). Long-poll consumer.
import { Application } from "@youneed/server";
import { createPubSub, pubsub } from "@youneed/server-plugin-pubsub";
import { sqsPubSub } from "@youneed/server-plugin-pubsub-sqs";
const bus = createPubSub(
sqsPubSub({
region: "us-east-1",
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
// channel === queue name, appended to this base URL prefix.
queuePrefix: "https://sqs.us-east-1.amazonaws.com/123456789012/",
}),
);
Application().plugin(pubsub(bus)).listen(3000, () => {});
// channel === SQS queue. handler receives (message, channel).
await bus.subscribe("orders", (message, channel) => {
console.log(`[${channel}]`, JSON.parse(message));
});
await bus.publish("orders", JSON.stringify({ id: 42 }));
README →
@youneed/server-plugin-queue
@youneed/server plugin: a durable background job queue (retries, backoff, dead-letter, delayed jobs, concurrent workers) persisted to a KV store, with a devtools tab.
import { Application } from "@youneed/server";
import { createQueue, queue } from "@youneed/server-plugin-queue";
import { redisKV } from "@youneed/kv-redis"; // or omit for the in-process MemoryKV
const jobs = createQueue({
store: redisKV({ url: process.env.REDIS_URL }), // durable + shared across instances
concurrency: 5,
maxAttempts: 3,
backoff: (attempt) => 1000 * 2 ** (attempt - 1), // 1s, 2s, 4s …
}).register("email", async ({ to }: { to: string }) => {
await sendEmail(to); // throw → retried, then dead-lettered after maxAttempts
});
const app = Application().plugin(queue(jobs)); // starts workers on listen, drains on shutdown
app.listen(3000);
// enqueue from anywhere
await jobs.add("email", { to: "ada@x.dev" });
await jobs.add("email", { to: "grace@x.dev" }, { delayMs: 60_000 }); // run in 1 min
README →
@youneed/server-plugin-rbac
@youneed/server plugin + controller provider + guard for @youneed/rbac: request-scoped `this.can`, an `authorize(action, resource)` guard, a roles/check introspection API, and a devtools RBAC tab.
import { Application, Controller } from "@youneed/server";
import { createRBAC, owns, rbac, rbacProvider, authorizeWith } from "@youneed/server-plugin-rbac";
const engine = createRBAC((role) => {
role("admin").can("*", "*");
role("editor").inherits("viewer").can(["create", "update"], "post").can("delete", "post", owns("authorId"));
role("viewer").can("read", "post");
});
// Bind the engine once so route guards need not carry it.
const authorize = authorizeWith(engine);
class PostController extends Controller("/posts", {
providers: [rbacProvider(engine)],
}) {
@Controller.guard(authorize("update", "post"))
@Controller.put("/:id")
update() {
/* only reached if the current subject may update a post */
}
@Controller.get()
list() {
// `this.can` is bound to the current request's subject
return this.can("read", "post") ? loadPosts() : [];
}
}
const app = Application(PostController).plugin(rbac(engine));
app.listen(3000);
README →
@youneed/server-plugin-secrets
@youneed/server plugin + controller provider for @youneed/secrets: request-safe `this.secrets`, SAFE introspection routes (names + masked presence probe — values NEVER exposed), and a devtools Secrets tab.
import { Application, Controller } from "@youneed/server";
import { createSecrets, EnvSecrets, secrets, secretsProvider } from "@youneed/server-plugin-secrets";
const engine = createSecrets(new EnvSecrets(), { cacheTtlMs: 60_000 });
class BillingController extends Controller("/billing", {
providers: [secretsProvider(engine)],
}) {
@Controller.post()
async charge() {
const key = await this.secrets.require("STRIPE_KEY"); // resolved server-side; never returned
// … call Stripe with `key` …
return { ok: true };
}
}
const app = Application(BillingController).plugin(secrets(engine));
app.listen(3000);
README →
@youneed/server-plugin-ssr
@youneed/server plugin: add SSR pages and satellite SSR modules (robots/sitemap/rss/llms/structured-data) to a server from the outside.
import { Application } from "@youneed/server";
import { ssr } from "@youneed/server-plugin-ssr";
import { robots } from "@youneed/ssr-plugin-robots";
import { sitemap } from "@youneed/ssr-plugin-sitemap";
import { rss } from "@youneed/ssr-plugin-rss";
import { llms } from "@youneed/ssr-plugin-llms";
import { structuredData, organization } from "@youneed/ssr-plugin-structured-data";
Application()
.plugin(
ssr({
origin: "https://example.com",
pages: [Home, About, Pricing], // @youneed/ssr Page classes
devtools: true,
modules: [
robots({ sitemap: true }),
sitemap({ defaults: { changefreq: "weekly" } }),
rss({ title: "Blog", description: "Latest", items: loadItems }),
llms({ title: "Example", includePages: true }),
structuredData({ schemas: organization({ name: "Example" }) }),
],
}),
)
.listen(3000, () => {});
README →
@youneed/server-plugin-storage
@youneed/server plugin: pluggable object/blob storage (memory, filesystem, S3) behind one StorageAdapter contract, with a devtools object-browser tab.
import { Application } from "@youneed/server";
import { storage, FileStorage, s3Storage, MemoryStorage } from "@youneed/server-plugin-storage";
// pick a backend
const files = new FileStorage("./data"); // local disk
// const files = s3Storage({ bucket: "my-bucket", region: "us-east-1" }); // S3
// const files = new MemoryStorage(); // dev / single instance
const app = Application().plugin(storage(files)); // exposes control routes + devtools tab
app.listen(3000);
// use it from anywhere
await files.put("docs/readme.txt", "hello", { contentType: "text/plain" });
const obj = await files.get("docs/readme.txt"); // { data: Uint8Array, contentType }
await files.list("docs/"); // [{ key, size, contentType, updatedAt }]
await files.delete("docs/readme.txt");
README →
@youneed/server-plugin-store
Distributed key-value contract (KV) + in-process MemoryKV. The backing store is chosen by adapter (redis/postgres/deno via @youneed/server-plugin-pubsub-*).
import { MemoryKV, namespaced, type KV } from "@youneed/server-plugin-store";
const kv: KV = new MemoryKV();
await kv.set("user:1", JSON.stringify({ name: "Ada" }), { ttl: 60 });
await kv.get("user:1"); // → '{"name":"Ada"}' (undefined once expired)
await kv.incr("hits", { ttl: 60 }); // → 1, atomic, expiry set only on creation
await kv.ttl("hits"); // → remaining seconds (-1 no expiry, -2 missing)
// Share one backend across consumers without key collisions:
const sessions = namespaced(kv, "sess"); // every key becomes "sess:<key>"
const rate = namespaced(kv, "rl");
README →
@youneed/server-upload
Streaming multipart/form-data file uploads for @youneed/server: web streams, progress, and size/extension/type/name/content guards.
import { Response } from "@youneed/server";
import { parseUpload, UploadError } from "@youneed/server-upload";
import { pipeline } from "node:stream/promises";
import { Readable } from "node:stream";
import fs from "node:fs";
app.post("/upload", async (ctx) => {
const form: Record<string, string> = {};
try {
for await (const part of parseUpload(ctx, {
maxFileSize: 10 * 1024 * 1024,
maxFiles: 5,
allowedExtensions: [".png", ".jpg", ".pdf"],
allowedTypes: ["image/png", "image/jpeg", "application/pdf"],
sniff: true, // magic bytes vs declared type
filenamePattern: /^[\w.\- ]+$/,
onProgress: (p) => report(p.bytesReceived, p.totalBytes),
})) {
if (part.kind === "field") form[part.name] = part.value;
else await pipeline(Readable.fromWeb(part.stream), fs.createWriteStream(`/tmp/${part.filename}`));
}
return { ok: true, fields: form };
} catch (e) {
if (e instanceof UploadError) return Response.json({ error: e.message }, { status: e.status });
throw e;
}
}, { body: false }); // ← required: handler reads the stream
README →
@youneed/ssr
Server-side rendering + Page entity for @youneed components.
import { renderToString, renderPage } from "@youneed/ssr";
renderToString(MyComponent); // → "<my-el><template shadowrootmode>…</template></my-el>"
renderPage(MyComponent, { title: "Home", clientScript: "/client.js" }); // full <!doctype html>
README →
@youneed/ssr-plugin-canonical
SSR page middleware for @youneed: emit <link rel="canonical"> and hreflang alternates per page.
import { ssr } from "@youneed/server-plugin-ssr";
import { canonical } from "@youneed/ssr-plugin-canonical";
class Pricing extends Page("/pricing", {
canonical: "/pricing", // string | false | (ctx) => string
alternates: [{ hreflang: "de", href: "/de/preise" }],
}) { /* … */ }
app.plugin(ssr({
origin: "https://example.com",
pages: [Pricing],
modules: [canonical({ trailingSlash: "strip" })],
}));
README →
@youneed/ssr-plugin-csp
SSR Content-Security-Policy for @youneed: per-request nonce, header builder, and inline-script nonce injection for document responses.
import { ssr } from "@youneed/server-plugin-ssr";
import { csp } from "@youneed/ssr-plugin-csp";
app.plugin(ssr({
pages: [Home],
modules: [csp({ directives: { "img-src": ["'self'", "https://cdn.example.com"] } })],
}));
README →
@youneed/ssr-plugin-feature-flags
SSR module for @youneed/server-plugin-ssr: evaluate feature flags on the server and inject the snapshot (window.__FLAGS__) into every page for client hydration.
import { ssr } from "@youneed/server-plugin-ssr";
import { createFlags } from "@youneed/feature-flags";
import { featureFlags } from "@youneed/ssr-plugin-feature-flags";
const flags = createFlags([
{ key: "new-dashboard", defaultValue: false, rollout: 20 }, // 20% of users
{ key: "checkout", defaultValue: "control",
variants: { control: "control", fast: "fast" },
rules: [{ attributes: { plan: "pro" }, variant: "fast" }] }, // pro → "fast"
]);
app.plugin(
ssr({
origin: "https://example.com",
modules: [
featureFlags(flags, {
// derive the evaluation context from each request (cookie, session, …)
context: (req) => ({ targetingKey: req.cookies?.uid, attributes: { plan: req.plan } }),
}),
],
}),
);
README →
@youneed/ssr-plugin-llms
SSR module for @youneed/server-plugin-ssr: serve an llms.txt (and optional llms-full.txt) guide for LLM crawlers.
import { ssr } from "@youneed/server-plugin-ssr";
import { llms } from "@youneed/ssr-plugin-llms";
app.plugin(
ssr({
origin: "https://example.com",
pages: [Home, Docs, Pricing],
modules: [
llms({
title: "Example",
summary: "A widget store with a public API.",
notes: ["All prices in USD."],
sections: [
{ title: "Docs", links: [{ title: "API", url: "/docs/api", notes: "REST reference" }] },
],
includePages: true, // append a "Pages" section from the mounted routes
}),
],
}),
);
README →
@youneed/ssr-plugin-meta
SSR page middleware for @youneed: emit SEO <meta> + OpenGraph + Twitter Card tags from per-page declarations.
import { ssr } from "@youneed/server-plugin-ssr";
import { meta } from "@youneed/ssr-plugin-meta";
class Post extends Page("/blog/:slug", {
title: "Hello world",
meta: {
description: "An introductory post.",
og: { type: "article", image: "/og/hello.png" },
twitter: { card: "summary_large_image" },
},
}) { /* … */ }
app.plugin(ssr({
origin: "https://example.com",
pages: [Post],
modules: [meta({ siteName: "Example", twitterSite: "@example" })],
}));
README →
@youneed/ssr-plugin-otel
@youneed/ssr module: real OpenTelemetry SDK — ssr.render spans per static page render, ssr.render metrics, via the shared @youneed/otel core.
import { Application } from "@youneed/server";
import { ssr } from "@youneed/server-plugin-ssr";
import { otelModule } from "@youneed/ssr-plugin-otel";
Application()
.plugin(ssr({
pages: [Home, About],
modules: [otelModule()],
}))
.listen(3000, () => {});
README →
@youneed/ssr-plugin-preload
SSR page middleware for @youneed: emit resource hints (<link rel=preload/modulepreload/preconnect/dns-prefetch/prefetch>) per page.
import { ssr } from "@youneed/server-plugin-ssr";
import { preload } from "@youneed/ssr-plugin-preload";
class Home extends Page("/", {
preload: [
{ rel: "preload", href: "/fonts/inter.woff2", as: "font", type: "font/woff2", crossorigin: true },
{ rel: "modulepreload", href: "/client.js" },
],
}) { /* … */ }
app.plugin(ssr({
origin: "https://example.com",
pages: [Home],
modules: [preload({ hints: [{ rel: "preconnect", href: "https://cdn.example.com" }] })],
}));
README →
@youneed/ssr-plugin-robots
SSR module for @youneed/server-plugin-ssr: generate robots.txt with per-agent allow/disallow rules and sitemap links.
import { ssr } from "@youneed/server-plugin-ssr";
import { robots } from "@youneed/ssr-plugin-robots";
app.plugin(
ssr({
origin: "https://example.com",
modules: [
robots({
policies: [
{ userAgent: "*", disallow: ["/admin", "/api"], allow: "/api/public" },
{ userAgent: ["GPTBot", "CCBot"], disallow: "/" },
],
sitemap: true, // → Sitemap: https://example.com/sitemap.xml
host: "example.com",
}),
],
}),
);
README →
@youneed/ssr-plugin-sitemap
SSR module for @youneed/server-plugin-ssr: generate sitemap.xml from mounted page routes plus explicit entries.
import { ssr } from "@youneed/server-plugin-ssr";
import { sitemap } from "@youneed/ssr-plugin-sitemap";
app.plugin(
ssr({
origin: "https://example.com",
pages: [Home, About, Pricing],
modules: [
sitemap({
exclude: ["/admin", /^\/internal/],
entries: [{ url: "/blog/launch", lastmod: "2026-06-01", priority: 0.8 }],
defaults: { changefreq: "weekly", priority: 0.5 },
}),
],
}),
);
README →
@youneed/ssr-plugin-speculation
SSR page middleware for @youneed/ssr: inject the Speculation Rules API <script> (prefetch/prerender) from a page's declared rules.
class Home extends Page("/", {
title: "Home",
speculation: { prerender: [{ source: "list", urls: [About.url], eagerness: "moderate" }] },
}) {
override render() { return HomeApp; }
}
README →
@youneed/ssr-plugin-structured-data
SSR module for @youneed/server-plugin-ssr: inject JSON-LD structured data (schema.org) into page <head>, with typed builders.
import { ssr } from "@youneed/server-plugin-ssr";
import { structuredData, organization, website } from "@youneed/ssr-plugin-structured-data";
app.plugin(
ssr({
origin: "https://example.com",
modules: [
structuredData({
schemas: [
organization({ name: "Example", url: "https://example.com", logo: "/logo.png" }),
website({ name: "Example", url: "https://example.com", searchUrl: "https://example.com/search?q=" }),
],
}),
],
}),
);
README →
@youneed/ssr-router
SSR router for @youneed: an ssr() module adding Error/404 pages (server), plus the @youneed/dom-router for client SPA navigation.
import { Application } from "@youneed/server";
import { ssr } from "@youneed/server-plugin-ssr";
import { router } from "@youneed/ssr-router";
import { Home, Blog, NotFound, ErrorPage } from "./pages";
const app = Application();
app.plugin(
ssr({
pages: [Home, Blog],
modules: [router({ notFound: NotFound, error: ErrorPage })],
}),
);
app.listen(3000, () => {});
README →
@youneed/test
you need test framework
import { Test, Fixture, TestApplication, expect } from "@youneed/test";
class Calculator {
add(a: number, b: number) { return a + b; }
}
class CalcFixture extends Fixture<Calculator>({ name: "calc", scope: "test" }) {
setup() { return new Calculator(); }
}
class CalcTest extends Test({ name: "Calculator" }) {
@Test.use(CalcFixture) calc!: Calculator;
@Test.it("adds two numbers")
adds() {
expect(this.calc.add(2, 3)).toBe(5);
}
}
await TestApplication().addTests(CalcTest).run();
README →
@youneed/test-devtools
Live web-UI devtools reporter for @youneed/test — streams the run to a browser over SSE
import { TestApplication } from "@youneed/test";
import { DevtoolsReporter } from "@youneed/test-devtools";
await TestApplication()
.addTests(MyTest)
.reporter(new DevtoolsReporter({ open: true })) // opens the UI in your browser
.run();
README →
@youneed/test-plugin-benchmark
Benchmark extension for @youneed/test (decorator + plugin + reporter)
import { TestApplication, Test } from "@youneed/test";
import { Benchmark, benchmark, BenchmarkReporter } from "@youneed/test-plugin-benchmark";
class Perf extends Test() {
data = Array.from({ length: 1000 }, (_, i) => i);
@Benchmark({ name: "sum 1k", iterations: 2000 }) // fixed count…
@Test.it()
reduceSum() { this.data.reduce((a, b) => a + b, 0); }
@Benchmark({ timeBudgetMs: 200 }) // …or sample for a time budget
@Test.it()
sort() { [...this.data].sort((a, b) => b - a); }
}
await TestApplication()
.addTests(Perf)
.use(benchmark()) // ① the plugin — turns marked cases into timed loops
.reporter(new BenchmarkReporter()) // ② its output (compose with ConsoleReporter etc.)
.run();
// ⚡ sum 1k 174,894 ops/sec ±2.1% (mean 0.0057ms · p99 0.0134ms · 2000 samples)
README →
@youneed/test-plugin-feature-flags
Deterministic feature-flag helpers for @youneed/test: a fresh FeatureFlags fixture that resets overrides between tests, plus a scoped withFlags(...) override helper.
import { Test, TestApplication, expect } from "@youneed/test";
import { flagsFixture, withFlags, expectFlag } from "@youneed/test-plugin-feature-flags";
import type { FeatureFlags } from "@youneed/feature-flags";
const Flags = flagsFixture([
{ key: "new-checkout", defaultValue: false },
{ key: "theme", defaultValue: "light", variants: { light: "light", dark: "dark" } },
]);
class Checkout extends Test() {
@Test.use(Flags) flags!: FeatureFlags; // …or decorator-free: flags = Flags.get();
@Test.it("off by default") off() {
expect(this.flags.isEnabled("new-checkout")).toBeFalsy();
}
@Test.it("forced on for THIS test only") on() {
this.flags.override("new-checkout", true);
expectFlag(this.flags, "new-checkout");
// the next test gets a fresh engine — no leak.
}
@Test.it("scoped override restores after the block") scoped() {
withFlags(this.flags, { "new-checkout": true }, () => {
expectFlag(this.flags, "new-checkout"); // enabled inside
});
expect(this.flags.isEnabled("new-checkout")).toBeFalsy(); // restored
}
}
TestApplication().addTests(Checkout).run();
README →
@youneed/test-plugin-i18n
Translation parity checks for @youneed/test: assert no missing/extra keys or drifted placeholders across locales.
import { Test, TestApplication, expect } from "@youneed/test";
import { assertParity, eachLocale } from "@youneed/test-plugin-i18n";
import { resources, i18n } from "./i18n.ts";
class I18n extends Test() {
@Test.it("every locale is complete") complete() {
assertParity(resources); // throws an AssertionError listing the gaps
}
@Test.it("greeting renders in every language") greet() {
eachLocale(i18n, () => expect(i18n("greeting", { name: "x" })).toContain("x"));
}
}
README →
@youneed/test-plugin-otel
@youneed/test plugin: real OpenTelemetry SDK — a span per test case with steps and failure status, test.* metrics, OTLP/HTTP export flushed at teardown.
import { TestApplication } from "@youneed/test";
import { otel } from "@youneed/test-plugin-otel";
await TestApplication()
.addTests(MySuite)
.use(otel({ serviceName: "my-tests", endpoint: "http://localhost:4318" }))
.run();
README →
@youneed/test-plugin-rbac
Deterministic RBAC helpers for @youneed/test: a fresh RBAC fixture that resets role tweaks between tests, ergonomic Subject builders, expectCan/expectCannot assertions, and a scoped withRole(...) helper.
import { Test, TestApplication } from "@youneed/test";
import { rbacFixture, subject, expectCan, expectCannot, withRole } from "@youneed/test-plugin-rbac";
import { owns, type RBAC } from "@youneed/rbac";
const Rbac = rbacFixture((role) => {
role("viewer").can("read", "post");
role("editor")
.inherits("viewer")
.can(["create", "update"], "post")
.can("delete", "post", owns("authorId")); // only own posts
});
class Posts extends Test() {
@Test.use(Rbac) rbac!: RBAC; // …or decorator-free: rbac = Rbac.get();
@Test.it("editor can update, viewer cannot") perms() {
expectCan(this.rbac, subject(["editor"]), "update", "post");
expectCannot(this.rbac, subject(["viewer"]), "update", "post");
}
@Test.it("ownership condition") owned() {
const editor = subject(["editor"], { id: "u1" });
expectCan(this.rbac, editor, "delete", "post", { authorId: "u1" }); // owns it
expectCannot(this.rbac, editor, "delete", "post", { authorId: "u2" }); // not owner
}
@Test.it("setRole applies for THIS test only") mutate() {
this.rbac.setRole({ name: "viewer", permissions: [{ action: "delete", resource: "post" }] });
expectCan(this.rbac, subject(["viewer"]), "delete", "post");
// the next test gets a fresh engine — no leak.
}
@Test.it("scoped role experiment restores after the block") scoped() {
withRole(this.rbac, { name: "viewer", permissions: [{ action: "delete", resource: "post" }] }, () => {
expectCan(this.rbac, subject(["viewer"]), "delete", "post"); // granted inside
});
expectCannot(this.rbac, subject(["viewer"]), "delete", "post"); // restored
}
}
TestApplication().addTests(Posts).run();
README →
@youneed/test-reporter-console
Colored console reporter for @youneed/test
import { TestApplication } from "@youneed/test";
import { ConsoleReporter } from "@youneed/test-reporter-console";
await TestApplication()
.addTests(MyTest)
.reporter(new ConsoleReporter()) // plug it in
.run();
README →
@youneed/test-reporter-html
HTML report reporter for @youneed/test
import { TestApplication } from "@youneed/test";
import { HTMLReporter } from "@youneed/test-reporter-html";
await TestApplication()
.addTests(MyTest)
.reporter(new HTMLReporter({ output: "report.html", title: "My suite" }))
.run();
README →
@youneed/test-reporter-junit
JUnit XML reporter for @youneed/test
import { JUnitReporter } from "@youneed/test-reporter-junit";
TestApplication().addTests(...).reporter(new JUnitReporter({ output: "junit.xml" })).run();
README →
@youneed/test-reporter-progress
Live, interactive per-lane progress reporter for @youneed/test
import { TestApplication } from "@youneed/test";
import { ProgressReporter } from "@youneed/test-reporter-progress";
await TestApplication()
.addTests(...suites)
.parallel(4) // run across 4 in-process lanes
.reporter(new ProgressReporter()) // live per-lane status
.run();
README →
@youneed/test-reporter-tap
TAP reporter for @youneed/test
import { TapReporter } from "@youneed/test-reporter-tap";
TestApplication().addTests(...).reporter(new TapReporter()).run();
README →
@youneed/test-resilience
Timeout and retry plugins for @youneed/test
import { TestApplication } from "@youneed/test";
import { timeout, retry, Timeout, Retry } from "@youneed/test-resilience";
class Flaky extends Test() {
@Retry(3) @Timeout(2000) @Test.it("eventually") work() { /* … */ }
}
TestApplication().addTests(Flaky)
.use(retry(2)) // outer — re-runs on failure
.use(timeout(5000)) // inner — each attempt times out
.run();
README →
@youneed/test-snapshot
Snapshot testing for @youneed/test
import { snapshot, toMatchSnapshot } from "@youneed/test-snapshot";
class Render extends Test() {
@Test.it() tree() { toMatchSnapshot(buildTree()); }
}
TestApplication().addTests(Render).use(snapshot()).run();
README →
@youneed/ts-plugin
TypeScript language-service plugin: completions inside html`` / css`` for @youneed/dom — custom-element tags, .props and @events.
import { defineComponentPreview, serveComponentPreview, runComponentPreview }
from "@youneed/ts-plugin/preview";
const config = defineComponentPreview({
file: "./src/components.ts", // entry that registers your @youneed/dom components
generate(c) { // optional: per-component props/markup/skip/width
if (c.tag === "todo-item") return { props: { text: "Buy milk" } };
},
});
await serveComponentPreview(config); // live gallery at http://127.0.0.1:5757
// or: await runComponentPreview({ ...config, outDir: "preview" }); // → preview/<tag>.png
README →
@youneed/vite-plugin
Vite plugin for @youneed components.
import { defineConfig } from "vite";
import { domFramework } from "@youneed/vite-plugin";
export default defineConfig({
plugins: [
domFramework(),
// react(), vue(), …
],
});
README →