Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
9a3d218
First commit
SukantGujar Mar 7, 2019
9422491
Add first draft (untested).
SukantGujar Mar 8, 2019
45439ef
Add prod build and rimraf.
SukantGujar Mar 8, 2019
2d2d20f
Split the code, add file server example.
SukantGujar Mar 11, 2019
33a4b1a
Fix compilation error.
SukantGujar Mar 11, 2019
ca0b97b
Remove express from deps, move out fCP in separate file.
SukantGujar Mar 11, 2019
7aa149d
Add description to the packaage.
SukantGujar Mar 11, 2019
5585661
Split code, fix typo and add readme.
SukantGujar Mar 12, 2019
fb8b485
Fix code example in readme.
SukantGujar Mar 12, 2019
3e5724a
Set theme jekyll-theme-slate
SukantGujar Mar 12, 2019
00cb4cd
Rename factory method, fix readme.
SukantGujar Mar 12, 2019
5675015
Merge branch 'master' of ssh://github.com-sukantgujar/SukantGujar/exp…
SukantGujar Mar 12, 2019
fb5a68e
Add npm registry related meta
SukantGujar Mar 12, 2019
1ab683b
Add tests for utils
SukantGujar Mar 12, 2019
0e03c83
Fix returned status when content is not found.
SukantGujar Mar 12, 2019
3b1ba65
Remove console.log
SukantGujar Mar 12, 2019
e44d08b
Add missing setContentRangeHeader test
SukantGujar Mar 12, 2019
805aa95
Add createPartialContentHandler tests
SukantGujar Mar 12, 2019
14b7ad9
Add parseRangeHeader tests.
SukantGujar Mar 12, 2019
81f1f58
v1.0.0
SukantGujar Mar 12, 2019
3e3858c
Create LICENSE
SukantGujar Mar 12, 2019
37d6644
Create CODE_OF_CONDUCT.md
SukantGujar Mar 12, 2019
b02a9d1
Create CONTRIBUTING.md
SukantGujar Mar 12, 2019
672e2fa
Update issue templates
SukantGujar Mar 12, 2019
4f44bf1
Merge branch 'master' of ssh://github.com-sukantgujar/SukantGujar/exp…
SukantGujar Mar 12, 2019
3899aab
Add travis yml
SukantGujar Mar 12, 2019
46f52f0
Specify nodejs version in travis yml
SukantGujar Mar 12, 2019
f31457c
Add travis ci status to readme.
SukantGujar Mar 12, 2019
a1d2ca7
Add yarn cache to travis yml
SukantGujar Mar 12, 2019
6262af1
Add nyc for code-coverage.
SukantGujar Mar 12, 2019
6387127
Add mongo content provider example.
SukantGujar Mar 19, 2019
4b28b40
Add copyfiles for bundling example assets.
SukantGujar Mar 19, 2019
36941da
Add createPartialContentHandler prefix to logs.
SukantGujar Mar 19, 2019
76ef80b
Update readme with examples
SukantGujar Mar 20, 2019
c883366
v1.0.1
SukantGujar Mar 22, 2019
b580e18
Add linting
SukantGujar Mar 26, 2019
f685315
Add npmjs.org friendly changes.
SukantGujar Mar 27, 2019
ff52b32
chore(deps): upgrade yarn
eliandoran Dec 11, 2024
3354d5d
chore(deps): upgrade typescript in order to be able to build on newer…
eliandoran Dec 11, 2024
3c21050
refactor(test): move outside of src folder
eliandoran Dec 11, 2024
46ee587
fix(deps): tests not running properly
eliandoran Dec 11, 2024
5e0fb0e
fix(utils): allow for Unicode characters in Content-Disposition
eliandoran Dec 11, 2024
b1be69d
feat(ci): deploy package using GitHub Actions
eliandoran Dec 11, 2024
478b981
fix(ci): setup yarn with corepack
eliandoran Dec 12, 2024
dd979cf
fix(ci): publish not working
eliandoran Dec 12, 2024
e7ee51f
v1.22.22
eliandoran Dec 12, 2024
19d6f10
feat(ci): switch to yarn classic
eliandoran Dec 12, 2024
12fd536
fix(ci): remove mongodb example
eliandoran Dec 12, 2024
91800df
chore(build): bump to 1.2.0
eliandoran Dec 12, 2024
e25c16b
chore(ci): deploy to npm
eliandoran Dec 13, 2024
5e8dff5
chore(build): change metadata
eliandoran Dec 13, 2024
0b38559
chore(ci): push on tag only
eliandoran Dec 13, 2024
a033692
fix(ci): issue with wrong yarn version
eliandoran Dec 13, 2024
d41e244
chore(build): bump to 1.0.1
eliandoran Dec 13, 2024
a3b6d4d
chore: convert to LF line ending
pano9000 Apr 3, 2025
de92391
deps: update to express ^5.1.0
pano9000 Apr 3, 2025
0449b19
Merge pull request #1 from TriliumNext/express_v5
JYC333 Apr 4, 2025
5e55118
chore(deps): update yarn.lock
eliandoran Apr 6, 2025
b80bfb7
chore(release): bump to 1.1.0
eliandoran Apr 6, 2025
5dc060f
Add '_regroup_monorepo/express-partial-content/' from commit 'b80bfb7…
eliandoran May 2, 2025
24224d2
chore(nx): create empty project
eliandoran May 2, 2025
adc5e89
chore(express-partial-content): move source files
eliandoran May 2, 2025
7976f43
chore(express-partial-content): solve import errors
eliandoran May 2, 2025
910cd68
chore(express-partial-content): solve type errors
eliandoran May 2, 2025
dc8a0c6
chore(express-partial-content): integrate with server
eliandoran May 2, 2025
abede62
fix(import): MP4 videos not recognized due to upstream bug
eliandoran May 2, 2025
d673682
chore(express-partial-content): integrate more files
eliandoran May 2, 2025
c1a5b1a
chore(express-partial-content): integrate tests and convert to vitest
eliandoran May 2, 2025
f8ccbb3
chore(express-partial-content): remove sample files
eliandoran May 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/edit-docs/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
"eslint.config.mjs"
],
"references": [
{
"path": "../server/tsconfig.app.json"
},
{
"path": "../desktop/tsconfig.app.json"
},
Expand Down
3 changes: 3 additions & 0 deletions apps/edit-docs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"files": [],
"include": [],
"references": [
{
"path": "../server"
},
{
"path": "../desktop"
},
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"@anthropic-ai/sdk": "0.39.0",
"@braintree/sanitize-url": "7.1.1",
"@triliumnext/commons": "workspace:*",
"@triliumnext/express-partial-content": "1.0.1",
"@triliumnext/express-partial-content": "workspace:*",
"@triliumnext/turndown-plugin-gfm": "workspace:*",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
Expand Down
5 changes: 5 additions & 0 deletions apps/server/src/services/import/mime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ describe("#getMime", () => {
["test.zip"], "application/zip"
],

[
"MP4 videos are supported",
["video.mp4"], "video/mp4"
],

[
"unknown MIME type not recognized by mimeTypes.lookup",
["test.fake"], false
Expand Down
3 changes: 2 additions & 1 deletion apps/server/src/services/import/mime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ const EXTENSION_TO_MIME = new Map<string, string>([
[".ts", "text/x-typescript"],
[".excalidraw", "application/json"],
[".mermaid", "text/vnd.mermaid"],
[".mmd", "text/vnd.mermaid"]
[".mmd", "text/vnd.mermaid"],
[".mp4", "video/mp4"] // https://github.com/jshttp/mime-types/issues/138
]);

/** @returns false if MIME is not detected */
Expand Down
3 changes: 3 additions & 0 deletions apps/server/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
{
"path": "../../packages/turndown-plugin-gfm/tsconfig.lib.json"
},
{
"path": "../../packages/express-partial-content/tsconfig.lib.json"
},
{
"path": "../../packages/commons/tsconfig.lib.json"
}
Expand Down
3 changes: 3 additions & 0 deletions apps/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
{
"path": "../../packages/turndown-plugin-gfm"
},
{
"path": "../../packages/express-partial-content"
},
{
"path": "../../packages/commons"
},
Expand Down
22 changes: 22 additions & 0 deletions packages/express-partial-content/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
},
"keepClassNames": true,
"externalHelpers": true,
"loose": true
},
"module": {
"type": "commonjs"
},
"sourceMaps": true,
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
}
21 changes: 21 additions & 0 deletions packages/express-partial-content/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Sukant Gujar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
149 changes: 149 additions & 0 deletions packages/express-partial-content/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
![Travis CI Status](https://travis-ci.com/SukantGujar/express-partial-content.svg?branch=master)

# About

A HTTP 206 Partial Content handler to serve any readable stream partially in Express.

Based on this blog post: https://www.codeproject.com/Articles/813480/HTTP-Partial-Content-In-Node-js.

# Installation

`yarn add express-partial-content`

OR

`npm install express-partial-content`

> Note: `Express` package is a peer dependency for `express-partial-content` and must be present in dependencies of the host package.

# Usage

From the `express-file-server` example:

1. Implement a `ContentProvider` function which prepares and returns a `Content` object:

import { promisify } from "util";
import fs from "fs";
import { Range, ContentDoesNotExistError, ContentProvider } from "express-partial-content";
import {logger} from "./logger";

const statAsync = promisify(fs.stat);
const existsAsync = promisify(fs.exists);

export const fileContentProvider: ContentProvider = async (req: Request) => {
// Read file name from route params.
const fileName = req.params.name;
const file = `${__dirname}/files/${fileName}`;
if (!(await existsAsync(file))) {
throw new ContentDoesNotExistError(`File doesn't exist: ${file}`);
}
const stats = await statAsync(file);
const totalSize = stats.size;
const mimeType = "application/octet-stream";
const getStream = (range?: Range) => {
if (!range) {
// Request if for complete content.
return fs.createReadStream(file);
}
// Partial content request.
const { start, end } = range;
logger.debug(`start: ${start}, end: ${end}`);
return fs.createReadStream(file, { start, end });
};
return {
fileName,
totalSize,
mimeType,
getStream
};
};

2. In your express code, use `createPartialContentHandler` factory method to generate an express handler for serving partial content for the route of your choice:

import {createPartialContentHandler} from "express-partial-content";
import {logger} from "./logger";

const handler = createPartialContentHandler(fileContentProvider, logger);

const app = express();
const port = 8080;

// File name is a route param.
app.get("/files/:name", handler);

app.listen(port, () => {
logger.debug("Server started!");
});

3. Run your server and use a multi-part/multi-connection download utility like [aria2c](https://aria2.github.io/) to test it:

aria -x5 -k1M http://localhost:8080/files/readme.txt

# Examples

There one examples in the `src/examples` folder:

1. `express-file-server`: Implements a file based `ContentProvider`.

## Running the examples:

1. `express-file-server`: Run the following commands, the server will listen on http://localhost:8080/.

yarn build:dev
yarn copy-assets
yarn run:examples:file

## Connecting to the running server:

Browse to `https://localhost:8080/files/readme.txt`

# Reference

## createPartialContentHandler function:

This is a factory method which generates a partial content handler for express routes.

### Arguments:

- `contentProvider`: An `async` function which returns a Promise resolved to a `Content` object (see below).
- `logger`: Any logging implementation which has a `debug(message:string, extra: any)` method. Either `winston` or `bunyan` loggers should work.

### Returns:

- Express Route Handler: `createPartialContentHandler` returns an express handler which can be mapped to an Express route to serve partial content.

## ContentProvider function:

This function _needs to be implemented by you_. It's purpose is to fetch and return `Content` object containing necessary metadata and methods to stream the content partially. This method is invoked by the express handler (returned by `createPartialContentHandler`) on each request.

### Arguments:

- `Request`: It receives the `Request` object as it's only input. Use the information available in `Request` to find the requested content, e.g. through `Request.params` or query string, headers etc.

### Returns:

- `Promise<Content>`: See below.

### Throws:

- `ContentDoesNotExistError`: Throw this to indicate that the content doesn't exist. The generated express handler will return a 404 in this case.
> Note: Any message provided to the `ContentDoesNotExistError` object is returned to the client.

## Content object:

This object contains metadata and methods which describe the content. The `ContentProvider` method builds and returns it.

### Properties:

All the properties of this object are used to return content metadata to the client as various `Response` headers.

- `fileName`: Used as the `Content-Disposition` header's `filename` value.
- `mimeType`: Used as the `Content-Type` header value.
- `totalSize`: Used as the `Content-Length` header value.

### Methods:

- `getStream(range?: Range)`: This method should return a readable stream initialized to the provided `range` (optional). You need to handle two cases:

- range is `null`: When `range` is not-specified, the client is requesting the full content. In this case, return the stream as it is.
- range is `{start, end}`: When client requests partial content, the `start` and `end` values will point to the corresponding byte positions (0 based and inclusive) of the content. You need to return stream limited to these positions.
23 changes: 23 additions & 0 deletions packages/express-partial-content/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import baseConfig from "../../eslint.config.mjs";

export default [
...baseConfig,
{
"files": [
"**/*.json"
],
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": [
"{projectRoot}/eslint.config.{js,cjs,mjs}"
]
}
]
},
"languageOptions": {
"parser": (await import('jsonc-eslint-parser'))
}
}
];
47 changes: 47 additions & 0 deletions packages/express-partial-content/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@triliumnext/express-partial-content",
"description": "A partial content handler implementation for any readable stream with Express. Based on this blog post: https://www.codeproject.com/Articles/813480/HTTP-Partial-Content-In-Node-js.",
"license": "MIT",
"version": "1.1.0",
"type": "module",
"private": true,
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"development": "./src/index.ts",
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"keywords": [
"partial-content",
"206",
"stream",
"typescript"
],
"nx": {
"name": "express-partial-content",
"targets": {
"build": {
"executor": "@nx/js:swc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "packages/express-partial-content/dist",
"tsConfig": "packages/express-partial-content/tsconfig.lib.json",
"packageJson": "packages/express-partial-content/package.json",
"main": "packages/express-partial-content/src/index.ts",
"stripLeadingPaths": true
}
}
}
},
"dependencies": {
"tslib": "^2.3.0"
}
}
22 changes: 22 additions & 0 deletions packages/express-partial-content/src/Content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Range } from "./Range.js";
import { Stream } from "stream";
export interface Content {
/**
* Returns a readable stream based on the provided range (optional).
* @param {Range} range The start-end range of stream data.
* @returns {Stream} A readable stream
*/
getStream(range?: Range): Stream;
/**
* Total size of the content
*/
readonly totalSize: number;
/**
* Mime type to be sent in Content-Type header
*/
readonly mimeType: string;
/**
* File name to be sent in Content-Disposition header
*/
readonly fileName: string;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export class ContentDoesNotExistError extends Error {
}
6 changes: 6 additions & 0 deletions packages/express-partial-content/src/ContentProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Request } from "express";
import type { Content } from "./Content.js";
/**
* @type {function (Request): Promise<Content>}
*/
export type ContentProvider = (req: Request) => Promise<Content>;
3 changes: 3 additions & 0 deletions packages/express-partial-content/src/Logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface Logger {
debug(message: string, extra?: any): void;
}
4 changes: 4 additions & 0 deletions packages/express-partial-content/src/Range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Range = {
start: number;
end: number;
};
5 changes: 5 additions & 0 deletions packages/express-partial-content/src/RangeParserError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class RangeParserError extends Error {
constructor(start: any, end: any) {
super(`Invalid start and end values: ${start}-${end}.`);
}
}
Loading
Loading