@milaboratories/pframes-rs-serv 1.0.68 → 1.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/serve.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"serve.js","sources":["../src/serve.ts"],"sourcesContent":["import {\n createServer as createHttpServer,\n type RequestListener,\n type Server as HttpServer,\n type ServerOptions\n} from 'node:http';\nimport {\n createServer as createHttpsServer,\n type Server as HttpsServer\n} from 'node:https';\nimport type { AddressInfo } from 'node:net';\nimport { Deferred } from '@milaboratories/helpers';\nimport type { PFrameInternal } from '@milaboratories/pl-model-middle-layer';\nimport {\n base64Encode,\n Base64Encoded,\n ensureError,\n PFrameError\n} from '@milaboratories/pl-model-common';\nimport { generate, type GenerateResult } from 'selfsigned';\nimport { randomUUID } from 'node:crypto';\nimport { authorizeRequestHandler } from './handler';\n\n/** Generate a self-signed certificate for localhost */\nasync function generateCertificate(): Promise<GenerateResult> {\n const generateResult = new Deferred<GenerateResult>();\n generate(\n [{ name: 'commonName', value: 'localhost' }],\n {\n keySize: 2048,\n algorithm: 'sha256',\n extensions: [\n {\n name: 'subjectAltName',\n altNames: [\n { type: 2, value: 'localhost' }, // DNS\n { type: 7, ip: '127.0.0.1' }, // IPv4\n { type: 7, ip: '::1' } // IPv6\n ]\n }\n ]\n },\n (error, result) => {\n if (error) {\n generateResult.reject(error);\n } else {\n generateResult.resolve(result);\n }\n }\n );\n return await generateResult.promise;\n}\n\n/** Create an object store URL from the server address info. */\nfunction createObjectStoreUrl(\n info: AddressInfo,\n http?: true\n): PFrameInternal.ObjectStoreUrl {\n const protocol = http ? 'http' : 'https';\n switch (info.family) {\n case 'IPv4':\n return `${protocol}://${info.address}:${info.port}/` as PFrameInternal.ObjectStoreUrl;\n case 'IPv6':\n return `${protocol}://[${info.address}]:${info.port}/` as PFrameInternal.ObjectStoreUrl;\n default:\n throw new PFrameError(\n `PFrame helper HTTP(S) server bound to 'localhost' has unknown address family: ${info.family}`\n );\n }\n}\n\n/**\n * Serve HTTP requests using the provided handler.\n * Returns a promise that resolves when the server is stopped.\n */\nexport async function serve({\n handler,\n port = 0,\n http,\n noAuth\n}: PFrameInternal.HttpServerOptions): Promise<PFrameInternal.HttpServer> {\n const started = new Deferred<PFrameInternal.HttpServer>();\n try {\n let stopped: Deferred<void> | null = null;\n\n let authToken: PFrameInternal.HttpAuthorizationToken | undefined;\n let effectiveHandler: RequestListener = handler;\n if (!noAuth) {\n authToken = randomUUID() as PFrameInternal.HttpAuthorizationToken;\n effectiveHandler = authorizeRequestHandler(effectiveHandler, authToken);\n }\n\n // Create HTTP server\n let certificateBase64:\n | Base64Encoded<PFrameInternal.PemCertificate>\n | undefined;\n const defaultOptions: ServerOptions = {\n keepAlive: true\n };\n let server: HttpServer | HttpsServer;\n\n if (http) {\n server = createHttpServer(defaultOptions, effectiveHandler);\n } else {\n const { cert, private: key, public: ca } = await generateCertificate();\n certificateBase64 = base64Encode(cert as PFrameInternal.PemCertificate);\n server = createHttpsServer(\n { ...defaultOptions, cert, key, ca },\n effectiveHandler\n );\n }\n\n const abortController = new AbortController();\n server\n .on('listening', () => {\n // Cast is safe by specification <https://nodejs.org/api/net.html#serveraddress>\n const address = createObjectStoreUrl(\n server.address() as AddressInfo,\n http\n );\n stopped = new Deferred<void>();\n\n started.resolve({\n get address(): PFrameInternal.ObjectStoreUrl {\n return address;\n },\n get authToken(): PFrameInternal.HttpAuthorizationToken | undefined {\n return authToken;\n },\n get encodedCaCert():\n | Base64Encoded<PFrameInternal.PemCertificate>\n | undefined {\n return certificateBase64;\n },\n get stopped(): Promise<void> {\n return stopped!.promise;\n },\n stop(): Promise<void> {\n abortController.abort();\n return stopped!.promise;\n }\n });\n })\n .on('error', (err) => {\n started.reject(err);\n stopped?.reject(err);\n })\n .on('close', () => stopped?.resolve())\n .listen({\n host: 'localhost',\n port,\n signal: abortController.signal\n });\n } catch (error: unknown) {\n started.reject(ensureError(error));\n }\n\n return started.promise;\n}\n"],"names":["createHttpServer","createHttpsServer"],"mappings":";;;;;;;;AAuBA;AACA,eAAe,mBAAmB,GAAA;AAChC,IAAA,MAAM,cAAc,GAAG,IAAI,QAAQ,EAAkB;AACrD,IAAA,QAAQ,CACN,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAC5C;AACE,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,SAAS,EAAE,QAAQ;AACnB,QAAA,UAAU,EAAE;AACV,YAAA;AACE,gBAAA,IAAI,EAAE,gBAAgB;AACtB,gBAAA,QAAQ,EAAE;oBACR,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE;oBAC/B,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE;oBAC5B,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE;AACvB;AACF;AACF;AACF,KAAA,EACD,CAAC,KAAK,EAAE,MAAM,KAAI;QAChB,IAAI,KAAK,EAAE;AACT,YAAA,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC;QAC9B;aAAO;AACL,YAAA,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC;QAChC;AACF,IAAA,CAAC,CACF;AACD,IAAA,OAAO,MAAM,cAAc,CAAC,OAAO;AACrC;AAEA;AACA,SAAS,oBAAoB,CAC3B,IAAiB,EACjB,IAAW,EAAA;IAEX,MAAM,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO;AACxC,IAAA,QAAQ,IAAI,CAAC,MAAM;AACjB,QAAA,KAAK,MAAM;YACT,OAAO,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,IAAI,CAAC,OAAO,CAAA,CAAA,EAAI,IAAI,CAAC,IAAI,CAAA,CAAA,CAAoC;AACvF,QAAA,KAAK,MAAM;YACT,OAAO,CAAA,EAAG,QAAQ,CAAA,IAAA,EAAO,IAAI,CAAC,OAAO,CAAA,EAAA,EAAK,IAAI,CAAC,IAAI,CAAA,CAAA,CAAoC;AACzF,QAAA;YACE,MAAM,IAAI,WAAW,CACnB,CAAA,8EAAA,EAAiF,IAAI,CAAC,MAAM,CAAA,CAAE,CAC/F;;AAEP;AAEA;;;AAGG;AACI,eAAe,KAAK,CAAC,EAC1B,OAAO,EACP,IAAI,GAAG,CAAC,EACR,IAAI,EACJ,MAAM,EAC2B,EAAA;AACjC,IAAA,MAAM,OAAO,GAAG,IAAI,QAAQ,EAA6B;AACzD,IAAA,IAAI;QACF,IAAI,OAAO,GAA0B,IAAI;AAEzC,QAAA,IAAI,SAA4D;QAChE,IAAI,gBAAgB,GAAoB,OAAO;QAC/C,IAAI,CAAC,MAAM,EAAE;YACX,SAAS,GAAG,UAAU,EAA2C;AACjE,YAAA,gBAAgB,GAAG,uBAAuB,CAAC,gBAAgB,EAAE,SAAS,CAAC;QACzE;;AAGA,QAAA,IAAI,iBAES;AACb,QAAA,MAAM,cAAc,GAAkB;AACpC,YAAA,SAAS,EAAE;SACZ;AACD,QAAA,IAAI,MAAgC;QAEpC,IAAI,IAAI,EAAE;AACR,YAAA,MAAM,GAAGA,YAAgB,CAAC,cAAc,EAAE,gBAAgB,CAAC;QAC7D;aAAO;AACL,YAAA,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,mBAAmB,EAAE;AACtE,YAAA,iBAAiB,GAAG,YAAY,CAAC,IAAqC,CAAC;AACvE,YAAA,MAAM,GAAGC,cAAiB,CACxB,EAAE,GAAG,cAAc,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,EACpC,gBAAgB,CACjB;QACH;AAEA,QAAA,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE;QAC7C;AACG,aAAA,EAAE,CAAC,WAAW,EAAE,MAAK;;YAEpB,MAAM,OAAO,GAAG,oBAAoB,CAClC,MAAM,CAAC,OAAO,EAAiB,EAC/B,IAAI,CACL;AACD,YAAA,OAAO,GAAG,IAAI,QAAQ,EAAQ;YAE9B,OAAO,CAAC,OAAO,CAAC;AACd,gBAAA,IAAI,OAAO,GAAA;AACT,oBAAA,OAAO,OAAO;gBAChB,CAAC;AACD,gBAAA,IAAI,SAAS,GAAA;AACX,oBAAA,OAAO,SAAS;gBAClB,CAAC;AACD,gBAAA,IAAI,aAAa,GAAA;AAGf,oBAAA,OAAO,iBAAiB;gBAC1B,CAAC;AACD,gBAAA,IAAI,OAAO,GAAA;oBACT,OAAO,OAAQ,CAAC,OAAO;gBACzB,CAAC;gBACD,IAAI,GAAA;oBACF,eAAe,CAAC,KAAK,EAAE;oBACvB,OAAO,OAAQ,CAAC,OAAO;gBACzB;AACD,aAAA,CAAC;AACJ,QAAA,CAAC;AACA,aAAA,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,KAAI;AACnB,YAAA,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;AACnB,YAAA,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC;AACtB,QAAA,CAAC;aACA,EAAE,CAAC,OAAO,EAAE,MAAM,OAAO,EAAE,OAAO,EAAE;AACpC,aAAA,MAAM,CAAC;AACN,YAAA,IAAI,EAAE,WAAW;YACjB,IAAI;YACJ,MAAM,EAAE,eAAe,CAAC;AACzB,SAAA,CAAC;IACN;IAAE,OAAO,KAAc,EAAE;QACvB,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACpC;IAEA,OAAO,OAAO,CAAC,OAAO;AACxB;;;;"}
1
+ {"version":3,"file":"serve.js","sources":["../src/serve.ts"],"sourcesContent":["import {\n createServer as createHttpServer,\n type RequestListener,\n type Server as HttpServer,\n type ServerOptions\n} from 'node:http';\nimport {\n createServer as createHttpsServer,\n type Server as HttpsServer\n} from 'node:https';\nimport type { AddressInfo } from 'node:net';\nimport { Deferred } from '@milaboratories/helpers';\nimport type { PFrameInternal } from '@milaboratories/pl-model-middle-layer';\nimport {\n base64Encode,\n Base64Encoded,\n ensureError\n} from '@milaboratories/pl-model-common';\nimport { generate, type GenerateResult } from 'selfsigned';\nimport { randomUUID } from 'node:crypto';\nimport { authorizeRequestHandler } from './handler';\n\n/** Generate a self-signed certificate for localhost */\nasync function generateCertificate(): Promise<GenerateResult> {\n const generateResult = new Deferred<GenerateResult>();\n generate(\n [{ name: 'commonName', value: 'localhost' }],\n {\n keySize: 2048,\n algorithm: 'sha256',\n extensions: [\n {\n name: 'subjectAltName',\n altNames: [\n { type: 2, value: 'localhost' }, // DNS\n { type: 7, ip: '127.0.0.1' }, // IPv4\n { type: 7, ip: '::1' } // IPv6\n ]\n }\n ]\n },\n (error, result) => {\n if (error) {\n generateResult.reject(error);\n } else {\n generateResult.resolve(result);\n }\n }\n );\n return await generateResult.promise;\n}\n\n/** Create an object store URL from the server address info. */\nfunction createObjectStoreUrl(\n info: AddressInfo,\n noHttps?: true\n): PFrameInternal.ObjectStoreUrl {\n const protocol = noHttps ? 'http' : 'https';\n switch (info.family) {\n case 'IPv4':\n return `${protocol}://${info.address}:${info.port}/` as PFrameInternal.ObjectStoreUrl;\n case 'IPv6':\n return `${protocol}://[${info.address}]:${info.port}/` as PFrameInternal.ObjectStoreUrl;\n default:\n return `${protocol}://localhost:${info.port}/` as PFrameInternal.ObjectStoreUrl;\n }\n}\n\n/**\n * Serve HTTP requests using the provided handler.\n * Returns a promise that resolves when the server is stopped.\n */\nexport async function serve({\n handler,\n port = 0,\n noHttps,\n noAuth\n}: PFrameInternal.HttpServerOptions): Promise<PFrameInternal.HttpServer> {\n const started = new Deferred<PFrameInternal.HttpServer>();\n try {\n let stopped: Deferred<void> | null = null;\n\n let authToken: PFrameInternal.HttpAuthorizationToken | undefined;\n let effectiveHandler: RequestListener = handler;\n if (!noAuth) {\n authToken = randomUUID() as PFrameInternal.HttpAuthorizationToken;\n effectiveHandler = authorizeRequestHandler(effectiveHandler, authToken);\n }\n\n // Create HTTP server\n let encodedCaCert: Base64Encoded<PFrameInternal.PemCertificate> | undefined;\n const defaultOptions: ServerOptions = {\n keepAlive: true\n };\n let server: HttpServer | HttpsServer;\n\n if (noHttps) {\n server = createHttpServer(defaultOptions, effectiveHandler);\n } else {\n const { cert, private: key, public: ca } = await generateCertificate();\n encodedCaCert = base64Encode(cert as PFrameInternal.PemCertificate);\n server = createHttpsServer(\n { ...defaultOptions, cert, key, ca },\n effectiveHandler\n );\n }\n\n server\n .on('listening', () => {\n // Cast is safe by specification <https://nodejs.org/api/net.html#serveraddress>\n const url = createObjectStoreUrl(\n server.address() as AddressInfo,\n noHttps\n );\n stopped = new Deferred<void>();\n\n started.resolve({\n get info(): PFrameInternal.HttpServerInfo {\n return { url, authToken, encodedCaCert };\n },\n get stopped(): Promise<void> {\n return stopped!.promise;\n },\n stop(): Promise<void> {\n server.close();\n return stopped!.promise;\n }\n });\n })\n .on('error', (err) => {\n started.reject(err);\n stopped?.reject(err);\n })\n .on('close', () => stopped?.resolve())\n .listen({\n host: 'localhost',\n port\n });\n } catch (error: unknown) {\n started.reject(ensureError(error));\n }\n\n return started.promise;\n}\n"],"names":["createHttpServer","createHttpsServer"],"mappings":"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"}
@@ -6,6 +6,7 @@ const HeaderName = {
6
6
  Allow: 'allow',
7
7
  Authorization: 'authorization',
8
8
  CacheControl: 'cache-control',
9
+ Connection: 'connection',
9
10
  ContentLength: 'content-length',
10
11
  ContentRange: 'content-range',
11
12
  ContentType: 'content-type',
@@ -18,6 +19,7 @@ const HeaderValue = {
18
19
  AcceptRanges: 'bytes',
19
20
  Allow: 'GET, HEAD',
20
21
  CacheControl: 'public, immutable, max-age=31536000',
22
+ Connection: 'close',
21
23
  ContentType: 'application/octet-stream',
22
24
  WWWAuthenticate: 'Bearer realm="parquet-server"'
23
25
  };
@@ -1 +1 @@
1
- {"version":3,"file":"headers.cjs","sources":["../../src/utils/headers.ts"],"sourcesContent":["/** HTTP header names used in the parquet server handler */\nexport const HeaderName = {\n Accept: 'accept',\n AcceptRanges: 'accept-ranges',\n Allow: 'allow',\n Authorization: 'authorization',\n CacheControl: 'cache-control',\n ContentLength: 'content-length',\n ContentRange: 'content-range',\n ContentType: 'content-type',\n Date: 'date',\n ETag: 'etag',\n IfMatch: 'if-match',\n IfModifiedSince: 'if-modified-since',\n IfNoneMatch: 'if-none-match',\n IfUnmodifiedSince: 'if-unmodified-since',\n LastModified: 'last-modified',\n Range: 'range',\n WWWAuthenticate: 'www-authenticate'\n} as const;\n\n/** HTTP header values used in the parquet server handler */\nexport const HeaderValue = {\n AcceptRanges: 'bytes',\n Allow: 'GET, HEAD',\n CacheControl: 'public, immutable, max-age=31536000',\n ContentType: 'application/octet-stream',\n WWWAuthenticate: 'Bearer realm=\"parquet-server\"'\n} as const;\n"],"names":[],"mappings":";;AAAA;AACO,MAAM,UAAU,GAAG;AACxB,IACA,YAAY,EAAE,eAAe;AAC7B,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,aAAa,EAAE,eAAe;AAC9B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,aAAa,EAAE,gBAAgB;AAC/B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,WAAW,EAAE,cAAc;AAC3B,IACA,IAAI,EAAE,MAAM;AACZ,IAIA,YAAY,EAAE,eAAe;AAC7B,IACA,eAAe,EAAE;;AAGnB;AACO,MAAM,WAAW,GAAG;AACzB,IAAA,YAAY,EAAE,OAAO;AACrB,IAAA,KAAK,EAAE,WAAW;AAClB,IAAA,YAAY,EAAE,qCAAqC;AACnD,IAAA,WAAW,EAAE,0BAA0B;AACvC,IAAA,eAAe,EAAE;;;;;;"}
1
+ {"version":3,"file":"headers.cjs","sources":["../../src/utils/headers.ts"],"sourcesContent":["/** HTTP header names used in the parquet server handler */\nexport const HeaderName = {\n Accept: 'accept',\n AcceptRanges: 'accept-ranges',\n Allow: 'allow',\n Authorization: 'authorization',\n CacheControl: 'cache-control',\n Connection: 'connection',\n ContentLength: 'content-length',\n ContentRange: 'content-range',\n ContentType: 'content-type',\n Date: 'date',\n ETag: 'etag',\n IfMatch: 'if-match',\n IfModifiedSince: 'if-modified-since',\n IfNoneMatch: 'if-none-match',\n IfUnmodifiedSince: 'if-unmodified-since',\n LastModified: 'last-modified',\n Range: 'range',\n WWWAuthenticate: 'www-authenticate'\n} as const;\n\n/** HTTP header values used in the parquet server handler */\nexport const HeaderValue = {\n AcceptRanges: 'bytes',\n Allow: 'GET, HEAD',\n CacheControl: 'public, immutable, max-age=31536000',\n Connection: 'close',\n ContentType: 'application/octet-stream',\n WWWAuthenticate: 'Bearer realm=\"parquet-server\"'\n} as const;\n"],"names":[],"mappings":";;AAAA;AACO,MAAM,UAAU,GAAG;AACxB,IACA,YAAY,EAAE,eAAe;AAC7B,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,aAAa,EAAE,eAAe;AAC9B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,UAAU,EAAE,YAAY;AACxB,IAAA,aAAa,EAAE,gBAAgB;AAC/B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,WAAW,EAAE,cAAc;AAC3B,IACA,IAAI,EAAE,MAAM;AACZ,IAIA,YAAY,EAAE,eAAe;AAC7B,IACA,eAAe,EAAE;;AAGnB;AACO,MAAM,WAAW,GAAG;AACzB,IAAA,YAAY,EAAE,OAAO;AACrB,IAAA,KAAK,EAAE,WAAW;AAClB,IAAA,YAAY,EAAE,qCAAqC;AACnD,IAAA,UAAU,EAAE,OAAO;AACnB,IAAA,WAAW,EAAE,0BAA0B;AACvC,IAAA,eAAe,EAAE;;;;;;"}
@@ -5,6 +5,7 @@ export declare const HeaderName: {
5
5
  readonly Allow: "allow";
6
6
  readonly Authorization: "authorization";
7
7
  readonly CacheControl: "cache-control";
8
+ readonly Connection: "connection";
8
9
  readonly ContentLength: "content-length";
9
10
  readonly ContentRange: "content-range";
10
11
  readonly ContentType: "content-type";
@@ -23,6 +24,7 @@ export declare const HeaderValue: {
23
24
  readonly AcceptRanges: "bytes";
24
25
  readonly Allow: "GET, HEAD";
25
26
  readonly CacheControl: "public, immutable, max-age=31536000";
27
+ readonly Connection: "close";
26
28
  readonly ContentType: "application/octet-stream";
27
29
  readonly WWWAuthenticate: "Bearer realm=\"parquet-server\"";
28
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/utils/headers.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;CAkBb,CAAC;AAEX,4DAA4D;AAC5D,eAAO,MAAM,WAAW;;;;;;CAMd,CAAC"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/utils/headers.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;CAmBb,CAAC;AAEX,4DAA4D;AAC5D,eAAO,MAAM,WAAW;;;;;;;CAOd,CAAC"}
@@ -4,6 +4,7 @@ const HeaderName = {
4
4
  Allow: 'allow',
5
5
  Authorization: 'authorization',
6
6
  CacheControl: 'cache-control',
7
+ Connection: 'connection',
7
8
  ContentLength: 'content-length',
8
9
  ContentRange: 'content-range',
9
10
  ContentType: 'content-type',
@@ -16,6 +17,7 @@ const HeaderValue = {
16
17
  AcceptRanges: 'bytes',
17
18
  Allow: 'GET, HEAD',
18
19
  CacheControl: 'public, immutable, max-age=31536000',
20
+ Connection: 'close',
19
21
  ContentType: 'application/octet-stream',
20
22
  WWWAuthenticate: 'Bearer realm="parquet-server"'
21
23
  };
@@ -1 +1 @@
1
- {"version":3,"file":"headers.js","sources":["../../src/utils/headers.ts"],"sourcesContent":["/** HTTP header names used in the parquet server handler */\nexport const HeaderName = {\n Accept: 'accept',\n AcceptRanges: 'accept-ranges',\n Allow: 'allow',\n Authorization: 'authorization',\n CacheControl: 'cache-control',\n ContentLength: 'content-length',\n ContentRange: 'content-range',\n ContentType: 'content-type',\n Date: 'date',\n ETag: 'etag',\n IfMatch: 'if-match',\n IfModifiedSince: 'if-modified-since',\n IfNoneMatch: 'if-none-match',\n IfUnmodifiedSince: 'if-unmodified-since',\n LastModified: 'last-modified',\n Range: 'range',\n WWWAuthenticate: 'www-authenticate'\n} as const;\n\n/** HTTP header values used in the parquet server handler */\nexport const HeaderValue = {\n AcceptRanges: 'bytes',\n Allow: 'GET, HEAD',\n CacheControl: 'public, immutable, max-age=31536000',\n ContentType: 'application/octet-stream',\n WWWAuthenticate: 'Bearer realm=\"parquet-server\"'\n} as const;\n"],"names":[],"mappings":"AAAA;AACO,MAAM,UAAU,GAAG;AACxB,IACA,YAAY,EAAE,eAAe;AAC7B,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,aAAa,EAAE,eAAe;AAC9B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,aAAa,EAAE,gBAAgB;AAC/B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,WAAW,EAAE,cAAc;AAC3B,IACA,IAAI,EAAE,MAAM;AACZ,IAIA,YAAY,EAAE,eAAe;AAC7B,IACA,eAAe,EAAE;;AAGnB;AACO,MAAM,WAAW,GAAG;AACzB,IAAA,YAAY,EAAE,OAAO;AACrB,IAAA,KAAK,EAAE,WAAW;AAClB,IAAA,YAAY,EAAE,qCAAqC;AACnD,IAAA,WAAW,EAAE,0BAA0B;AACvC,IAAA,eAAe,EAAE;;;;;"}
1
+ {"version":3,"file":"headers.js","sources":["../../src/utils/headers.ts"],"sourcesContent":["/** HTTP header names used in the parquet server handler */\nexport const HeaderName = {\n Accept: 'accept',\n AcceptRanges: 'accept-ranges',\n Allow: 'allow',\n Authorization: 'authorization',\n CacheControl: 'cache-control',\n Connection: 'connection',\n ContentLength: 'content-length',\n ContentRange: 'content-range',\n ContentType: 'content-type',\n Date: 'date',\n ETag: 'etag',\n IfMatch: 'if-match',\n IfModifiedSince: 'if-modified-since',\n IfNoneMatch: 'if-none-match',\n IfUnmodifiedSince: 'if-unmodified-since',\n LastModified: 'last-modified',\n Range: 'range',\n WWWAuthenticate: 'www-authenticate'\n} as const;\n\n/** HTTP header values used in the parquet server handler */\nexport const HeaderValue = {\n AcceptRanges: 'bytes',\n Allow: 'GET, HEAD',\n CacheControl: 'public, immutable, max-age=31536000',\n Connection: 'close',\n ContentType: 'application/octet-stream',\n WWWAuthenticate: 'Bearer realm=\"parquet-server\"'\n} as const;\n"],"names":[],"mappings":"AAAA;AACO,MAAM,UAAU,GAAG;AACxB,IACA,YAAY,EAAE,eAAe;AAC7B,IAAA,KAAK,EAAE,OAAO;AACd,IAAA,aAAa,EAAE,eAAe;AAC9B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,UAAU,EAAE,YAAY;AACxB,IAAA,aAAa,EAAE,gBAAgB;AAC/B,IAAA,YAAY,EAAE,eAAe;AAC7B,IAAA,WAAW,EAAE,cAAc;AAC3B,IACA,IAAI,EAAE,MAAM;AACZ,IAIA,YAAY,EAAE,eAAe;AAC7B,IACA,eAAe,EAAE;;AAGnB;AACO,MAAM,WAAW,GAAG;AACzB,IAAA,YAAY,EAAE,OAAO;AACrB,IAAA,KAAK,EAAE,WAAW;AAClB,IAAA,YAAY,EAAE,qCAAqC;AACnD,IAAA,UAAU,EAAE,OAAO;AACnB,IAAA,WAAW,EAAE,0BAA0B;AACvC,IAAA,eAAe,EAAE;;;;;"}
@@ -9,6 +9,7 @@ const StatusCode = {
9
9
  Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>
10
10
  NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>
11
11
  MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>
12
+ RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>
12
13
  Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>
13
14
  PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>
14
15
  RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>
@@ -1 +1 @@
1
- {"version":3,"file":"status.cjs","sources":["../../src/utils/status.ts"],"sourcesContent":["/** HTTP status codes used in the parquet server handler */\nexport const StatusCode = {\n Ok: 200, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200>\n PartialContent: 206, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206>\n NotModified: 304, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>\n BadRequest: 400, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>\n Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>\n NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>\n MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>\n Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>\n PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>\n RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>\n InternalServerError: 500, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500>\n GatewayTimeout: 504 // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504>\n} as const;\n"],"names":[],"mappings":";;AAAA;AACO,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,GAAG;IACP,cAAc,EAAE,GAAG;IACnB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,GAAG;IACjB,QAAQ,EAAE,GAAG;IACb,gBAAgB,EAAE,GAAG;IACrB,IAAI,EAAE,GAAG;IACT,kBAAkB,EAAE,GAAG;IACvB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,cAAc,EAAE,GAAG;;;;;"}
1
+ {"version":3,"file":"status.cjs","sources":["../../src/utils/status.ts"],"sourcesContent":["/** HTTP status codes used in the parquet server handler */\nexport const StatusCode = {\n Ok: 200, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200>\n PartialContent: 206, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206>\n NotModified: 304, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>\n BadRequest: 400, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>\n Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>\n NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>\n MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>\n RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>\n Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>\n PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>\n RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>\n InternalServerError: 500, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500>\n GatewayTimeout: 504 // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504>\n} as const;\n"],"names":[],"mappings":";;AAAA;AACO,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,GAAG;IACP,cAAc,EAAE,GAAG;IACnB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,GAAG;IACjB,QAAQ,EAAE,GAAG;IACb,gBAAgB,EAAE,GAAG;IACrB,cAAc,EAAE,GAAG;IACnB,IAAI,EAAE,GAAG;IACT,kBAAkB,EAAE,GAAG;IACvB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,cAAc,EAAE,GAAG;;;;;"}
@@ -7,6 +7,7 @@ export declare const StatusCode: {
7
7
  readonly Unauthorized: 401;
8
8
  readonly NotFound: 404;
9
9
  readonly MethodNotAllowed: 405;
10
+ readonly RequestTimeout: 408;
10
11
  readonly Gone: 410;
11
12
  readonly PreconditionFailed: 412;
12
13
  readonly RangeNotSatisfiable: 416;
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU;;;;;;;;;;;;;CAab,CAAC"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;CAcb,CAAC"}
@@ -7,6 +7,7 @@ const StatusCode = {
7
7
  Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>
8
8
  NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>
9
9
  MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>
10
+ RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>
10
11
  Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>
11
12
  PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>
12
13
  RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sources":["../../src/utils/status.ts"],"sourcesContent":["/** HTTP status codes used in the parquet server handler */\nexport const StatusCode = {\n Ok: 200, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200>\n PartialContent: 206, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206>\n NotModified: 304, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>\n BadRequest: 400, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>\n Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>\n NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>\n MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>\n Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>\n PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>\n RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>\n InternalServerError: 500, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500>\n GatewayTimeout: 504 // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504>\n} as const;\n"],"names":[],"mappings":"AAAA;AACO,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,GAAG;IACP,cAAc,EAAE,GAAG;IACnB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,GAAG;IACjB,QAAQ,EAAE,GAAG;IACb,gBAAgB,EAAE,GAAG;IACrB,IAAI,EAAE,GAAG;IACT,kBAAkB,EAAE,GAAG;IACvB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,cAAc,EAAE,GAAG;;;;;"}
1
+ {"version":3,"file":"status.js","sources":["../../src/utils/status.ts"],"sourcesContent":["/** HTTP status codes used in the parquet server handler */\nexport const StatusCode = {\n Ok: 200, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200>\n PartialContent: 206, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206>\n NotModified: 304, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304>\n BadRequest: 400, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400>\n Unauthorized: 401, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401>\n NotFound: 404, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404>\n MethodNotAllowed: 405, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405>\n RequestTimeout: 408, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408>\n Gone: 410, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410>\n PreconditionFailed: 412, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412>\n RangeNotSatisfiable: 416, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416>\n InternalServerError: 500, // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500>\n GatewayTimeout: 504 // <https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504>\n} as const;\n"],"names":[],"mappings":"AAAA;AACO,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,GAAG;IACP,cAAc,EAAE,GAAG;IACnB,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,GAAG;IACf,YAAY,EAAE,GAAG;IACjB,QAAQ,EAAE,GAAG;IACb,gBAAgB,EAAE,GAAG;IACrB,cAAc,EAAE,GAAG;IACnB,IAAI,EAAE,GAAG;IACT,kBAAkB,EAAE,GAAG;IACvB,mBAAmB,EAAE,GAAG;IACxB,mBAAmB,EAAE,GAAG;IACxB,cAAc,EAAE,GAAG;;;;;"}
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "proxy",
9
9
  "server"
10
10
  ],
11
- "version": "1.0.68",
11
+ "version": "1.0.70",
12
12
  "type": "module",
13
13
  "types": "./dist/index.d.ts",
14
14
  "main": "./dist/index.js",
@@ -30,11 +30,12 @@
30
30
  "dependencies": {
31
31
  "@milaboratories/helpers": "1.6.22",
32
32
  "@milaboratories/pl-model-common": "1.19.14",
33
- "@milaboratories/pl-model-middle-layer": "1.8.17",
33
+ "@milaboratories/pl-model-middle-layer": "1.8.21",
34
34
  "commander": "^14.0.0",
35
35
  "selfsigned": "^3.0.1"
36
36
  },
37
37
  "devDependencies": {
38
+ "@datadog/pprof": "^5.9.0",
38
39
  "@milaboratories/ts-builder": "1.0.5",
39
40
  "@milaboratories/ts-configs": "1.0.6",
40
41
  "@types/autocannon": "^7.12.7",
@@ -43,7 +44,7 @@
43
44
  "autocannon": "^8.0.0",
44
45
  "tslib": "^2.8.1",
45
46
  "typescript": "^5.9.2",
46
- "undici": "^7.14.0",
47
+ "undici": "^7.15.0",
47
48
  "vite": "^7.1.3",
48
49
  "vitest": "^3.2.4"
49
50
  },
package/src/fs-store.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { Readable } from 'node:stream';
2
- import { createReadStream } from 'node:fs';
3
2
  import { stat, open, type FileHandle } from 'node:fs/promises';
4
3
  import { join, resolve } from 'node:path';
5
4
  import { PFrameInternal } from '@milaboratories/pl-model-middle-layer';
6
- import { ensureError } from '@milaboratories/pl-model-common';
5
+ import { ensureError, isAbortError } from '@milaboratories/pl-model-common';
7
6
 
8
7
  /** Object store for serving files from a local directory */
9
8
  export class FileSystemStore extends PFrameInternal.ObjectStore {
@@ -46,19 +45,24 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
46
45
  callback: (response: PFrameInternal.ObjectStoreResponse) => Promise<void>;
47
46
  }
48
47
  ): Promise<void> {
49
- let file: FileHandle;
50
- try {
51
- const path = join(this.rootDir, filename);
52
- file = await open(path, 'r');
53
- } catch (error: unknown) {
54
- this.logger(
55
- 'error',
56
- `File system store failed to open file ${filename}: ${ensureError(error)}`
57
- );
58
- return await params.callback({ type: 'NotFound' });
59
- }
48
+ let file: FileHandle | undefined;
49
+ const respond = async (response: PFrameInternal.ObjectStoreResponse) => {
50
+ await params.callback(response).finally(async () => await file?.close());
51
+ };
60
52
 
61
53
  try {
54
+ try {
55
+ const path = join(this.rootDir, filename);
56
+ file = await open(path, 'r');
57
+ } catch (error: unknown) {
58
+ this.logger(
59
+ 'error',
60
+ `File system store failed to open file ${filename}: ${ensureError(error)}`
61
+ );
62
+ return await respond({ type: 'NotFound' });
63
+ }
64
+ params.signal.throwIfAborted();
65
+
62
66
  let size: number;
63
67
  try {
64
68
  ({ size } = await file.stat());
@@ -67,26 +71,25 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
67
71
  'error',
68
72
  `File system store failed to get size of file ${filename}: ${ensureError(error)}`
69
73
  );
70
- return await params.callback({ type: 'InternalError' });
74
+ return await respond({ type: 'InternalError' });
71
75
  }
76
+ params.signal.throwIfAborted();
72
77
 
73
78
  const range = this.translate(size, params.range);
74
79
  if (!range) {
75
- return await params.callback({ type: 'RangeNotSatisfiable', size });
80
+ return await respond({ type: 'RangeNotSatisfiable', size });
76
81
  }
77
82
 
78
83
  if (params.method === 'HEAD') {
79
- return await params.callback({ type: 'Ok', size, range });
84
+ return await respond({ type: 'Ok', size, range });
80
85
  }
81
86
 
82
87
  let data: Readable;
83
88
  try {
84
- data = createReadStream('ignored', {
85
- fd: file.fd,
86
- autoClose: false,
89
+ data = file.createReadStream({
87
90
  start: range.start,
88
91
  end: range.end,
89
- signal: params.signal
92
+ autoClose: false
90
93
  });
91
94
  this.logger(
92
95
  'info',
@@ -97,14 +100,25 @@ export class FileSystemStore extends PFrameInternal.ObjectStore {
97
100
  'error',
98
101
  `File system store failed to create read stream for ${filename}[${range.start}..=${range.end}]: ${ensureError(error)}`
99
102
  );
100
- return await params.callback({ type: 'InternalError' });
103
+ return await respond({ type: 'InternalError' });
101
104
  }
102
105
 
103
- return await params
104
- .callback({ type: 'Ok', size, range, data })
105
- .catch(() => {});
106
- } finally {
107
- await file.close();
106
+ try {
107
+ return await respond({ type: 'Ok', size, range, data });
108
+ } catch (error: unknown) {
109
+ this.logger(
110
+ 'error',
111
+ `File system store received unexpected rejection from callback: ${ensureError(error)}`
112
+ );
113
+ }
114
+ } catch (error: unknown) {
115
+ if (!isAbortError(error)) {
116
+ this.logger(
117
+ 'error',
118
+ `File system store unhandled error: ${ensureError(error)}`
119
+ );
120
+ }
121
+ return await respond({ type: 'InternalError' });
108
122
  }
109
123
  }
110
124
  }
package/src/handler.ts CHANGED
@@ -18,13 +18,14 @@ import {
18
18
  HeaderName,
19
19
  HeaderValue
20
20
  } from './utils';
21
+ import { isAbortError } from '@milaboratories/pl-model-common';
21
22
 
22
23
  /** Main request handler for parquet files */
23
- async function handleRequest(
24
+ function handleRequest(
24
25
  request: IncomingMessage,
25
26
  response: ServerResponse,
26
27
  store: PFrameInternal.ObjectStore
27
- ): Promise<void> {
28
+ ): void {
28
29
  // RFC 9110 section 6.6.1: Date header should be present in all responses
29
30
  response.sendDate = true;
30
31
  // RFC 9110 section 8.6: Content-Length 0 as default for error responses
@@ -87,7 +88,11 @@ async function handleRequest(
87
88
  signal,
88
89
  // pipeline automatically destroys the streams if they were not gracefully closed
89
90
  callback: async (result) => {
90
- if (response.destroyed) return void response.destroy();
91
+ if (request.destroyed) {
92
+ // request has timed out, close the connection
93
+ response.setHeader(HeaderName.Connection, HeaderValue.Connection);
94
+ return void response.writeHead(StatusCode.RequestTimeout).end();
95
+ }
91
96
 
92
97
  switch (result.type) {
93
98
  case 'InternalError':
@@ -131,11 +136,11 @@ async function handleRequest(
131
136
  return void response.end();
132
137
  }
133
138
 
134
- return await pipeline(result.data!, response, { signal }).catch(() => {
135
- // Pipeline errors are expected when request is aborted or connection is lost
136
- // Response head was already written, so we can't change status code
137
- // Just mute the error - pipeline destroys the response stream
138
- });
139
+ try {
140
+ return await pipeline(result.data!, response, { signal });
141
+ } catch (error: unknown) {
142
+ if (!isAbortError(error)) throw error;
143
+ }
139
144
  }
140
145
  });
141
146
  }
@@ -153,7 +158,7 @@ export function createRequestHandler(
153
158
  options: PFrameInternal.RequestHandlerOptions
154
159
  ): RequestListener {
155
160
  const { store } = options;
156
- return (request, response) => void handleRequest(request, response, store);
161
+ return (request, response) => handleRequest(request, response, store);
157
162
  }
158
163
 
159
164
  /** Request authorization middleware */
@@ -2,23 +2,22 @@ import { type ChildProcess, spawn } from 'node:child_process';
2
2
  import { createInterface, type Interface } from 'node:readline/promises';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { Command } from 'commander';
6
- import { FileSystemStore } from './fs-store';
7
- import { createRequestHandler } from './handler';
8
- import { serve } from './serve';
5
+ import { Command, InvalidArgumentError } from 'commander';
6
+ import { HttpHelpers } from './export';
9
7
  import {
10
8
  parseJson,
11
- type StringifiedJson,
12
- stringifyJson
9
+ stringifyJson,
10
+ type StringifiedJson
13
11
  } from '@milaboratories/pl-model-common';
14
12
  import { type PFrameInternal } from '@milaboratories/pl-model-middle-layer';
15
13
 
16
14
  const Options = {
17
- Http: '--http',
18
- NoAuth: '--no-auth'
15
+ NoHttps: '--no-https',
16
+ NoAuth: '--no-auth',
17
+ Port: '--port'
19
18
  } as const;
20
19
 
21
- type Config = StringifiedJson<PFrameInternal.ParquetServerConfig>;
20
+ type Info = StringifiedJson<PFrameInternal.HttpServerInfo>;
22
21
 
23
22
  /**
24
23
  * Serves parquet files from the given root directory.
@@ -31,17 +30,36 @@ export async function runParquetServer(): Promise<void> {
31
30
  .name('parquet-server')
32
31
  .description('Serve parquet files from a directory over HTTP(S)')
33
32
  .argument('<root-directory>', 'Root directory containing parquet files')
34
- .option(Options.Http, 'Use HTTP instead of HTTPS', false)
35
- .option(Options.NoAuth, 'Disable authentication')
33
+ .option(Options.NoHttps, 'Downgrade HTTPS to HTTP')
34
+ .option(Options.NoAuth, 'Disable authorization')
35
+ .option(
36
+ `${Options.Port} <number>`,
37
+ 'Port to listen on',
38
+ (value) => {
39
+ const port = parseInt(value, 10);
40
+ if (isNaN(port) || port < 0 || port > 65535) {
41
+ throw new InvalidArgumentError('valid port numbers are 0-65535');
42
+ }
43
+ return port;
44
+ },
45
+ 0
46
+ )
36
47
  .action(
37
- async (rootDir: string, options: { http: boolean; auth: boolean }) => {
48
+ async (
49
+ rootDir: string,
50
+ options: {
51
+ https: boolean;
52
+ auth: boolean;
53
+ port: number;
54
+ }
55
+ ) => {
38
56
  const abortController = new AbortController();
39
57
  process
40
58
  .on('SIGINT', () => abortController.abort())
41
59
  .on('SIGTERM', () => abortController.abort());
42
60
  abortController.signal.throwIfAborted();
43
61
 
44
- const store = await FileSystemStore.init({
62
+ const store = await HttpHelpers.createFsStore({
45
63
  rootDir,
46
64
  logger: (level, message) => {
47
65
  const timestamp = new Date(Date.now()).toISOString();
@@ -49,22 +67,19 @@ export async function runParquetServer(): Promise<void> {
49
67
  }
50
68
  });
51
69
  abortController.signal.throwIfAborted();
52
- const handler = createRequestHandler({ store });
70
+ const handler = HttpHelpers.createRequestHandler({ store });
53
71
 
54
- const server = await serve({
72
+ const server = await HttpHelpers.createHttpServer({
55
73
  handler,
56
- ...(options.http && { http: true }),
57
- ...(!options.auth && { noAuth: true })
74
+ ...(!options.https && { noHttps: true }),
75
+ ...(!options.auth && { noAuth: true }),
76
+ port: options.port
58
77
  });
59
78
  abortController.signal.onabort = () => server.stop();
60
79
  abortController.signal.throwIfAborted();
61
80
 
62
- const serverConfig: Config = stringifyJson({
63
- url: server.address,
64
- ...(server.authToken && { authToken: server.authToken }),
65
- ...(server.encodedCaCert && { caCert: server.encodedCaCert })
66
- });
67
- console.log(serverConfig);
81
+ const serverInfo: Info = stringifyJson(server.info);
82
+ console.log(serverInfo);
68
83
 
69
84
  await server.stopped;
70
85
  }
@@ -81,32 +96,32 @@ export async function runParquetServer(): Promise<void> {
81
96
  */
82
97
  export class ParquetServer implements Disposable {
83
98
  readonly #process: ChildProcess;
84
- readonly #config: PFrameInternal.ParquetServerConfig;
99
+ readonly #info: PFrameInternal.HttpServerInfo;
85
100
  readonly #lineReader: Interface;
86
101
 
87
102
  private constructor(
88
103
  process: ChildProcess,
89
- config: PFrameInternal.ParquetServerConfig,
104
+ info: PFrameInternal.HttpServerInfo,
90
105
  lineReader: Interface
91
106
  ) {
92
107
  this.#process = process;
93
- this.#config = config;
108
+ this.#info = info;
94
109
  this.#lineReader = lineReader;
95
110
  }
96
111
 
97
- get config(): PFrameInternal.ParquetServerConfig {
98
- return this.#config;
112
+ get info(): PFrameInternal.HttpServerInfo {
113
+ return this.#info;
99
114
  }
100
115
 
101
116
  static async serve(
102
117
  rootDir: string,
103
118
  options?: {
104
- http?: boolean;
105
- noAuth?: boolean;
119
+ noHttps?: true;
120
+ noAuth?: true;
121
+ port?: number;
106
122
  }
107
123
  ): Promise<ParquetServer> {
108
- const nodeFileUrl = import.meta.url;
109
- const nodeDirname = dirname(fileURLToPath(nodeFileUrl));
124
+ const nodeDirname = dirname(fileURLToPath(import.meta.url));
110
125
  const binPath = join(nodeDirname, '..', 'bin', 'parquet-server.mjs');
111
126
 
112
127
  const serverProcess = spawn(
@@ -114,8 +129,9 @@ export class ParquetServer implements Disposable {
114
129
  [
115
130
  binPath,
116
131
  rootDir,
117
- ...(options?.http ? [Options.Http] : []),
118
- ...(options?.noAuth ? [Options.NoAuth] : [])
132
+ ...(options?.noHttps ? [Options.NoHttps] : []),
133
+ ...(options?.noAuth ? [Options.NoAuth] : []),
134
+ ...(options?.port ? [Options.Port, options.port.toString()] : [])
119
135
  ],
120
136
  {
121
137
  stdio: ['ignore', 'pipe', 'ignore']
@@ -125,11 +141,11 @@ export class ParquetServer implements Disposable {
125
141
  const lineReader = createInterface({ input: serverProcess.stdout! });
126
142
 
127
143
  const firstLine = await lineReader[Symbol.asyncIterator]().next();
128
- const serverConfig = parseJson(firstLine.value as Config);
144
+ const serverInfo = parseJson(firstLine.value as Info);
129
145
 
130
146
  lineReader.on('line', console.log);
131
147
 
132
- return new ParquetServer(serverProcess, serverConfig, lineReader);
148
+ return new ParquetServer(serverProcess, serverInfo, lineReader);
133
149
  }
134
150
 
135
151
  [Symbol.dispose](): void {