[READ-ONLY] Mirror of https://github.com/bombshell-dev/tty. Platform independent 2D layout engine for terminal applications based on Clay
5

Configure Feed

Select the types of activity you want to include in your feed.

✨ add interactive keyboard demo with full 104-key layout

Charles Lowell (Mar 26, 2026, 4:25 PM -0500) 8acdbff2 0642ea41

+730 -6
+604
demo/keyboard.ts
··· 1 + import { each, ensure, main, until } from "effection"; 2 + import { 3 + close, 4 + createTerm, 5 + fixed, 6 + grow, 7 + type InputEvent, 8 + type Op, 9 + open, 10 + rgba, 11 + text, 12 + } from "../mod.ts"; 13 + import { useInput } from "./use-input.ts"; 14 + import { useStdin } from "./use-stdin.ts"; 15 + 16 + let active = rgba(60, 120, 220); 17 + let inactive = rgba(50, 50, 60); 18 + let on = rgba(40, 180, 80); 19 + let label = rgba(220, 220, 220); 20 + let dim = rgba(100, 100, 120); 21 + let highlight = rgba(255, 220, 80); 22 + 23 + let KEY_W = 5; 24 + let GAP = 1; 25 + 26 + interface KeyDef { 27 + label: string; 28 + width?: number; 29 + match: (event: InputEvent) => boolean; 30 + } 31 + 32 + function is(char: string): (event: InputEvent) => boolean { 33 + return (e) => 34 + (e.type === "char" || e.type === "key") && 35 + e.key.toUpperCase() === char.toUpperCase(); 36 + } 37 + 38 + function mod(name: "ctrl" | "alt" | "shift"): (event: InputEvent) => boolean { 39 + return (e) => (e.type === "char" || e.type === "key") && e[name] === true; 40 + } 41 + 42 + function never(): boolean { 43 + return false; 44 + } 45 + 46 + function key(ops: Op[], k: KeyDef, ctx: AppContext): void { 47 + let bg = ctx.event && k.match(ctx.event) ? active : inactive; 48 + let w = k.width ?? KEY_W; 49 + ops.push( 50 + open("box", { 51 + layout: { 52 + width: fixed(w), 53 + height: grow(), 54 + padding: { left: 1, right: 1 }, 55 + alignX: 2, 56 + alignY: 2, 57 + }, 58 + bg, 59 + }), 60 + text(k.label, { color: label }), 61 + close(), 62 + ); 63 + } 64 + 65 + function row(ops: Op[], keys: KeyDef[], ctx: AppContext): void { 66 + ops.push( 67 + open("box", { layout: { direction: "ltr", gap: GAP, height: fixed(3) } }), 68 + ); 69 + for (let k of keys) { 70 + key(ops, k, ctx); 71 + } 72 + ops.push(close()); 73 + } 74 + 75 + function spacer(ops: Op[], width: number): void { 76 + ops.push( 77 + open("box", { layout: { width: fixed(width), height: grow() } }), 78 + close(), 79 + ); 80 + } 81 + 82 + function mainKeys(ops: Op[], ctx: AppContext): void { 83 + ops.push( 84 + open("box", { layout: { direction: "ttb", gap: GAP } }), 85 + ); 86 + 87 + row(ops, [ 88 + { label: "Esc", width: 11, match: is("Escape") }, 89 + { label: "F1", match: is("F1") }, 90 + { label: "F2", match: is("F2") }, 91 + { label: "F3", match: is("F3") }, 92 + { label: "F4", match: is("F4") }, 93 + { label: "F5", match: is("F5") }, 94 + { label: "F6", match: is("F6") }, 95 + { label: "F7", match: is("F7") }, 96 + { label: "F8", match: is("F8") }, 97 + { label: "F9", match: is("F9") }, 98 + { label: "F10", match: is("F10") }, 99 + { label: "F11", match: is("F11") }, 100 + { label: "F12", match: is("F12") }, 101 + ], ctx); 102 + 103 + row(ops, [ 104 + { label: "`", match: is("`") }, 105 + { label: "1", match: is("1") }, 106 + { label: "2", match: is("2") }, 107 + { label: "3", match: is("3") }, 108 + { label: "4", match: is("4") }, 109 + { label: "5", match: is("5") }, 110 + { label: "6", match: is("6") }, 111 + { label: "7", match: is("7") }, 112 + { label: "8", match: is("8") }, 113 + { label: "9", match: is("9") }, 114 + { label: "0", match: is("0") }, 115 + { label: "-", match: is("-") }, 116 + { label: "=", match: is("=") }, 117 + { label: "Bksp", width: 9, match: is("Backspace") }, 118 + ], ctx); 119 + 120 + row(ops, [ 121 + { label: "Tab", width: 7, match: is("Tab") }, 122 + { label: "Q", match: is("q") }, 123 + { label: "W", match: is("w") }, 124 + { label: "E", match: is("e") }, 125 + { label: "R", match: is("r") }, 126 + { label: "T", match: is("t") }, 127 + { label: "Y", match: is("y") }, 128 + { label: "U", match: is("u") }, 129 + { label: "I", match: is("i") }, 130 + { label: "O", match: is("o") }, 131 + { label: "P", match: is("p") }, 132 + { label: "[", match: is("[") }, 133 + { label: "]", match: is("]") }, 134 + { label: "\\", width: 7, match: is("\\") }, 135 + ], ctx); 136 + 137 + row(ops, [ 138 + { label: "Caps", width: 9, match: never }, 139 + { label: "A", match: is("a") }, 140 + { label: "S", match: is("s") }, 141 + { label: "D", match: is("d") }, 142 + { label: "F", match: is("f") }, 143 + { label: "G", match: is("g") }, 144 + { label: "H", match: is("h") }, 145 + { label: "J", match: is("j") }, 146 + { label: "K", match: is("k") }, 147 + { label: "L", match: is("l") }, 148 + { label: ";", match: is(";") }, 149 + { label: "'", match: is("'") }, 150 + { label: "Enter", width: 10, match: is("Enter") }, 151 + ], ctx); 152 + 153 + row(ops, [ 154 + { label: "Shift", width: 11, match: mod("shift") }, 155 + { label: "Z", match: is("z") }, 156 + { label: "X", match: is("x") }, 157 + { label: "C", match: is("c") }, 158 + { label: "V", match: is("v") }, 159 + { label: "B", match: is("b") }, 160 + { label: "N", match: is("n") }, 161 + { label: "M", match: is("m") }, 162 + { label: ",", match: is(",") }, 163 + { label: ".", match: is(".") }, 164 + { label: "/", match: is("/") }, 165 + { label: "Shift", width: 13, match: mod("shift") }, 166 + ], ctx); 167 + 168 + row(ops, [ 169 + { label: "Ctrl", width: 7, match: mod("ctrl") }, 170 + { label: "Win", width: 6, match: never }, 171 + { label: "Alt", width: 6, match: mod("alt") }, 172 + { label: "", width: 33, match: is(" ") }, 173 + { label: "Alt", width: 6, match: mod("alt") }, 174 + { label: "Win", width: 6, match: never }, 175 + { label: "Menu", width: 6, match: never }, 176 + { label: "Ctrl", width: 7, match: mod("ctrl") }, 177 + ], ctx); 178 + 179 + ops.push(close()); 180 + } 181 + 182 + function navKeys(ops: Op[], ctx: AppContext): void { 183 + ops.push( 184 + open("box", { layout: { direction: "ttb", gap: GAP } }), 185 + ); 186 + 187 + // top section: Ins/Home/PgUp, Del/End/PgDn 188 + row(ops, [ 189 + { label: "Ins", width: 6, match: is("Insert") }, 190 + { label: "Home", width: 6, match: is("Home") }, 191 + { label: "PgUp", width: 6, match: is("PageUp") }, 192 + ], ctx); 193 + 194 + row(ops, [ 195 + { label: "Del", width: 6, match: is("Delete") }, 196 + { label: "End", width: 6, match: is("End") }, 197 + { label: "PgDn", width: 6, match: is("PageDown") }, 198 + ], ctx); 199 + 200 + // gap before arrows 201 + ops.push( 202 + open("box", { layout: { height: fixed(3) } }), 203 + close(), 204 + ); 205 + 206 + // arrow up 207 + ops.push( 208 + open("box", { layout: { direction: "ltr", gap: GAP, height: fixed(3) } }), 209 + ); 210 + spacer(ops, 6); 211 + key(ops, { label: "\u2191", width: 6, match: is("ArrowUp") }, ctx); 212 + spacer(ops, 6); 213 + ops.push(close()); 214 + 215 + // arrow left/down/right 216 + row(ops, [ 217 + { label: "\u2190", width: 6, match: is("ArrowLeft") }, 218 + { label: "\u2193", width: 6, match: is("ArrowDown") }, 219 + { label: "\u2192", width: 6, match: is("ArrowRight") }, 220 + ], ctx); 221 + 222 + ops.push(close()); 223 + } 224 + 225 + function numpad(ops: Op[], ctx: AppContext): void { 226 + ops.push( 227 + open("box", { layout: { direction: "ttb", gap: GAP } }), 228 + ); 229 + 230 + row(ops, [ 231 + { label: "Num", width: 6, match: never }, 232 + { label: "/", width: 6, match: never }, 233 + { label: "*", width: 6, match: never }, 234 + { label: "-", width: 6, match: never }, 235 + ], ctx); 236 + 237 + // rows 2-3 grouped horizontally so + spans both 238 + ops.push( 239 + open("box", { layout: { direction: "ltr", gap: GAP } }), 240 + ); 241 + 242 + // left side: 7-8-9 and 4-5-6 stacked 243 + ops.push( 244 + open("box", { layout: { direction: "ttb", gap: GAP } }), 245 + ); 246 + row(ops, [ 247 + { label: "7", width: 6, match: never }, 248 + { label: "8", width: 6, match: never }, 249 + { label: "9", width: 6, match: never }, 250 + ], ctx); 251 + row(ops, [ 252 + { label: "4", width: 6, match: never }, 253 + { label: "5", width: 6, match: never }, 254 + { label: "6", width: 6, match: never }, 255 + ], ctx); 256 + ops.push(close()); 257 + 258 + // + spanning both rows 259 + ops.push( 260 + open("box", { 261 + layout: { 262 + width: fixed(6), 263 + height: grow(), 264 + padding: { left: 1, right: 1 }, 265 + alignX: 2, 266 + alignY: 2, 267 + }, 268 + bg: inactive, 269 + }), 270 + text("+", { color: label }), 271 + close(), 272 + ); 273 + 274 + ops.push(close()); 275 + 276 + // rows 4-5 grouped horizontally so Enter spans both 277 + ops.push( 278 + open("box", { layout: { direction: "ltr", gap: GAP } }), 279 + ); 280 + 281 + // left side: 1-2-3 and 0-. stacked 282 + ops.push( 283 + open("box", { layout: { direction: "ttb", gap: GAP } }), 284 + ); 285 + row(ops, [ 286 + { label: "1", width: 6, match: never }, 287 + { label: "2", width: 6, match: never }, 288 + { label: "3", width: 6, match: never }, 289 + ], ctx); 290 + row(ops, [ 291 + { label: "0", width: 13, match: never }, 292 + { label: ".", width: 6, match: never }, 293 + ], ctx); 294 + ops.push(close()); 295 + 296 + // Enter spanning both rows 297 + ops.push( 298 + open("box", { 299 + layout: { 300 + width: fixed(6), 301 + height: grow(), 302 + padding: { left: 1, right: 1 }, 303 + alignX: 2, 304 + alignY: 2, 305 + }, 306 + bg: inactive, 307 + }), 308 + text("Ent", { color: label }), 309 + close(), 310 + ); 311 + 312 + ops.push(close()); 313 + 314 + ops.push(close()); 315 + } 316 + 317 + function toggle(ops: Op[], enabled: boolean, name: string): void { 318 + let indicator = enabled 319 + ? "\u25cf\u2500\u2500\u2500" 320 + : "\u2500\u2500\u2500\u25cb"; 321 + ops.push( 322 + open("box", { 323 + layout: { 324 + direction: "ltr", 325 + height: fixed(1), 326 + gap: 1, 327 + }, 328 + }), 329 + text(indicator, { color: enabled ? on : dim }), 330 + text(name, { color: enabled ? label : dim }), 331 + close(), 332 + ); 333 + } 334 + 335 + let flagNames: (keyof Omit<AppContext, "mode" | "event">)[] = [ 336 + "Disambiguate escape codes", 337 + "Report event types", 338 + "Report alternate keys", 339 + "Report all keys as escapes", 340 + "Report associated text", 341 + ]; 342 + 343 + function flagPanel(ops: Op[], ctx: AppContext): void { 344 + let color = ctx.mode === "config" ? active : rgba(0, 0, 0, 0); 345 + ops.push(open("box", { 346 + layout: { 347 + direction: "ttb", 348 + gap: 1, 349 + padding: { left: 1, right: 1, top: 1, bottom: 1 }, 350 + }, 351 + border: { color, left: 1, right: 1, top: 1, bottom: 1 }, 352 + })); 353 + 354 + ops.push( 355 + open("box", { layout: { height: fixed(1) } }), 356 + text("Keyboard Protocol", { color: highlight }), 357 + close(), 358 + ); 359 + 360 + for (let i = 0; i < flagNames.length; i++) { 361 + let name = flagNames[i]; 362 + ops.push( 363 + open("box", { layout: { direction: "ltr", height: fixed(1), gap: 1 } }), 364 + ); 365 + ops.push(text(`${i + 1}.`, { color: dim })); 366 + toggle(ops, ctx[name], name); 367 + ops.push(close()); 368 + } 369 + 370 + ops.push(close()); 371 + } 372 + 373 + function keyboard(ctx: AppContext): Op[] { 374 + let ops: Op[] = []; 375 + 376 + // root 377 + ops.push( 378 + open("box", { 379 + layout: { 380 + width: grow(), 381 + height: grow(), 382 + direction: "ttb", 383 + alignX: 2, 384 + alignY: 2, 385 + padding: { left: 2, top: 1 }, 386 + }, 387 + }), 388 + ); 389 + 390 + // keyboard + toggles wrapper 391 + ops.push( 392 + open("box", { layout: { direction: "ttb" } }), 393 + ); 394 + 395 + // mode badge 396 + let badgeBg = ctx.mode === "input" ? rgba(40, 120, 200) : rgba(200, 120, 40); 397 + let badgeLabel = ctx.mode === "input" ? "input" : "config"; 398 + let badgeHint = ctx.mode === "input" 399 + ? "Ctrl+X Ctrl+X to enter config" 400 + : "Set flags with keys [1-5], Escape to exit"; 401 + ops.push( 402 + open("box", { layout: { direction: "ltr", height: fixed(1), padding: { bottom: 1 } } }), 403 + open("box", { layout: { padding: { left: 1, right: 1 } }, bg: rgba(60, 60, 60) }), 404 + text("mode", { color: rgba(220, 220, 220) }), 405 + close(), 406 + open("box", { layout: { padding: { left: 1, right: 1 } }, bg: badgeBg }), 407 + text(badgeLabel, { color: rgba(255, 255, 255) }), 408 + close(), 409 + text(` ${badgeHint}`, { color: dim }), 410 + close(), 411 + ); 412 + 413 + // toggles right-aligned above keyboard 414 + ops.push( 415 + open("box", { 416 + layout: { 417 + width: grow(), 418 + direction: "ltr", 419 + alignX: 1, 420 + padding: { bottom: 1 }, 421 + }, 422 + }), 423 + ); 424 + flagPanel(ops, ctx); 425 + ops.push(close()); 426 + 427 + // three keyboard groups side by side, bottom-aligned 428 + let kbColor = ctx.mode === "input" ? active : rgba(0, 0, 0, 0); 429 + ops.push( 430 + open("box", { 431 + layout: { 432 + direction: "ltr", 433 + gap: 3, 434 + alignY: 1, 435 + padding: { left: 1, right: 1, top: 1, bottom: 1 }, 436 + }, 437 + border: { color: kbColor, left: 1, right: 1, top: 1, bottom: 1 }, 438 + }), 439 + ); 440 + 441 + mainKeys(ops, ctx); 442 + navKeys(ops, ctx); 443 + numpad(ops, ctx); 444 + 445 + ops.push(close()); 446 + 447 + ops.push(close()); // keyboard + toggles wrapper 448 + 449 + // raw event display 450 + ops.push( 451 + open("box", { layout: { height: fixed(1), padding: { top: 1 } } }), 452 + text(ctx.event ? JSON.stringify(ctx.event) : "Press any key...", { 453 + color: highlight, 454 + }), 455 + close(), 456 + ); 457 + 458 + ops.push(close()); 459 + 460 + return ops; 461 + } 462 + 463 + let encoder = new TextEncoder(); 464 + let esc = (s: string) => Deno.stdout.writeSync(encoder.encode(s)); 465 + 466 + function ttyFlags(ctx: AppContext): Uint8Array { 467 + let bits = 0; 468 + if (ctx["Disambiguate escape codes"]) bits |= 1; 469 + if (ctx["Report event types"]) bits |= 2; 470 + if (ctx["Report alternate keys"]) bits |= 4; 471 + if (ctx["Report all keys as escapes"]) bits |= 8; 472 + if (ctx["Report associated text"]) bits |= 16; 473 + return encoder.encode(`\x1b[=${bits};3u`); 474 + } 475 + 476 + await main(function* () { 477 + let { columns, rows } = Deno.stdout.isTerminal() 478 + ? Deno.consoleSize() 479 + : { columns: 80, rows: 24 }; 480 + 481 + Deno.stdin.setRaw(true); 482 + 483 + let stdin = yield* useStdin(); 484 + let input = useInput(stdin); 485 + 486 + let term = yield* until(createTerm({ width: columns, height: rows })); 487 + 488 + esc("\x1b[?1049h\x1b[?25l"); 489 + yield* ensure(() => { 490 + esc("\x1b[?25h\x1b[?1049l"); 491 + }); 492 + 493 + let modality = recognizer(); 494 + 495 + let context = modality.next().value; 496 + 497 + Deno.stdout.writeSync(term.render(keyboard(context))); 498 + 499 + for (let event of yield* each(input)) { 500 + if (event.type === "char" && event.ctrl && event.key === "c") { 501 + break; 502 + } 503 + 504 + context = modality.next(event).value; 505 + 506 + Deno.stdout.writeSync(ttyFlags(context)); 507 + 508 + Deno.stdout.writeSync(term.render(keyboard(context))); 509 + 510 + yield* each.next(); 511 + } 512 + }); 513 + 514 + function* recognizer(): Iterator<AppContext, never, InputEvent> { 515 + let current: AppContext = { 516 + mode: "input", 517 + "Disambiguate escape codes": false, 518 + "Report event types": false, 519 + "Report alternate keys": false, 520 + "Report all keys as escapes": false, 521 + "Report associated text": false, 522 + event: null, 523 + }; 524 + 525 + let event: InputEvent = yield current; 526 + 527 + let mode = inputmode({ ...current, event }); 528 + 529 + while (true) { 530 + mode = yield* mode; 531 + } 532 + } 533 + 534 + type Mode = Iterable<AppContext, Mode, InputEvent>; 535 + 536 + function* inputmode(context: AppContext): Mode { 537 + context = { ...context, mode: "input" }; 538 + let event = context.event ? context.event : yield context; 539 + while (true) { 540 + context = { ...context, event }; 541 + if (event.type === "char" && event.key === "x" && event.ctrl) { 542 + let next = yield context; 543 + context = { 544 + ...context, 545 + event: next, 546 + }; 547 + if (next.type === "char" && next.key === "x" && next.ctrl) { 548 + return configmode({ 549 + ...context, 550 + event: null, 551 + }); 552 + } 553 + } 554 + event = yield context; 555 + } 556 + } 557 + 558 + function* configmode(context: AppContext): Mode { 559 + context = { ...context, mode: "config" }; 560 + let event = yield context; 561 + while (true) { 562 + if (event.type === "key" && event.key === "Escape") { 563 + return inputmode({...context, event: null }); 564 + } 565 + if (event.type === "char") { 566 + switch (event.key) { 567 + case "1": { 568 + context = {...context, ["Disambiguate escape codes"]: !context["Disambiguate escape codes"]}; 569 + break; 570 + } 571 + case "2": { 572 + context = {...context, ["Report event types"]: !context["Report event types"]}; 573 + break; 574 + } 575 + case "3": { 576 + context = {...context, ["Report alternate keys"]: !context["Report alternate keys"]}; 577 + break; 578 + } 579 + case "4": { 580 + context = {...context, ["Report all keys as escapes"]: !context["Report all keys as escapes"]}; 581 + break; 582 + } 583 + case "5": { 584 + context = {...context, ["Report associated text"]: !context["Report associated text"]}; 585 + break; 586 + } 587 + } 588 + } 589 + event = yield context; 590 + } 591 + } 592 + 593 + 594 + 595 + type AppContext = { 596 + mode: "input" | "config"; 597 + event: InputEvent | null; 598 + ["Disambiguate escape codes"]: boolean; 599 + ["Report event types"]: boolean; 600 + ["Report alternate keys"]: boolean; 601 + ["Report all keys as escapes"]: boolean; 602 + ["Report associated text"]: boolean; 603 + }; 604 +
+65
demo/use-input.ts
··· 1 + import { 2 + call, 3 + createChannel, 4 + each, 5 + type Operation, 6 + race, 7 + resource, 8 + sleep, 9 + spawn, 10 + type Stream, 11 + suspend, 12 + until, 13 + } from "effection"; 14 + import { createInput, type InputEvent, type InputOptions } from "../mod.ts"; 15 + 16 + function nothing() { 17 + return suspend() as unknown as Operation< 18 + IteratorResult<Uint8Array, void> 19 + >; 20 + } 21 + 22 + export function useInput( 23 + stream: Stream<Uint8Array, void>, 24 + options?: InputOptions, 25 + ): Stream<InputEvent, void> { 26 + return resource(function* (provide) { 27 + let input = yield* until(createInput(options)); 28 + let subscription = yield* stream; 29 + 30 + let pending = nothing(); 31 + 32 + let events = createChannel<InputEvent, void>(); 33 + 34 + yield* spawn(function* () { 35 + let next = yield* subscription.next(); 36 + while (!next.done) { 37 + let result = input.scan(next.value); 38 + pending = result.pending ? rescan(result.pending.delay) : nothing(); 39 + for (let event of result.events) { 40 + yield* events.send(event); 41 + } 42 + next = yield* race([subscription.next(), pending]); 43 + } 44 + yield* events.close(); 45 + }); 46 + 47 + yield* race([provide(yield* events), drain(events)]); 48 + }); 49 + } 50 + 51 + function rescan(delay: number): ReturnType<typeof nothing> { 52 + return call(function* (): Operation<IteratorResult<Uint8Array, void>> { 53 + yield* sleep(delay); 54 + return { 55 + done: false, 56 + value: new Uint8Array(), 57 + }; 58 + }); 59 + } 60 + 61 + function* drain<T, TClose>(stream: Stream<T, TClose>): Operation<void> { 62 + for (let _ of yield* each(stream)) { 63 + yield* each.next(); 64 + } 65 + }
+35
demo/use-stdin.ts
··· 1 + import { 2 + createChannel, 3 + each, 4 + type Operation, 5 + race, 6 + resource, 7 + spawn, 8 + type Stream, 9 + until, 10 + } from "effection"; 11 + 12 + export function useStdin(): Operation<Stream<Uint8Array, void>> { 13 + return resource(function* (provide) { 14 + let channel = createChannel<Uint8Array, void>(); 15 + 16 + let iterator = Deno.stdin.readable[Symbol.asyncIterator](); 17 + 18 + yield* spawn(function* () { 19 + let next = yield* until(iterator.next()); 20 + while (!next.done) { 21 + yield* channel.send(next.value); 22 + next = yield* until(iterator.next()); 23 + } 24 + yield* channel.close(); 25 + }); 26 + 27 + yield* race([provide(channel), drain(channel)]); 28 + }); 29 + } 30 + 31 + function* drain<T, TClose>(stream: Stream<T, TClose>): Operation<void> { 32 + for (let _ of yield* each(stream)) { 33 + yield* each.next(); 34 + } 35 + }
+4 -2
deno.json
··· 6 6 "fmt": "deno fmt && clang-format -i src/*.c src/*.h", 7 7 "fmt:check": "deno fmt --check && clang-format --dry-run --Werror src/*.c src/*.h", 8 8 "build:npm": "deno run -A tasks/build-npm.ts", 9 - "build:jsr": "deno run -A tasks/build-jsr.ts" 9 + "build:jsr": "deno run -A tasks/build-jsr.ts", 10 + "demo": "deno run -A demo/keyboard.ts" 10 11 }, 11 12 "imports": { 12 13 "@std/testing": "jsr:@std/testing@1", 13 14 "@std/expect": "jsr:@std/expect@1", 14 15 "@sinclair/typebox": "npm:@sinclair/typebox@^0.34", 15 - "dnt": "jsr:@deno/dnt@0.42.3" 16 + "dnt": "jsr:@deno/dnt@0.42.3", 17 + "effection": "npm:effection@^4.0.2" 16 18 }, 17 19 "exports": { 18 20 ".": "./mod.ts",
+22 -4
deno.lock
··· 6 6 "jsr:@effection/effection@*": "4.0.2", 7 7 "jsr:@std/assert@^1.0.14": "1.0.18", 8 8 "jsr:@std/assert@^1.0.17": "1.0.18", 9 + "jsr:@std/async@^1.1.0": "1.1.1", 10 + "jsr:@std/data-structures@^1.0.10": "1.0.10", 9 11 "jsr:@std/expect@1": "1.0.17", 10 12 "jsr:@std/fmt@1": "1.0.8", 11 13 "jsr:@std/fs@1": "1.0.22", 14 + "jsr:@std/fs@^1.0.22": "1.0.22", 12 15 "jsr:@std/internal@^1.0.10": "1.0.12", 13 16 "jsr:@std/internal@^1.0.12": "1.0.12", 14 17 "jsr:@std/path@1": "1.1.4", ··· 18 21 "jsr:@ts-morph/common@0.27": "0.27.0", 19 22 "npm:@sinclair/typebox@*": "0.34.48", 20 23 "npm:@sinclair/typebox@0.34": "0.34.48", 24 + "npm:effection@^4.0.2": "4.0.2", 21 25 "npm:valrs@*": "0.1.0" 22 26 }, 23 27 "jsr": { ··· 29 33 "dependencies": [ 30 34 "jsr:@david/code-block-writer", 31 35 "jsr:@std/fmt", 32 - "jsr:@std/fs", 36 + "jsr:@std/fs@1", 33 37 "jsr:@std/path@1", 34 38 "jsr:@ts-morph/bootstrap" 35 39 ] ··· 43 47 "jsr:@std/internal@^1.0.12" 44 48 ] 45 49 }, 50 + "@std/async@1.1.1": { 51 + "integrity": "8a79beb3378cc229ce65ba2c746cfd03e4855ddd891d1eb6b9e32128e0d5339c" 52 + }, 53 + "@std/data-structures@1.0.10": { 54 + "integrity": "f574f86b0e07c69b9edc555fcc814b57d29258bad39fd5a34ba8a80ecf033cfe" 55 + }, 46 56 "@std/expect@1.0.17": { 47 57 "integrity": "316b47dd65c33e3151344eb3267bf42efba17d1415425f07ed96185d67fc04d9", 48 58 "dependencies": [ ··· 73 83 "integrity": "87bdc2700fa98249d48a17cd72413352d3d3680dcfbdb64947fd0982d6bbf681", 74 84 "dependencies": [ 75 85 "jsr:@std/assert@^1.0.17", 76 - "jsr:@std/internal@^1.0.12" 86 + "jsr:@std/async", 87 + "jsr:@std/data-structures", 88 + "jsr:@std/fs@^1.0.22", 89 + "jsr:@std/internal@^1.0.12", 90 + "jsr:@std/path@^1.1.4" 77 91 ] 78 92 }, 79 93 "@ts-morph/bootstrap@0.27.0": { ··· 85 99 "@ts-morph/common@0.27.0": { 86 100 "integrity": "c7b73592d78ce8479b356fd4f3d6ec3c460d77753a8680ff196effea7a939052", 87 101 "dependencies": [ 88 - "jsr:@std/fs", 102 + "jsr:@std/fs@1", 89 103 "jsr:@std/path@1" 90 104 ] 91 105 } ··· 94 108 "@sinclair/typebox@0.34.48": { 95 109 "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==" 96 110 }, 111 + "effection@4.0.2": { 112 + "integrity": "sha512-O8WMGP10nPuJDwbNGILcaCNWS+CvDYjcdsUSD79nWZ+WtUQ8h1MEV7JJwCSZCSeKx8+TdEaZ/8r6qPTR2o/o8w==" 113 + }, 97 114 "valrs@0.1.0": { 98 115 "integrity": "sha512-BqVkjx3qhsRLHerblLDoqEx0OEx7ms0DB6LPv40oWkMfFKUVKrqVuklaGdrPrHyubC5hSHYfEtUiQXrCkC6xHQ==" 99 116 } ··· 103 120 "jsr:@deno/dnt@0.42.3", 104 121 "jsr:@std/expect@1", 105 122 "jsr:@std/testing@1", 106 - "npm:@sinclair/typebox@0.34" 123 + "npm:@sinclair/typebox@0.34", 124 + "npm:effection@^4.0.2" 107 125 ] 108 126 } 109 127 }