A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/streamich/json-type-cli below:

GitHub - streamich/json-type-cli: ...

JSON Type CLI is a powerful Node.js package for building command-line interface (CLI) utilities that implement the JSON Rx RPC protocol. It uses JSON as the primary data format and provides request/response communication pattern where each CLI interaction is a request that produces a response.

This package enables you to build type-safe CLI tools where:

The library implements the JSON Rx RPC protocol, where each CLI interaction follows the request/response pattern. You define methods with typed schemas using the JSON Type system, and the CLI handles parsing, validation, method calling, and response formatting automatically.

npm install json-type-cli
import { createCli } from 'json-type-cli';
import { ObjectValue } from '@jsonjoy.com/json-type/lib/value/ObjectValue';

// Create a router with your methods
const router = ObjectValue.create()
  .prop('greet', 
    t.Function(
      t.Object(t.prop('name', t.str)),  // Request schema
      t.Object(t.prop('message', t.str)) // Response schema
    ).options({
      title: 'Greet a person',
      description: 'Returns a greeting message for the given name'
    }),
    async ({ name }) => ({ 
      message: `Hello, ${name}!` 
    })
  );

// Create and run CLI
const cli = createCli({
  router,
  version: 'v1.0.0',
  cmd: 'my-cli'
});

cli.run();

Save this as my-cli.js and run:

node my-cli.js greet '{"name": "World"}'
# Output: {"message": "Hello, World!"}

node my-cli.js greet --str/name=Alice  
# Output: {"message": "Hello, Alice!"}
JSON Rx RPC Protocol Implementation

This CLI tool implements the JSON Rx RPC protocol with the following characteristics:

  1. Request Composition: CLI parses arguments and builds request payload
  2. Method Invocation: Calls the specified method with the request data
  3. Response Generation: Method returns response payload
  4. Response Encoding: Encodes response in the specified format (JSON, CBOR, etc.)

This follows the JSON Rx RPC Request Complete message pattern where the client (CLI) sends a complete request and receives a complete response.

Methods are defined using the JSON Type system and added to a router:

import { createCli } from 'json-type-cli';
import { ObjectValue } from '@jsonjoy.com/json-type/lib/value/ObjectValue';

const router = ObjectValue.create();
const { t } = router;

// Simple echo method
router.prop('echo', 
  t.Function(t.any, t.any).options({
    title: 'Echo input',
    description: 'Returns the input unchanged'
  }),
  async (input) => input
);

// Math operations
router.prop('math.add',
  t.Function(
    t.Object(
      t.prop('a', t.num),
      t.prop('b', t.num)
    ),
    t.Object(t.prop('result', t.num))
  ).options({
    title: 'Add two numbers',
    description: 'Adds two numbers and returns the result'
  }),
  async ({ a, b }) => ({ result: a + b })
);

// File processing
router.prop('file.process',
  t.Function(
    t.Object(
      t.prop('filename', t.str),
      t.propOpt('encoding', t.str)
    ),
    t.Object(
      t.prop('size', t.num),
      t.prop('content', t.str)
    )
  ).options({
    title: 'Process a file',
    description: 'Reads and processes a file'
  }),
  async ({ filename, encoding = 'utf8' }) => {
    const fs = require('fs');
    const content = fs.readFileSync(filename, encoding);
    return {
      size: content.length,
      content: content
    };
  }
);

For larger applications, organize routes into modules:

// routes/user.ts
export const defineUserRoutes = <Routes extends ObjectType<any>>(r: ObjectValue<Routes>) => {
  return r.extend((t, r) => [
    r('user.create',
      t.Function(
        t.Object(
          t.prop('name', t.str),
          t.prop('email', t.str)
        ),
        t.Object(
          t.prop('id', t.str),
          t.prop('name', t.str),
          t.prop('email', t.str)
        )
      ).options({
        title: 'Create a user',
        description: 'Creates a new user account'
      }),
      async ({ name, email }) => ({
        id: generateId(),
        name,
        email
      })
    ),

    r('user.get',
      t.Function(
        t.Object(t.prop('id', t.str)),
        t.Object(
          t.prop('id', t.str),
          t.prop('name', t.str),
          t.prop('email', t.str)
        )
      ).options({
        title: 'Get user by ID',
        description: 'Retrieves user information by ID'
      }),
      async ({ id }) => getUserById(id)
    )
  ]);
};

// main.ts
import { defineUserRoutes } from './routes/user';

const router = defineUserRoutes(ObjectValue.create());
const cli = createCli({ router });
Input Sources (Request Composition)

The CLI composes request data from three sources in this priority order:

1. Command Line JSON Parameter

Provide JSON directly as the second argument:

