OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / hyper-svc / examples / cli.js

163 lines · javascript ref:
#!/usr/bin/env node
/**
 * Mini "echo service" demo for hyper-svc.
 *
 * Demonstrates the daemon + client lifecycle in two modes:
 *
 *   node examples/cli.js daemon                Start the echo daemon (UDS+HTTP)
 *   node examples/cli.js call <method> [json]  Connect to the daemon and call a method
 *   node examples/cli.js demo                  Run an in-process round-trip end-to-end
 *
 * The daemon registers two methods:
 *   - "echo"    → returns whatever you send
 *   - "uppercase" → returns the upper-cased string
 *   - "info"    → returns service metadata (name, handlers, transports)
 *
 * Storage: HYPER_SVC_STORE env var (default: ./.hyper-svc-cli)
 * Socket:  HYPER_SVC_SOCK  env var (default: ./.hyper-svc-cli/svc.sock)
 *
 * NOTE: storage path is provided for parity with other modules' examples; the
 * echo demo doesn't actually use it. Real consumers (pear-git-v3) would pass
 * a corestore for the swarm transport to replicate.
 */

import { Service, Client } from '../index.js'
import { mkdir } from '#node:fs/promises'
import { join, dirname } from '#node:path'
import process from '#node:process'

const storagePath = process.env.HYPER_SVC_STORE || './.hyper-svc-cli'
const sockPath = process.env.HYPER_SVC_SOCK || join(storagePath, 'svc.sock')
const httpPort = parseInt(process.env.HYPER_SVC_HTTP || '4090')

const args = process.argv.slice(2)
const cmd = args[0]

function buildService () {
  const svc = new Service({
    name: 'echo',
    uds: sockPath,
    http: { port: httpPort }
  })
  svc.handle('echo', async (ctx, params) => {
    return { from: ctx.transport, params }
  })
  svc.handle('uppercase', async (ctx, params) => {
    return String(params.text || '').toUpperCase()
  })
  svc.handle('info', async (ctx) => {
    return {
      name: svc.name,
      transport: ctx.transport,
      identity: ctx.identity,
      handlers: svc.listHandlers(),
      mounts: svc.listMounts()
    }
  })
  return svc
}

async function main () {
  if (cmd === 'daemon') {
    await mkdir(dirname(sockPath), { recursive: true })
    const svc = buildService()
    await svc.listen()
    console.log('hyper-svc echo daemon')
    console.log('  uds:  %s', sockPath)
    console.log('  http: http://127.0.0.1:%d', svc.http.port)
    console.log('  handlers: %s', svc.listHandlers().join(', '))
    console.log('  (Ctrl-C to stop)')

    process.on('SIGINT', async () => {
      console.log('\nshutting down...')
      try { await svc.close() } catch {}
      process.exit(0)
    })
    return
  }

  if (cmd === 'call') {
    const method = args[1]
    if (!method) { console.error('Usage: cli.js call <method> [json-params]'); process.exit(1) }
    let params = {}
    if (args[2]) {
      try { params = JSON.parse(args[2]) } catch (e) {
        console.error('bad JSON params:', e.message)
        process.exit(1)
      }
    }
    const client = await Client.discover({
      uds: sockPath,
      http: `http://127.0.0.1:${httpPort}`
    })
    console.log('connected via', client.kind)
    try {
      const result = await client.call(method, params)
      console.log(JSON.stringify(result, null, 2))
    } finally {
      await client.close()
    }
    return
  }

  if (cmd === 'demo') {
    // In-process: spin up a daemon, connect to it, make calls, shut it down
    await mkdir(dirname(sockPath), { recursive: true })
    const svc = buildService()
    await svc.listen()
    console.log('--- hyper-svc demo ---\n')
    console.log('Started service "echo"')
    console.log('  uds:  %s', sockPath)
    console.log('  http: http://127.0.0.1:%d\n', svc.http.port)

    console.log('1. Discover (prefers UDS)')
    const client = await Client.discover({
      uds: sockPath,
      http: `http://127.0.0.1:${svc.http.port}`
    })
    console.log('   → connected via', client.kind, '\n')

    console.log('2. Call echo({ hello: "world" })')
    const a = await client.call('echo', { hello: 'world' })
    console.log('   →', JSON.stringify(a), '\n')

    console.log('3. Call uppercase({ text: "hyper-svc" })')
    const b = await client.call('uppercase', { text: 'hyper-svc' })
    console.log('   →', JSON.stringify(b), '\n')

    console.log('4. Call info()')
    const c = await client.call('info', {})
    console.log('   →', JSON.stringify(c, null, 2), '\n')

    console.log('5. Force HTTP transport')
    await client.close()
    const httpClient = await Client.discover({
      http: `http://127.0.0.1:${svc.http.port}`,
      prefer: 'http'
    })
    console.log('   → connected via', httpClient.kind)
    const d = await httpClient.call('echo', { from: 'http' })
    console.log('   →', JSON.stringify(d), '\n')

    await httpClient.close()
    await svc.close()
    console.log('shutdown complete.')
    return
  }

  console.log('hyper-svc echo service example')
  console.log('  daemon                       Start the daemon (UDS + HTTP)')
  console.log('  call <method> [json-params]  Call a method on a running daemon')
  console.log('  demo                         Run an in-process round-trip')
  console.log('')
  console.log('Storage: HYPER_SVC_STORE env var (default: ./.hyper-svc-cli)')
  console.log('Socket:  HYPER_SVC_SOCK  env var (default: <storage>/svc.sock)')
  console.log('HTTP:    HYPER_SVC_HTTP  env var (default: 4090)')
  console.log('')
  console.log('Note: this CLI does NOT share state with examples/web.js — they use')
  console.log('different storage paths because the corestore single-writer lock applies')
  console.log('once you start mounting real modules.')
}

main().catch((err) => { console.error(err.message); process.exit(1) })