Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Dec 11, 2024
0 parents commit 6608370
Show file tree
Hide file tree
Showing 22 changed files with 3,605 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true

[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
24 changes: 24 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Push

on: push

jobs:
test:
name: Lint, build, and test on node 20.x and ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Install deps (with cache)
uses: bahmutov/npm-install@v1
- name: Lint codebase
run: yarn lint
- name: Build codebase
run: yarn build
- name: Test codebase
run: yarn test --coverage
- name: Upload coverage
run: bash <(curl -s https://codecov.io/bash)
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
esm
node_modules
coverage
dist
*.log
*.swp
.vscode/
.eslintcache
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"arrowParens": "avoid",
"proseWrap": "always"
}
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# generic-filehandle2

[![NPM version](https://img.shields.io/npm/v/generic-filehandle2.svg?style=flat-square)](https://npmjs.org/package/generic-filehandle2)
[![Coverage Status](https://img.shields.io/codecov/c/github/GMOD/generic-filehandle2/master.svg?style=flat-square)](https://codecov.io/gh/GMOD/generic-filehandle2/branch/master)
[![Build Status](https://img.shields.io/github/actions/workflow/status/GMOD/generic-filehandle2/push.yml?branch=master)](https://github.com/GMOD/generic-filehandle2/actions)

Provides a uniform interface for accessing binary data from local files, remote
HTTP resources, and Blob data in the browser.

## Usage

```js
import { LocalFile, RemoteFile, BlobFile } from 'generic-filehandle2'

// operate on a local file path
const local = new LocalFile('/some/file/path/file.txt')

// operate on a remote file path
const remote = new RemoteFile('http://somesite.com/file.txt')

// operate on blob objects
const blobfile = new BlobFile(new Blob([some_data], { type: 'text/plain' }))

// read slice of file, works on remote files with range request
const buf1 = await remote.read(10, 10)
// read whole file
const buf2 = await remote.readFile()
```

Important: under node.js, you should supply a fetch function to the RemoteFile
constructor

```js
import { RemoteFile } from 'generic-filehandle2'
import fetch from 'node-fetch'
const remote = new RemoteFile('http://somesite.com/file.txt', { fetch })
```

## API

### `async read(length: number, position: number=0, opts?: Options): Promise<Uint8Array>`

- length - a length of data to read
- position - the byte offset in the file to read from
- opts - optional Options object

Returns a Promise for the Uint8Array

### `async readFile(opts?: Options): Promise<Uint8Array | string>`

Returns a Promise for Uint8Array or string containing the contents of the whole
file.

### `async stat() : Promise<{size: number}>`

Returns a Promise for an object containing as much information about the file as
is available. At minimum, the `size` of the file will be present.

### Options

The Options object for the constructor, `read` and `readFile` can contain abort
signal to customize behavior. All entries are optional.

- signal `<AbortSignal>` - an AbortSignal that is passed to remote file fetch()
API or other file readers
- headers `<Object <string, string> >`- extra HTTP headers to pass to remote
file fetch() API
- overrides `<Object>` - extra parameters to pass to the remote file fetch() API
- fetch `<Function>` - a custom fetch callback, otherwise defaults to the
environment (initialized in constructor)
- encoding `<string>` - if specified, then this function returns a string.
Otherwise it returns a Uint8Array. Currently only `utf8` encoding is
supported.

The Options object for `readFile` can also contain an entry `encoding`. The
default is no encoding, in which case the file contents are returned as a
Uint8Array. Currently, the only available encoding is `utf8`, and specifying
that will cause the file contents to be returned as a string. For compatibility
with the Node API, the `readFile` method will accept the string "utf8" instead
of an Options object.

## References

This module attempts to modernize the original generic-filehandle API by not
requiring node.js Buffer polyfill, and in doing so disconnected somewhat with
the true Node.js fs API https://github.com/GMOD/generic-filehandle
104 changes: 104 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import eslint from '@eslint/js'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{
ignores: [
'webpack.config.js',
'dist/*',
'esm/*',
'example/*',
'eslint.config.mjs',
],
},
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.lint.json'],
tsconfigRootDir: import.meta.dirname,
},
},
},
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylisticTypeChecked,
...tseslint.configs.strictTypeChecked,

eslintPluginUnicorn.configs['flat/recommended'],
{
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
'no-console': [
'warn',
{
allow: ['error', 'warn'],
},
],
'no-underscore-dangle': 0,
curly: 'error',
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/ban-ts-comment': 0,
semi: ['error', 'never'],
'unicorn/no-new-array': 'off',
'unicorn/no-empty-file': 'off',
'unicorn/prefer-type-error': 'off',
'unicorn/prefer-modern-math-apis': 'off',
'unicorn/prefer-node-protocol': 'off',
'unicorn/no-unreadable-array-destructuring': 'off',
'unicorn/no-abusive-eslint-disable': 'off',
'unicorn/no-array-callback-reference': 'off',
'unicorn/number-literal-case': 'off',
'unicorn/prefer-add-event-listener': 'off',
'unicorn/prefer-top-level-await': 'off',
'unicorn/consistent-function-scoping': 'off',
'unicorn/no-await-expression-member': 'off',
'unicorn/no-lonely-if': 'off',
'unicorn/consistent-destructuring': 'off',
'unicorn/prefer-module': 'off',
'unicorn/prefer-optional-catch-binding': 'off',
'unicorn/no-useless-undefined': 'off',
'unicorn/no-null': 'off',
'unicorn/no-nested-ternary': 'off',
'unicorn/filename-case': 'off',
'unicorn/catch-error-name': 'off',
'unicorn/prevent-abbreviations': 'off',
'unicorn/prefer-code-point': 'off',
'unicorn/numeric-separators-style': 'off',
'unicorn/no-array-for-each': 'off',
'unicorn/prefer-spread': 'off',
'unicorn/explicit-length-check': 'off',
'unicorn/prefer-regexp-test': 'off',
'unicorn/relative-url-style': 'off',
'unicorn/prefer-math-trunc': 'off',
'unicorn/prefer-query-selector': 'off',
'unicorn/no-negated-condition': 'off',
'unicorn/switch-case-braces': 'off',
'unicorn/prefer-switch': 'off',
'unicorn/better-regex': 'off',
'unicorn/no-for-loop': 'off',
'unicorn/escape-case': 'off',
'unicorn/prefer-number-properties': 'off',
'unicorn/no-process-exit': 'off',
'unicorn/prefer-at': 'off',
'unicorn/prefer-blob-reading-methods': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/prefer-nullish-coalescing': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-base-to-string': 'off',
'@typescript-eslint/no-unnecessary-type-parameters': 'off',
},
},
)
67 changes: 67 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "generic-filehandle2",
"description": "uniform interface for accessing binary data from local files, remote HTTP resources, and browser Blob data",
"version": "3.2.0",
"main": "dist/index.js",
"module": "esm/index.js",
"repository": "GMOD/generic-filehandle2",
"license": "MIT",
"author": {
"name": "Colin Diesh",
"email": "[email protected]",
"url": "https://github.com/cmdcolin"
},
"engines": {
"node": ">=12"
},
"files": [
"dist",
"esm",
"src"
],
"scripts": {
"test": "vitest",
"coverage": "npm test -- --coverage",
"lint": "eslint --report-unused-disable-directives --max-warnings 0 src test",
"clean": "rimraf dist esm",
"prebuild": "yarn clean",
"build:esm": "tsc --outDir esm",
"build:es5": "tsc --module commonjs --outDir dist",
"build": "yarn build:esm && yarn build:es5",
"watch": "babel src --out-dir dist --extensions \".ts,.tsx\" --source-maps inline --watch",
"preversion": "yarn lint && yarn test --run && yarn build",
"postversion": "git push --follow-tags"
},
"keywords": [
"bionode",
"biojs",
"ucsc",
"genomics"
],
"devDependencies": {
"@types/fetch-mock": "^7.3.8",
"@types/node": "^20.11.24",
"@types/range-parser": "^1.2.7",
"@typescript-eslint/eslint-plugin": "^8.16.0",
"@typescript-eslint/parser": "^8.16.0",
"@vitest/coverage-v8": "^2.1.8",
"eslint": "^9.16.0",
"eslint-plugin-unicorn": "^56.0.1",
"fetch-mock": "^9.0.0",
"node-fetch": "^2.0.0",
"prettier": "^3.4.1",
"range-parser": "^1.2.1",
"rimraf": "^6.0.0",
"standard-changelog": "^6.0.0",
"typescript": "^5.7.0",
"typescript-eslint": "^8.18.0",
"vitest": "^2.1.8"
},
"publishConfig": {
"access": "public"
},
"browser": {
"./dist/localFile.js": false,
"./esm/localFile.js": false
}
}
64 changes: 64 additions & 0 deletions src/blobFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { GenericFilehandle, FilehandleOptions, Stats } from './filehandle'

/**
* Blob of binary data fetched from a local file (with FileReader).
*
* Adapted by Robert Buels and Garrett Stevens from the BlobFetchable object in
* the Dalliance Genome Explorer, which is copyright Thomas Down 2006-2011.
*/
export default class BlobFile implements GenericFilehandle {
private blob: Blob
private size: number

public constructor(blob: Blob) {
this.blob = blob
this.size = blob.size
}

public async read(
length: number,
position = 0,
): Promise<Uint8Array<ArrayBuffer>> {
// short-circuit a read of 0 bytes here, because browsers actually sometimes
// crash if you try to read 0 bytes from a local file!
if (!length) {
return new Uint8Array(0)
}

const start = position
const end = start + length

return new Uint8Array(await this.blob.slice(start, end).arrayBuffer())
}

public async readFile(): Promise<Uint8Array<ArrayBuffer>>
public async readFile(options: BufferEncoding): Promise<string>
public async readFile<T extends undefined>(
options:
| Omit<FilehandleOptions, 'encoding'>
| (Omit<FilehandleOptions, 'encoding'> & { encoding: T }),
): Promise<Uint8Array<ArrayBuffer>>
public async readFile<T extends BufferEncoding>(
options: Omit<FilehandleOptions, 'encoding'> & { encoding: T },
): Promise<string>
public async readFile(
options?: FilehandleOptions | BufferEncoding,
): Promise<Uint8Array<ArrayBuffer> | string> {
const encoding = typeof options === 'string' ? options : options?.encoding
if (encoding === 'utf8') {
return this.blob.text()
} else if (encoding) {
throw new Error(`unsupported encoding: ${encoding}`)
} else {
return new Uint8Array(await this.blob.arrayBuffer())
}
}

public async stat(): Promise<Stats> {
return { size: this.size }
}

public async close(): Promise<void> {
return
}
}
1 change: 1 addition & 0 deletions src/declare.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'es6-promisify'
Loading

0 comments on commit 6608370

Please sign in to comment.