@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/fs-store.cjs +30 -22
- package/dist/fs-store.cjs.map +1 -1
- package/dist/fs-store.d.ts.map +1 -1
- package/dist/fs-store.js +31 -23
- package/dist/fs-store.js.map +1 -1
- package/dist/handler.cjs +15 -9
- package/dist/handler.cjs.map +1 -1
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +15 -9
- package/dist/handler.js.map +1 -1
- package/dist/parquet-server.cjs +33 -30
- package/dist/parquet-server.cjs.map +1 -1
- package/dist/parquet-server.d.ts +4 -3
- package/dist/parquet-server.d.ts.map +1 -1
- package/dist/parquet-server.js +33 -30
- package/dist/parquet-server.js.map +1 -1
- package/dist/serve.cjs +12 -20
- package/dist/serve.cjs.map +1 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +13 -21
- package/dist/serve.js.map +1 -1
- package/dist/utils/headers.cjs +2 -0
- package/dist/utils/headers.cjs.map +1 -1
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.d.ts.map +1 -1
- package/dist/utils/headers.js +2 -0
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/status.cjs +1 -0
- package/dist/utils/status.cjs.map +1 -1
- package/dist/utils/status.d.ts +1 -0
- package/dist/utils/status.d.ts.map +1 -1
- package/dist/utils/status.js +1 -0
- package/dist/utils/status.js.map +1 -1
- package/package.json +4 -3
- package/src/fs-store.ts +40 -26
- package/src/handler.ts +14 -9
- package/src/parquet-server.ts +52 -36
- package/src/serve.ts +14 -29
- package/src/utils/headers.ts +2 -0
- package/src/utils/status.ts +1 -0
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
|
|
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":";;;;;;;;AAsBA;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,OAAc,EAAA;IAEd,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO;AAC3C,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;AACE,YAAA,OAAO,GAAG,QAAQ,CAAA,aAAA,EAAgB,IAAI,CAAC,IAAI,GAAoC;;AAErF;AAEA;;;AAGG;AACI,eAAe,KAAK,CAAC,EAC1B,OAAO,EACP,IAAI,GAAG,CAAC,EACR,OAAO,EACP,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,aAAuE;AAC3E,QAAA,MAAM,cAAc,GAAkB;AACpC,YAAA,SAAS,EAAE;SACZ;AACD,QAAA,IAAI,MAAgC;QAEpC,IAAI,OAAO,EAAE;AACX,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,aAAa,GAAG,YAAY,CAAC,IAAqC,CAAC;AACnE,YAAA,MAAM,GAAGC,cAAiB,CACxB,EAAE,GAAG,cAAc,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,EACpC,gBAAgB,CACjB;QACH;QAEA;AACG,aAAA,EAAE,CAAC,WAAW,EAAE,MAAK;;YAEpB,MAAM,GAAG,GAAG,oBAAoB,CAC9B,MAAM,CAAC,OAAO,EAAiB,EAC/B,OAAO,CACR;AACD,YAAA,OAAO,GAAG,IAAI,QAAQ,EAAQ;YAE9B,OAAO,CAAC,OAAO,CAAC;AACd,gBAAA,IAAI,IAAI,GAAA;AACN,oBAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,aAAa,EAAE;gBAC1C,CAAC;AACD,gBAAA,IAAI,OAAO,GAAA;oBACT,OAAO,OAAQ,CAAC,OAAO;gBACzB,CAAC;gBACD,IAAI,GAAA;oBACF,MAAM,CAAC,KAAK,EAAE;oBACd,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;AACD,SAAA,CAAC;IACN;IAAE,OAAO,KAAc,EAAE;QACvB,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACpC;IAEA,OAAO,OAAO,CAAC,OAAO;AACxB;;;;"}
|
package/dist/utils/headers.cjs
CHANGED
|
@@ -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;;;;;;"}
|
package/dist/utils/headers.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/utils/headers.js
CHANGED
|
@@ -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;;;;;"}
|
package/dist/utils/status.cjs
CHANGED
|
@@ -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;;;;;"}
|
package/dist/utils/status.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/utils/status.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,eAAO,MAAM,UAAU;;;;;;;;;;;;;;CAcb,CAAC"}
|
package/dist/utils/status.js
CHANGED
|
@@ -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>
|
package/dist/utils/status.js.map
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
80
|
+
return await respond({ type: 'RangeNotSatisfiable', size });
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
if (params.method === 'HEAD') {
|
|
79
|
-
return await
|
|
84
|
+
return await respond({ type: 'Ok', size, range });
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
let data: Readable;
|
|
83
88
|
try {
|
|
84
|
-
data = createReadStream(
|
|
85
|
-
fd: file.fd,
|
|
86
|
-
autoClose: false,
|
|
89
|
+
data = file.createReadStream({
|
|
87
90
|
start: range.start,
|
|
88
91
|
end: range.end,
|
|
89
|
-
|
|
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
|
|
103
|
+
return await respond({ type: 'InternalError' });
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
24
|
+
function handleRequest(
|
|
24
25
|
request: IncomingMessage,
|
|
25
26
|
response: ServerResponse,
|
|
26
27
|
store: PFrameInternal.ObjectStore
|
|
27
|
-
):
|
|
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 (
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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) =>
|
|
161
|
+
return (request, response) => handleRequest(request, response, store);
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
/** Request authorization middleware */
|
package/src/parquet-server.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
18
|
-
NoAuth: '--no-auth'
|
|
15
|
+
NoHttps: '--no-https',
|
|
16
|
+
NoAuth: '--no-auth',
|
|
17
|
+
Port: '--port'
|
|
19
18
|
} as const;
|
|
20
19
|
|
|
21
|
-
type
|
|
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.
|
|
35
|
-
.option(Options.NoAuth, 'Disable
|
|
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 (
|
|
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
|
|
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
|
|
72
|
+
const server = await HttpHelpers.createHttpServer({
|
|
55
73
|
handler,
|
|
56
|
-
...(options.
|
|
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
|
|
63
|
-
|
|
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 #
|
|
99
|
+
readonly #info: PFrameInternal.HttpServerInfo;
|
|
85
100
|
readonly #lineReader: Interface;
|
|
86
101
|
|
|
87
102
|
private constructor(
|
|
88
103
|
process: ChildProcess,
|
|
89
|
-
|
|
104
|
+
info: PFrameInternal.HttpServerInfo,
|
|
90
105
|
lineReader: Interface
|
|
91
106
|
) {
|
|
92
107
|
this.#process = process;
|
|
93
|
-
this.#
|
|
108
|
+
this.#info = info;
|
|
94
109
|
this.#lineReader = lineReader;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
get
|
|
98
|
-
return this.#
|
|
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
|
-
|
|
105
|
-
noAuth?:
|
|
119
|
+
noHttps?: true;
|
|
120
|
+
noAuth?: true;
|
|
121
|
+
port?: number;
|
|
106
122
|
}
|
|
107
123
|
): Promise<ParquetServer> {
|
|
108
|
-
const
|
|
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?.
|
|
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
|
|
144
|
+
const serverInfo = parseJson(firstLine.value as Info);
|
|
129
145
|
|
|
130
146
|
lineReader.on('line', console.log);
|
|
131
147
|
|
|
132
|
-
return new ParquetServer(serverProcess,
|
|
148
|
+
return new ParquetServer(serverProcess, serverInfo, lineReader);
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
[Symbol.dispose](): void {
|