my-cli greet '{"name": "Alice", "age": 30}'
my-cli math.add '{"a": 5, "b": 3}'

Pipe JSON data to the CLI:

echo '{"name": "Bob"}' | my-cli greet
cat user.json | my-cli user.create
curl -s api.example.com/data.json | my-cli process.data

Use typed parameters to build the request object:

# String values
my-cli greet --str/name=Alice --str/title="Ms."

# Numeric values  
my-cli math.add --num/a=10 --num/b=20

# Boolean values
my-cli user.update --bool/active=true --bool/verified=false

# JSON values
my-cli config.set --json/settings='{"theme": "dark", "lang": "en"}'

# Nested paths using JSON Pointer (requires parent structure to exist)
my-cli user.update '{"profile": {}}' --str/profile/name="Alice" --num/profile/age=25

# To create nested structures, provide the base structure first
my-cli config.set '{"database": {}}' --str/database/host=localhost --num/database/port=5432

All sources can be combined. Command line options override STDIN data, which overrides the JSON parameter:

echo '{"name": "Default", "age": 0}' | my-cli greet --str/name=Alice --num/age=30
# Result: {"name": "Alice", "age": 30}
Output Formats (Response Encoding)

The CLI supports multiple output formats through codecs:

Codec Description Use Case json Standard JSON (default) Human-readable, web APIs json2 Pretty JSON (2 spaces) Development, debugging json4 Pretty JSON (4 spaces) Documentation, config files cbor CBOR binary format Compact binary, IoT msgpack MessagePack binary High performance, caching ubjson Universal Binary JSON Cross-platform binary text Formatted text output Human-readable reports tree Tree visualization Debugging, data exploration raw Raw data output Binary data, strings
# Default JSON output
my-cli user.get '{"id": "123"}'

# Pretty-printed JSON
my-cli user.get '{"id": "123"}' --format=json4

# Binary formats
my-cli data.export --format=cbor > data.cbor
my-cli data.export --format=msgpack > data.msgpack

# Text visualization  
my-cli config.get --format=tree
my-cli config.get --format=text

# Different input/output formats
cat data.cbor | my-cli process.data --format=cbor:json
echo '{"test": 123}' | my-cli echo --format=json:tree

Use the --stdout or --out parameter to extract specific parts of the response:

# Extract specific field
my-cli user.get '{"id": "123"}' --out=/user/name

# Extract nested data
my-cli api.fetch '{"url": "example.com"}' --out=/response/data/items

# Combine with format conversion
my-cli data.complex --out=/results/summary --format=json:text

All parameter paths use JSON Pointer syntax as defined in RFC 6901. JSON Pointers provide a standardized way to reference specific values within JSON documents using slash-separated paths.

When a path doesn't exist in the target object, the CLI automatically creates the necessary nested structure. For example, --str/database/host=localhost will create the object {"database": {"host": "localhost"}} even if neither database nor host existed previously.

String Values (--str or --s)
my-cli greet --str/name=Alice
my-cli config.set --s/database/host=localhost --s/database/name=mydb
Numeric Values (--num or --n)
my-cli math.add --num/a=10 --num/b=20
my-cli server.start --n/port=3000 --n/workers=4
Boolean Values (--bool or --b)
my-cli user.update --bool/active=true
my-cli feature.toggle --b/enabled=false
JSON Values (--json or --j)
my-cli config.merge --json/settings='{"theme": "dark"}'
my-cli api.call --j/payload='[1,2,3]'  
my-cli optional.field --und/optionalParam
File Input (--file or --f)

Read values from files with optional format and path extraction:

# Read JSON file
my-cli process.data --file/input=data.json

# Read with specific codec
my-cli import.data --f/data=data.cbor:cbor

# Extract path from file  
my-cli user.create --f/profile=user.json:json:/personalInfo

# Chain: file -> codec -> path
my-cli complex.import --f/config=settings.msgpack:msgpack:/database/credentials
Command Execution (--cmd or --c)

Execute commands and use their output as values:

# Use command output as string
my-cli log.write --cmd/message='(echo "Current time: $(date)"):text'

# Use command output as JSON
my-cli api.send --c/data='(curl -s api.example.com/data):json'

# Extract path from command output
my-cli process.status --c/info='(ps aux | grep node):json:/0/pid'
Format Control (--format or --fmt)

Control input and output encoding:

# Single format (for both input and output)  
my-cli echo --format=cbor

# Separate input and output formats
my-cli convert --format=cbor:json
my-cli transform --fmt=json:tree
STDIN Control (--stdin or --in)

Explicitly control STDIN data mapping:

