Skip to content

Parameters

Parameters are the values a route reads from the command line. There are three kinds:

  • option() reads a named value, such as --limit 30.
  • toggle() reads an on or off switch, such as --draft.
  • argument() reads a positional value, such as the bombshell-dev/router in gh repo clone bombshell-dev/router.

Each parameter belongs to the route that declares it, and its value appears in that route’s model.

option(name("body")) reads --body <value> or --body=<value>. The flag is the kebab-case form of the name, so option(name("bodyFile")) reads --body-file. When the flag is absent, the value is undefined.

Without a schema, a parameter’s type is unknown. Add schema() with any Standard Schema library to give it a type, a default, or a requirement:

import
var process: NodeJS.Process
process
from "node:process";
import {
function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command
,
function name<N extends string>(name: N): Definition<N>
name
,
function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option
,
function parse<const R extends AnyRoute>(route: R, input: Input): Parse<R>
parse
,
function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const app: Route<"create", "help" | "execute", readonly [Done<{
body: unknown;
base: string;
title: string;
}, []>]>
app
=
command<"create", readonly [ModelElement<ParamModel<"body", unknown>>, ModelElement<ParamModel<"base", string>>, ModelElement<ParamModel<"title", string>>]>(start: Definition<...>, elements_0: ModelElement<ParamModel<"body", unknown>>, elements_1: ModelElement<ParamModel<"base", string>>, elements_2: ModelElement<ParamModel<"title", string>>): Route<...>
command
(
name<"create">(name: "create"): Definition<"create">
name
("create"),
option<"body", readonly []>(named: Definition<"body">): ModelElement<ParamModel<"body", unknown>>
option
(
name<"body">(name: "body"): Definition<"body">
name
("body")),
option<"base", readonly [TransformElement<SchemaTransform<string>>]>(named: Definition<"base">, elements_0: TransformElement<SchemaTransform<string>>): ModelElement<...>
option
(
name<"base">(name: "base"): Definition<"base">
name
("base"),
schema<z.ZodDefault<z.ZodString>>(schema: z.ZodDefault<z.ZodString>): TransformElement<SchemaTransform<string>>
schema
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
().
ZodType<string, ZodStringDef, string>.default(def: string): z.ZodDefault<z.ZodString> (+1 overload)
default
("main"))),
option<"title", readonly [TransformElement<SchemaTransform<string>>]>(named: Definition<"title">, elements_0: TransformElement<SchemaTransform<string>>): ModelElement<...>
option
(
name<"title">(name: "title"): Definition<"title">
name
("title"),
schema<z.ZodString>(schema: z.ZodString): TransformElement<SchemaTransform<string>>
schema
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
())),
);
const
const result: Parse<Route<"create", "help" | "execute", readonly [Done<{
body: unknown;
base: string;
title: string;
}, []>]>>
result
=
parse<Route<"create", "help" | "execute", readonly [Done<{
body: unknown;
base: string;
title: string;
}, []>]>>(route: Route<"create", "help" | "execute", readonly [Done<{
body: unknown;
base: string;
title: string;
}, []>]>, input: Input): Parse<...>
parse
(
const app: Route<"create", "help" | "execute", readonly [Done<{
body: unknown;
base: string;
title: string;
}, []>]>
app
, {
argv: string[]
argv
:
var process: NodeJS.Process
process
.
NodeJS.Process.argv: string[]

The process.argv property returns an array containing the command-line arguments passed when the Node.js process was launched. The first element will be

execPath

. See process.argv0 if access to the original value of argv[0] is needed. The second element will be the path to the JavaScript file being executed. The remaining elements will be any additional command-line arguments.

For example, assuming the following script for process-args.js:

import { argv } from 'node:process';
// print process.argv
argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
});

Launching the Node.js process as:

Terminal window
node process-args.js one two=three four

Would generate the output:

0: /usr/local/bin/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four

@since ― v0.1.27

argv
.
Array<string>.slice(start?: number, end?: number): string[]

Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array.

@param ― start The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end of the array.

