{"version":3,"file":"parquet-server.cjs","names":["Command","InvalidArgumentError","HttpHelpers","#process","#info","#lineReader"],"sources":["../src/parquet-server.ts"],"sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { createInterface, type Interface } from \"node:readline/promises\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command, InvalidArgumentError } from \"commander\";\nimport { HttpHelpers } from \"./export\";\nimport { parseJson, stringifyJson, type StringifiedJson } from \"@milaboratories/pl-model-common\";\nimport { type PFrameInternal } from \"@milaboratories/pl-model-middle-layer\";\n\nconst Options = {\n  NoHttps: \"--no-https\",\n  NoAuth: \"--no-auth\",\n  Port: \"--port\",\n} as const;\n\ntype Info = StringifiedJson<PFrameInternal.HttpServerInfo>;\n\n/**\n * Serves parquet files from the given root directory.\n * Manages the server lifecycle with graceful shutdown.\n */\nexport async function runParquetServer(): Promise<void> {\n  const program = new Command();\n\n  program\n    .name(\"parquet-server\")\n    .description(\"Serve parquet files from a directory over HTTP(S)\")\n    .argument(\"<root-directory>\", \"Root directory containing parquet files\")\n    .option(Options.NoHttps, \"Downgrade HTTPS to HTTP\")\n    .option(Options.NoAuth, \"Disable authorization\")\n    .option(\n      `${Options.Port} <number>`,\n      \"Port to listen on\",\n      (value) => {\n        const port = parseInt(value, 10);\n        if (isNaN(port) || port < 0 || port > 65535) {\n          throw new InvalidArgumentError(\"valid port numbers are 0-65535\");\n        }\n        return port;\n      },\n      0,\n    )\n    .action(\n      async (\n        rootDir: string,\n        options: {\n          https: boolean;\n          auth: boolean;\n          port: number;\n        },\n      ) => {\n        const abortController = new AbortController();\n        process\n          .on(\"SIGINT\", () => abortController.abort())\n          .on(\"SIGTERM\", () => abortController.abort());\n        abortController.signal.throwIfAborted();\n\n        const store = await HttpHelpers.createFsStore({\n          rootDir,\n          logger: (level, message) => {\n            const timestamp = new Date(Date.now()).toISOString();\n            console.log(`[${timestamp}] [${level}] ${message}`);\n          },\n        });\n        abortController.signal.throwIfAborted();\n        const handler = HttpHelpers.createRequestHandler({ store });\n\n        const server = await HttpHelpers.createHttpServer({\n          handler,\n          ...(!options.https && { noHttps: true }),\n          ...(!options.auth && { noAuth: true }),\n          port: options.port,\n        });\n        abortController.signal.onabort = () => server.stop();\n        abortController.signal.throwIfAborted();\n\n        const serverInfo: Info = stringifyJson(server.info);\n        console.log(serverInfo);\n\n        await server.stopped;\n      },\n    );\n\n  await program.parseAsync();\n}\n\n/**\n * Reference implementation of a parquet server runner for tests:\n * - Reads the server configuration from the spawned process stdout\n * - Forwards the server logs to the console\n * - Shuts down the server on dispose\n */\nexport class ParquetServer implements Disposable {\n  readonly #process: ChildProcess;\n  readonly #info: PFrameInternal.HttpServerInfo;\n  readonly #lineReader: Interface;\n\n  private constructor(\n    process: ChildProcess,\n    info: PFrameInternal.HttpServerInfo,\n    lineReader: Interface,\n  ) {\n    this.#process = process;\n    this.#info = info;\n    this.#lineReader = lineReader;\n  }\n\n  get info(): PFrameInternal.HttpServerInfo {\n    return this.#info;\n  }\n\n  static async serve(\n    rootDir: string,\n    options?: {\n      noHttps?: true;\n      noAuth?: true;\n      port?: number;\n    },\n  ): Promise<ParquetServer> {\n    const nodeDirname = dirname(fileURLToPath(import.meta.url));\n    const binPath = join(nodeDirname, \"..\", \"bin\", \"parquet-server.mjs\");\n\n    const serverProcess = spawn(\n      \"node\",\n      [\n        binPath,\n        rootDir,\n        ...(options?.noHttps ? [Options.NoHttps] : []),\n        ...(options?.noAuth ? [Options.NoAuth] : []),\n        ...(options?.port ? [Options.Port, options.port.toString()] : []),\n      ],\n      {\n        stdio: [\"ignore\", \"pipe\", \"inherit\"],\n      },\n    );\n\n    const lineReader = createInterface({ input: serverProcess.stdout! });\n\n    const exitPromise = new Promise<never>((_, reject) => {\n      serverProcess.once(\"exit\", (code, signal) => {\n        reject(\n          new Error(\n            `parquet-server exited before emitting server info (code=${code}, signal=${signal})`,\n          ),\n        );\n      });\n      serverProcess.once(\"error\", reject);\n    });\n\n    const firstLine = await Promise.race([lineReader[Symbol.asyncIterator]().next(), exitPromise]);\n    if (firstLine.value === undefined) {\n      throw new Error(\"parquet-server stdout closed without emitting server info\");\n    }\n    const serverInfo = parseJson(firstLine.value as Info);\n\n    lineReader.on(\"line\", console.log);\n\n    return new ParquetServer(serverProcess, serverInfo, lineReader);\n  }\n\n  [Symbol.dispose](): void {\n    this.#lineReader.close();\n    this.#process.kill();\n  }\n}\n"],"mappings":"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"}