@milaboratories/pframes-rs-serv 1.1.3 → 1.1.5
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/bin/parquet-server.mjs +2 -2
- package/dist/export.cjs +1 -1
- package/dist/export.cjs.map +1 -1
- package/dist/export.d.ts +1 -1
- package/dist/export.d.ts.map +1 -1
- package/dist/export.js +1 -1
- package/dist/export.js.map +1 -1
- package/dist/fs-store.cjs +18 -20
- package/dist/fs-store.cjs.map +1 -1
- package/dist/fs-store.d.ts +1 -1
- package/dist/fs-store.d.ts.map +1 -1
- package/dist/fs-store.js +18 -20
- package/dist/fs-store.js.map +1 -1
- package/dist/handler.cjs +5 -5
- package/dist/handler.cjs.map +1 -1
- package/dist/handler.d.ts +2 -2
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +5 -5
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/parquet-server.cjs +19 -19
- package/dist/parquet-server.cjs.map +1 -1
- package/dist/parquet-server.d.ts +1 -1
- package/dist/parquet-server.d.ts.map +1 -1
- package/dist/parquet-server.js +19 -19
- package/dist/parquet-server.js.map +1 -1
- package/dist/serve.cjs +20 -20
- package/dist/serve.cjs.map +1 -1
- package/dist/serve.d.ts +2 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +20 -20
- package/dist/serve.js.map +1 -1
- package/dist/utils/etag.cjs +1 -1
- package/dist/utils/etag.cjs.map +1 -1
- package/dist/utils/etag.d.ts +3 -3
- package/dist/utils/etag.js +1 -1
- package/dist/utils/etag.js.map +1 -1
- package/dist/utils/filename.cjs.map +1 -1
- package/dist/utils/filename.d.ts +2 -2
- package/dist/utils/filename.js.map +1 -1
- package/dist/utils/headers.cjs +17 -17
- package/dist/utils/headers.cjs.map +1 -1
- package/dist/utils/headers.js +17 -17
- package/dist/utils/headers.js.map +1 -1
- package/dist/utils/index.d.ts +7 -7
- package/dist/utils/method.cjs +3 -3
- package/dist/utils/method.cjs.map +1 -1
- package/dist/utils/method.d.ts +5 -5
- package/dist/utils/method.js +3 -3
- package/dist/utils/method.js.map +1 -1
- package/dist/utils/options.cjs +10 -10
- package/dist/utils/options.cjs.map +1 -1
- package/dist/utils/options.d.ts +2 -2
- package/dist/utils/options.d.ts.map +1 -1
- package/dist/utils/options.js +10 -10
- package/dist/utils/options.js.map +1 -1
- package/dist/utils/range.cjs +4 -4
- package/dist/utils/range.cjs.map +1 -1
- package/dist/utils/range.d.ts +2 -2
- package/dist/utils/range.d.ts.map +1 -1
- package/dist/utils/range.js +4 -4
- package/dist/utils/range.js.map +1 -1
- package/dist/utils/status.cjs +1 -1
- package/dist/utils/status.cjs.map +1 -1
- package/dist/utils/status.js +1 -1
- package/dist/utils/status.js.map +1 -1
- package/package.json +25 -26
- package/src/export.ts +9 -11
- package/src/fs-store.ts +32 -43
- package/src/handler.ts +20 -28
- package/src/index.ts +5 -5
- package/src/parquet-server.ts +33 -37
- package/src/serve.ts +33 -49
- package/src/utils/etag.ts +4 -4
- package/src/utils/filename.ts +3 -3
- package/src/utils/headers.ts +24 -24
- package/src/utils/index.ts +7 -7
- package/src/utils/method.ts +10 -10
- package/src/utils/options.ts +14 -18
- package/src/utils/range.ts +7 -9
- package/src/utils/status.ts +1 -1
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pframes-rs-serv",
|
|
3
|
+
"version": "1.1.5",
|
|
3
4
|
"description": "PFrames - Node.js HTTP(S) Parquet Server",
|
|
4
5
|
"keywords": [
|
|
5
6
|
"http",
|
|
@@ -8,10 +9,23 @@
|
|
|
8
9
|
"proxy",
|
|
9
10
|
"server"
|
|
10
11
|
],
|
|
11
|
-
"
|
|
12
|
+
"homepage": "https://github.com/milaboratory/pframes-rs#readme",
|
|
13
|
+
"license": "UNLICENSED",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git://github.com/milaboratory/pframes-rs.git"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"parquet-server": "./bin/parquet-server.mjs"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src/**/*",
|
|
23
|
+
"bin/**/*",
|
|
24
|
+
"dist/**/*"
|
|
25
|
+
],
|
|
12
26
|
"type": "module",
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
14
27
|
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
15
29
|
"exports": {
|
|
16
30
|
".": {
|
|
17
31
|
"types": "./dist/index.d.ts",
|
|
@@ -19,24 +33,19 @@
|
|
|
19
33
|
"require": "./dist/index.cjs"
|
|
20
34
|
}
|
|
21
35
|
},
|
|
22
|
-
"
|
|
23
|
-
"
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"registry": "https://registry.npmjs.org"
|
|
24
38
|
},
|
|
25
|
-
"files": [
|
|
26
|
-
"src/**/*",
|
|
27
|
-
"bin/**/*",
|
|
28
|
-
"dist/**/*"
|
|
29
|
-
],
|
|
30
39
|
"dependencies": {
|
|
31
40
|
"@milaboratories/helpers": "1.13.1",
|
|
32
|
-
"@milaboratories/pl-model-common": "1.24.
|
|
33
|
-
"@milaboratories/pl-model-middle-layer": "1.11.
|
|
41
|
+
"@milaboratories/pl-model-common": "1.24.7",
|
|
42
|
+
"@milaboratories/pl-model-middle-layer": "1.11.9",
|
|
34
43
|
"commander": "^14.0.2",
|
|
35
44
|
"selfsigned": "^5.4.0"
|
|
36
45
|
},
|
|
37
46
|
"devDependencies": {
|
|
38
47
|
"@datadog/pprof": "^5.13.1",
|
|
39
|
-
"@milaboratories/ts-builder": "1.2.
|
|
48
|
+
"@milaboratories/ts-builder": "1.2.9",
|
|
40
49
|
"@milaboratories/ts-configs": "1.2.0",
|
|
41
50
|
"@types/autocannon": "^7.12.7",
|
|
42
51
|
"@types/node": "~22.19.5",
|
|
@@ -46,26 +55,16 @@
|
|
|
46
55
|
"tslib": "^2.8.1",
|
|
47
56
|
"typescript": "^5.9.3",
|
|
48
57
|
"undici": "^7.18.2",
|
|
49
|
-
"vite": "^7.3.1",
|
|
50
58
|
"vitest": "^4.0.17"
|
|
51
59
|
},
|
|
52
|
-
"license": "UNLICENSED",
|
|
53
|
-
"publishConfig": {
|
|
54
|
-
"registry": "https://registry.npmjs.org"
|
|
55
|
-
},
|
|
56
|
-
"homepage": "https://github.com/milaboratory/pframes-rs#readme",
|
|
57
|
-
"repository": {
|
|
58
|
-
"type": "git",
|
|
59
|
-
"url": "git://github.com/milaboratory/pframes-rs.git"
|
|
60
|
-
},
|
|
61
60
|
"scripts": {
|
|
62
61
|
"bump-version": "cargo run -p pframes_rs_meta --bin npm_version -r --locked",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
62
|
+
"fix": "ts-builder format",
|
|
63
|
+
"check": "ts-builder linter --check --target node && ts-builder formatter --check",
|
|
64
|
+
"build": "ts-builder build --target node",
|
|
65
65
|
"postts-build": "pnpm exec rimraf package.tgz && pnpm pack --out package.tgz",
|
|
66
|
-
"build": "pnpm run ts-build",
|
|
67
66
|
"pretest": "pnpm run build",
|
|
68
|
-
"test": "
|
|
67
|
+
"test": "ts-builder type-check --target node && pnpm exec vitest run --coverage",
|
|
69
68
|
"ci-test": "pnpm run test"
|
|
70
69
|
}
|
|
71
70
|
}
|
package/src/export.ts
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import type { RequestListener } from
|
|
2
|
-
import { FileSystemStore } from
|
|
3
|
-
import { createRequestHandler } from
|
|
4
|
-
import { serve } from
|
|
5
|
-
import type { PFrameInternal } from
|
|
1
|
+
import type { RequestListener } from "node:http";
|
|
2
|
+
import { FileSystemStore } from "./fs-store";
|
|
3
|
+
import { createRequestHandler } from "./handler";
|
|
4
|
+
import { serve } from "./serve";
|
|
5
|
+
import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
|
|
6
6
|
|
|
7
7
|
export const HttpHelpers: PFrameInternal.HttpHelpers = {
|
|
8
8
|
createFsStore: async (
|
|
9
|
-
options: PFrameInternal.FsStoreOptions
|
|
9
|
+
options: PFrameInternal.FsStoreOptions,
|
|
10
10
|
): Promise<PFrameInternal.ObjectStore> => {
|
|
11
11
|
return await FileSystemStore.init(options);
|
|
12
12
|
},
|
|
13
|
-
createRequestHandler: (
|
|
14
|
-
options: PFrameInternal.RequestHandlerOptions
|
|
15
|
-
): RequestListener => {
|
|
13
|
+
createRequestHandler: (options: PFrameInternal.RequestHandlerOptions): RequestListener => {
|
|
16
14
|
return createRequestHandler(options);
|
|
17
15
|
},
|
|
18
16
|
createHttpServer: async (
|
|
19
|
-
options: PFrameInternal.HttpServerOptions
|
|
17
|
+
options: PFrameInternal.HttpServerOptions,
|
|
20
18
|
): Promise<PFrameInternal.HttpServer> => {
|
|
21
19
|
return await serve(options);
|
|
22
|
-
}
|
|
20
|
+
},
|
|
23
21
|
};
|
package/src/fs-store.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { Readable } from
|
|
2
|
-
import { stat, open, type FileHandle } from
|
|
3
|
-
import { join, resolve } from
|
|
4
|
-
import { PFrameInternal } from
|
|
5
|
-
import { ensureError, isAbortError } from
|
|
1
|
+
import type { Readable } from "node:stream";
|
|
2
|
+
import { stat, open, type FileHandle } from "node:fs/promises";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
|
|
5
|
+
import { ensureError, isAbortError } from "@milaboratories/pl-model-common";
|
|
6
6
|
|
|
7
7
|
/** Object store for serving files from a local directory */
|
|
8
8
|
export class FileSystemStore extends PFrameInternal.BaseObjectStore {
|
|
@@ -14,25 +14,19 @@ export class FileSystemStore extends PFrameInternal.BaseObjectStore {
|
|
|
14
14
|
this.rootDir = options.rootDir;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
static async init(
|
|
18
|
-
options: PFrameInternal.FsStoreOptions
|
|
19
|
-
): Promise<FileSystemStore> {
|
|
17
|
+
static async init(options: PFrameInternal.FsStoreOptions): Promise<FileSystemStore> {
|
|
20
18
|
const resolvedRootDir = resolve(options.rootDir);
|
|
21
19
|
|
|
22
20
|
const rootStats = await stat(resolvedRootDir).catch(() => {
|
|
23
|
-
throw new Error(
|
|
24
|
-
`File system store root directory does not exist: ${resolvedRootDir}`
|
|
25
|
-
);
|
|
21
|
+
throw new Error(`File system store root directory does not exist: ${resolvedRootDir}`);
|
|
26
22
|
});
|
|
27
23
|
if (!rootStats.isDirectory()) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`File system store root path is not a directory: ${resolvedRootDir}`
|
|
30
|
-
);
|
|
24
|
+
throw new Error(`File system store root path is not a directory: ${resolvedRootDir}`);
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
return new FileSystemStore({
|
|
34
28
|
...options,
|
|
35
|
-
rootDir: resolvedRootDir
|
|
29
|
+
rootDir: resolvedRootDir,
|
|
36
30
|
});
|
|
37
31
|
}
|
|
38
32
|
|
|
@@ -43,18 +37,16 @@ export class FileSystemStore extends PFrameInternal.BaseObjectStore {
|
|
|
43
37
|
range?: PFrameInternal.HttpRange;
|
|
44
38
|
signal: AbortSignal;
|
|
45
39
|
callback: (response: PFrameInternal.ObjectStoreResponse) => Promise<void>;
|
|
46
|
-
}
|
|
40
|
+
},
|
|
47
41
|
): Promise<void> {
|
|
48
42
|
let file: FileHandle | undefined;
|
|
49
43
|
const respond = async (response: PFrameInternal.ObjectStoreResponse) => {
|
|
50
44
|
try {
|
|
51
|
-
await params
|
|
52
|
-
.callback(response)
|
|
53
|
-
.finally(async () => await file?.close());
|
|
45
|
+
await params.callback(response).finally(async () => await file?.close());
|
|
54
46
|
} catch (error: unknown) {
|
|
55
47
|
this.logger(
|
|
56
|
-
|
|
57
|
-
`File system store received unexpected rejection from callback: ${ensureError(error)}
|
|
48
|
+
"warn",
|
|
49
|
+
`File system store received unexpected rejection from callback: ${ensureError(error)}`,
|
|
58
50
|
);
|
|
59
51
|
}
|
|
60
52
|
};
|
|
@@ -62,13 +54,13 @@ export class FileSystemStore extends PFrameInternal.BaseObjectStore {
|
|
|
62
54
|
try {
|
|
63
55
|
try {
|
|
64
56
|
const path = join(this.rootDir, filename);
|
|
65
|
-
file = await open(path,
|
|
57
|
+
file = await open(path, "r");
|
|
66
58
|
} catch (error: unknown) {
|
|
67
59
|
this.logger(
|
|
68
|
-
|
|
69
|
-
`File system store failed to open file ${filename}: ${ensureError(error)}
|
|
60
|
+
"error",
|
|
61
|
+
`File system store failed to open file ${filename}: ${ensureError(error)}`,
|
|
70
62
|
);
|
|
71
|
-
return await respond({ type:
|
|
63
|
+
return await respond({ type: "NotFound" });
|
|
72
64
|
}
|
|
73
65
|
params.signal.throwIfAborted();
|
|
74
66
|
|
|
@@ -77,20 +69,20 @@ export class FileSystemStore extends PFrameInternal.BaseObjectStore {
|
|
|
77
69
|
({ size } = await file.stat());
|
|
78
70
|
} catch (error: unknown) {
|
|
79
71
|
this.logger(
|
|
80
|
-
|
|
81
|
-
`File system store failed to get size of file ${filename}: ${ensureError(error)}
|
|
72
|
+
"error",
|
|
73
|
+
`File system store failed to get size of file ${filename}: ${ensureError(error)}`,
|
|
82
74
|
);
|
|
83
|
-
return await respond({ type:
|
|
75
|
+
return await respond({ type: "InternalError" });
|
|
84
76
|
}
|
|
85
77
|
params.signal.throwIfAborted();
|
|
86
78
|
|
|
87
79
|
const range = this.translate(size, params.range);
|
|
88
80
|
if (!range) {
|
|
89
|
-
return await respond({ type:
|
|
81
|
+
return await respond({ type: "RangeNotSatisfiable", size });
|
|
90
82
|
}
|
|
91
83
|
|
|
92
|
-
if (params.method ===
|
|
93
|
-
return await respond({ type:
|
|
84
|
+
if (params.method === "HEAD") {
|
|
85
|
+
return await respond({ type: "Ok", size, range });
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
let data: Readable;
|
|
@@ -98,29 +90,26 @@ export class FileSystemStore extends PFrameInternal.BaseObjectStore {
|
|
|
98
90
|
data = file.createReadStream({
|
|
99
91
|
start: range.start,
|
|
100
92
|
end: range.end,
|
|
101
|
-
autoClose: false
|
|
93
|
+
autoClose: false,
|
|
102
94
|
});
|
|
103
95
|
this.logger(
|
|
104
|
-
|
|
105
|
-
`File system store created read stream for ${filename}[${range.start}..=${range.end}]
|
|
96
|
+
"info",
|
|
97
|
+
`File system store created read stream for ${filename}[${range.start}..=${range.end}]`,
|
|
106
98
|
);
|
|
107
99
|
} catch (error: unknown) {
|
|
108
100
|
this.logger(
|
|
109
|
-
|
|
110
|
-
`File system store failed to create read stream for ${filename}[${range.start}..=${range.end}]: ${ensureError(error)}
|
|
101
|
+
"error",
|
|
102
|
+
`File system store failed to create read stream for ${filename}[${range.start}..=${range.end}]: ${ensureError(error)}`,
|
|
111
103
|
);
|
|
112
|
-
return await respond({ type:
|
|
104
|
+
return await respond({ type: "InternalError" });
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
return await respond({ type:
|
|
107
|
+
return await respond({ type: "Ok", size, range, data });
|
|
116
108
|
} catch (error: unknown) {
|
|
117
109
|
if (!isAbortError(error)) {
|
|
118
|
-
this.logger(
|
|
119
|
-
'warn',
|
|
120
|
-
`File system store unhandled error: ${ensureError(error)}`
|
|
121
|
-
);
|
|
110
|
+
this.logger("warn", `File system store unhandled error: ${ensureError(error)}`);
|
|
122
111
|
}
|
|
123
|
-
return await respond({ type:
|
|
112
|
+
return await respond({ type: "InternalError" });
|
|
124
113
|
}
|
|
125
114
|
}
|
|
126
115
|
}
|
package/src/handler.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from 'node:http';
|
|
6
|
-
import { pipeline } from 'node:stream/promises';
|
|
7
|
-
import { timingSafeEqual } from 'node:crypto';
|
|
8
|
-
import type { PFrameInternal } from '@milaboratories/pl-model-middle-layer';
|
|
1
|
+
import type { IncomingMessage, RequestListener, ServerResponse } from "node:http";
|
|
2
|
+
import { pipeline } from "node:stream/promises";
|
|
3
|
+
import { timingSafeEqual } from "node:crypto";
|
|
4
|
+
import type { PFrameInternal } from "@milaboratories/pl-model-middle-layer";
|
|
9
5
|
import {
|
|
10
6
|
createETag,
|
|
11
7
|
getFilenameFromUrl,
|
|
@@ -16,15 +12,15 @@ import {
|
|
|
16
12
|
Options,
|
|
17
13
|
StatusCode,
|
|
18
14
|
HeaderName,
|
|
19
|
-
HeaderValue
|
|
20
|
-
} from
|
|
21
|
-
import { isAbortError } from
|
|
15
|
+
HeaderValue,
|
|
16
|
+
} from "./utils";
|
|
17
|
+
import { isAbortError } from "@milaboratories/pl-model-common";
|
|
22
18
|
|
|
23
19
|
/** Main request handler for parquet files */
|
|
24
20
|
function handleRequest(
|
|
25
21
|
request: IncomingMessage,
|
|
26
22
|
response: ServerResponse,
|
|
27
|
-
store: PFrameInternal.ObjectStore
|
|
23
|
+
store: PFrameInternal.ObjectStore,
|
|
28
24
|
): void {
|
|
29
25
|
// RFC 9110 section 6.6.1: Date header should be present in all responses
|
|
30
26
|
response.sendDate = true;
|
|
@@ -79,7 +75,7 @@ function handleRequest(
|
|
|
79
75
|
}
|
|
80
76
|
|
|
81
77
|
const abortController = new AbortController();
|
|
82
|
-
request.on(
|
|
78
|
+
request.on("close", () => abortController.abort());
|
|
83
79
|
const signal = abortController.signal;
|
|
84
80
|
|
|
85
81
|
store.request(filename, {
|
|
@@ -101,17 +97,17 @@ function handleRequest(
|
|
|
101
97
|
}
|
|
102
98
|
|
|
103
99
|
switch (result.type) {
|
|
104
|
-
case
|
|
100
|
+
case "InternalError":
|
|
105
101
|
// object store encountered network error, retry by client can help
|
|
106
102
|
return void response.writeHead(StatusCode.InternalServerError).end();
|
|
107
|
-
case
|
|
103
|
+
case "NotFound":
|
|
108
104
|
// RFC 9110 section 15.4.5: Not found
|
|
109
105
|
return void response.writeHead(StatusCode.NotFound).end();
|
|
110
|
-
case
|
|
106
|
+
case "RangeNotSatisfiable":
|
|
111
107
|
// RFC 9110 section 15.5.17: Range not satisfiable
|
|
112
108
|
response.setHeader(HeaderName.ContentRange, `bytes */${result.size}`);
|
|
113
109
|
return void response.writeHead(StatusCode.RangeNotSatisfiable).end();
|
|
114
|
-
case
|
|
110
|
+
case "Ok":
|
|
115
111
|
break;
|
|
116
112
|
}
|
|
117
113
|
|
|
@@ -122,13 +118,10 @@ function handleRequest(
|
|
|
122
118
|
|
|
123
119
|
if (range) {
|
|
124
120
|
// RFC 9110 section 14.4: Partial content response
|
|
125
|
-
response.setHeader(
|
|
126
|
-
HeaderName.ContentLength,
|
|
127
|
-
result.range.end - result.range.start + 1
|
|
128
|
-
);
|
|
121
|
+
response.setHeader(HeaderName.ContentLength, result.range.end - result.range.start + 1);
|
|
129
122
|
response.setHeader(
|
|
130
123
|
HeaderName.ContentRange,
|
|
131
|
-
`bytes ${result.range.start}-${result.range.end}/${result.size}
|
|
124
|
+
`bytes ${result.range.start}-${result.range.end}/${result.size}`,
|
|
132
125
|
);
|
|
133
126
|
response.writeHead(StatusCode.PartialContent);
|
|
134
127
|
} else {
|
|
@@ -147,7 +140,7 @@ function handleRequest(
|
|
|
147
140
|
} catch (error: unknown) {
|
|
148
141
|
if (!isAbortError(error)) throw error;
|
|
149
142
|
}
|
|
150
|
-
}
|
|
143
|
+
},
|
|
151
144
|
});
|
|
152
145
|
}
|
|
153
146
|
|
|
@@ -161,7 +154,7 @@ function handleRequest(
|
|
|
161
154
|
* Assumes that files are immutable (and sets cache headers accordingly)
|
|
162
155
|
*/
|
|
163
156
|
export function createRequestHandler(
|
|
164
|
-
options: PFrameInternal.RequestHandlerOptions
|
|
157
|
+
options: PFrameInternal.RequestHandlerOptions,
|
|
165
158
|
): RequestListener {
|
|
166
159
|
const { store } = options;
|
|
167
160
|
return (request, response) => handleRequest(request, response, store);
|
|
@@ -172,7 +165,7 @@ function authorizeRequest(
|
|
|
172
165
|
request: IncomingMessage,
|
|
173
166
|
response: ServerResponse,
|
|
174
167
|
handler: RequestListener,
|
|
175
|
-
authHeader: string
|
|
168
|
+
authHeader: string,
|
|
176
169
|
): void {
|
|
177
170
|
// RFC 9110 section 6.6.1: Date header should be present in all responses
|
|
178
171
|
response.sendDate = true;
|
|
@@ -210,9 +203,8 @@ function authorizeRequest(
|
|
|
210
203
|
/** Apply Bearer token authorization to @param handler */
|
|
211
204
|
export function authorizeRequestHandler(
|
|
212
205
|
handler: RequestListener,
|
|
213
|
-
authToken: PFrameInternal.HttpAuthorizationToken
|
|
206
|
+
authToken: PFrameInternal.HttpAuthorizationToken,
|
|
214
207
|
): RequestListener {
|
|
215
208
|
const authHeader = `Bearer ${authToken}`;
|
|
216
|
-
return (request, response) =>
|
|
217
|
-
authorizeRequest(request, response, handler, authHeader);
|
|
209
|
+
return (request, response) => authorizeRequest(request, response, handler, authHeader);
|
|
218
210
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
1
|
+
export * from "./handler";
|
|
2
|
+
export * from "./fs-store";
|
|
3
|
+
export * from "./serve";
|
|
4
|
+
export * from "./export";
|
|
5
|
+
export * from "./parquet-server";
|
package/src/parquet-server.ts
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import { type ChildProcess, spawn } from
|
|
2
|
-
import { createInterface, type Interface } from
|
|
3
|
-
import { join, dirname } from
|
|
4
|
-
import { fileURLToPath } from
|
|
5
|
-
import { Command, InvalidArgumentError } from
|
|
6
|
-
import { HttpHelpers } from
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
stringifyJson,
|
|
10
|
-
type StringifiedJson
|
|
11
|
-
} from '@milaboratories/pl-model-common';
|
|
12
|
-
import { type PFrameInternal } from '@milaboratories/pl-model-middle-layer';
|
|
1
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
+
import { createInterface, type Interface } from "node:readline/promises";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
6
|
+
import { HttpHelpers } from "./export";
|
|
7
|
+
import { parseJson, stringifyJson, type StringifiedJson } from "@milaboratories/pl-model-common";
|
|
8
|
+
import { type PFrameInternal } from "@milaboratories/pl-model-middle-layer";
|
|
13
9
|
|
|
14
10
|
const Options = {
|
|
15
|
-
NoHttps:
|
|
16
|
-
NoAuth:
|
|
17
|
-
Port:
|
|
11
|
+
NoHttps: "--no-https",
|
|
12
|
+
NoAuth: "--no-auth",
|
|
13
|
+
Port: "--port",
|
|
18
14
|
} as const;
|
|
19
15
|
|
|
20
16
|
type Info = StringifiedJson<PFrameInternal.HttpServerInfo>;
|
|
@@ -27,22 +23,22 @@ export async function runParquetServer(): Promise<void> {
|
|
|
27
23
|
const program = new Command();
|
|
28
24
|
|
|
29
25
|
program
|
|
30
|
-
.name(
|
|
31
|
-
.description(
|
|
32
|
-
.argument(
|
|
33
|
-
.option(Options.NoHttps,
|
|
34
|
-
.option(Options.NoAuth,
|
|
26
|
+
.name("parquet-server")
|
|
27
|
+
.description("Serve parquet files from a directory over HTTP(S)")
|
|
28
|
+
.argument("<root-directory>", "Root directory containing parquet files")
|
|
29
|
+
.option(Options.NoHttps, "Downgrade HTTPS to HTTP")
|
|
30
|
+
.option(Options.NoAuth, "Disable authorization")
|
|
35
31
|
.option(
|
|
36
32
|
`${Options.Port} <number>`,
|
|
37
|
-
|
|
33
|
+
"Port to listen on",
|
|
38
34
|
(value) => {
|
|
39
35
|
const port = parseInt(value, 10);
|
|
40
36
|
if (isNaN(port) || port < 0 || port > 65535) {
|
|
41
|
-
throw new InvalidArgumentError(
|
|
37
|
+
throw new InvalidArgumentError("valid port numbers are 0-65535");
|
|
42
38
|
}
|
|
43
39
|
return port;
|
|
44
40
|
},
|
|
45
|
-
0
|
|
41
|
+
0,
|
|
46
42
|
)
|
|
47
43
|
.action(
|
|
48
44
|
async (
|
|
@@ -51,12 +47,12 @@ export async function runParquetServer(): Promise<void> {
|
|
|
51
47
|
https: boolean;
|
|
52
48
|
auth: boolean;
|
|
53
49
|
port: number;
|
|
54
|
-
}
|
|
50
|
+
},
|
|
55
51
|
) => {
|
|
56
52
|
const abortController = new AbortController();
|
|
57
53
|
process
|
|
58
|
-
.on(
|
|
59
|
-
.on(
|
|
54
|
+
.on("SIGINT", () => abortController.abort())
|
|
55
|
+
.on("SIGTERM", () => abortController.abort());
|
|
60
56
|
abortController.signal.throwIfAborted();
|
|
61
57
|
|
|
62
58
|
const store = await HttpHelpers.createFsStore({
|
|
@@ -64,7 +60,7 @@ export async function runParquetServer(): Promise<void> {
|
|
|
64
60
|
logger: (level, message) => {
|
|
65
61
|
const timestamp = new Date(Date.now()).toISOString();
|
|
66
62
|
console.log(`[${timestamp}] [${level}] ${message}`);
|
|
67
|
-
}
|
|
63
|
+
},
|
|
68
64
|
});
|
|
69
65
|
abortController.signal.throwIfAborted();
|
|
70
66
|
const handler = HttpHelpers.createRequestHandler({ store });
|
|
@@ -73,7 +69,7 @@ export async function runParquetServer(): Promise<void> {
|
|
|
73
69
|
handler,
|
|
74
70
|
...(!options.https && { noHttps: true }),
|
|
75
71
|
...(!options.auth && { noAuth: true }),
|
|
76
|
-
port: options.port
|
|
72
|
+
port: options.port,
|
|
77
73
|
});
|
|
78
74
|
abortController.signal.onabort = () => server.stop();
|
|
79
75
|
abortController.signal.throwIfAborted();
|
|
@@ -82,7 +78,7 @@ export async function runParquetServer(): Promise<void> {
|
|
|
82
78
|
console.log(serverInfo);
|
|
83
79
|
|
|
84
80
|
await server.stopped;
|
|
85
|
-
}
|
|
81
|
+
},
|
|
86
82
|
);
|
|
87
83
|
|
|
88
84
|
await program.parseAsync();
|
|
@@ -102,7 +98,7 @@ export class ParquetServer implements Disposable {
|
|
|
102
98
|
private constructor(
|
|
103
99
|
process: ChildProcess,
|
|
104
100
|
info: PFrameInternal.HttpServerInfo,
|
|
105
|
-
lineReader: Interface
|
|
101
|
+
lineReader: Interface,
|
|
106
102
|
) {
|
|
107
103
|
this.#process = process;
|
|
108
104
|
this.#info = info;
|
|
@@ -119,23 +115,23 @@ export class ParquetServer implements Disposable {
|
|
|
119
115
|
noHttps?: true;
|
|
120
116
|
noAuth?: true;
|
|
121
117
|
port?: number;
|
|
122
|
-
}
|
|
118
|
+
},
|
|
123
119
|
): Promise<ParquetServer> {
|
|
124
120
|
const nodeDirname = dirname(fileURLToPath(import.meta.url));
|
|
125
|
-
const binPath = join(nodeDirname,
|
|
121
|
+
const binPath = join(nodeDirname, "..", "bin", "parquet-server.mjs");
|
|
126
122
|
|
|
127
123
|
const serverProcess = spawn(
|
|
128
|
-
|
|
124
|
+
"node",
|
|
129
125
|
[
|
|
130
126
|
binPath,
|
|
131
127
|
rootDir,
|
|
132
128
|
...(options?.noHttps ? [Options.NoHttps] : []),
|
|
133
129
|
...(options?.noAuth ? [Options.NoAuth] : []),
|
|
134
|
-
...(options?.port ? [Options.Port, options.port.toString()] : [])
|
|
130
|
+
...(options?.port ? [Options.Port, options.port.toString()] : []),
|
|
135
131
|
],
|
|
136
132
|
{
|
|
137
|
-
stdio: [
|
|
138
|
-
}
|
|
133
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
134
|
+
},
|
|
139
135
|
);
|
|
140
136
|
|
|
141
137
|
const lineReader = createInterface({ input: serverProcess.stdout! });
|
|
@@ -143,7 +139,7 @@ export class ParquetServer implements Disposable {
|
|
|
143
139
|
const firstLine = await lineReader[Symbol.asyncIterator]().next();
|
|
144
140
|
const serverInfo = parseJson(firstLine.value as Info);
|
|
145
141
|
|
|
146
|
-
lineReader.on(
|
|
142
|
+
lineReader.on("line", console.log);
|
|
147
143
|
|
|
148
144
|
return new ParquetServer(serverProcess, serverInfo, lineReader);
|
|
149
145
|
}
|