slice
(2) });
if (
const result: Parse<Route<"create", "help" | "execute", readonly [Done<{
body: unknown;
base: string;
title: string;
}, []>]>>
result
.
ok: boolean
ok
&&
const result: Help<"/"> | Execute<"/", {
"/": {
body: unknown;
base: string;
title: string;
};
}>
result
.
Intent<M extends Method, P extends RoutePath>.method: "help" | "execute"
method
=== "execute") {
const result: Execute<"/", {
"/": {
body: unknown;
base: string;
title: string;
};
}>
result
.
Execute<"/", { "/": { body: unknown; base: string; title: string; }; }>.model: {
body: unknown;
base: string;
title: string;
}
model
.
body: unknown
body
; // unknown
const result: Execute<"/", {
"/": {
body: unknown;
base: string;
title: string;
};
}>
result
.
Execute<"/", { "/": { body: unknown; base: string; title: string; }; }>.model: {
body: unknown;
base: string;
title: string;
}
model
.
base: string
base
; // string
const result: Execute<"/", {
"/": {
body: unknown;
base: string;
title: string;
};
}>
result
.
Execute<"/", { "/": { body: unknown; base: string; title: string; }; }>.model: {
body: unknown;
base: string;
title: string;
}
model
.
title: string
title
; // string
}

A schema that rejects undefined makes the parameter required. Here --title is required, so create alone fails with unprocessable-content:

title: Invalid input: expected string, received undefined

Invalid values fail the same way, and they never reach your application.

Command-line text is decoded before the schema sees it. The router tries a number first, then the original text, and the schema picks the first candidate it accepts. Without a schema, the first candidate wins:

InputNo schemaschema(z.string())
--milestone 20242024"2024"
--milestone 0077"007"
--milestone 1e31000"1e3"
--milestone v2"v2""v2"

To keep text that looks like a number, such as a milestone or a version, use a string schema.

toggle(name("draft")) reads a switch. It is true with --draft, false with --no-draft, and false when absent. Its type is always boolean, so it needs no schema. --draft=false is not accepted.

argument(name("repository")) reads one positional value. A second positional value, such as the docs in clone bombshell-dev/router docs, fails with unexpected: `docs` . As with options, add a schema to make the argument required or typed:

import {
function argument<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
argument
,
function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command
,
function name<N extends string>(name: N): Definition<N>
name
,
function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const clone: Route<"clone", "help" | "execute", readonly [Done<{
repository: string;
}, []>]>
clone
=
command<"clone", readonly [ModelElement<ParamModel<"repository", string>>]>(start: Definition<"clone">, elements_0: ModelElement<ParamModel<"repository", string>>): Route<...>
command
(
name<"clone">(name: "clone"): Definition<"clone">
name
("clone"),
argument<"repository", readonly [TransformElement<SchemaTransform<string>>]>(named: Definition<"repository">, elements_0: TransformElement<SchemaTransform<string>>): ModelElement<...>
argument
(
name<"repository">(name: "repository"): Definition<"repository">
name
("repository"),
schema<z.ZodString>(schema: z.ZodString): TransformElement<SchemaTransform<string>>
schema
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
())),
);

multiple() turns a parameter into a list. An option collects every occurrence, so --label bug --label docs reads ["bug", "docs"]. An argument collects every positional value, so gist create a.ts b.ts reads ["a.ts", "b.ts"]. With multiple(), the schema validates the whole list:

import {
function argument<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
argument
,
function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command
,
function multiple(): TransformElement<MultipleTransform>
multiple
,
function name<N extends string>(name: N): Definition<N>
name
,
function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option
,
function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema
,
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const prCreate: Route<"create", "help" | "execute", readonly [Done<{
label: string[];
}, []>]>
prCreate
=
command<"create", readonly [ModelElement<ParamModel<"label", string[]>>]>(start: Definition<"create">, elements_0: ModelElement<ParamModel<"label", string[]>>): Route<...>
command
(
name<"create">(name: "create"): Definition<"create">
name
("create"),
option<"label", readonly [TransformElement<MultipleTransform>, TransformElement<SchemaTransform<string[]>>]>(named: Definition<...>, elements_0: TransformElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option
(
name<"label">(name: "label"): Definition<"label">
name
("label"),
function multiple(): TransformElement<MultipleTransform>
multiple
(),
schema<z.ZodDefault<z.ZodArray<z.ZodString, "many">>>(schema: z.ZodDefault<z.ZodArray<z.ZodString, "many">>): TransformElement<SchemaTransform<string[]>>
schema
(
import z
z
.
array<z.ZodString>(schema: z.ZodString, params?: z.RawCreateParams): z.ZodArray<z.ZodString, "many">
export array
array
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
()).
ZodType<string[], ZodArrayDef<ZodString>, string[]>.default(def: string[]): z.ZodDefault<z.ZodArray<z.ZodString, "many">> (+1 overload)
default
([])),
),
);
const
const gistCreate: Route<"create", "help" | "execute", readonly [Done<{
files: string[];
}, []>]>
gistCreate
=
command<"create", readonly [ModelElement<ParamModel<"files", string[]>>]>(start: Definition<"create">, elements_0: ModelElement<ParamModel<"files", string[]>>): Route<...>
command
(
name<"create">(name: "create"): Definition<"create">
name
("create"),
argument<"files", readonly [TransformElement<MultipleTransform>, TransformElement<SchemaTransform<string[]>>]>(named: Definition<...>, elements_0: TransformElement<...>, elements_1: TransformElement<...>): ModelElement<...>
argument
(
name<"files">(name: "files"): Definition<"files">
name
("files"),
function multiple(): TransformElement<MultipleTransform>
multiple
(),
schema<z.ZodArray<z.ZodString, "many">>(schema: z.ZodArray<z.ZodString, "many">): TransformElement<SchemaTransform<string[]>>
schema
(
import z
z
.
array<z.ZodString>(schema: z.ZodString, params?: z.RawCreateParams): z.ZodArray<z.ZodString, "many">
export array
array
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
())),
),
);

