runtime.ts 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import * as Effect from "effect/Effect"
  2. import * as Command from "effect/unstable/cli/Command"
  3. import { Spec } from "./spec"
  4. import { Daemon } from "../services/daemon"
  5. export type Input<Value> =
  6. Value extends Spec.Node<infer _Name, infer Command, infer _Commands>
  7. ? Input<Command>
  8. : Value extends Command.Command<infer _Name, infer Input, infer _Context, infer _Error, infer _Requirements>
  9. ? Input
  10. : never
  11. type RuntimeHandler = (input: unknown) => Effect.Effect<void, unknown, Daemon.Service>
  12. type Loader<Node extends Spec.Any> = () => Promise<{
  13. default: (input: Input<Node>) => Effect.Effect<void, any, Daemon.Service>
  14. }>
  15. type ProvidedCommand = Command.Command<string, unknown, unknown, unknown, Daemon.Service>
  16. export type Handlers<Node extends Spec.Any> = keyof Node["commands"] extends never
  17. ? Loader<Node>
  18. : { readonly $?: Loader<Node> } & { readonly [Key in keyof Node["commands"]]: Handlers<Node["commands"][Key]> }
  19. interface LazyHandler {
  20. readonly spec: Command.Command.Any
  21. readonly load: () => Promise<{ default: RuntimeHandler }>
  22. }
  23. type RuntimeHandlers =
  24. | (() => Promise<{ default: RuntimeHandler }>)
  25. | {
  26. readonly $?: () => Promise<{ default: RuntimeHandler }>
  27. readonly [key: string]: RuntimeHandlers | (() => Promise<{ default: RuntimeHandler }>) | undefined
  28. }
  29. export function handler<const Node extends Spec.Any, Error, Requirements>(
  30. _node: Node,
  31. run: (input: Input<Node>) => Effect.Effect<void, Error, Requirements>,
  32. ) {
  33. return run
  34. }
  35. export function handlers<const Root extends Spec.Any>(root: Root, handlers: Handlers<Root>) {
  36. const result: LazyHandler[] = []
  37. function add(node: Spec.Any, value: RuntimeHandlers) {
  38. if (typeof value === "function") {
  39. result.push({ spec: node.spec, load: value as () => Promise<{ default: RuntimeHandler }> })
  40. return
  41. }
  42. if (value.$) result.push({ spec: node.spec, load: value.$ as () => Promise<{ default: RuntimeHandler }> })
  43. for (const [name, child] of Object.entries(node.commands)) add(child, value[name] as RuntimeHandlers)
  44. }
  45. add(root, handlers as RuntimeHandlers)
  46. return result
  47. }
  48. export function run(commands: Spec.Any, handlers: ReadonlyArray<LazyHandler>, options: { readonly version: string }) {
  49. return Command.run(provide(commands, handlers), options) as Effect.Effect<void, unknown, Command.Environment>
  50. }
  51. function provide(node: Spec.Any, handlers: ReadonlyArray<LazyHandler>): ProvidedCommand {
  52. const spec: Command.Command.Any = Object.keys(node.commands).length
  53. ? (node.spec as Command.Command<string, unknown>).pipe(
  54. Command.withSubcommands(Object.values(node.commands).map((child) => provide(child, handlers))),
  55. )
  56. : node.spec
  57. const handler = handlers.find((handler) => handler.spec === node.spec)
  58. if (!handler) return spec as ProvidedCommand
  59. return spec.pipe(
  60. Command.withHandler((input) =>
  61. Effect.gen(function* () {
  62. yield* Effect.flatMap(Effect.promise(handler.load), (module) => module.default(input))
  63. }),
  64. ),
  65. ) as ProvidedCommand
  66. }
  67. export * as Runtime from "./runtime"