Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ history.

## Unreleased

- **BREAKING CHANGES:** `fjs/cli` migrates from authored TypeScript
(`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting its
type-level API into a sibling `types.ts` — importers must use the
`.f.mjs` specifier for runtime values and the `types.ts` specifier
for types. `proof.f.ts` stays TypeScript for now
[#1491](https://github.com/functionalscript/functionalscript/pull/1491)
- **BREAKING CHANGES:** `fjs/website` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`) — importers
must use the `.f.mjs` specifier. `proof.f.ts` stays TypeScript for
now
[#1491](https://github.com/functionalscript/functionalscript/pull/1491)
- **BREAKING CHANGES:** `fjs/dev/update` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`) — importers
must use the `.f.mjs` specifier. `proof.f.ts` stays TypeScript for
now
[#1491](https://github.com/functionalscript/functionalscript/pull/1491)
- **BREAKING CHANGES:** `fjs/text/sgr` migrates from authored
TypeScript (`.f.ts`) to JSDoc-typed JavaScript (`.f.mjs`), splitting
its type-level API into a sibling `types.ts` — importers must use
Expand Down
2 changes: 1 addition & 1 deletion fjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,5 @@ fjs run ./my-tool.f.ts foo bar # options.args === ['foo', 'bar']
```
fjs/module.ts — Node.js entry point (runs main via the node runner)
fjs/module.f.ts — FunctionalScript command dispatcher (Commands list + dispatch)
fjs/cli/module.f.ts — generic Command/Commands/dispatch primitives
fjs/cli/module.f.mjs — generic dispatch primitive (Command/Commands types in fjs/cli/types.ts)
```
3 changes: 2 additions & 1 deletion fjs/cas/cli/module.f.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { cBase32ToVec, vecToCBase32 } from '../../basen/cbase32/module.f.mjs'
import { forEachStep, pure, step } from '../../effects/module.f.mjs'
import { errorExit, log, writeFromStream } from '../../effects/node/module.f.mjs'
import type { All, Read, Write, WriteFile } from '../../effects/node/types.ts'
import { dispatch, type Commands } from '../../cli/module.f.ts'
import { dispatch } from '../../cli/module.f.mjs'
import type { Commands } from '../../cli/types.ts'
import type { MemOp } from '../../effects/memory/types.ts'
import { casAddFile, fileCas, type FileCasOperation } from '../module.f.ts'

Expand Down
2 changes: 1 addition & 1 deletion fjs/cas/cli/proof.f.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computeSync, sha256 } from '../../crypto/sha2/module.f.mjs'
import { maxLength, vec, vec8 } from '../../types/bit_vec/module.f.mjs'
import { defaultNodeProgramOptions, emptyState, virtual } from '../../effects/node/virtual/module.f.ts'
import type { NodeProgramOptions } from '../../effects/node/types.ts'
import { dispatch } from '../../cli/module.f.ts'
import { dispatch } from '../../cli/module.f.mjs'
import { vecToCBase32 } from '../../basen/cbase32/module.f.mjs'
import { assert, assertEq } from '../../asserts/module.f.mjs'

Expand Down
28 changes: 14 additions & 14 deletions fjs/cli/module.f.ts → fjs/cli/module.f.mjs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
/**
* CLI command dispatch table.
*
* See `./types.ts` for the type-level API.
*
* @module
*/

import { errorExit, log } from '../effects/node/module.f.mjs'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add a blank line after each module header

When declaration files are emitted, placing the first import immediately after the closing @module block causes TypeScript to drop the module documentation from the generated .d.mts. The same adjacency occurs in fjs/dev/update/module.f.mjs:6 and fjs/website/module.f.mjs:6, so all three newly migrated modules lose their module-level documentation in the published declarations; insert a blank line after each header.

Useful? React with 👍 / 👎.

import type { NodeOp, NodeProgramOptions, Write } from '../effects/node/types.ts'
/** @import { NodeOp, NodeProgramOptions, Write } from '../effects/node/types.ts' */
import { pure, step } from '../effects/module.f.mjs'
import type { Effect } from '../effects/types.ts'
/** @import { Effect } from '../effects/types.ts' */
import { at, fromEntries } from '../types/object/module.f.mjs'

type Handler<O extends NodeOp> = (options: NodeProgramOptions) => Effect<O, number>

export type Command<O extends NodeOp> = {
readonly names: readonly string[]
readonly description: string
readonly handler: Handler<O> | Commands<O>
}

export type Commands<O extends NodeOp> = readonly Command<O>[]
/** @import { Commands } from './types.ts' */

const helpMeta = { names: ['help', 'h', '?'], description: 'Print this help message' }

export const dispatch = <O extends NodeOp>(commands: Commands<O>) => (options: NodeProgramOptions): Effect<O | Write, number> => {
/** @type {<O extends NodeOp>(commands: Commands<O>) => (options: NodeProgramOptions) => Effect<O | Write, number>} */
export const dispatch = commands => options => {
const [cmd, ...rest] = options.args
const rows = [...commands, helpMeta]
const nameCol = rows.map(({names}) => names.join(', '))
Expand All @@ -25,7 +25,7 @@ export const dispatch = <O extends NodeOp>(commands: Commands<O>) => (options: N
'Available commands:',
...rows.map(({description}, i) => ` ${nameCol[i].padEnd(width)} ${description}`)
].join('\n')
const map = fromEntries(commands.flatMap(c => c.names.map(n => [n, c] as const)))
const map = fromEntries(commands.flatMap(c => c.names.map(n => /** @type {const} */ ([n, c]))))
if (cmd === undefined) {
return errorExit(`Error: command is required.\n${helpText}`)
}
Expand Down
3 changes: 2 additions & 1 deletion fjs/cli/proof.f.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { pure } from '../effects/module.f.mjs'
import type { NodeOp, NodeProgramOptions } from '../effects/node/types.ts'
import { defaultNodeProgramOptions, emptyState, virtual } from '../effects/node/virtual/module.f.ts'
import { dispatch, type Commands } from './module.f.ts'
import { dispatch } from './module.f.mjs'
import type { Commands } from './types.ts'
import { assert, assertEq } from '../asserts/module.f.mjs'

const makeOptions = (args: readonly string[]): NodeProgramOptions =>
Expand Down
10 changes: 5 additions & 5 deletions fjs/cli/todo/dispatch-help-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### Problem

`dispatch` (`fjs/cli/module.f.ts:17-46`) interleaves three distinct concerns —
`dispatch` (`fjs/cli/module.f.mjs:18-50`) interleaves three distinct concerns —
building the name→command lookup map, rendering the aligned help table, and
routing — and computes the first two eagerly at the top of every call:

Expand All @@ -22,9 +22,9 @@ const map = fromEntries(commands.flatMap(c => c.names.map(n => [n, c] as const))
```

`helpText` — the column-measuring and padding work — is consumed only by the
three error/help branches (`:28`, `:38`, `:42`); on the common success path
three error/help branches (`:29`, `:39-41`, `:45`); on the common success path
it is dead work. And because `dispatch` recurses for nested command groups
(`:35`, `:45`), the table and the map are rebuilt at every level of the
(`:36`, `:48`), the table and the map are rebuilt at every level of the
descent even when neither is used.

Per AGENTS.md, separation of concerns is "always appropriate" even with a
Expand All @@ -34,7 +34,7 @@ its own named, independently testable function.

### Proposal

Extract a module-scope pure helper in `fjs/cli/module.f.ts` (the layer that
Extract a module-scope pure helper in `fjs/cli/module.f.mjs` (the layer that
owns `Command`/`Commands`):

```ts
Expand Down Expand Up @@ -64,6 +64,6 @@ help rendering is the clear win.

### Related

- `fjs/cli/module.f.ts:17-46` — current `dispatch`.
- `fjs/cli/module.f.mjs:18-50` — current `dispatch`.
- [positional-arity-check](./positional-arity-check.md) — separate concern
(argument validation), independent of this change.
2 changes: 1 addition & 1 deletion fjs/cli/todo/options-edsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

### Problem

`Command` (`fjs/cli/module.f.ts`) hands every handler a raw
`Command` (`fjs/cli/types.ts`) hands every handler a raw
`args: readonly string[]` and has no concept of a named option. Any command
that wants a flag has to hand-parse it:

Expand Down
4 changes: 2 additions & 2 deletions fjs/cli/todo/positional-arity-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ handler: ({ home, args: [hashCBase32, path, ...rest] }) => {

### Proposal

A combinator in `fjs/cli/module.f.ts`, the layer that already owns
`Command`/`dispatch`:
A combinator in `fjs/cli/module.f.mjs`, the layer that already owns
`dispatch` (`Command` is defined in `fjs/cli/types.ts`):

```ts
export const exact = <O extends NodeOp>(name: string, n: number) =>
Expand Down
18 changes: 18 additions & 0 deletions fjs/cli/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Types for the CLI command dispatch table.
*
* @module
*/

import type { NodeOp, NodeProgramOptions } from '../effects/node/types.ts'
import type { Effect } from '../effects/types.ts'

type Handler<O extends NodeOp> = (options: NodeProgramOptions) => Effect<O, number>

export type Command<O extends NodeOp> = {
readonly names: readonly string[]
readonly description: string
readonly handler: Handler<O> | Commands<O>
}

export type Commands<O extends NodeOp> = readonly Command<O>[]
27 changes: 18 additions & 9 deletions fjs/dev/update/module.f.ts → fjs/dev/update/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@
*
* @module
*/

import { history, historyStep, mapStep, step } from '../../effects/module.f.mjs'
import type { Effect } from '../../effects/types.ts'
/** @import { Effect } from '../../effects/types.ts' */
import { mkdir, readUtf8File, writeUtf8File } from '../../effects/node/module.f.mjs'
import type { Mkdir, NodeProgram, ReadFile, WriteFile } from '../../effects/node/types.ts'
/** @import { Mkdir, NodeProgram, ReadFile, WriteFile } from '../../effects/node/types.ts' */
import { unwrap } from '../../types/result/module.f.mjs'

const source = '.copilot/mcp.json' as const
const targetDirectory = '.vscode' as const
const target = '.vscode/mcp.json' as const
const source = /** @type {const} */ ('.copilot/mcp.json')
const targetDirectory = /** @type {const} */ ('.vscode')
const target = /** @type {const} */ ('.vscode/mcp.json')

/** Regenerates VS Code's local MCP configuration from the canonical Copilot configuration. */
export const syncMcp = (): Effect<Mkdir | ReadFile | WriteFile, void> => {
/**
* Regenerates VS Code's local MCP configuration from the canonical Copilot configuration.
*
* @type {() => Effect<Mkdir | ReadFile | WriteFile, void>}
*/
export const syncMcp = () => {
const sourceText = history(mapStep(readUtf8File(source), unwrap))
const targetDirectoryReady = historyStep(
sourceText,
Expand All @@ -23,5 +28,9 @@ export const syncMcp = (): Effect<Mkdir | ReadFile | WriteFile, void> => {
return mapStep(targetWritten, unwrap)
}

/** Runs all local development configuration generators. */
export const main: NodeProgram = () => mapStep(syncMcp(), () => 0)
/**
* Runs all local development configuration generators.
*
* @type {NodeProgram}
*/
export const main = () => mapStep(syncMcp(), () => 0)
2 changes: 1 addition & 1 deletion fjs/dev/update/proof.f.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { assert, assertEq } from '../../asserts/module.f.mjs'
import { utf8 } from '../../text/module.f.mjs'
import { readUtf8File } from '../../effects/node/module.f.mjs'
import { defaultNodeProgramOptions, emptyState, virtual } from '../../effects/node/virtual/module.f.ts'
import { main, syncMcp } from './module.f.ts'
import { main, syncMcp } from './module.f.mjs'
import { step } from '../../effects/module.f.mjs'

const mcp = '{"servers":{}}' as const
Expand Down
6 changes: 3 additions & 3 deletions fjs/effects/todo/map-step-combinator.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ Also `fjs/dev/module.f.ts`, `fjs/cas/evo/module.f.ts`,
yield an exit code" shape of a `NodeProgram`:

```ts
// fjs/cli/module.f.ts:38-40
// fjs/cli/module.f.mjs:39-41
return step(
log(helpText),
() => pure(0))

// fjs/website/module.f.ts:17-19
const program: Effect<WriteFile, number> = step(
// fjs/website/module.f.mjs:19-21
const program = step(
writeFile('index.html', html),
() => pure(0))
```
Expand Down
3 changes: 2 additions & 1 deletion fjs/module.f.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { commands as casCommands } from './cas/cli/module.f.ts'
import { main as ciMain } from './ci/module.f.ts'
import { import_ } from './effects/node/module.f.mjs'
import type { NodeOp, NodeProgram } from './effects/node/types.ts'
import { dispatch, type Commands } from './cli/module.f.ts'
import { dispatch } from './cli/module.f.mjs'
import type { Commands } from './cli/types.ts'
import { casMcpServer } from './mcp/module.f.ts'
import { pure, step } from './effects/module.f.mjs'
import { unwrap } from './types/result/module.f.mjs'
Expand Down
5 changes: 3 additions & 2 deletions fjs/todo/66g-fjs-run-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ runtime. Every downstream caller (`fjs run`, bin scripts) already goes through
export type NodeMain = NodeProgram | Commands<NodeOp>
```

Widen `dispatch` in `fjs/cli/module.f.ts` to accept either a `Commands` array
Widen `dispatch` in `fjs/cli/module.f.mjs` to accept either a `Commands` array
or a `Program` function, and short-circuit to the function when it receives one:

```ts
Expand Down Expand Up @@ -61,4 +61,5 @@ wrapper in `fjs/cas/module.f.ts` simplifies to `export const main = commands`.
- `fjs/module.f.ts` — the `run` handler at line 39.
- `fjs/cas/module.f.ts` — the `main = dispatch(commands)` boilerplate this issue
eliminates.
- `fjs/cli/module.f.ts` — `dispatch` and `Commands` used by the new branch.
- `fjs/cli/module.f.mjs` — `dispatch` used by the new branch; `Commands`
is defined in `fjs/cli/types.ts`.
11 changes: 6 additions & 5 deletions fjs/website/module.f.ts → fjs/website/module.f.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
*
* @module
*/

import { htmlUtf8 } from '../media/html/module.f.mjs'
import { writeFile } from '../effects/node/module.f.mjs'
import type { WriteFile } from '../effects/node/types.ts'
/** @import { WriteFile } from '../effects/node/types.ts' */
import { pure, step } from '../effects/module.f.mjs'
import type { Effect } from '../effects/types.ts'
import type { Vec } from '../types/bit_vec/types.ts'
/** @import { Effect } from '../effects/types.ts' */

const html: Vec = htmlUtf8()(
const html = htmlUtf8()(
['a',
{ href: 'https://github.com/functionalscript/functionalscript' },
'GitHub Repository'
])

const program: Effect<WriteFile, number> = step(
/** @type {Effect<WriteFile, number>} */
const program = step(
writeFile('index.html', html),
() => pure(0))

Expand Down
2 changes: 1 addition & 1 deletion fjs/website/proof.f.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { main } from './module.f.ts'
import { main } from './module.f.mjs'
import { emptyState, virtual } from '../effects/node/virtual/module.f.ts'
import { assertEq, assertNotNullish } from '../asserts/module.f.mjs'

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"cov": "node --test --experimental-test-coverage --test-coverage-include=**/module.f.ts --test-coverage-include=**/module.f.mjs",
"start": "node ./fjs/module.ts",
"ci-update": "node ./fjs/module.ts ci",
"dev-update": "node ./fjs/module.ts r ./fjs/dev/update/module.f.ts",
"dev-update": "node ./fjs/module.ts r ./fjs/dev/update/module.f.mjs",
"update": "npm run ci-update && npm install && deno install && bun install",
"index-html": "node ./fjs/module.ts r ./fjs/website/module.f.ts",
"index-html": "node ./fjs/module.ts r ./fjs/website/module.f.mjs",
"website": "npm run prepack && npm run index-html"
},
"engines": {
Expand Down
Loading