geonix 1.12.2 → 1.20.0

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/src/Util.js CHANGED
@@ -1,19 +1,18 @@
1
- import { createHash, randomBytes } from 'crypto'
2
- import { URL, fileURLToPath } from 'url'
3
- import { readFile } from 'fs/promises'
4
- import { join } from 'path'
5
- import { Transform } from 'node:stream'
6
- import { networkInterfaces } from 'os'
7
- import http from 'http'
8
- import https from 'http'
9
- import net from 'net'
1
+ import { createHash, randomBytes } from "crypto";
2
+ import { URL, fileURLToPath } from "url";
3
+ import { readFile } from "fs/promises";
4
+ import { join } from "path";
5
+ import { Transform } from "node:stream";
6
+ import * as http from "http";
7
+ import * as https from "https";
8
+ import * as net from "net";
10
9
 
11
10
  /**
12
11
  * Wait for {delay} ms
13
12
  * @param {number} delay
14
13
  * @returns
15
14
  */
16
- export const sleep = delay => new Promise(resolve => setTimeout(resolve, delay))
15
+ export const sleep = delay => new Promise(resolve => setTimeout(resolve, delay));
17
16
 
18
17
  /**
19
18
  * Parse nats:// URL
@@ -21,14 +20,19 @@ export const sleep = delay => new Promise(resolve => setTimeout(resolve, delay))
21
20
  * @returns
22
21
  */
23
22
  export function parseURL(url) {
24
- const parsed = new URL(url)
23
+ const parsed = new URL(url);
25
24
 
26
- return {
25
+ const basic = {
27
26
  servers: `${parsed.hostname}:${parsed.port || 4222}`,
28
- user: parsed.password ? parsed.username : '',
27
+ user: parsed.password ? parsed.username : "",
29
28
  pass: parsed.password,
30
29
  token: parsed.username && !parsed.password ? parsed.username : undefined
31
- }
30
+ };
31
+
32
+ return {
33
+ ...basic,
34
+ ...Object.fromEntries(parsed.searchParams)
35
+ };
32
36
  }
33
37
 
34
38
  /**
@@ -36,14 +40,14 @@ export function parseURL(url) {
36
40
  * @param {number} size
37
41
  * @returns
38
42
  */
39
- export const picoid = (size = 12) => randomBytes(size).toString('base64')
43
+ export const picoid = (size = 12) => randomBytes(size).toString("base64");
40
44
 
41
45
  /**
42
46
  * Get SHA256 hash of a string or a buffer
43
47
  * @param {string|Buffer} data
44
48
  * @returns
45
49
  */
46
- export const hash = (data) => createHash('sha256').update(data).digest('hex')
50
+ export const hash = (data) => createHash("sha256").update(data).digest("hex");
47
51
 
48
52
  /**
49
53
  * Create TCP or HTTP server at specified port
@@ -53,17 +57,17 @@ export const hash = (data) => createHash('sha256').update(data).digest('hex')
53
57
  * @returns
54
58
  */
55
59
  export const createServerAtPort = (port, pkg, handler) =>
56
- new Promise((resolve, reject) => {
57
- const server = pkg.createServer(handler)
58
- server.on('error', (error) => {
60
+ new Promise((resolve) => {
61
+ const server = pkg.createServer(handler);
62
+ server.on("error", (_error) => {
59
63
  // console.log('error', error.message)
60
- resolve(null)
61
- })
64
+ resolve(null);
65
+ });
62
66
  server.listen(port, () => {
63
- const { port } = server.address()
64
- resolve({ server, port })
65
- })
66
- })
67
+ const { port } = server.address();
68
+ resolve({ server, port });
69
+ });
70
+ });
67
71
 
68
72
  /**
69
73
  * Create TCP or HTTP server at first free port in rage
@@ -76,34 +80,36 @@ export const createServerAtPort = (port, pkg, handler) =>
76
80
  export const createServerAtFreePort = async (pkg, handler, start = 30000, poolSize = 20000) => {
77
81
  for (let port = start; port < start + poolSize; port++)
78
82
  try {
79
- const result = await createServerAtPort(port, pkg, handler)
83
+ const result = await createServerAtPort(port, pkg, handler);
80
84
  // console.log(port, '=', result)
81
- if (result) return result
82
- } catch (e) { }
83
- }
85
+ if (result) return result;
86
+ } catch {
87
+ // silenty ignore errors
88
+ }
89
+ };
84
90
 
85
91
  /**
86
92
  * Create TCP server at random port
87
93
  * @param {Function} handler
88
94
  * @returns
89
95
  */
