-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: @deephaven/jsapi-nodejs npm package (#2260)
@deephaven/jsapi-nodejs npm package This is initially to serve the vscode extension but should work for any nodejs based app wanting to consume DH jsapis.
- Loading branch information
Showing
11 changed files
with
1,066 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# @deephaven/jsapi-components | ||
|
||
Deephaven utils for consuming Jsapi from a server from a nodejs app. It can | ||
optionally convert the server module format from `ESM` -> `CJS` or `CJS` -> `ESM` | ||
if the server and consumer don't use the same module format. | ||
|
||
## Install | ||
|
||
```bash | ||
npm install --save @deephaven/jsapi-nodejs | ||
``` | ||
|
||
## Usage | ||
|
||
```typescript | ||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
|
||
import { loadModules } from '@deephaven/jsapi-nodejs'; | ||
|
||
const tmpDir = path.join(__dirname, 'tmp'); | ||
|
||
// Download jsapi `ESM` files from DH Community server and export as `CJS` module. | ||
const dhc = await loadModules({ | ||
serverUrl: new URL('http://localhost:10000'), | ||
serverPaths: ['jsapi/dh-core.js', 'jsapi/dh-internal.js'], | ||
download: true, | ||
storageDir: tmpDir, | ||
sourceModuleType: 'esm', | ||
targetModuleType: 'cjs', | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "@deephaven/jsapi-nodejs", | ||
"version": "0.96.0", | ||
"description": "Deephaven utils for consuming Jsapi from a server", | ||
"author": "Deephaven Data Labs LLC", | ||
"license": "Apache-2.0", | ||
"type": "module", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/deephaven/web-client-ui.git", | ||
"directory": "packages/jsapi-nodejs" | ||
}, | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"source": "src/index.ts", | ||
"engines": { | ||
"node": ">=16" | ||
}, | ||
"scripts": { | ||
"build": "cross-env NODE_ENV=production run-p build:*", | ||
"build:babel": "babel ./src --out-dir ./dist --extensions \".ts,.tsx,.js,.jsx\" --source-maps --root-mode upward" | ||
}, | ||
"dependencies": { | ||
"esbuild": "^0.24.0", | ||
"ws": "^8.18.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^22.7.5", | ||
"@types/ws": "^8.5.12" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"sideEffects": false, | ||
"publishConfig": { | ||
"access": "public" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/** | ||
* Return true if given error has a code:string prop. Optionally check if the | ||
* code matches a given value. | ||
* @param err Error to check | ||
* @param code Optional code to check | ||
*/ | ||
export function hasErrorCode( | ||
err: unknown, | ||
code?: string | ||
): err is { code: string } { | ||
if ( | ||
err != null && | ||
typeof err === 'object' && | ||
'code' in err && | ||
typeof err.code === 'string' | ||
) { | ||
return code == null || err.code === code; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Returns true if the given error is an AggregateError. Optionally checks if | ||
* a given code matches the error's code. | ||
* @param err Error to check | ||
* @param code Optional code to check | ||
*/ | ||
export function isAggregateError( | ||
err: unknown, | ||
code?: string | ||
): err is { code: string } { | ||
return hasErrorCode(err, code) && String(err) === 'AggregateError'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
|
||
/** | ||
* Create directories if they do not exist. | ||
* @param dirPaths The paths of the directories to create. | ||
*/ | ||
export function ensureDirectoriesExist(dirPaths: string[]): void { | ||
dirPaths.forEach(dirPath => { | ||
if (!fs.existsSync(dirPath)) { | ||
fs.mkdirSync(dirPath, { recursive: true }); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Get download paths for a list of server paths. | ||
* @param targetDir The directory to download the files to. | ||
* @param serverPaths The paths of the files on the server. | ||
* @returns The paths to download the files to. | ||
*/ | ||
export function getDownloadPaths( | ||
targetDir: string, | ||
serverPaths: string[] | ||
): string[] { | ||
return serverPaths.map(filePath => | ||
path.join(targetDir, path.basename(filePath)) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * from './errorUtils'; | ||
export * from './fsUtils'; | ||
export * from './loaderUtils'; | ||
export * from './polyfillWs'; | ||
export * from './serverUtils'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
import esbuild from 'esbuild'; | ||
|
||
import { downloadFromURL, urlToDirectoryName } from './serverUtils'; | ||
import { polyfillWs } from './polyfillWs'; | ||
import { ensureDirectoriesExist, getDownloadPaths } from './fsUtils'; | ||
|
||
type NonEmptyArray<T> = [T, ...T[]]; | ||
|
||
/** | ||
* Load a list of modules from a server. | ||
* @param serverUrl The URL of the server to load from. | ||
* @param serverPaths The paths of the modules on the server. | ||
* @param download Whether to download the modules from the server. If set to false, | ||
* it's assumed that the modules have already been downloaded and still exist in | ||
* the storage directory. | ||
* @param storageDir The directory to store the downloaded modules. | ||
* @param sourceModuleType module format from the server. | ||
* @param targetFormat (optional) format to be exported. Defaults to | ||
* sourceModuleType. | ||
* @returns The default export of the first module in `serverPaths`. | ||
*/ | ||
export async function loadModules<TMainModule>({ | ||
serverUrl, | ||
serverPaths, | ||
download, | ||
storageDir, | ||
sourceModuleType, | ||
targetModuleType = sourceModuleType, | ||
}: { | ||
serverUrl: URL; | ||
serverPaths: NonEmptyArray<string>; | ||
download: boolean; | ||
storageDir: string; | ||
sourceModuleType: 'esm' | 'cjs'; | ||
targetModuleType?: 'esm' | 'cjs'; | ||
}): Promise<TMainModule> { | ||
polyfillWs(); | ||
|
||
const serverStorageDir = path.join(storageDir, urlToDirectoryName(serverUrl)); | ||
const targetDir = path.join(serverStorageDir, 'target'); | ||
|
||
if (download) { | ||
const needsTranspile = sourceModuleType !== targetModuleType; | ||
const sourceDir = path.join(serverStorageDir, 'source'); | ||
|
||
ensureDirectoriesExist( | ||
needsTranspile ? [sourceDir, targetDir] : [targetDir] | ||
); | ||
|
||
// Download from server | ||
const serverUrls = serverPaths.map( | ||
serverPath => new URL(serverPath, serverUrl) | ||
); | ||
const contents = await Promise.all( | ||
serverUrls.map(url => downloadFromURL(url)) | ||
); | ||
|
||
// Write to disk | ||
const downloadPaths = getDownloadPaths( | ||
needsTranspile ? sourceDir : targetDir, | ||
serverPaths | ||
); | ||
downloadPaths.forEach((downloadPath, i) => { | ||
fs.writeFileSync(downloadPath, contents[i]); | ||
}); | ||
|
||
// Transpile if source and target module types differ | ||
if (needsTranspile) { | ||
await esbuild.build({ | ||
entryPoints: downloadPaths, | ||
bundle: false, | ||
format: targetModuleType, | ||
logLevel: 'error', | ||
platform: 'node', | ||
outdir: targetDir, | ||
}); | ||
} | ||
} | ||
|
||
// We assume the first module is the main module | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const firstFileName = serverPaths[0].split('/').pop()!; | ||
const mainModulePath = path.join(targetDir, firstFileName); | ||
|
||
if (targetModuleType === 'esm') { | ||
return import(mainModulePath); | ||
} | ||
|
||
// eslint-disable-next-line import/no-dynamic-require, global-require | ||
return require(mainModulePath); | ||
} | ||
|
||
export default loadModules; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import ws from 'ws'; | ||
|
||
export function polyfillWs(): void { | ||
if (globalThis.WebSocket == null) { | ||
// Newer versions of NodeJS should eventually have first-class support for | ||
// WebSocket, but for older versions as late as v20, we need to polyfill it. | ||
globalThis.WebSocket = ws as unknown as (typeof globalThis)['WebSocket']; | ||
} | ||
} | ||
|
||
export default polyfillWs; |
Oops, something went wrong.