# Use all STDIN data
echo '{"name": "Alice"}' | my-cli greet --stdin

# Map STDIN to specific path
echo '{"users": [...]}' | my-cli process.users --in/data=/users

# Map with path extraction
echo '{"response": {"users": [...]}}' | my-cli save.users --in/users=/response/users
Output Control (--stdout or --out)

Extract specific parts of the response:

# Extract single field
my-cli user.get '{"id": "123"}' --out=/name

# Extract nested object
my-cli api.fetch --out=/response/data

# Use with format conversion
my-cli complex.data --out=/results --format=json:tree
my-cli --help                    # General help
my-cli method.name --help        # Method-specific help  
Version (--version or --v)
my-cli complex.operation --plan  # Show what would be executed

Get information about available methods and their schemas:

# List all methods
my-cli .type

# Get method schema
my-cli .type --out=/methodName
my-cli .type --out=/user.create/req --format=tree
my-cli .type --out=/user.create/res --format=json4

The CLI supports binary data through various codecs:

# Process binary data
cat image.jpg | my-cli image.process --format=raw:json

# Convert between binary formats  
cat data.cbor | my-cli convert --format=cbor:msgpack > data.msgpack

# Encode JSON as binary
my-cli data.export '{"large": "dataset"}' --format=json:cbor > export.cbor

Errors follow JSON Rx RPC error format and are sent to STDERR:

my-cli invalid.method 2>errors.log
my-cli user.get '{"invalid": "data"}' 2>validation-errors.json

Error objects include:

For high-performance scenarios:

# Use binary formats for large data
my-cli large.dataset --format=msgpack

# Use raw format for simple string/binary output  
my-cli get.file.content --format=raw

# Stream processing with STDIN/STDOUT
cat large-file.json | my-cli process.stream --format=json:cbor | my-cli save.processed --format=cbor

Here's a complete example building a file processing CLI:

import { createCli } from '@jsonjoy.com/json-type-cli';
import { ObjectValue } from '@jsonjoy.com/json-type/lib/value/ObjectValue';
import * as fs from 'fs';
import * as path from 'path';

const router = ObjectValue.create();
const { t } = router;

// File operations
router
  .prop('file.read',
    t.Function(
      t.Object(
        t.prop('path', t.str),
        t.propOpt('encoding', t.str)
      ),
      t.Object(
        t.prop('content', t.str),
        t.prop('size', t.num)
      )
    ).options({
      title: 'Read file content',
      description: 'Reads a file and returns its content and size'
    }),
    async ({ path: filePath, encoding = 'utf8' }) => {
      const content = fs.readFileSync(filePath, encoding);
      return {
        content,
        size: content.length
      };
    }
  )
  .prop('file.write',
    t.Function(
      t.Object(
        t.prop('path', t.str),
        t.prop('content', t.str),
        t.propOpt('encoding', t.str)
      ),
      t.Object(
        t.prop('success', t.bool),
        t.prop('bytesWritten', t.num)
      )
    ).options({
      title: 'Write file content',
      description: 'Writes content to a file'
    }),
    async ({ path: filePath, content, encoding = 'utf8' }) => {
      fs.writeFileSync(filePath, content, encoding);
      return {
        success: true,
        bytesWritten: Buffer.from(content, encoding).length
      };
    }
  )
  .prop('file.list',
    t.Function(
      t.Object(
        t.prop('directory', t.str),
        t.propOpt('pattern', t.str)
      ),
      t.Object(
        t.prop('files', t.Array(t.str)),
        t.prop('count', t.num)
      )
    ).options({
      title: 'List directory files',
      description: 'Lists files in a directory, optionally filtered by pattern'
    }),
    async ({ directory, pattern }) => {
      let files = fs.readdirSync(directory);
      
      if (pattern) {
        const regex = new RegExp(pattern);
        files = files.filter(file => regex.test(file));
      }
      
      return {
        files,
        count: files.length
      };
    }
  );

const cli = createCli({
  router,
  version: 'v1.0.0',
  cmd: 'file-cli'
});

cli.run();

Usage examples:

# Read a file
file-cli file.read --str/path=package.json --format=json4

# Write content from STDIN
echo "Hello World" | file-cli file.write --str/path=output.txt --in/content

# List JavaScript files  
file-cli file.list --str/directory=src --str/pattern='\\.js$' --out=/files

# Chain operations: read -> transform -> write
file-cli file.read --str/path=input.json | 
  file-cli transform.data | 
  file-cli file.write --str/path=output.json --in/content --format=json:raw

This example demonstrates the full power of JSON Type CLI for building robust, type-safe command-line tools that implement the JSON Rx RPC protocol with rich input/output capabilities.


RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4