90
- export const createTCPServer = (handler, start = 30000, poolSize = 20000) => createServerAtFreePort(net, handler, start, poolSize)
96
+ export const createTCPServer = (handler, start = 30000, poolSize = 20000) => createServerAtFreePort(net, handler, start, poolSize);
91
97
 
92
98
  /**
93
99
  * Create HTTP server at random port
94
100
  * @param {Function} handler
95
101
  * @returns
96
102
  */
97
- export const createHTTPServer = (handler, start = 30000, poolSize = 20000) => createServerAtFreePort(net, handler, start, poolSize)
103
+ export const createHTTPServer = (handler, start = 30000, poolSize = 20000) => createServerAtFreePort(net, handler, start, poolSize);
98
104
 
99
105
  /**
100
106
  * Return number of seconds passed from the start of the day (0-86399)
101
107
  * @returns Number
102
108
  */
103
109
  export const getSecondsSinceMidnight = () => {
104
- const date = new Date()
105
- return Math.floor((date.getTime() - date.setHours(0, 0, 0, 0)) / 1000)
106
- }
110
+ const date = new Date();
111
+ return Math.floor((date.getTime() - date.setHours(0, 0, 0, 0)) / 1000);
112
+ };
107
113
 
108
114
  /**
109
115
  * Parse function body and return array of param names
@@ -111,51 +117,51 @@ export const getSecondsSinceMidnight = () => {
111
117
  * @returns string[]
112
118
  */
113
119
  export const getFunctionParams = (fn) => {
114
- const code = fn.toString()
115
- const endParenthesisPosition = code.indexOf(')')
116
- let params
120
+ const code = fn.toString();
121
+ const endParenthesisPosition = code.indexOf(")");
122
+ let params;
117
123
 
118
124
  if (endParenthesisPosition != -1)
119
- params = code.substring(code.indexOf('(') + 1, endParenthesisPosition)
125
+ params = code.substring(code.indexOf("(") + 1, endParenthesisPosition);
120
126
  else
121
- params = code.substring(0, code.indexOf('=>'))
127
+ params = code.substring(0, code.indexOf("=>"));
122
128
 
123
129
  params = params
124
130
  // cleanup spaces
125
- .replaceAll(' ', '')
131
+ .replaceAll(" ", "")
126
132
  // split into array
127
- .split(',')
133
+ .split(",");
128
134
 
129
135
  // remove potential default values
130
136
  for (let index = 0; index < params.length; index++) {
131
- const defaultValueAssignmentPosition = params[index].indexOf('=')
137
+ const defaultValueAssignmentPosition = params[index].indexOf("=");
132
138
  if (defaultValueAssignmentPosition != -1)
133
- params[index] = params[index].substring(0, defaultValueAssignmentPosition - 1)
139
+ params[index] = params[index].substring(0, defaultValueAssignmentPosition - 1);
134
140
  }
135
141
 
136
- return params
137
- }
142
+ return params;
143
+ };
138
144
 
139
145
  export const proxyHttp = (target, req, res) =>
140
146
  new Promise((resolve, reject) => {
141
- const remoteTarget = `${target}${req.originalUrl}`
147
+ const remoteTarget = `${target}${req.originalUrl}`;
142
148
  const options = {
143
149
  method: req.method,
144
150
  headers: req.headers
145
- }
151
+ };
146
152
 
147
- const protocol = req.protocol === 'https' ? https : http
153
+ const protocol = req.protocol === "https" ? https : http;
148
154
  const proxyReq = protocol.request(remoteTarget, options, (proxyRes) => {
149
- res.status(proxyRes.statusCode)
155
+ res.status(proxyRes.statusCode);
150
156
  for (const header in proxyRes.headers)
151
- res.set(header, proxyRes.headers[header])
152
- proxyRes.pipe(res)
153
- })
154
- proxyReq.on('error', (error) => reject(error))
155
- req.pipe(proxyReq)
156
- req.on('error', (error) => reject(error))
157
- req.on('end', () => resolve())
158
- })
157
+ res.set(header, proxyRes.headers[header]);
158
+ proxyRes.pipe(res);
159
+ });
160
+ proxyReq.on("error", (error) => reject(error));
161
+ req.pipe(proxyReq);
162
+ req.on("error", (error) => reject(error));
163
+ req.on("end", () => resolve());
164
+ });
159
165
 
160
166
  /**
161
167
  * Create a object proxy that overlays overlay object
@@ -163,87 +169,42 @@ export const proxyHttp = (target, req, res) =>
163
169
  * @param {*} overlay
164
170
  * @returns
165
171
  */
