|
| 1 | +--- |
| 2 | +outline: deep |
| 3 | +--- |
| 4 | + |
| 5 | +# Utilities |
| 6 | + |
| 7 | +Devframe ships a set of small, stable helpers under the `devframe/utils/*` subpaths. They cover the most common ancillary tasks a devtool needs — colorising terminal output, hashing arbitrary values, opening files in an editor — without forcing every author to pick (and install) their own library. |
| 8 | + |
| 9 | +Each helper is bundled inside devframe. Importing from `devframe/utils/*` is enough — there's no separate `npm install` for these dependencies. |
| 10 | + |
| 11 | +## Reference |
| 12 | + |
| 13 | +### `devframe/utils/colors` |
| 14 | + |
| 15 | +Terminal ANSI colors. Each entry is callable as a plain function or as a tagged template. |
| 16 | + |
| 17 | +```ts |
| 18 | +import { colors as c } from 'devframe/utils/colors' |
| 19 | + |
| 20 | +console.log(c.green('Server ready')) |
| 21 | +console.log(c.cyan`listening on port ${port}`) |
| 22 | +console.log(`${c.bold(c.red('fatal:'))} something went wrong`) |
| 23 | +``` |
| 24 | + |
| 25 | +Exports `colors` (`blue`, `cyan`, `gray`, `green`, `red`, `yellow`, `bold`, `dim`, `reset`, `underline`). |
| 26 | + |
| 27 | +### `devframe/utils/open` |
| 28 | + |
| 29 | +Open a URL, file, or other target in the OS default handler. |
| 30 | + |
| 31 | +```ts |
| 32 | +import { open } from 'devframe/utils/open' |
| 33 | + |
| 34 | +await open('https://localhost:7777') |
| 35 | +await open('./report.html', { wait: true }) |
| 36 | +``` |
| 37 | + |
| 38 | +### `devframe/utils/launch-editor` |
| 39 | + |
| 40 | +Open a file in the user's editor. Target accepts `file`, `file:line`, or `file:line:column`. Pass an optional editor command (e.g. `'code'`, `'subl'`) to override the auto-detected editor. |
| 41 | + |
| 42 | +```ts |
| 43 | +import { launchEditor } from 'devframe/utils/launch-editor' |
| 44 | + |
| 45 | +launchEditor('src/main.ts:42:7') |
| 46 | +launchEditor('src/main.ts:42:7', 'code') |
| 47 | +``` |
| 48 | + |
| 49 | +The auto-detection reads the `LAUNCH_EDITOR` environment variable and falls back to common defaults. Most devframes consume this through the prebuilt `openInEditor` recipe — see [Open helpers](./standalone-cli#open-helpers). |
| 50 | + |
| 51 | +### `devframe/utils/hash` |
| 52 | + |
| 53 | +Stable, deterministic hash of any structured-cloneable value. Useful for cache keys and dedup. |
| 54 | + |
| 55 | +```ts |
| 56 | +import { hash } from 'devframe/utils/hash' |
| 57 | + |
| 58 | +const key = hash({ functionName, args }) |
| 59 | +``` |
| 60 | + |
| 61 | +### `devframe/utils/structured-clone` |
| 62 | + |
| 63 | +JSON-safe serialization for the structured-clone algorithm — round-trips `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances. Used internally by the RPC wire format; exposed for tools that need the same encoding. |
| 64 | + |
| 65 | +```ts |
| 66 | +import { |
| 67 | + structuredCloneDeserialize, |
| 68 | + structuredCloneParse, |
| 69 | + structuredCloneSerialize, |
| 70 | + structuredCloneStringify, |
| 71 | +} from 'devframe/utils/structured-clone' |
| 72 | + |
| 73 | +const wire = structuredCloneStringify(new Map([['a', 1]])) |
| 74 | +const value = structuredCloneParse<Map<string, number>>(wire) |
| 75 | +``` |
| 76 | + |
| 77 | +### `devframe/utils/human-id` |
| 78 | + |
| 79 | +Generate a human-readable, lowercase, dash-separated random ID. |
| 80 | + |
| 81 | +```ts |
| 82 | +import { humanId } from 'devframe/utils/human-id' |
| 83 | + |
| 84 | +humanId() // 'bright-orange-tiger' |
| 85 | +``` |
| 86 | + |
| 87 | +### `devframe/utils/nanoid` |
| 88 | + |
| 89 | +Tiny URL-safe random ID generator (vendored, no runtime dependency). |
| 90 | + |
| 91 | +```ts |
| 92 | +import { nanoid } from 'devframe/utils/nanoid' |
| 93 | + |
| 94 | +nanoid() // 21 chars |
| 95 | +nanoid(10) // 10 chars |
| 96 | +``` |
| 97 | + |
| 98 | +### `devframe/utils/promise` |
| 99 | + |
| 100 | +Promise constructor with externally-controlled resolution. |
| 101 | + |
| 102 | +```ts |
| 103 | +import { promiseWithResolver } from 'devframe/utils/promise' |
| 104 | + |
| 105 | +const { promise, resolve, reject } = promiseWithResolver<number>() |
| 106 | +``` |
| 107 | + |
| 108 | +### `devframe/utils/events` |
| 109 | + |
| 110 | +Generic typed event emitter — `on(event, cb)` returns an unsubscribe function. Used as the eventing primitive across devframe's hosts. |
| 111 | + |
| 112 | +```ts |
| 113 | +import { createEventEmitter } from 'devframe/utils/events' |
| 114 | + |
| 115 | +const events = createEventEmitter<{ change: (n: number) => void }>() |
| 116 | +const off = events.on('change', n => console.log(n)) |
| 117 | +events.emit('change', 42) |
| 118 | +off() |
| 119 | +``` |
| 120 | + |
| 121 | +### `devframe/utils/shared-state` |
| 122 | + |
| 123 | +Underlying immutable state container used by `ctx.rpc.sharedState`. Most devframes interact with it indirectly — see [Shared State](./shared-state). Available directly when you need a state hub outside the RPC host. |
| 124 | + |
| 125 | +```ts |
| 126 | +import { createSharedState } from 'devframe/utils/shared-state' |
| 127 | + |
| 128 | +const state = createSharedState({ initialValue: { count: 0 } }) |
| 129 | +state.mutate((draft) => { |
| 130 | + draft.count += 1 |
| 131 | +}) |
| 132 | +state.value() // { count: 1 } |
| 133 | +``` |
| 134 | + |
| 135 | +### `devframe/utils/streaming-channel` |
| 136 | + |
| 137 | +Low-level sink/reader primitives for streamed RPC payloads. Most devframes consume these through `ctx.rpc.streaming` — see [Streaming](./streaming). |
| 138 | + |
| 139 | +### `devframe/utils/when` |
| 140 | + |
| 141 | +Statically-validated when-clause expressions for conditional UI visibility. The runtime + types ship from here; the consumer fields (`when` on docks and commands) are kit-side. See [When Clauses](./when-clauses). |
| 142 | + |
| 143 | +## Why a `utils/*` subpath |
| 144 | + |
| 145 | +The utilities are exposed as **stable wrappers over their underlying libraries** rather than bare re-exports. Two consequences: |
| 146 | + |
| 147 | +- **One install.** Consumers do not list these libraries in their own `package.json`. Bundling them inside devframe means version drift across devtools is impossible. |
| 148 | +- **Swappable internals.** The wrapper signatures are deliberately narrower than upstream. Devframe can change the implementation (`ansis` → `picocolors`, `ohash` → `crypto.subtle.digest`, …) without a breaking change to dependent devtools. |
| 149 | + |
| 150 | +When you need a feature outside the wrapper's minimal surface, prefer extending the wrapper inside devframe over bypassing it. |
0 commit comments