Both values are typed string[]. Without --label, the default gives an empty list.

cli() replaces the flags an option reads. After cli(["--repo", "-R"]), an option named repository reads --repo and -R, and --repository is no longer accepted:

import {
function cli(names: readonly string[], options?: CLIOptions): IdentityElement<AnyParam>
cli
,
function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command
,
function name<N extends string>(name: N): Definition<N>
name
,
function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option
,
function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const app: Route<"list", "help" | "execute", readonly [Done<{
repository: string;
web: unknown;
}, []>]>
app
=
command<"list", readonly [ModelElement<ParamModel<"repository", string>>, ModelElement<ParamModel<"web", unknown>>]>(start: Definition<"list">, elements_0: ModelElement<ParamModel<"repository", string>>, elements_1: ModelElement<ParamModel<"web", unknown>>): Route<...>
command
(
name<"list">(name: "list"): Definition<"list">
name
("list"),
option<"repository", readonly [IdentityElement<AnyParam>, TransformElement<SchemaTransform<string>>]>(named: Definition<"repository">, elements_0: IdentityElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option
(
name<"repository">(name: "repository"): Definition<"repository">
name
("repository"),
function cli(names: readonly string[], options?: CLIOptions): IdentityElement<AnyParam>
cli
(["--repo", "-R"]),
schema<z.ZodString>(schema: z.ZodString): TransformElement<SchemaTransform<string>>
schema
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
())),
option<"web", readonly [IdentityElement<AnyParam>]>(named: Definition<"web">, elements_0: IdentityElement<AnyParam>): ModelElement<...>
option
(
name<"web">(name: "web"): Definition<"web">
name
("web"),
function cli(names: readonly string[], options?: CLIOptions): IdentityElement<AnyParam>
cli
(["--web", "-w"], {
CLIOptions.switch?: true
switch
: true })),
);

For most on or off flags, use toggle() instead. It reads --web and --no-web, defaults to false, and is always a boolean.

{ switch: true } makes an option take no value. --web or -w reads true, the value is undefined when absent, and its type is unknown unless you add a schema. Use it only when you need something a toggle cannot do, such as a short flag like -w.

When you pass the environment to parse(), each parameter also reads a variable named after its route and its name. The repo option of the /pr/list route reads PR_LIST_REPO. env() replaces that name:

import
var process: NodeJS.Process
process
from "node:process";
import {
function command<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, Normalize<E>>>
command
,
function env(key: string): IdentityElement<AnyParam>
env
,
function name<N extends string>(name: N): Definition<N>
name
,
function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<...>
option
,
function parse<const R extends AnyRoute>(route: R, input: Input): Parse<R>
parse
,
function route<const N extends string, const E extends readonly Input[]>(start: Definition<N>, ...elements: E & Validate<RouteZero<N>, E>): Materialize<Fold<RouteZero<N>, Normalize<E>>>
route
,
function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>
schema
,
} from "@bomb.sh/router";
import * as
import z
z
from "zod";
const
const app: Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
repo: string | undefined;
}, []>]>]>]>]>]>
app
=
command<"gh", readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
repo: string | undefined;
}, []>]>]>]>]>(start: Definition<"gh">, elements_0: Route<...>): Route<...>
command
(
name<"gh">(name: "gh"): Definition<"gh">
name
("gh"),
route<"pr", readonly [Route<"list", "help" | "execute", readonly [Done<{
repo: string | undefined;
}, []>]>]>(start: Definition<"pr">, elements_0: Route<"list", "help" | "execute", readonly [...]>): Route<...>
route
(
name<"pr">(name: "pr"): Definition<"pr">
name
("pr"),
command<"list", readonly [ModelElement<ParamModel<"repo", string | undefined>>]>(start: Definition<"list">, elements_0: ModelElement<ParamModel<"repo", string | undefined>>): Route<...>
command
(
name<"list">(name: "list"): Definition<"list">
name
("list"),
option<"repo", readonly [IdentityElement<AnyParam>, TransformElement<SchemaTransform<string | undefined>>]>(named: Definition<"repo">, elements_0: IdentityElement<...>, elements_1: TransformElement<...>): ModelElement<...>
option
(
name<"repo">(name: "repo"): Definition<"repo">
name
("repo"),
function env(key: string): IdentityElement<AnyParam>
env
("GH_REPO"),
schema<z.ZodOptional<z.ZodString>>(schema: z.ZodOptional<z.ZodString>): TransformElement<SchemaTransform<string | undefined>>
schema
(
import z
z
.
function string(params?: z.RawCreateParams & {
coerce?: true;
}): z.ZodString
export string
string
().
ZodType<string, ZodStringDef, string>.optional(): z.ZodOptional<z.ZodString>
optional
()),
),
),
),
);
const
const result: Parse<Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
repo: string | undefined;
}, []>]>]>]>]>]>>
result
=
parse<Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
repo: string | undefined;
}, []>]>]>]>]>]>>(route: Route<...>, input: Input): Parse<...>
parse
(
const app: Route<"gh", "help" | "execute", readonly [Done<{}, readonly [Route<"pr", "help", readonly [Done<{}, readonly [Route<"list", "help" | "execute", readonly [Done<{
repo: string | undefined;
}, []>]>]>]>]>]>
app
, {
argv: string[]
argv
:
var process: NodeJS.Process
process
.
NodeJS.Process.argv: string[]

The process.argv property returns an array containing the command-line arguments passed when the Node.js process was launched. The first element will be

execPath

. See process.argv0 if access to the original value of argv[0] is needed. The second element will be the path to the JavaScript file being executed. The remaining elements will be any additional command-line arguments.

For example, assuming the following script for process-args.js:

import { argv } from 'node:process';
// print process.argv
argv.forEach((val, index) => {
console.log(`${index}: ${val}`);
});

Launching the Node.js process as:

Terminal window
node process-args.js one two=three four

Would generate the output:

0: /usr/local/bin/node
1: /Users/mjr/work/node/process-args.js
2: one
3: two=three
4: four

@since ― v0.1.27

argv
.
Array<string>.slice(start?: number, end?: number): string[]

Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array.

@param ― start The beginning index of the specified portion of the array. If start is undefined, then the slice begins at index 0.

@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'. If end is undefined, then the slice extends to the end of the array.

slice
(2),
envs?: readonly EnvSource[]
envs
: [{
EnvSource.name: string
name
: "process",
EnvSource.value: Readonly<Record<string, string | undefined>>
value
:
var process: NodeJS.Process
process
.
NodeJS.Process.env: NodeJS.ProcessEnv

The process.env property returns an object containing the user environment. See environ(7).

An example of this object looks like:

{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}

It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other Worker threads. In other words, the following example would not work:

Terminal window
node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo

While the following will:

import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);

Assigning a property on process.env will implicitly convert the value to a string. This behavior is deprecated. Future versions of Node.js may throw an error when the value is not a string, number, or boolean.

import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'

Use delete to delete a property from process.env.

import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined

On Windows operating systems, environment variables are case-insensitive.

import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1

Unless explicitly specified when creating a Worker instance, each Worker thread has its own copy of process.env, based on its parent thread's process.env, or whatever was specified as the env option to the Worker constructor. Changes to process.env will not be visible across Worker threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner unlike the main thread.

@since ― v0.1.27

env
}],
});

With env("GH_REPO"), GH_REPO=bombshell-dev/router gh pr list sets the repository, and PR_LIST_REPO is ignored.