166
- export const OverlayObject = (object, overlay) => new Proxy(object, { get: (t, p) => overlay[p] !== undefined ? overlay[p] : t[p] })
172
+ export const OverlayObject = (object, overlay) => new Proxy(object, { get: (t, p) => overlay[p] !== undefined ? overlay[p] : t[p] });
167
173
 
168
- let _geonix_version = 'N/A'
174
+ let _geonix_version = "N/A";
169
175
  try {
170
- const __dirname = fileURLToPath(new URL('..', import.meta.url));
171
- const local = JSON.parse(await readFile(join(__dirname, 'package.json')))
172
- _geonix_version = local.version
173
- } catch (e) {
176
+ const __dirname = fileURLToPath(new URL("..", import.meta.url));
177
+ const local = JSON.parse(await readFile(join(__dirname, "package.json")));
178
+ _geonix_version = local.version;
179
+ } catch {
180
+ // ignore errors
174
181
  }
175
- export const GeonixVersion = _geonix_version
176
182
 
177
- export const StreamChunker = (chunkSize = 65536) => {
178
- let buffer = Buffer.alloc(chunkSize)
183
+ export const GeonixVersion = _geonix_version;
179
184
 
185
+ export const StreamChunker = (chunkSize = 65536) => {
180
186
  let chunker = new Transform();
181
187
 
182
188
  chunker._transform = function (chunk, encoding, done) {
183
- let offset = 0
189
+ let offset = 0;
184
190
  while (offset < chunk.length) {
185
- const sliceSize = Math.min(chunkSize, chunk.length - offset)
186
- this.push(chunk.slice(offset, offset + sliceSize))
187
- offset += sliceSize
191
+ const sliceSize = Math.min(chunkSize, chunk.length - offset);
192
+ this.push(chunk.slice(offset, offset + sliceSize));
193
+ offset += sliceSize;
188
194
  }
189
195
 
190
- done()
191
- }
196
+ done();
197
+ };
192
198
 
193
199
  chunker._flush = function (done) {
194
- done()
195
- }
200
+ done();
201
+ };
196
202
 
197
203
  return chunker;
198
- }
199
-
200
- export async function* timeoutAsyncGenerator(target, ms) {
201
- const iterator = target[Symbol.asyncIterator]()
202
-
203
- while (true) {
204
- const { value, done } = await Promise.race([iterator.next(), sleep(ms)]) || {};
205
- if (done || (value === undefined && done === undefined)) {
206
- break;
207
- }
208
-
209
- yield value;
210
- }
211
- }
212
-
213
- export async function waitForAbort(abortSignal, result) {
214
- return new Promise(resolve => {
215
- if (abortSignal.aborted) {
216
- resolve(result)
217
- }
218
-
219
- abortSignal.addEventListener('abort', () => {
220
- console.log('aborted')
221
- resolve(result)
222
- })
223
- })
224
- }
225
-
226
- export async function* abortableAsyncGenerator(target, abortSignal) {
227
- const iterator = target[Symbol.asyncIterator]()
228
-
229
- while (true) {
230
- const { value, done } = await Promise.race([iterator.next(), waitForAbort(abortSignal)]) || {};
231
- if (done || (value === undefined && done === undefined)) {
232
- break;
233
- }
234
-
235
- yield value;
236
- }
237
- }
238
-
239
- export function getNetworkAddresses() {
240
- const list = []
241
- const interfaces = networkInterfaces()
242
-
243
- for (let interfaceAddresses of Object.values(interfaces))
244
- for (let addressObject of interfaceAddresses)
245
- if (addressObject.family === 'IPv4')
246
- list.push(addressObject.address)
204
+ };
247
205
 
248
- return list
206
+ export async function getFirstItemFromAsyncIterable(asyncIterable) {
207
+ const iterator = asyncIterable[Symbol.asyncIterator]();
208
+ const result = await iterator.next();
209
+ return result.value;
249
210
  }
package/src/WebServer.js CHANGED
@@ -1,81 +1,72 @@
1
- import 'express-async-errors'
2
- import express, { Router } from 'express'
3
- import expressWs from 'express-ws'
4
- import { networkInterfaces } from 'os'
5
- import { createServerAtFreePort, createServerAtPort, sleep } from './Util.js'
6
- import * as http from 'http'
7
- import { Service } from './Service.js'
8
- import * as path from 'path'
1
+ import "express-async-errors";
2
+ import express, { Router } from "express";
3
+ import expressWs from "express-ws";
4
+ import { networkInterfaces } from "os";
5
+ import { createServerAtFreePort, createServerAtPort, sleep } from "./Util.js";
6
+ import * as http from "http";
7
+ import { Service } from "./Service.js";
8
+ import * as path from "path";
9
9
 
