├── .husky └── .gitignore ├── .prettierignore ├── packages ├── pdf-generator │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── injection-tokens │ │ │ └── pdf-generator-options.injection-token.ts │ │ │ ├── interfaces │ │ │ ├── to-pdf-options.interface.ts │ │ │ ├── to-pdf-result.interface.ts │ │ │ └── pdf-generator-options.interface.ts │ │ │ ├── exceptions │ │ │ └── pdf-generation.exception.ts │ │ │ ├── puppeteer-web-page-to-pdf │ │ │ ├── puppeteer-web-page-to-pdf.service.spec.ts │ │ │ └── puppeteer-web-page-to-pdf.service.ts │ │ │ ├── pdf-generator.module.ts │ │ │ └── pdf-generator.controller.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── jest.config.js │ ├── tsconfig.lib.json │ ├── CHANGELOG.md │ ├── README.md │ └── package.json └── streaming │ ├── src │ ├── lib │ │ ├── shared │ │ │ └── interfaces │ │ │ │ ├── streaming-params.ts │ │ │ │ └── streamable-resource.ts │ │ ├── streaming │ │ │ ├── shared │ │ │ │ ├── interfaces │ │ │ │ │ ├── streaming-type.ts │ │ │ │ │ ├── response.streamer.ts │ │ │ │ │ └── response-streamer.factory.ts │ │ │ │ └── utils │ │ │ │ │ └── prepare-haders.utils.ts │ │ │ ├── express │ │ │ │ ├── streaming-express.module.ts │ │ │ │ └── response-streaming │ │ │ │ │ ├── full-response.streamer.ts │ │ │ │ │ ├── response-streamer.factory.ts │ │ │ │ │ ├── partial-response.streamer.ts │ │ │ │ │ ├── full-response.streamer.spec.ts │ │ │ │ │ └── partial-response.streamer.spec.ts │ │ │ └── fastify │ │ │ │ ├── streaming-fastify.module.ts │ │ │ │ └── response-streaming │ │ │ │ ├── response-streamer.factory.ts │ │ │ │ ├── full-response.streamer.ts │ │ │ │ ├── partial-response.streamer.ts │ │ │ │ ├── full-response.streamer.spec.ts │ │ │ │ └── partial-response.streamer.spec.ts │ │ ├── download │ │ │ ├── shared │ │ │ │ ├── interfaces │ │ │ │ │ ├── downloadable-resource.ts │ │ │ │ │ ├── download.provider.ts │ │ │ │ │ └── download-provider.factory.ts │ │ │ │ └── utils │ │ │ │ │ └── prepare-headers.ts │ │ │ ├── fastify │ │ │ │ ├── download-provider.factory.ts │ │ │ │ ├── download-fastify.module.ts │ │ │ │ ├── download-provider.service.spec.ts │ │ │ │ └── download-provider.service.ts │ │ │ └── express │ │ │ │ ├── download-provider-express.factory.ts │ │ │ │ ├── download-express.module.ts │ │ │ │ ├── download-provider-express.service.ts │ │ │ │ └── download-provider-express.service.spec.ts │ │ └── url-streaming │ │ │ ├── interfaces │ │ │ └── url-streaming.provider.ts │ │ │ └── native │ │ │ ├── url-streamer-native.module.ts │ │ │ └── native-url-streamer.provider.ts │ └── index.ts │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── tsconfig.spec.json │ ├── jest.config.js │ ├── tsconfig.lib.json │ ├── CHANGELOG.md │ ├── package.json │ └── README.md ├── .idea ├── misc.xml ├── vcs.xml ├── .gitignore ├── modules.xml ├── nestjs-packages.iml └── inspectionProfiles │ └── Project_Default.xml ├── jest.config.js ├── lerna.json ├── .prettierrc ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── pr.yml │ └── master.yml ├── nx.json ├── .eslintrc.json ├── package.json ├── README.md ├── commitlint.config.js ├── CONTRIBUTING.md └── .gitignore /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/dist/**/* 2 | **/CHANGELOG.md 3 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/pdf-generator.module'; 2 | -------------------------------------------------------------------------------- /packages/pdf-generator/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": {}, 4 | "ignorePatterns": ["!**/*"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/shared/interfaces/streaming-params.ts: -------------------------------------------------------------------------------- 1 | export interface StreamingParams { 2 | end?: number; 3 | start: number; 4 | } 5 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/shared/interfaces/streaming-type.ts: -------------------------------------------------------------------------------- 1 | export enum StreamingType { 2 | Full = 'full', 3 | Partial = 'partial' 4 | } 5 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/injection-tokens/pdf-generator-options.injection-token.ts: -------------------------------------------------------------------------------- 1 | export const PDF_GENERATOR_OPTIONS = Symbol('PDF_GENERATOR_OPTIONS'); 2 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/interfaces/to-pdf-options.interface.ts: -------------------------------------------------------------------------------- 1 | export interface ToPdfOptions { 2 | headers?: Record; 3 | waitFor?: string; 4 | } 5 | -------------------------------------------------------------------------------- /packages/streaming/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/pdf-generator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jest"] 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/interfaces/to-pdf-result.interface.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | 3 | export interface ToPdfResult { 4 | stream: Readable; 5 | name: string; 6 | } 7 | -------------------------------------------------------------------------------- /packages/streaming/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "rules": { 4 | "@typescript-eslint/no-explicit-any": ["off"] 5 | }, 6 | "ignorePatterns": ["!**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/shared/interfaces/downloadable-resource.ts: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | 3 | export interface DownloadableResource { 4 | contentType: string; 5 | name: string; 6 | stream: Promise; 7 | } 8 | -------------------------------------------------------------------------------- /packages/streaming/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc/libs/typed-urls", 5 | "types": ["jest", "node"] 6 | }, 7 | "include": ["**/*.spec.ts", "**/*.d.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/pdf-generator/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc/libs/typed-urls", 5 | "types": ["jest", "node"] 6 | }, 7 | "include": ["**/*.spec.ts", "**/*.d.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/streaming/jest.config.js: -------------------------------------------------------------------------------- 1 | const pack = require('./package'); 2 | 3 | module.exports = { 4 | displayName: pack.name, 5 | name: pack.name, 6 | preset: '../../jest.config.js', 7 | globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json' } } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/pdf-generator/jest.config.js: -------------------------------------------------------------------------------- 1 | const pack = require('./package'); 2 | 3 | module.exports = { 4 | displayName: pack.name, 5 | name: pack.name, 6 | preset: '../../jest.config.js', 7 | globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json' } } 8 | }; 9 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/exceptions/pdf-generation.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatus } from '@nestjs/common'; 2 | 3 | export class PdfGenerationException extends Error { 4 | constructor(message: string, public statusCode?: HttpStatus) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/url-streaming/interfaces/url-streaming.provider.ts: -------------------------------------------------------------------------------- 1 | import { StreamableResource } from '../../shared/interfaces/streamable-resource'; 2 | 3 | export abstract class UrlStreamingProvider { 4 | abstract getStream(url: string): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], 3 | transform: { 4 | '^.+\\.(ts|js|html)$': 'ts-jest' 5 | }, 6 | moduleFileExtensions: ['ts', 'js', 'html'], 7 | verbose: true, 8 | coverageDirectory: '/coverage/' 9 | }; 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "packages": ["packages/*"], 4 | "version": "independent", 5 | "command": { 6 | "version": { 7 | "allowBranch": "master", 8 | "message": "chore(release): publish" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 100, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "jsxBracketSameLine": false, 8 | "semi": true, 9 | "arrowParens": "avoid", 10 | "proseWrap": "preserve", 11 | "bracketSpacing": true 12 | } 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/shared/interfaces/streamable-resource.ts: -------------------------------------------------------------------------------- 1 | import { StreamingParams } from './streaming-params'; 2 | import { Readable } from 'stream'; 3 | 4 | export interface StreamableResource { 5 | contentType: string; 6 | size: number; 7 | stream: (options?: StreamingParams) => Promise; 8 | } 9 | -------------------------------------------------------------------------------- /.idea/nestjs-packages.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/fastify/download-provider.factory.ts: -------------------------------------------------------------------------------- 1 | import { DownloadProviderService } from './download-provider.service'; 2 | import { DownloadProviderFactory } from '../shared/interfaces/download-provider.factory'; 3 | 4 | export const downloadProviderFactory: DownloadProviderFactory = (res: any) => 5 | new DownloadProviderService(res); 6 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/shared/interfaces/download.provider.ts: -------------------------------------------------------------------------------- 1 | import { DownloadableResource } from './downloadable-resource'; 2 | 3 | export abstract class DownloadProvider { 4 | /** 5 | * Prepare response to give the possibility of downloading stream data 6 | */ 7 | abstract provide(resource: DownloadableResource, tryToOpen?: boolean): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/express/download-provider-express.factory.ts: -------------------------------------------------------------------------------- 1 | import { DownloadProviderExpressService } from './download-provider-express.service'; 2 | import { DownloadProviderFactory } from '../shared/interfaces/download-provider.factory'; 3 | 4 | export const downloadProviderExpressFactory: DownloadProviderFactory = (res: any) => 5 | new DownloadProviderExpressService(res); 6 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/shared/interfaces/download-provider.factory.ts: -------------------------------------------------------------------------------- 1 | import { Response } from 'express'; 2 | import { DownloadProvider } from './download.provider'; 3 | import { FastifyReply } from 'fastify'; 4 | 5 | export const downloadProviderFactoryToken = Symbol('downloadProviderFactoryToken'); 6 | 7 | export type DownloadProviderFactory = (res: Response | FastifyReply) => DownloadProvider; 8 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/shared/interfaces/response.streamer.ts: -------------------------------------------------------------------------------- 1 | import { StreamableResource } from '../../../shared/interfaces/streamable-resource'; 2 | import { StreamingParams } from '../../../shared/interfaces/streaming-params'; 3 | 4 | export abstract class ResponseStreamer { 5 | abstract stream( 6 | stream: StreamableResource, 7 | params?: StreamingParams, 8 | withCache?: number 9 | ): Promise; 10 | } 11 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/interfaces/pdf-generator-options.interface.ts: -------------------------------------------------------------------------------- 1 | import { AngularUniversalOptions } from '@nestjs/ng-universal'; 2 | 3 | export interface PdfGeneratorOptions extends AngularUniversalOptions { 4 | /** 5 | * Array of names of headers forwarded to the Angular. 6 | */ 7 | forwardHeaders?: string[]; 8 | /** 9 | * Selector of the element that has to be invisible 10 | */ 11 | waitFor?: string; 12 | } 13 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/url-streaming/native/url-streamer-native.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { NativeUrlStreamerProvider } from './native-url-streamer.provider'; 3 | import { UrlStreamingProvider } from '../interfaces/url-streaming.provider'; 4 | 5 | @Module({ 6 | controllers: [], 7 | providers: [{ provide: UrlStreamingProvider, useClass: NativeUrlStreamerProvider }], 8 | exports: [UrlStreamingProvider] 9 | }) 10 | export class UrlStreamerNativeModule {} 11 | -------------------------------------------------------------------------------- /packages/pdf-generator/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/cjs", 5 | "target": "es2015", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "importHelpers": false, 12 | "types": [], 13 | "lib": ["dom", "es2018"], 14 | "rootDir": "./src" 15 | }, 16 | "exclude": ["**/*.spec.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/shared/interfaces/response-streamer.factory.ts: -------------------------------------------------------------------------------- 1 | import { FastifyReply } from 'fastify'; 2 | import { StreamingType } from './streaming-type'; 3 | import { Response } from 'express'; 4 | import { ResponseStreamer } from './response.streamer'; 5 | 6 | export const responseStreamerFactoryToken = Symbol('responseStreamerFactoryToken'); 7 | 8 | export interface ResponseStreamerFactory { 9 | (type: StreamingType, res: FastifyReply | Response): ResponseStreamer; 10 | } 11 | -------------------------------------------------------------------------------- /packages/streaming/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/cjs", 5 | "target": "es2015", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": false, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "importHelpers": false, 12 | "types": [], 13 | "lib": ["dom", "es2018"], 14 | "rootDir": "./src" 15 | }, 16 | "exclude": ["**/*.spec.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/fastify/download-fastify.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { downloadProviderFactoryToken } from '../shared/interfaces/download-provider.factory'; 3 | import { downloadProviderFactory } from './download-provider.factory'; 4 | 5 | @Module({ 6 | providers: [ 7 | { 8 | provide: downloadProviderFactoryToken, 9 | useValue: downloadProviderFactory 10 | } 11 | ], 12 | exports: [downloadProviderFactoryToken] 13 | }) 14 | export class DownloadFastifyModule {} 15 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/shared/utils/prepare-headers.ts: -------------------------------------------------------------------------------- 1 | import { OutgoingHttpHeaders } from 'http'; 2 | 3 | export const prepareHeaders = ( 4 | params: { fileName: string; contentType: string }, 5 | options?: { tryToOpen?: boolean } 6 | ): OutgoingHttpHeaders => { 7 | return { 8 | 'Access-Control-Expose-Headers': 'Content-Disposition', 9 | 'Content-Disposition': `${options?.tryToOpen ? 'inline' : 'attachment'}; filename=${ 10 | params.fileName 11 | }`, 12 | 'Content-Type': params.contentType 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/express/download-express.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { downloadProviderFactoryToken } from '../shared/interfaces/download-provider.factory'; 3 | import { downloadProviderExpressFactory } from './download-provider-express.factory'; 4 | 5 | @Module({ 6 | providers: [ 7 | { 8 | provide: downloadProviderFactoryToken, 9 | useValue: downloadProviderExpressFactory 10 | } 11 | ], 12 | exports: [downloadProviderFactoryToken] 13 | }) 14 | export class DownloadExpressModule {} 15 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/express/streaming-express.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { responseStreamerFactoryToken } from '../shared/interfaces/response-streamer.factory'; 3 | import { responseStreamerFactory } from './response-streaming/response-streamer.factory'; 4 | 5 | @Module({ 6 | providers: [ 7 | { 8 | provide: responseStreamerFactoryToken, 9 | useValue: responseStreamerFactory 10 | } 11 | ], 12 | exports: [responseStreamerFactoryToken] 13 | }) 14 | export class StreamingExpressModule {} 15 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/fastify/streaming-fastify.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { responseStreamerFactoryToken } from '../shared/interfaces/response-streamer.factory'; 3 | import { responseStreamerFactory } from './response-streaming/response-streamer.factory'; 4 | 5 | @Module({ 6 | providers: [ 7 | { 8 | provide: responseStreamerFactoryToken, 9 | useValue: responseStreamerFactory 10 | } 11 | ], 12 | exports: [responseStreamerFactoryToken] 13 | }) 14 | export class StreamingFastifyModule {} 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "module": "commonjs", 5 | "declaration": true, 6 | "noImplicitAny": true, 7 | "removeComments": false, 8 | "strict": true, 9 | "strictNullChecks": true, 10 | "noLib": false, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "target": "es2017", 14 | "lib": ["es7"], 15 | "baseUrl": "./src", 16 | "paths": { 17 | "@house-of-angular/*": ["./*/src"] 18 | } 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: MaciejSikorski 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/puppeteer-web-page-to-pdf/puppeteer-web-page-to-pdf.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PuppeteerWebPageToPdfService } from './puppeteer-web-page-to-pdf.service'; 3 | 4 | describe('PuppeteerWebPageToPdfService', () => { 5 | let service: PuppeteerWebPageToPdfService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PuppeteerWebPageToPdfService] 10 | }).compile(); 11 | 12 | service = module.get(PuppeteerWebPageToPdfService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/streaming/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/download/express/download-express.module'; 2 | export * from './lib/download/fastify/download-fastify.module'; 3 | export * from './lib/download/shared/interfaces/download.provider'; 4 | export * from './lib/download/shared/interfaces/download-provider.factory'; 5 | export * from './lib/streaming/express/streaming-express.module'; 6 | export * from './lib/streaming/fastify/streaming-fastify.module'; 7 | export * from './lib/streaming/shared/interfaces/response-streamer.factory'; 8 | export * from './lib/streaming/shared/interfaces/response.streamer'; 9 | export * from './lib/url-streaming/native/url-streamer-native.module'; 10 | export * from './lib/url-streaming/interfaces/url-streaming.provider'; 11 | export * from './lib/streaming/shared/interfaces/streaming-type'; 12 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/fastify/download-provider.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { DownloadProviderService } from './download-provider.service'; 3 | import { FastifyReply } from 'fastify'; 4 | 5 | describe('DownloadProviderService', () => { 6 | let service: DownloadProviderService; 7 | 8 | beforeEach(async () => { 9 | const module = await Test.createTestingModule({ 10 | providers: [ 11 | { 12 | provide: DownloadProviderService, 13 | useFactory: (): DownloadProviderService => 14 | new DownloadProviderService(({} as unknown) as FastifyReply) 15 | } 16 | ] 17 | }).compile(); 18 | 19 | service = module.get(DownloadProviderService); 20 | }); 21 | 22 | it('should be defined', () => { 23 | expect(service).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/express/download-provider-express.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Response } from 'express'; 3 | import { prepareHeaders } from '../shared/utils/prepare-headers'; 4 | import { DownloadableResource } from '../shared/interfaces/downloadable-resource'; 5 | import { DownloadProvider } from '../shared/interfaces/download.provider'; 6 | 7 | @Injectable() 8 | export class DownloadProviderExpressService implements DownloadProvider { 9 | constructor(private response: Response) {} 10 | 11 | async provide(resource: DownloadableResource, tryToOpen = false): Promise { 12 | const head = prepareHeaders( 13 | { fileName: resource.name, contentType: resource.contentType }, 14 | { tryToOpen } 15 | ); 16 | this.response.writeHead(200, head); 17 | (await resource.stream).pipe(this.response); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/fastify/download-provider.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { prepareHeaders } from '../shared/utils/prepare-headers'; 3 | import { DownloadableResource } from '../shared/interfaces/downloadable-resource'; 4 | import { DownloadProvider } from '../shared/interfaces/download.provider'; 5 | import { FastifyReply } from 'fastify'; 6 | 7 | @Injectable() 8 | export class DownloadProviderService implements DownloadProvider { 9 | constructor(private response: FastifyReply) {} 10 | 11 | async provide(resource: DownloadableResource, tryToOpen = false): Promise { 12 | const head = prepareHeaders( 13 | { fileName: resource.name, contentType: resource.contentType }, 14 | { tryToOpen } 15 | ); 16 | this.response 17 | .code(200) 18 | .headers(head) 19 | .send(await resource.stream); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/download/express/download-provider-express.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test } from '@nestjs/testing'; 2 | import { DownloadProviderExpressService } from './download-provider-express.service'; 3 | import { Response } from 'express'; 4 | 5 | describe('DownloadProviderExpressService', () => { 6 | let service: DownloadProviderExpressService; 7 | 8 | beforeEach(async () => { 9 | const module = await Test.createTestingModule({ 10 | providers: [ 11 | { 12 | provide: DownloadProviderExpressService, 13 | useFactory: (): DownloadProviderExpressService => 14 | new DownloadProviderExpressService(({} as unknown) as Response) 15 | } 16 | ] 17 | }).compile(); 18 | 19 | service = module.get(DownloadProviderExpressService); 20 | }); 21 | 22 | it('should be defined', () => { 23 | expect(service).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/express/response-streaming/full-response.streamer.ts: -------------------------------------------------------------------------------- 1 | import { Response } from 'express'; 2 | import { StreamableResource } from '../../../shared/interfaces/streamable-resource'; 3 | import { StreamingParams } from '../../../shared/interfaces/streaming-params'; 4 | import { prepareHeadersForFullStreaming } from '../../shared/utils/prepare-haders.utils'; 5 | import { ResponseStreamer } from '../../shared/interfaces/response.streamer'; 6 | 7 | export class FullResponseStreamer extends ResponseStreamer { 8 | constructor(protected res: Response) { 9 | super(); 10 | } 11 | 12 | async stream( 13 | stream: StreamableResource, 14 | options?: StreamingParams, 15 | withCache?: number 16 | ): Promise { 17 | const head = prepareHeadersForFullStreaming(stream, { withCache }); 18 | this.res.writeHead(200, head); 19 | (await stream.stream()).pipe(this.res); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/express/response-streaming/response-streamer.factory.ts: -------------------------------------------------------------------------------- 1 | import { NotImplementedException } from '@nestjs/common'; 2 | import { FullResponseStreamer } from './full-response.streamer'; 3 | import { PartialResponseStreamer } from './partial-response.streamer'; 4 | import { StreamingType } from '../../shared/interfaces/streaming-type'; 5 | import { ResponseStreamerFactory } from '../../shared/interfaces/response-streamer.factory'; 6 | 7 | export const responseStreamerFactory: ResponseStreamerFactory = (type: StreamingType, res: any) => { 8 | let exhaustCheck: never; 9 | switch (type) { 10 | case StreamingType.Full: 11 | return new FullResponseStreamer(res); 12 | case StreamingType.Partial: 13 | return new PartialResponseStreamer(res); 14 | default: 15 | exhaustCheck = type; 16 | throw new NotImplementedException(`Streamer for streaming type ${type} is not implemented`); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/fastify/response-streaming/response-streamer.factory.ts: -------------------------------------------------------------------------------- 1 | import { NotImplementedException } from '@nestjs/common'; 2 | import { FullResponseStreamer } from './full-response.streamer'; 3 | import { PartialResponseStreamer } from './partial-response.streamer'; 4 | import { StreamingType } from '../../shared/interfaces/streaming-type'; 5 | import { ResponseStreamerFactory } from '../../shared/interfaces/response-streamer.factory'; 6 | 7 | export const responseStreamerFactory: ResponseStreamerFactory = (type: StreamingType, res: any) => { 8 | let exhaustCheck: never; 9 | switch (type) { 10 | case StreamingType.Full: 11 | return new FullResponseStreamer(res); 12 | case StreamingType.Partial: 13 | return new PartialResponseStreamer(res); 14 | default: 15 | exhaustCheck = type; 16 | throw new NotImplementedException(`Streamer for streaming type ${type} is not implemented`); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/express/response-streaming/partial-response.streamer.ts: -------------------------------------------------------------------------------- 1 | import { Response } from 'express'; 2 | import { StreamableResource } from '../../../shared/interfaces/streamable-resource'; 3 | import { StreamingParams } from '../../../shared/interfaces/streaming-params'; 4 | import { prepareHeadersForPartialStreaming } from '../../shared/utils/prepare-haders.utils'; 5 | import { ResponseStreamer } from '../../shared/interfaces/response.streamer'; 6 | 7 | export class PartialResponseStreamer extends ResponseStreamer { 8 | constructor(protected res: Response) { 9 | super(); 10 | } 11 | 12 | async stream( 13 | stream: StreamableResource, 14 | params: StreamingParams, 15 | withCache?: number 16 | ): Promise { 17 | const head = prepareHeadersForPartialStreaming({ ...stream, ...params }, { withCache }); 18 | this.res.writeHead(206, head); 19 | (await stream.stream(params)).pipe(this.res); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/fastify/response-streaming/full-response.streamer.ts: -------------------------------------------------------------------------------- 1 | import { StreamableResource } from '../../../shared/interfaces/streamable-resource'; 2 | import { StreamingParams } from '../../../shared/interfaces/streaming-params'; 3 | import { FastifyReply } from 'fastify'; 4 | import { prepareHeadersForFullStreaming } from '../../shared/utils/prepare-haders.utils'; 5 | import { ResponseStreamer } from '../../shared/interfaces/response.streamer'; 6 | 7 | export class FullResponseStreamer extends ResponseStreamer { 8 | constructor(protected res: FastifyReply) { 9 | super(); 10 | } 11 | 12 | async stream( 13 | stream: StreamableResource, 14 | options?: StreamingParams, 15 | withCache?: number 16 | ): Promise { 17 | const head = prepareHeadersForFullStreaming(stream, { withCache }); 18 | 19 | this.res 20 | .code(200) 21 | .headers(head) 22 | .send(await stream.stream()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/fastify/response-streaming/partial-response.streamer.ts: -------------------------------------------------------------------------------- 1 | import { StreamableResource } from '../../../shared/interfaces/streamable-resource'; 2 | import { StreamingParams } from '../../../shared/interfaces/streaming-params'; 3 | import { FastifyReply } from 'fastify'; 4 | import { prepareHeadersForPartialStreaming } from '../../shared/utils/prepare-haders.utils'; 5 | import { ResponseStreamer } from '../../shared/interfaces/response.streamer'; 6 | 7 | export class PartialResponseStreamer extends ResponseStreamer { 8 | constructor(protected res: FastifyReply) { 9 | super(); 10 | } 11 | 12 | async stream( 13 | stream: StreamableResource, 14 | params: StreamingParams, 15 | withCache?: number 16 | ): Promise { 17 | const head = prepareHeadersForPartialStreaming({ ...stream, ...params }, { withCache }); 18 | this.res 19 | .code(206) 20 | .headers(head) 21 | .send(await stream.stream(params)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: MaciejSikorski 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/pdf-generator.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common'; 2 | import { PuppeteerWebPageToPdfService } from './puppeteer-web-page-to-pdf/puppeteer-web-page-to-pdf.service'; 3 | import { PdfGeneratorController } from './pdf-generator.controller'; 4 | import { AngularUniversalModule } from '@nestjs/ng-universal'; 5 | import { DownloadExpressModule } from '@house-of-angular/nestjs-streaming'; 6 | import { PdfGeneratorOptions } from './interfaces/pdf-generator-options.interface'; 7 | import { PDF_GENERATOR_OPTIONS } from './injection-tokens/pdf-generator-options.injection-token'; 8 | 9 | @Module({ 10 | providers: [PuppeteerWebPageToPdfService], 11 | controllers: [PdfGeneratorController] 12 | }) 13 | export class PdfGeneratorModule { 14 | static forRoot(options: PdfGeneratorOptions): DynamicModule { 15 | return { 16 | module: PdfGeneratorModule, 17 | imports: [AngularUniversalModule.forRoot(options), DownloadExpressModule], 18 | providers: [{ provide: PDF_GENERATOR_OPTIONS, useValue: options }] 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/url-streaming/native/native-url-streamer.provider.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import * as https from 'https'; 3 | import { UrlStreamingProvider } from '../interfaces/url-streaming.provider'; 4 | import { StreamableResource } from '../../shared/interfaces/streamable-resource'; 5 | 6 | @Injectable() 7 | export class NativeUrlStreamerProvider extends UrlStreamingProvider { 8 | getStream(url: string): Promise { 9 | return new Promise((resolve, reject) => { 10 | https.get(url, response => { 11 | if ( 12 | !response.statusCode || 13 | response.statusCode > 300 || 14 | !response.headers['content-length'] || 15 | !response.headers['content-type'] 16 | ) { 17 | reject(); 18 | } else { 19 | resolve({ 20 | size: +response.headers['content-length'], 21 | contentType: response.headers['content-type'], 22 | stream: () => Promise.resolve(response) 23 | }); 24 | } 25 | }); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksRunnerOptions": { 3 | "default": { 4 | "runner": "nx/tasks-runners/default", 5 | "options": { 6 | "cacheableOperations": [ 7 | "build", 8 | "lint", 9 | "test", 10 | "preversion", 11 | "prepare" 12 | ] 13 | } 14 | } 15 | }, 16 | "targetDefaults": { 17 | "build": { 18 | "dependsOn": [ 19 | "^build" 20 | ], 21 | "outputs": [ 22 | "{projectRoot}/build" 23 | ] 24 | }, 25 | "lint": { 26 | "dependsOn": [ 27 | "^lint" 28 | ] 29 | }, 30 | "test": { 31 | "dependsOn": [ 32 | "^test" 33 | ] 34 | }, 35 | "preversion": { 36 | "dependsOn": [ 37 | "^preversion" 38 | ] 39 | }, 40 | "prepare": { 41 | "dependsOn": [ 42 | "^prepare" 43 | ] 44 | } 45 | }, 46 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 47 | "namedInputs": { 48 | "default": [ 49 | "{projectRoot}/**/*", 50 | "sharedGlobals" 51 | ], 52 | "sharedGlobals": [], 53 | "production": [ 54 | "default" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 2018, 6 | "sourceType": "module", 7 | "project": "./tsconfig.json" 8 | }, 9 | "ignorePatterns": ["**/*"], 10 | "plugins": ["@typescript-eslint"], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "prettier", 16 | "prettier/@typescript-eslint" 17 | ], 18 | "rules": { 19 | "@typescript-eslint/explicit-member-accessibility": "off", 20 | "@typescript-eslint/explicit-function-return-type": "error", 21 | "@typescript-eslint/no-parameter-properties": "off", 22 | "@typescript-eslint/no-empty-interface": "off", 23 | "@typescript-eslint/no-unused-vars": [ 24 | "error", 25 | { 26 | "varsIgnorePattern": "exhaustCheck" 27 | } 28 | ], 29 | "@typescript-eslint/ban-ts-comment": "warn", 30 | "@typescript-eslint/explicit-module-boundary-types": "off" 31 | }, 32 | "overrides": [ 33 | { 34 | "files": ["*.tsx"], 35 | "rules": { 36 | "@typescript-eslint/no-unused-vars": "off" 37 | } 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist 2 | 3 | Please check if your PR fulfills the following requirements: 4 | 5 | - [ ] The commit message follows our guidelines: https://github.com/HouseOfAngular/nestjs-packages/blob/master/CONTRIBUTING.md#git-guidelines 6 | - [ ] Tests for the changes have been added (for bug fixes / features) 7 | 8 | ## PR Type 9 | 10 | What kind of change does this PR introduce? 11 | 12 | 13 | 14 | ``` 15 | [ ] Bugfix 16 | [ ] Feature 17 | [ ] Code style update (formatting, local variables) 18 | [ ] Refactoring (no functional changes, no api changes) 19 | [ ] Build related changes 20 | [ ] CI related changes 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## What is the current behavior? 26 | 27 | 28 | 29 | Issue Number: N/A 30 | 31 | ## What is the new behavior? 32 | 33 | ## Does this PR introduce a breaking change? 34 | 35 | ``` 36 | [ ] Yes 37 | [ ] No 38 | ``` 39 | 40 | 41 | 42 | ## Other information 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ], 7 | "scripts": { 8 | "audit": "lerna-audit", 9 | "build": "lerna run build", 10 | "format:check": "prettier --check .", 11 | "format:check:package": "prettier --check \"packages/${PACKAGE}/**/*\"", 12 | "format:write": "prettier --write .", 13 | "lerna": "lerna", 14 | "lint": "lerna run lint", 15 | "test": "jest" 16 | }, 17 | "devDependencies": { 18 | "@commitlint/cli": "^17.6.5", 19 | "@commitlint/config-conventional": "^17.6.5", 20 | "@nestjs/common": "^9.4.3", 21 | "@nestjs/core": "^9.4.3", 22 | "@nestjs/platform-express": "^9.4.3", 23 | "@nestjs/platform-fastify": "^9.4.3", 24 | "@nestjs/testing": "^9.4.3", 25 | "@types/express": "^4.17.6", 26 | "@types/jest": "^29.5.4", 27 | "@typescript-eslint/eslint-plugin": "^6.4.1", 28 | "@typescript-eslint/parser": "^6.4.1", 29 | "eslint": "^7.12.1", 30 | "eslint-config-prettier": "^6.0.0", 31 | "fastify": "^4.18.0", 32 | "jest": "^29.6.4", 33 | "lerna": "^7.0.1", 34 | "lerna-audit": "^1.2.0", 35 | "nx": "^16.3.2", 36 | "prettier": "^2.1.2", 37 | "reflect-metadata": "^0.1.13", 38 | "ts-jest": "^29.1.1", 39 | "typescript": "^4.0.5" 40 | }, 41 | "engines": { 42 | "node": ">=18.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/pdf-generator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.1.2](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-ng-pdf-generator@0.1.1...@valueadd/nestjs-ng-pdf-generator@0.1.2) (2021-01-18) 7 | 8 | 9 | ### Bug Fixes 10 | 11 | * **pdf-generator:** disable cors checks ([cfc1f72](https://github.com/valueadd-poland/nestjs-packages/commit/cfc1f72a4893ef9e01fed8d088fc048e18227323)) 12 | 13 | 14 | 15 | 16 | 17 | ## [0.1.1](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-ng-pdf-generator@0.1.0...@valueadd/nestjs-ng-pdf-generator@0.1.1) (2020-11-26) 18 | 19 | **Note:** Version bump only for package @valueadd/nestjs-ng-pdf-generator 20 | 21 | 22 | 23 | 24 | 25 | # [0.1.0](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-ng-pdf-generator@0.1.0-alpha.0...@valueadd/nestjs-ng-pdf-generator@0.1.0) (2020-11-10) 26 | 27 | **Note:** Version bump only for package @valueadd/nestjs-ng-pdf-generator 28 | 29 | 30 | 31 | 32 | 33 | # 0.1.0-alpha.0 (2020-11-10) 34 | 35 | 36 | ### Features 37 | 38 | * **pdf-generator:** add package ([a9074d3](https://github.com/valueadd-poland/nestjs-packages/commit/a9074d3602373918090b6f7dc003d1b3a102343d)) 39 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/shared/utils/prepare-haders.utils.ts: -------------------------------------------------------------------------------- 1 | import { OutgoingHttpHeaders } from 'http'; 2 | 3 | const prepareDefaultStreamingHeaders = ( 4 | params: { contentType: string }, 5 | options?: { withCache?: number } 6 | ): OutgoingHttpHeaders => { 7 | let head: OutgoingHttpHeaders = { 8 | 'Content-Type': params.contentType 9 | }; 10 | if (options?.withCache) { 11 | head = { 12 | ...head, 13 | 'Cache-Control': `max-age=${options.withCache}, public`, 14 | Expires: new Date(new Date().getDate() + options.withCache).toDateString() 15 | }; 16 | } 17 | 18 | return head; 19 | }; 20 | export const prepareHeadersForFullStreaming = ( 21 | params: { size: number; contentType: string }, 22 | options?: { withCache?: number } 23 | ): OutgoingHttpHeaders => { 24 | const head = prepareDefaultStreamingHeaders(params, options); 25 | return { ...head, 'Content-Length': params.size }; 26 | }; 27 | 28 | export const prepareHeadersForPartialStreaming = ( 29 | params: { size: number; start: number; end?: number; contentType: string }, 30 | options: { withCache?: number } 31 | ): OutgoingHttpHeaders => { 32 | const head = prepareDefaultStreamingHeaders(params, options); 33 | 34 | const end = params.end || params.size - 1; 35 | return { 36 | ...head, 37 | 'Content-Range': `bytes ${params.start}-${end}/${params.size}`, 38 | 'Accept-Ranges': 'bytes' 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /packages/streaming/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | # [0.2.0](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-streaming@0.1.0...@valueadd/nestjs-streaming@0.2.0) (2020-11-26) 7 | 8 | 9 | ### Features 10 | 11 | * **streaming:** add fastify support ([43ce447](https://github.com/valueadd-poland/nestjs-packages/commit/43ce447d0a4ffe740a6772b26ea4cbc66f1fd741)) 12 | 13 | 14 | 15 | 16 | 17 | # [0.1.0](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-streaming@0.1.0-alpha.0...@valueadd/nestjs-streaming@0.1.0) (2020-11-09) 18 | 19 | **Note:** Version bump only for package @valueadd/nestjs-streaming 20 | 21 | 22 | 23 | 24 | 25 | # [0.1.0-alpha.0](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-streaming@0.0.1...@valueadd/nestjs-streaming@0.1.0-alpha.0) (2020-11-09) 26 | 27 | 28 | ### Features 29 | 30 | * **streaming:** add streaming and url streamer modules ([bc4deb6](https://github.com/valueadd-poland/nestjs-packages/commit/bc4deb65a0cd72b4164e906bc2395c12f9811d94)) 31 | 32 | 33 | 34 | 35 | 36 | ## [0.0.1](https://github.com/valueadd-poland/nestjs-packages/compare/@valueadd/nestjs-streaming@0.0.1-alpha.1...@valueadd/nestjs-streaming@0.0.1) (2020-11-05) 37 | 38 | **Note:** Version bump only for package @valueadd/nestjs-streaming 39 | -------------------------------------------------------------------------------- /packages/pdf-generator/README.md: -------------------------------------------------------------------------------- 1 | # @house-of-angular/nestjs-ng-pdf-generator 2 | 3 | [![version](https://img.shields.io/npm/v/@house-of-angular/nestjs-ng-pdf-generator.svg)](https://www.npmjs.com/package/@house-of-angular/nestjs-ng-pdf-generator) 4 | [![downloads](https://img.shields.io/npm/dt/@house-of-angular/nestjs-ng-pdf-generator.svg)](https://www.npmjs.com/package/@house-of-angular/nestjs-ng-pdf-generator) 5 | 6 | > Extension to the [@nestjs/ng-universal](https://github.com/nestjs/ng-universal) that adds the generic endpoint 'pdf-generator' that will return Angular view converted to the pdf. 7 | > GET request to the pdf-generator/:any-path will open your app at the "any-path" and trigger the "to pdf" browser action. 8 | 9 | ## Usage 10 | 11 | 1. Adjust your angular app for the SSR with NestJS (sample: https://github.com/TrilonIO/universal-nest). 12 | 2. Instead of `AngularUniversalModule.forRoot` import `PdfGeneratorModule.forRoot` 13 | 14 | ## Notice 15 | 16 | - Name of the generated pdf will be taken from the tab title. 17 | - Your client app will be opened as a localhost. It may cause a CORS exception on requests to your API. 18 | - Your Angular app have access to the Request object. You can pass headers from the request to the 'pdf-generator/' by adding them to the forwardHeaders option in `PdfGeneratorModule.forRoot`. You can use it to authenticate your Angular app by forwarding Authentication header. 19 | - Exceptions from the HttpClient in the client application are passed as the result of PdfGenerator. 20 | -------------------------------------------------------------------------------- /packages/streaming/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@house-of-angular/nestjs-streaming", 3 | "version": "0.3.0", 4 | "description": "Useful providers that allows working with streaming like responses", 5 | "keywords": [ 6 | "nestjs", 7 | "NestJS" 8 | ], 9 | "homepage": "https://github.com/HouseOfAngular/nestjs-packages", 10 | "bugs": { 11 | "url": "https://github.com/HouseOfAngular/nestjs-packages/issues" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/HouseOfAngular/nestjs-packages.git", 16 | "directory": "packages/streaming" 17 | }, 18 | "license": "MIT", 19 | "author": { 20 | "name": "ValueAdd sp. z o.o.", 21 | "email": "contact@houseofangular.io", 22 | "url": "https://houseofangular.io/" 23 | }, 24 | "main": "./dist/cjs/index.js", 25 | "typings": "./dist/cjs/index.d.ts", 26 | "directories": { 27 | "lib": "src", 28 | "test": "__tests__" 29 | }, 30 | "files": [ 31 | "dist", 32 | "package.json", 33 | "README.md" 34 | ], 35 | "scripts": { 36 | "build": "rm -rf dist && tsc --build tsconfig.lib.json", 37 | "lint": "eslint src/**/**", 38 | "test": "jest", 39 | "preversion": "npm run build" 40 | }, 41 | "peerDependencies": { 42 | "@nestjs/common": "^9.4.3" 43 | }, 44 | "optionalDependencies": { 45 | "@nestjs/platform-express": "^9.4.3", 46 | "@nestjs/platform-fastify": "^9.4.3" 47 | }, 48 | "devDependencies": { 49 | "stream-mock": "^2.0.5" 50 | }, 51 | "publishConfig": { 52 | "access": "public" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/fastify/response-streaming/full-response.streamer.spec.ts: -------------------------------------------------------------------------------- 1 | import { FullResponseStreamer } from './full-response.streamer'; 2 | 3 | describe('FullResponseStreamer', () => { 4 | describe('#stream', () => { 5 | test('set headers and streams', async () => { 6 | const result = {}; 7 | const res: any = { headers: jest.fn(() => res), code: jest.fn(() => res), send: jest.fn() }; 8 | const streamer = new FullResponseStreamer(res); 9 | const stream = jest.fn(); 10 | 11 | stream.mockImplementation(() => result); 12 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }); 13 | 14 | expect(res.code).toHaveBeenCalledWith(200); 15 | expect(res.headers).toHaveBeenCalledWith({ 16 | 'Content-Length': 2048, 17 | 'Content-Type': 'video/mp4' 18 | }); 19 | expect(stream).toHaveBeenCalledWith(); 20 | expect(res.send).toHaveBeenCalledWith(result); 21 | }); 22 | 23 | test('set headers and streams with cache', async () => { 24 | const result = {}; 25 | const res: any = { headers: jest.fn(() => res), code: jest.fn(() => res), send: jest.fn() }; 26 | const streamer = new FullResponseStreamer(res); 27 | const stream = jest.fn(); 28 | 29 | stream.mockImplementation(() => result); 30 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, undefined, 2592000); 31 | 32 | expect(res.code).toHaveBeenCalledWith(200); 33 | expect(res.headers).toHaveBeenCalledWith({ 34 | 'Content-Length': 2048, 35 | 'Content-Type': 'video/mp4', 36 | 'Cache-Control': `max-age=2592000, public`, 37 | Expires: new Date(new Date().getDate() + 2592000).toDateString() 38 | }); 39 | expect(stream).toHaveBeenCalledWith(); 40 | expect(res.send).toHaveBeenCalledWith(result); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/pdf-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@house-of-angular/nestjs-ng-pdf-generator", 3 | "version": "0.2.0", 4 | "description": "Pdf generator with for NestJS with templates based on Angular", 5 | "keywords": [ 6 | "pdf", 7 | "nestjs", 8 | "angular", 9 | "angular-universal" 10 | ], 11 | "homepage": "https://github.com/HouseOfAngular/nestjs-packages", 12 | "bugs": { 13 | "url": "https://github.com/HouseOfAngular/nestjs-packages/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/HouseOfAngular/nestjs-packages.git" 18 | }, 19 | "license": "MIT", 20 | "author": "Maciej Sikorski ", 21 | "main": "dist/cjs/index.js", 22 | "typings": "dist/cjs/index.d.ts", 23 | "files": [ 24 | "dist", 25 | "package.json", 26 | "README.md" 27 | ], 28 | "scripts": { 29 | "build": "rm -rf dist && tsc --build tsconfig.lib.json", 30 | "lint": "eslint src/**/**", 31 | "test": "jest", 32 | "preversion": "npm run build" 33 | }, 34 | "dependencies": { 35 | "@nestjs/ng-universal": "^7.1.3", 36 | "@nguniversal/express-engine": "^15.2.1", 37 | "@house-of-angular/nestjs-streaming": "^0.3.0", 38 | "puppeteer": "^20.7.1" 39 | }, 40 | "devDependencies": { 41 | "@angular/common": "15.2.9", 42 | "@angular/core": "15.2.9", 43 | "@angular/platform-server": "15.2.9" 44 | }, 45 | "peerDependencies": { 46 | "@angular/animations": "^15.2.1", 47 | "@angular/common": "^15.2.1", 48 | "@angular/platform-server": "^15.2.1", 49 | "@nestjs/common": "^9.4.3", 50 | "@nestjs/core": "^9.4.3", 51 | "@nestjs/platform-express": "^9.4.3", 52 | "@nguniversal/common": "15.2.1", 53 | "@nguniversal/express-engine": "^15.2.1", 54 | "express": "^4.17.1", 55 | "zone.js": "^0.10.0 || ^0.11.0" 56 | }, 57 | "publishConfig": { 58 | "access": "public" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/express/response-streaming/full-response.streamer.spec.ts: -------------------------------------------------------------------------------- 1 | import { ObjectReadableMock, ObjectWritableMock } from 'stream-mock'; 2 | import { FullResponseStreamer } from './full-response.streamer'; 3 | 4 | describe('FullResponseStreamer', () => { 5 | const streamData = [0, 0, 0, 0]; 6 | 7 | describe('#stream', () => { 8 | test('set headers and streams', async () => { 9 | const res: any = new ObjectWritableMock(); 10 | res.writeHead = jest.fn(); 11 | const streamer = new FullResponseStreamer(res); 12 | const stream = jest.fn(); 13 | 14 | stream.mockImplementation(() => new ObjectReadableMock(streamData)); 15 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }); 16 | 17 | expect(res.writeHead).toHaveBeenCalledWith(200, { 18 | 'Content-Length': 2048, 19 | 'Content-Type': 'video/mp4' 20 | }); 21 | expect(stream).toHaveBeenCalledWith(); 22 | 23 | res.on('finish', () => { 24 | expect(res.data).toEqual(streamData); 25 | }); 26 | }); 27 | 28 | test('set headers and streams with cache', async () => { 29 | const res: any = new ObjectWritableMock(); 30 | res.writeHead = jest.fn(); 31 | const streamer = new FullResponseStreamer(res); 32 | const stream = jest.fn(); 33 | 34 | stream.mockImplementation(() => new ObjectReadableMock(streamData)); 35 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, undefined, 2592000); 36 | 37 | expect(res.writeHead).toHaveBeenCalledWith(200, { 38 | 'Content-Length': 2048, 39 | 'Content-Type': 'video/mp4', 40 | 'Cache-Control': `max-age=2592000, public`, 41 | Expires: new Date(new Date().getDate() + 2592000).toDateString() 42 | }); 43 | expect(stream).toHaveBeenCalledWith(); 44 | 45 | res.on('finish', () => { 46 | expect(res.data).toEqual(streamData); 47 | }); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # House of Angular NestJS Packages 2 | 3 | [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) 4 | [![build](https://github.com/HouseOfAngular/nestjs-packages/workflows/MASTER%20CI/badge.svg)](https://github.com/HouseOfAngular/nestjs-packages/actions?query=workflow%3A%22MASTER+CI%22) 5 | 6 | A collection of packages, modules and utilities for NestJS. 7 | 8 | | Package | Description | Version | Changelog | 9 | | ------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | 10 | | [`@house-of-angular/nestjs-streaming`](packages/streaming) | Useful providers that allows working with streaming like responses | [![version](https://img.shields.io/npm/v/@house-of-angular/nestjs-streaming.svg)](https://www.npmjs.com/package/@house-of-angular/nestjs-streaming) | [changelog](packages/streaming/CHANGELOG.md) | 11 | | [`@house-of-angular/nestjs-ng-pdf-generator`](packages/pdf-generator) | Extension to the @nestjs/ng-universal with pdf-generator api | [![version](https://img.shields.io/npm/v/@house-of-angular/nestjs-ng-pdf-generator.svg)](https://www.npmjs.com/package/@house-of-angular/nestjs-ng-pdf-generator) | [changelog](packages/pdf-generator/CHANGELOG.md) | 12 | 13 | ## Development 14 | 15 | ### Setup 16 | 17 | - `$ npm install` 18 | - `$ npm run lerna bootstrap` 19 | 20 | ### Publish packages 21 | 22 | - `npm run lerna version -- --conventional-commits` 23 | - `npm run lerna publish from-git` 24 | -------------------------------------------------------------------------------- /packages/streaming/README.md: -------------------------------------------------------------------------------- 1 | # @house-of-angular/nestjs-streaming 2 | 3 | [![version](https://img.shields.io/npm/v/@house-of-angular/nestjs-streaming.svg)](https://www.npmjs.com/package/@house-of-angular/nestjs-streaming) 4 | [![downloads](https://img.shields.io/npm/dt/@house-of-angular/nestjs-streaming.svg)](https://www.npmjs.com/package/@house-of-angular/nestjs-streaming) 5 | 6 | Useful providers that allows working with streaming like responses 7 | 8 | ## Installation 9 | 10 | `npm install --save-dev @house-of-angular/nestjs-streaming` 11 | 12 | ## Available modules 13 | 14 | - DownloadExpressModule 15 | - StreamingExpressModule 16 | - UrlStreamerNativeModule 17 | 18 | ## Usage 19 | 20 | Download nad Streaming modules provide services that pass your stream as a response and set headers appropriate for each case. 21 | 22 | UrlStreamer provide a service that allows you to easily get the url as a stream. 23 | 24 | #### Example 25 | 26 | #### **`app.controller.ts`** 27 | 28 | ```ts 29 | import { Controller, Get, Inject, Res } from '@nestjs/common'; 30 | import { 31 | ResponseStreamerFactory, 32 | responseStreamerFactoryToken, 33 | StreamingType 34 | } from '@house-of-angular/nestjs-streaming'; 35 | 36 | @Controller() 37 | export class AppController { 38 | constructor( 39 | @Inject(responseStreamerFactoryToken) 40 | private responseStreamerFactory: ResponseStreamerFactory 41 | ) {} 42 | 43 | @Get('') 44 | async downloadFile(@Res() res): Promise { 45 | return this.responseStreamerFactory(StreamingType.Full, res).stream({ 46 | contentType: 'application/png', 47 | size: 1024, 48 | stream: () => fs.createReadStream('file.png') 49 | }); 50 | } 51 | } 52 | ``` 53 | 54 | #### **`app.module.ts`** 55 | 56 | ```ts 57 | import { Module } from '@nestjs/common'; 58 | import { DownloadExpressModule } from '@house-of-angular/nestjs-streaming'; 59 | import { AppController } from './app.controller'; 60 | 61 | @Module({ 62 | imports: [DownloadExpressModule], 63 | controllers: [AppController] 64 | }) 65 | export class AppModule {} 66 | ``` 67 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/pdf-generator.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Inject, Req, Res } from '@nestjs/common'; 2 | import { HttpAdapterHost } from '@nestjs/core'; 3 | import { Request, Response } from 'express'; 4 | import { PuppeteerWebPageToPdfService } from './puppeteer-web-page-to-pdf/puppeteer-web-page-to-pdf.service'; 5 | import { DownloadProviderFactory, downloadProviderFactoryToken } from '@house-of-angular/nestjs-streaming'; 6 | import { URL } from 'url'; 7 | import { PdfGeneratorOptions } from './interfaces/pdf-generator-options.interface'; 8 | import { PDF_GENERATOR_OPTIONS } from './injection-tokens/pdf-generator-options.injection-token'; 9 | 10 | @Controller('pdf-generator') 11 | export class PdfGeneratorController { 12 | constructor( 13 | @Inject(downloadProviderFactoryToken) private downloadProviderFactory: DownloadProviderFactory, 14 | private webPageToPdfService: PuppeteerWebPageToPdfService, 15 | private readonly httpAdapterHost: HttpAdapterHost, 16 | @Inject(PDF_GENERATOR_OPTIONS) private pdfGeneratorOptions: PdfGeneratorOptions 17 | ) {} 18 | 19 | @Get('*') 20 | async generatePdf(@Req() request: Request, @Res() response: Response): Promise { 21 | const port = this.httpAdapterHost.httpAdapter.getHttpServer().address().port; 22 | const templateUrl = `http://localhost:${port}/${request.url.replace('/pdf-generator', '')}`; 23 | 24 | let headers; 25 | if (this.pdfGeneratorOptions.forwardHeaders) { 26 | headers = {} as Record; 27 | for (const header of this.pdfGeneratorOptions.forwardHeaders) { 28 | const headerValue = request.header(header); 29 | if (headerValue) { 30 | headers[header] = headerValue; 31 | } 32 | } 33 | } 34 | 35 | const { name, stream } = await this.webPageToPdfService.toPdf(new URL(templateUrl), { 36 | headers: headers, 37 | waitFor: this.pdfGeneratorOptions.waitFor 38 | }); 39 | 40 | return this.downloadProviderFactory(response).provide({ 41 | stream: Promise.resolve(stream), 42 | name, 43 | contentType: 'application/pdf' 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | const Configuration = { 2 | /* 3 | * Resolve and load @commitlint/config-conventional from node_modules. 4 | * Referenced packages must be installed 5 | */ 6 | extends: ['@commitlint/config-conventional'], 7 | /* 8 | * Resolve and load conventional-changelog-atom from node_modules. 9 | * Referenced packages must be installed 10 | */ 11 | // parserPreset: 'conventional-changelog-atom', 12 | /* 13 | * Resolve and load @commitlint/format from node_modules. 14 | * Referenced package must be installed 15 | */ 16 | formatter: '@commitlint/format', 17 | /* 18 | * Any rules defined here will override rules from @commitlint/config-conventional 19 | */ 20 | rules: { 21 | 'body-leading-blank': [2, 'always'], 22 | 'footer-leading-blank': [2, 'always'], 23 | 'header-max-length': [2, 'always', 72], 24 | 'body-max-line-length': [2, 'always', 72], 25 | 'footer-max-line-length': [2, 'always', 72], 26 | 'scope-case': [2, 'always', 'lower-case'], 27 | 'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']], 28 | 'subject-empty': [2, 'never'], 29 | 'subject-full-stop': [2, 'never', '.'], 30 | 'type-case': [2, 'always', 'lower-case'], 31 | 'type-empty': [2, 'never'], 32 | 'type-enum': [ 33 | 2, 34 | 'always', 35 | ['build', 'chore', 'ci', 'docs', 'deps', 'feat', 'fix', 'perf', 'refactor', 'style', 'test'] 36 | ], 37 | 'scope-enum': [2, 'always', ['pdf-generator', 'release', 'streaming']] 38 | }, 39 | /* 40 | * Functions that return true if commitlint should ignore the given message. 41 | */ 42 | ignores: [(commit) => commit === ''], 43 | /* 44 | * Whether commitlint uses the default ignore rules. 45 | */ 46 | defaultIgnores: true, 47 | /* 48 | * Custom URL to show upon failure 49 | */ 50 | helpUrl: 51 | 'https://github.com/conventional-changelog/commitlint/#what-is-commitlint', 52 | /* 53 | * Custom prompt configs 54 | */ 55 | prompt: { 56 | messages: {}, 57 | questions: { 58 | type: { 59 | description: 'please input type:', 60 | }, 61 | }, 62 | }, 63 | }; 64 | 65 | module.exports = Configuration; 66 | 67 | // module.exports = {extends: ['@commitlint/config-conventional']}; 68 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR CI 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [16.20.0] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Use Node.js ${{ matrix.node-version }} 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - name: npm i package lock 18 | run: npm install --package-lock-only 19 | - name: npm install puppeteer 20 | run: npm install -g puppeteer --unsafe-perm 21 | - name: npm install 22 | run: npm ci 23 | - name: build 24 | run: npm run lerna run build 25 | 26 | test: 27 | runs-on: ubuntu-latest 28 | strategy: 29 | matrix: 30 | node-version: [16.20.0] 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Use Node.js ${{ matrix.node-version }} 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | - name: npm i package lock 38 | run: npm install --package-lock-only 39 | - name: npm install puppeteer 40 | run: npm install -g puppeteer --unsafe-perm 41 | - name: npm install 42 | run: npm ci 43 | - name: test 44 | run: npm run lerna run test 45 | 46 | validate: 47 | runs-on: ubuntu-latest 48 | strategy: 49 | matrix: 50 | node-version: [16.20.0] 51 | steps: 52 | - uses: actions/checkout@v3 53 | with: 54 | fetch-depth: 0 55 | - name: Use Node.js ${{ matrix.node-version }} 56 | uses: actions/setup-node@v3 57 | with: 58 | node-version: ${{ matrix.node-version }} 59 | - name: npm i package lock 60 | run: npm install --package-lock-only 61 | - name: npm install puppeteer 62 | run: npm install -g puppeteer --unsafe-perm 63 | - name: npm install 64 | run: npm ci 65 | - name: Install commitlint 66 | run: | 67 | npm install conventional-changelog-conventionalcommits 68 | npm install commitlint@latest 69 | - name: Validate current commit (last commit) with commitlint 70 | if: github.event_name == 'push' 71 | run: npx commitlint --from HEAD~1 --to HEAD --verbose 72 | - name: Validate PR commits with commitlint 73 | if: github.event_name == 'pull_request' 74 | run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose 75 | - name: lint 76 | run: npm run lint 77 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: MASTER CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node-version: [16.20.0] 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: npm i package lock 21 | run: npm install --package-lock-only 22 | - name: npm install puppeteer 23 | run: npm install -g puppeteer --unsafe-perm 24 | - name: npm install 25 | run: npm ci 26 | - name: build 27 | run: npm run lerna run build 28 | 29 | test: 30 | runs-on: ubuntu-latest 31 | strategy: 32 | matrix: 33 | node-version: [16.20.0] 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: Use Node.js ${{ matrix.node-version }} 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: ${{ matrix.node-version }} 40 | - name: npm i package lock 41 | run: npm install --package-lock-only 42 | - name: npm install puppeteer 43 | run: npm install -g puppeteer --unsafe-perm 44 | - name: npm install 45 | run: npm ci 46 | - name: test 47 | run: npm run lerna run test 48 | 49 | validate: 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | node-version: [16.20.0] 54 | steps: 55 | - uses: actions/checkout@v3 56 | with: 57 | fetch-depth: 0 58 | - name: checkout 59 | run: git checkout master 60 | - name: Use Node.js ${{ matrix.node-version }} 61 | uses: actions/setup-node@v3 62 | with: 63 | node-version: ${{ matrix.node-version }} 64 | - name: npm i package lock 65 | run: npm install --package-lock-only 66 | - name: npm install puppeteer 67 | run: npm install -g puppeteer --unsafe-perm 68 | - name: npm install 69 | run: npm ci 70 | - name: Install commitlint 71 | run: | 72 | npm install conventional-changelog-conventionalcommits 73 | npm install commitlint@latest 74 | - name: Validate current commit (last commit) with commitlint 75 | if: github.event_name == 'push' 76 | run: npx commitlint --from HEAD~1 --to HEAD --verbose 77 | - name: Validate PR commits with commitlint 78 | if: github.event_name == 'pull_request' 79 | run: npx commitlint --from ${{ github.event.pull_request.head.sha }}~${{ github.event.pull_request.commits }} --to ${{ github.event.pull_request.head.sha }} --verbose 80 | - name: prettier check 81 | run: npm run format:check 82 | - name: lint 83 | run: npm run lint 84 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/fastify/response-streaming/partial-response.streamer.spec.ts: -------------------------------------------------------------------------------- 1 | import { PartialResponseStreamer } from './partial-response.streamer'; 2 | 3 | describe('PartialResponseStreamer', () => { 4 | describe('#stream', () => { 5 | test('streams with end option', async () => { 6 | const result = {}; 7 | const res: any = { headers: jest.fn(() => res), code: jest.fn(() => res), send: jest.fn() }; 8 | const streamer = new PartialResponseStreamer(res); 9 | const params = { start: 100, end: 200 }; 10 | const stream = jest.fn(); 11 | 12 | stream.mockImplementation(() => result); 13 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, params); 14 | 15 | expect(res.code).toHaveBeenCalledWith(206); 16 | expect(res.headers).toHaveBeenCalledWith({ 17 | 'Accept-Ranges': 'bytes', 18 | 'Content-Range': 'bytes 100-200/2048', 19 | 'Content-Type': 'video/mp4' 20 | }); 21 | expect(stream).toHaveBeenCalledWith(params); 22 | expect(res.send).toHaveBeenCalledWith(result); 23 | }); 24 | 25 | test('streams without end option', async () => { 26 | const result = {}; 27 | const res: any = { headers: jest.fn(() => res), code: jest.fn(() => res), send: jest.fn() }; 28 | const streamer = new PartialResponseStreamer(res); 29 | const params = { start: 100 }; 30 | const stream = jest.fn(); 31 | 32 | stream.mockImplementation(() => result); 33 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, params); 34 | 35 | expect(res.code).toHaveBeenCalledWith(206); 36 | expect(res.headers).toHaveBeenCalledWith({ 37 | 'Accept-Ranges': 'bytes', 38 | 'Content-Range': 'bytes 100-2047/2048', 39 | 'Content-Type': 'video/mp4' 40 | }); 41 | expect(stream).toHaveBeenCalledWith(params); 42 | expect(res.send).toHaveBeenCalledWith(result); 43 | }); 44 | 45 | test('streams with cache', async () => { 46 | const result = {}; 47 | const res: any = { headers: jest.fn(() => res), code: jest.fn(() => res), send: jest.fn() }; 48 | const streamer = new PartialResponseStreamer(res); 49 | const params = { start: 100 }; 50 | const stream = jest.fn(); 51 | 52 | stream.mockImplementation(() => result); 53 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, params, 2592000); 54 | 55 | expect(res.code).toHaveBeenCalledWith(206); 56 | expect(res.headers).toHaveBeenCalledWith({ 57 | 'Accept-Ranges': 'bytes', 58 | 'Content-Range': 'bytes 100-2047/2048', 59 | 'Content-Type': 'video/mp4', 60 | 'Cache-Control': `max-age=2592000, public`, 61 | Expires: new Date(new Date().getDate() + 2592000).toDateString() 62 | }); 63 | expect(stream).toHaveBeenCalledWith(params); 64 | expect(res.send).toHaveBeenCalledWith(result); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/pdf-generator/src/lib/puppeteer-web-page-to-pdf/puppeteer-web-page-to-pdf.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Readable } from 'stream'; 3 | import { URL } from 'url'; 4 | import { PdfGenerationException } from '../exceptions/pdf-generation.exception'; 5 | import { ToPdfResult } from '../interfaces/to-pdf-result.interface'; 6 | import { ToPdfOptions } from '../interfaces/to-pdf-options.interface'; 7 | import {launch, HTTPRequest, HTTPResponse, TimeoutError} from "puppeteer"; 8 | 9 | @Injectable() 10 | export class PuppeteerWebPageToPdfService { 11 | private static readonly timeout = 20000; 12 | 13 | async toPdf(url: URL, options: ToPdfOptions): Promise { 14 | const browser = await launch({ 15 | args: [ 16 | // Disable cors checks 17 | '--disable-web-security', 18 | // Required for Docker version of Puppeteer 19 | '--no-sandbox', 20 | '--disable-setuid-sandbox', 21 | // This will write shared memory files into /tmp instead of /dev/shm, 22 | // because Docker’s default for /dev/shm is 64MB 23 | '--disable-dev-shm-usage' 24 | ] 25 | }); 26 | try { 27 | const page = await browser.newPage(); 28 | 29 | await page.setRequestInterception(true); 30 | page.on('request', (request: HTTPRequest) => { 31 | // Do nothing in case of non-navigation requests. 32 | if (!request.isNavigationRequest()) { 33 | request.continue(); 34 | return; 35 | } 36 | // Add a new header for navigation request. 37 | let headers = request.headers(); 38 | 39 | headers = { ...headers, ...(options.headers || {}) }; 40 | request.continue({ headers }); 41 | }); 42 | const response = await page.goto(url.toString(), { 43 | waitUntil: 'networkidle2', 44 | timeout: PuppeteerWebPageToPdfService.timeout 45 | }); 46 | if (!response) { 47 | throw new PdfGenerationException('Response not received'); 48 | } 49 | await this.assertResponse(response); 50 | 51 | if (options.waitFor) { 52 | await page.waitForSelector(options.waitFor, { 53 | hidden: true, 54 | timeout: PuppeteerWebPageToPdfService.timeout 55 | }); 56 | } 57 | const pdf: Buffer = await page.pdf({ format: 'A4', printBackground: true }); 58 | const name = await page.title(); 59 | 60 | return { stream: Readable.from(pdf), name: `${name}.pdf` }; 61 | } catch (e) { 62 | if (e instanceof TimeoutError) { 63 | throw new PdfGenerationException('Pdf generation timeout'); 64 | } 65 | throw e; 66 | } finally { 67 | await browser.close(); 68 | } 69 | } 70 | 71 | private async assertResponse(response: HTTPResponse): Promise { 72 | const statusCode = response.status(); 73 | if (statusCode !== 200) { 74 | const message = (await response.json()) as string; 75 | throw new PdfGenerationException(message, statusCode); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/streaming/src/lib/streaming/express/response-streaming/partial-response.streamer.spec.ts: -------------------------------------------------------------------------------- 1 | import { ObjectReadableMock, ObjectWritableMock } from 'stream-mock'; 2 | import { PartialResponseStreamer } from './partial-response.streamer'; 3 | 4 | describe('PartialResponseStreamer', () => { 5 | const streamData = [0, 0, 0, 0]; 6 | 7 | describe('#stream', () => { 8 | test('streams with end option', async () => { 9 | const res: any = new ObjectWritableMock(); 10 | res.writeHead = jest.fn(); 11 | const streamer = new PartialResponseStreamer(res); 12 | const params = { start: 100, end: 200 }; 13 | const stream = jest.fn(); 14 | 15 | stream.mockImplementation(() => new ObjectReadableMock(streamData)); 16 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, params); 17 | 18 | expect(res.writeHead).toHaveBeenCalledWith(206, { 19 | 'Accept-Ranges': 'bytes', 20 | 'Content-Range': 'bytes 100-200/2048', 21 | 'Content-Type': 'video/mp4' 22 | }); 23 | expect(stream).toHaveBeenCalledWith(params); 24 | 25 | res.on('finish', () => { 26 | expect(res.data).toEqual(streamData); 27 | }); 28 | }); 29 | 30 | test('streams without end option', async () => { 31 | const res: any = new ObjectWritableMock(); 32 | res.writeHead = jest.fn(); 33 | const streamer = new PartialResponseStreamer(res); 34 | const params = { start: 100 }; 35 | const stream = jest.fn(); 36 | 37 | stream.mockImplementation(() => new ObjectReadableMock(streamData)); 38 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, params); 39 | 40 | expect(res.writeHead).toHaveBeenCalledWith(206, { 41 | 'Accept-Ranges': 'bytes', 42 | 'Content-Range': 'bytes 100-2047/2048', 43 | 'Content-Type': 'video/mp4' 44 | }); 45 | expect(stream).toHaveBeenCalledWith(params); 46 | 47 | res.on('finish', () => { 48 | expect(res.data).toEqual(streamData); 49 | }); 50 | }); 51 | 52 | test('streams with cache', async () => { 53 | const res: any = new ObjectWritableMock(); 54 | res.writeHead = jest.fn(); 55 | const streamer = new PartialResponseStreamer(res); 56 | const params = { start: 100 }; 57 | const stream = jest.fn(); 58 | 59 | stream.mockImplementation(() => new ObjectReadableMock(streamData)); 60 | await streamer.stream({ size: 2048, contentType: 'video/mp4', stream }, params, 2592000); 61 | 62 | expect(res.writeHead).toHaveBeenCalledWith(206, { 63 | 'Accept-Ranges': 'bytes', 64 | 'Content-Range': 'bytes 100-2047/2048', 65 | 'Content-Type': 'video/mp4', 66 | 'Cache-Control': `max-age=2592000, public`, 67 | Expires: new Date(new Date().getDate() + 2592000).toDateString() 68 | }); 69 | expect(stream).toHaveBeenCalledWith(params); 70 | 71 | res.on('finish', () => { 72 | expect(res.data).toEqual(streamData); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to NestJS Packages 2 | 3 | #### Welcome 4 | 5 | We are glad you are interested to contribute code via pull requests, to 6 | file issues, to help people asking for help, discuss changes, suggest a new 7 | feature or add any other value to the project. 8 | 9 | ## Code of conduct 10 | 11 | Before you start working on your first pull request, please discuss the change 12 | you wish to make via issue. Familiarize yourself with our code of conduct. 13 | 14 | We expect contributors to act professionally and respectfully 15 | to make our work space safe and welcoming. Be kind. Respect people, their culture, their work. 16 | Listen them. Consider their viewpoint. 17 | 18 | ## Developing for NestJS Packages 19 | 20 | The most important part, to add any value to NestJS Packages you have to follow up this list. 21 | This section describes our methodologies and conventions. 22 | 23 | 1. Familiarize with Git flow - [read more](//nvie.com/posts/a-successful-git-branching-model/), 24 | here a [gist](https://gist.github.com/markreid/12e7c2203916b93d23c27a263f6091a0) about rebasing. 25 | 26 | 2. Check out our [Git guidelines](#git-guidelines). If a commit messages will not follow with these rules the CI 27 | will be rejecting it. 28 | 29 | 3. Even the work you are doing is likely to be a trivial effort, file a new issue and discuss 30 | that with the rest of the team. 31 | 32 | 4. When your issue was marked as to do, fork the Github repository, create a branch on your Github 33 | fork of the repository and implement your change. 34 | 35 | 5. Submit the branch as a PR to relevant NestJS Packages repository. 36 | 37 | 6. Make sure your PR passes the build at CI. 38 | 39 | 7. When everything is green and your changes looks good to reviewer, then wait for a feedback from someone from the core team. 40 | 41 | ## Git guidelines 42 | 43 | Make sure your branch's name keep that convention: 44 | 45 | ci/* // changes to our CI configuration files and script 46 | feature/* // a new feature 47 | bugfix/* // a bug ifx 48 | release/* // a new production release 49 | hotfix/* // like a release branch but fix mess at production 50 | 51 | Each commit message has to consist a **header**, a **body**, and a **footer**. 52 | The header should strictly follow the special format that consists 53 | of a **type**, a **scope**, and a **subject**: 54 | 55 | (): 56 | 57 | 58 | 59 |