23 पॉइंट द्वारा hongminhee 2025-08-23 | 1 टिप्पणियां | WhatsApp पर शेयर करें

नमस्ते! TypeScript में अक्सर CLI टूल बनाते-बनाते मुझे मौजूदा लाइब्रेरीज़ की सीमाएँ खटकती रहीं, इसलिए मैंने एक नया CLI parser बनाया। जिन लोगों की इसमें दिलचस्पी हो सकती है, उनके लिए यह परिचय लिख रहा हूँ.

CLI application विकसित करते समय एक बात हमेशा असुविधाजनक लगी। ज़्यादातर मौजूदा CLI parser libraries configuration object या imperative API से CLI structure को define करती हैं, लेकिन इस तरीके में type safety कमज़ोर हो जाती है और जटिल CLI संरचनाओं को व्यक्त करना भी मुश्किल होता है.

खासकर mutually exclusive option groups को व्यक्त करने के लिए अलग-अलग validation logic को इधर-उधर बिखेरना पड़ता था। “यह option और वह option एक साथ इस्तेमाल नहीं किए जा सकते”, “इस mode में सिर्फ़ ऐसे options ही allowed हैं” जैसी constraints को code में साफ़-सुथरे ढंग से व्यक्त करना आसान नहीं था। और TypeScript इस्तेमाल करने पर भी parsing result का type अक्सर manually define करना पड़ता था.

functional parser combinator नाम का एक approach

इसी वजह से मैंने Haskell के optparse-applicative से प्रेरित होकर functional parser combinator शैली में TypeScript CLI parser बनाया.

मौजूदा तरीका:

// मौजूदा libraries का typical तरीका  
const program = new Command()  
  .option('-p, --port <number>', 'port number')  
  .option('-h, --host <string>', 'hostname')  
  .action((options) => {  
    // options का type any है, या इसे manually define करना पड़ता है  
  });  

Optique का तरीका:

// छोटे parsers को combine करके बड़ा structure बनाना  
const serverConfig = object({  
  port: option("-p", "--port", integer({ min: 1, max: 65535 })),  
  host: option("-h", "--host", string()),  
  verbose: option("-v", "--verbose")  
});  
  
// TypeScript अपने-आप type infer करता है!  
// { port: number, host: string, verbose: boolean }  
const config = run(serverConfig);  

फ़र्क 1: mutually exclusive options को structure के रूप में व्यक्त करना

सबसे बड़ा फ़र्क यह है कि mutually exclusive option groups को स्वाभाविक रूप से व्यक्त किया जा सकता है। मौजूदा libraries में ऐसी constraints को अलग validation logic से संभालना पड़ता है, लेकिन Optique में or() combinator की मदद से constraint को सीधे structure में समाहित किया जा सकता है.

// server mode vs client mode - पूरी तरह अलग option sets  
const parser = or(  
  object({  
    mode: constant("server"),  
    port: option("-p", "--port", integer()),  
    workers: option("-w", "--workers", integer()),  
    ssl: option("--ssl")  
  }),  
  object({  
    mode: constant("client"),   
    connect: option("-c", "--connect", string()),  
    timeout: option("-t", "--timeout", integer()),  
    retries: option("--retries", integer())  
  })  
);  
  
// TypeScript अपने-आप discriminated union बनाता है  
// { mode: "server", port: number, workers: number, ssl: boolean } |   
// { mode: "client", connect: string, timeout: number, retries: number }  

मौजूदा libraries में इस तरह की validation manually करनी पड़ती:

// मौजूदा तरीके की झंझट  
if (options.mode === "server" && options.connect) {  
  throw new Error("--connect को server mode में इस्तेमाल नहीं किया जा सकता");  
}  
if (options.mode === "client" && options.workers) {  
  throw new Error("--workers को client mode में इस्तेमाल नहीं किया जा सकता");  
}  

फ़र्क 2: पूरी तरह automatic type inference

const gitLike = or(  
  command("add", object({  
    type: constant("add"),  
    files: multiple(argument(string())),  
    all: option("-A", "--all")  
  })),  
  command("commit", object({  
    type: constant("commit"),  
    message: option("-m", "--message", string()),  
    amend: option("--amend")  
  }))  
);  
  
// result अपने-आप discriminated union के रूप में infer होता है  
const result = run(gitLike);  
if (result.type === "add") {  
  // TypeScript अपने-आप type narrowing कर देता है  
  console.log(`Adding ${result.files.join(", ")}`);  
}  

फ़र्क 3: modularity और reusability

merge() combinator की मदद से option groups को reuse किया जा सकता है, इसलिए कई commands में common options को आसानी से share किया जा सकता है.

// reusable option group की definition  
const networkOptions = object({  
  host: option("--host", string()),  
  port: option("--port", integer())  
});  
  
const authOptions = object({  
  username: option("-u", "--user", string()),  
  password: optional(option("-p", "--password", string()))  
});  
  
// ज़रूरत के हिसाब से combine करना  
const devMode = merge(networkOptions, object({ debug: option("--debug") }));  
const prodMode = merge(networkOptions, authOptions, loggingOptions);  

फ़र्क 4: समृद्ध built-in validation

value parsers सिर्फ़ simple type conversion से आगे बढ़कर meaningful validation भी देते हैं.

const parser = object({  
  // file system में वास्तविक मौजूदगी की जाँच  
  inputFile: option("--input", path({ mustExist: true })),  
  
  // port number range validation  
  port: option("-p", "--port", integer({ min: 1, max: 65535 })),  
  
  // URL protocol restriction  
  api: option("--api", url({ allowedProtocols: ["https:"] })),  
  
  // choices restriction  
  logLevel: option("--log", choice(["debug", "info", "warn", "error"]))  
});  

runtime support

  • @optique/core: सभी JavaScript runtimes का समर्थन (browser, edge functions आदि)
  • @optique/run: Node.js, Bun, Deno के लिए batteries-included version

इंस्टॉलेशन:

deno add --jsr @optique/core @optique/run  
npm  add       @optique/core @optique/run  
pnpm add       @optique/core @optique/run  
yarn add       @optique/core @optique/run  
bun  add       @optique/core @optique/run  

अंत में

अगर मौजूदा CLI libraries “configuration के जरिए parser बनाने” के तरीके पर आधारित हैं, तो Optique “छोटे parsers को जोड़कर बड़ा parser बनाने” वाला एक functional approach है.

खासकर mutually exclusive option groups को व्यक्त करते समय यह फ़र्क साफ़ दिखाई देता है। जटिल CLI constraints को अलग validation logic के बिना parser structure में ही व्यक्त किया जा सकता है, इसलिए type safety और code simplicity दोनों एक साथ मिलती हैं.

बेशक यह अभी शुरुआती development stage में है, इसलिए API बदल सकता है। लेकिन अगर आप functional parser combinator की elegance को TypeScript CLI development में लाना चाहते हैं, तो इसे एक बार आज़माना अच्छा रहेगा.

1 टिप्पणियां

 
spilist2 2025-08-25

वाह, काफ़ी अच्छा है! शेयर करने के लिए धन्यवाद। मुझे भी CLI बनाते समय इसे आज़माना पड़ेगा।