10
- export const HEALTH_CHECK_ENDPOINT = '/pA4vY7fT9oG5aI8cA4yV3qW5fP9qR1vI'
11
-
12
- const logger = (req, res, next) => {
13
- console.log(`HTTP ${req.method} ${req.url}`)
14
-
15
- next()
16
- }
10
+ export const HEALTH_CHECK_ENDPOINT = "/pA4vY7fT9oG5aI8cA4yV3qW5fP9qR1vI";
17
11
 
18
12
  export const ServeStatic = (root, options = {}) => {
19
- const router = Router()
20
- const absoluteRoot = path.resolve(root)
13
+ const router = Router();
14
+ const absoluteRoot = path.resolve(root);
21
15
 
22
16
  router.use((req, res, next) => {
23
17
  if (options.root) {
24
18
  // remove trailing slash
25
- if (options.root.endsWith('/'))
26
- options.root = options.root.slice(0, -1)
19
+ if (options.root.endsWith("/"))
20
+ options.root = options.root.slice(0, -1);
27
21
 
28
22
  // replace root prefix
29
23
  if (req.url.startsWith(options.root))
30
- req.url = req.url.replace(options.root, '')
24
+ req.url = req.url.replace(options.root, "");
31
25
  }
32
26
 
33
- next()
34
- })
27
+ next();
28
+ });
35
29
 
36
- router.use(express.static(root, options))
30
+ router.use(express.static(root, options));
37
31
 
38
32
  if (options.indexOn404)
39
- router.get('*', (req, res) => {
40
- console.log(path.join(absoluteRoot, 'index.html'))
41
- res.sendFile(path.join(absoluteRoot, 'index.html'))
42
- })
33
+ router.get("*", (req, res) => {
34
+ console.log(path.join(absoluteRoot, "index.html"));
35
+ res.sendFile(path.join(absoluteRoot, "index.html"));
36
+ });
43
37
 
44
- return router
45
- }
38
+ return router;
39
+ };
46
40
 
47
41
  class WebServer {
48
42
 
49
- #app = express()
50
- #server
51
- #port
52
- #ready = false
53
- #started = false
54
-
55
- constructor() {
56
- }
43
+ #app = express();
44
+ #server;
45
+ #port;
46
+ #ready = false;
47
+ #started = false;
57
48
 
58
49
  async start() {
59
50
  if (this.#started)
60
- return
51
+ return;
61
52
 
62
- this.#started = true
53
+ this.#started = true;
63
54
 
64
- let port, server
55
+ let port, server;
65
56
  if (process.env.LOCAL_PORT) {
66
- ({ server, port } = await createServerAtPort(process.env.LOCAL_PORT, http, this.#app))
57
+ ({ server, port } = await createServerAtPort(process.env.LOCAL_PORT, http, this.#app));
67
58
  } else {
68
- ({ server, port } = await createServerAtFreePort(http, this.#app))
59
+ ({ server, port } = await createServerAtFreePort(http, this.#app));
69
60
  }
70
61
 
71
- this.#server = server
72
- this.#port = port
62
+ this.#server = server;
63
+ this.#port = port;
73
64
 
74
- expressWs(this.#app, server)
65
+ expressWs(this.#app, server);
75
66
 
76
67
  this.#app.get(HEALTH_CHECK_ENDPOINT, (req, res) => {
77
- res.send({ status: 'healthy', services: Service.serviceClasses })
78
- })
68
+ res.send({ status: "healthy", services: Service.serviceClasses });
69
+ });
79
70
 
80
71
  // this.#app.use((req, res, next) => {
81
72
  // next()
@@ -85,47 +76,59 @@ class WebServer {
85
76
  // this should provide more than enough time to start all the services
86
77
  setTimeout(() => {
87
78
  // default answer
88
- this.#app.all('*', (req, res) => {
79
+ this.#app.all("*", (req, res) => {
89
80
  res.status(404).send({
90
81
  error: 404,
91
- source: 'ws'
92
- })
93
- })
94
- }, 2000)
82
+ source: "ws"
83
+ });
84
+ });
85
+ }, 2000);
95
86
 
96
87
  // config
97
- this.#app.disable('x-powered-by')
98
- this.#app.disable('etag')
88
+ this.#app.disable("x-powered-by");
89
+ this.#app.disable("etag");
90
+
91
+ console.log(`gx.webserver.start: listening on http://127.0.0.1:${this.#port}`);
92
+
93
+ this.#ready = true;
94
+ }
95
+
96
+ getAddresses() {
97
+ const list = [];
98
+ const interfaces = networkInterfaces();
99
99
 
100
- console.log(`gx.webserver.start: listening on http://127.0.0.1:${this.#port}`)
100
+ for (let interfaceAddresses of Object.values(interfaces))
101
+ for (let addressObject of interfaceAddresses)
102
+ if (addressObject.family === "IPv4")
103
+ list.push(addressObject.address);
101
104
 
102
- this.#ready = true
105
+ return list;
103
106
  }
104
107
 
105
108
  getPort() {
106
- return this.#port
109
+ return this.#port;
107
110
  }
108
111
 
109
112
  router() {
110
- const router = Router()
111
- this.#app.use(router)
112
- return router
113
+ const router = Router();
114
+ this.#app.use(router);
115
+ return router;
113
116
  }
114
117
 
115
118
  async waitUntilReady() {
116
- await this.start()
119
+ await this.start();
117
120
 
118
121
  while (!this.#ready)
119
- await sleep(100)
122
+ await sleep(100);
120
123
  }
121
124
 
122
125
  stop() {
123
126
  if (this.#server) {
124
- this.#server.close()
125
- console.log('gx.webserver.stop')
127
+ this.#server.close();
128
+ console.log("gx.webserver.stop");
126
129
  }
127
130
  }
128
131
 
129
132
  }
130
133
 
131
- export const webserver = new WebServer()
134
+ export const webserver = new WebServer();
@@ -0,0 +1,15 @@
1
+ import { Gateway, Service } from "../exports.js";
2
+
3
+ class TestService extends Service {
4
+
5
+ 'GET /'(req, res) {
6
+ res.send('Hello World')
7
+ }
8
+ }
9
+
10
+ TestService.start()
11
+ Gateway.start({
12
+ beforeRequest: (req, res) => {
13
+ res.set('X-Test', 'Test')
14
+ }
15
+ })
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "test",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "type": "module",
6
+ "main": "stream.js",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "dependencies": {
14
+ "geonix": "file:.."
15
+ }
16
+ }
package/test/stream.js ADDED
@@ -0,0 +1,38 @@
1
+ import { randomBytes } from 'node:crypto'
2
+ import { Stream, getReadable, connection } from 'geonix'
3
+ import { createWriteStream, readFileSync } from 'node:fs'
4
+ import { createHash } from 'node:crypto'
5
+ import { pipeline } from 'node:stream/promises'
6
+
7
+ await connection.waitUntilReady()
8
+
9
+ const hash = data => createHash('sha512').update(data).digest('base64')
10
+
11
+ const PAYLOAD_SIZE = 1024 * 1024 * 1024
12
+ const TEMP_FILE = '/tmp/geonix.stream_test'
13
+
14
+ console.time('test')
15
+ try {
16
+ const payload = randomBytes(PAYLOAD_SIZE)
17
+ const sourceHash = hash(payload)
18
+
19
+ const source = await getReadable(Stream(payload))
20
+ const dest = createWriteStream(TEMP_FILE)
21
+
22
+ await pipeline(source, dest)
23
+
24
+ const check = readFileSync(TEMP_FILE)
25
+ const destHash = hash(check)
26
+
27
+ if (sourceHash == destHash) {
28
+ console.log('MATCH')
29
+ } else {
30
+ console.error('Destination does not match the source!')
31
+ console.log(payload)
32
+ console.log(check)
33
+ }
34
+ } catch (e) {
35
+ console.error(e)
36
+ } finally {
37
+ console.timeEnd('test')
38
+ }
package/tsconfig.json CHANGED
@@ -6,15 +6,15 @@
6
6
  "src/status/**"
7
7
  ],
8
8
  "compilerOptions": {
9
- "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
10
- "module": "commonjs", /* Specify what module code is generated. */
11
- "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
12
- "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
13
- "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
14
- "outDir": "./build", /* Specify an output folder for all emitted files. */
15
- "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
16
- "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
17
- "strict": true, /* Enable all strict type-checking options. */
18
- "skipLibCheck": true /* Skip type checking all .d.ts files. */
9
+ "target": "es2016",
10
+ "module": "commonjs",
11
+ "allowJs": true,
12
+ "declaration": true,
13
+ "emitDeclarationOnly": true,
14
+ "outDir": "./build",
15
+ "esModuleInterop": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "strict": true,
18
+ "skipLibCheck": true
19
19
  }
20
20
  }