New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@travetto/asset

Package Overview
Dependencies
Maintainers
0
Versions
310
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@travetto/asset - npm Package Compare versions

Comparing version 4.1.3 to 5.0.0-rc.0

src/response.ts

3

__index__.ts
export * from './src/types';
export * from './src/service';
export * from './src/naming';
export * from './src/util';
export * from './src/util';
export * from './src/response';
{
"name": "@travetto/asset",
"version": "4.1.3",
"version": "5.0.0-rc.0",
"description": "Modular library for storing and retrieving binary assets",

@@ -28,4 +28,4 @@ "keywords": [

"dependencies": {
"@travetto/di": "^4.1.1",
"@travetto/model": "^4.1.3",
"@travetto/di": "^5.0.0-rc.0",
"@travetto/model": "^5.0.0-rc.0",
"@types/mime": "^3.0.4",

@@ -36,3 +36,3 @@ "file-type": "^16.5.4",

"peerDependencies": {
"@travetto/test": "^4.1.1"
"@travetto/test": "^5.0.0-rc.0"
},

@@ -39,0 +39,0 @@ "peerDependenciesMeta": {

@@ -27,3 +27,3 @@ <!-- This file was generated by @travetto/doc and should not be modified directly -->

Currently, the following are packages that provide [Streaming](https://github.com/travetto/travetto/tree/main/module/model/src/service/stream.ts#L3) support:
* [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") - [FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L50), [MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L53)
* [Data Modeling Support](https://github.com/travetto/travetto/tree/main/module/model#readme "Datastore abstraction for core operations.") - [FileModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/file.ts#L50), [MemoryModelService](https://github.com/travetto/travetto/tree/main/module/model/src/provider/memory.ts#L54)
* [MongoDB Model Support](https://github.com/travetto/travetto/tree/main/module/model-mongo#readme "Mongo backing for the travetto model module.")

@@ -56,3 +56,3 @@ * [S3 Model Support](https://github.com/travetto/travetto/tree/main/module/model-s3#readme "S3 backing for the travetto model module.")

Reading of and writing assets uses the [AssetService](https://github.com/travetto/travetto/tree/main/module/asset/src/service.ts#L18). Below you can see an example dealing with a user's profile image.
Reading of and writing assets uses the [AssetService](https://github.com/travetto/travetto/tree/main/module/asset/src/service.ts#L20). Below you can see an example dealing with a user's profile image.

@@ -90,3 +90,3 @@ **Code: User Profile Images**

The underlying contract for a [AssetNamingStrategy](https://github.com/travetto/travetto/tree/main/module/asset/src/naming.ts#L9) looks like:
The underlying contract for a [AssetNamingStrategy](https://github.com/travetto/travetto/tree/main/module/asset/src/naming.ts#L8) looks like:

@@ -126,13 +126,2 @@ **Code: AssetNamingStrategy**

}
/**
* An asset response
*/
export interface AssetResponse extends StreamMeta {
stream(): Readable;
/**
* Response byte range, inclusive
*/
range?: [start: number, end: number];
}
```

@@ -139,0 +128,0 @@

@@ -1,4 +0,3 @@

import { getExtension } from 'mime';
import { StreamMeta } from '@travetto/model';
import { AssetUtil } from './util';

@@ -38,6 +37,6 @@ /**

resolve(asset: StreamMeta): string {
let ext = '';
let ext: string | undefined = '';
if (asset.contentType) {
ext = getExtension(asset.contentType)!;
ext = AssetUtil.getExtension(asset.contentType);
} else if (asset.filename) {

@@ -44,0 +43,0 @@ const dot = asset.filename.indexOf('.');

@@ -1,9 +0,11 @@

import { PassThrough } from 'node:stream';
import { PassThrough, Readable } from 'node:stream';
import { Inject, Injectable } from '@travetto/di';
import { ModelStreamSupport, ExistsError, NotFoundError, StreamMeta } from '@travetto/model';
import { StreamUtil } from '@travetto/base';
import { ModelStreamSupport, ExistsError, NotFoundError, StreamMeta, StreamRange } from '@travetto/model';
import { enforceRange } from '@travetto/model/src/internal/service/stream';
import { Asset, AssetResponse } from './types';
import { Asset } from './types';
import { AssetNamingStrategy, SimpleNamingStrategy } from './naming';
import { AssetUtil } from './util';
import { StreamResponse } from './response';

@@ -50,2 +52,17 @@ export const AssetModelⲐ = Symbol.for('@travetto/asset:model');

*
* @param blob The blob to store
* @param meta The optional metadata for the blob
* @param overwriteIfFound Overwrite the asset if found
* @param strategy The naming strategy to use, defaults to the service's strategy if not provided
*/
async upsertBlob(blob: Blob, meta: Partial<StreamMeta> = {}, overwriteIfFound = true, strategy?: AssetNamingStrategy): Promise<{ asset: Asset, location: string }> {
const asset = await AssetUtil.blobToAsset(blob, meta);
const location = await this.upsert(asset, overwriteIfFound, strategy);
return { asset, location };
}
/**
* Stores an asset with the optional ability to overwrite if the file is already found. If not
* overwriting and file exists, an error will be thrown.
*
* @param asset The asset to store

@@ -75,3 +92,11 @@ * @param overwriteIfFound Overwrite the asset if found

const stream = await StreamUtil.toStream(source);
let stream: Readable;
if (typeof source === 'string') {
stream = Readable.from(source, { encoding: source.endsWith('=') ? 'base64' : 'utf8' });
} else if (Buffer.isBuffer(source)) {
stream = Readable.from(source);
} else {
stream = source;
}
await this.#store.upsertStream(location, stream, asset);

@@ -88,15 +113,13 @@ return location;

*/
async get(location: string, start?: number, end?: number): Promise<AssetResponse> {
const info = await this.describe(location);
async get(location: string, range?: StreamRange): Promise<StreamResponse> {
const meta = await this.describe(location);
if (range) {
range = enforceRange(range, meta.size);
}
const stream = new PassThrough();
const extra: Partial<AssetResponse> = {};
let load: () => void;
if (start === undefined) {
load = (): void => { this.#store.getStream(location).then(v => v.pipe(stream)); };
} else {
extra.range = StreamUtil.enforceRange(start, end, info.size);
load = (): void => { this.#store.getStreamPartial(location, start, end).then(v => v.stream.pipe(stream)); };
}
return { stream: () => (load(), stream), ...info, ...extra };
const load = (): void => { this.#store.getStream(location, range).then(v => v.pipe(stream)); };
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return new StreamResponse(() => (load(), stream), meta, range as Required<StreamRange>);
}
}

@@ -13,13 +13,2 @@ import { Readable } from 'node:stream';

localFile?: string;
}
/**
* An asset response
*/
export interface AssetResponse extends StreamMeta {
stream(): Readable;
/**
* Response byte range, inclusive
*/
range?: [start: number, end: number];
}

@@ -6,8 +6,7 @@ import fs from 'node:fs/promises';

import crypto from 'node:crypto';
import path from 'node:path';
import { getExtension, getType } from 'mime';
import { path } from '@travetto/manifest';
import { StreamMeta } from '@travetto/model';
import { AppError } from '@travetto/base';

@@ -22,7 +21,11 @@ import { Asset } from './types';

/**
* Compute hash from a file location on disk
* Compute hash from a file location on disk or a blob
*/
static async hashFile(pth: string): Promise<string> {
static async computeHash(input: string | Blob | Buffer): Promise<string> {
const hasher = crypto.createHash('sha256').setEncoding('hex');
const str = createReadStream(pth);
const str = typeof input === 'string' ?
createReadStream(input) :
Buffer.isBuffer(input) ?
Readable.from(input) :
Readable.fromWeb(input.stream());
await pipeline(str, hasher);

@@ -33,7 +36,7 @@ return hasher.read().toString();

/**
* Read a chunk from a file, primarily used for mime detection
* Read a chunk from a file
*/
static async readChunk(input: string | Readable | Buffer, bytes: number): Promise<Buffer> {
if (Buffer.isBuffer(input)) {
return input;
return input.subarray(0, bytes);
} else if (typeof input === 'string') {

@@ -58,3 +61,3 @@ const fd = await fs.open(input, 'r');

}
return Buffer.concat(chunks);
return Buffer.concat(chunks).subarray(0, bytes);
}

@@ -64,3 +67,3 @@ }

/**
* Detect file type from location on disk
* Detect file type
*/

@@ -83,3 +86,3 @@ static async detectFileType(input: string | Buffer | Readable): Promise<{ ext: string, mime: string } | undefined> {

const type = await this.resolveFileType(filePath);
const ext = getExtension(type);
const ext = this.getExtension(type);
const baseName = path.basename(filePath, path.extname(filePath));

@@ -97,2 +100,10 @@ const newFile = `${baseName}.${ext}`;

/**
* Get extension for a given content type
* @param contentType
*/
static getExtension(contentType: string): string | undefined {
return getExtension(contentType)!;
}
/**
* Read content type from location on disk

@@ -117,3 +128,3 @@ */

const hash = metadata.hash ?? await this.hashFile(file);
const hash = metadata.hash ?? await this.computeHash(file);
const size = metadata.size ?? (await fs.stat(file)).size;

@@ -127,3 +138,3 @@ const contentType = metadata.contentType ?? await this.resolveFileType(file);

if (!extName) {
const ext = getExtension(contentType);
const ext = this.getExtension(contentType);
if (ext) {

@@ -139,2 +150,5 @@ filename = `${filename}.${ext}`;

contentType,
contentEncoding: metadata.contentEncoding,
contentLanguage: metadata.contentLanguage,
cacheControl: metadata.cacheControl,
localFile: file,

@@ -147,2 +161,32 @@ source: createReadStream(file),

/**
* Convert blob to asset structure
*/
static async blobToAsset(blob: Blob, metadata: Partial<StreamMeta> = {}): Promise<Asset> {
const hash = metadata.hash ??= await this.computeHash(blob);
const size = metadata.size ?? blob.size;
const contentType = metadata.contentType ?? blob.type;
let filename = metadata.filename;
if (!filename) {
filename = `unknown.${Date.now()}`;
const ext = this.getExtension(contentType);
if (ext) {
filename = `${filename}.${ext}`;
}
}
return {
size,
filename,
contentType,
contentEncoding: metadata.contentEncoding,
contentLanguage: metadata.contentLanguage,
cacheControl: metadata.cacheControl,
source: Readable.fromWeb(blob.stream()),
hash
};
}
/**
* Fetch bytes from a url

@@ -158,3 +202,3 @@ */

if (!str.ok) {
throw new AppError('Invalid url for hashing', 'data');
throw new Error('Invalid url for hashing');
}

@@ -190,6 +234,4 @@

static async hashUrl(url: string, byteLimit = -1): Promise<string> {
const hasher = crypto.createHash('sha256').setEncoding('hex');
const finalData = await this.fetchBytes(url, byteLimit);
return hasher.update(finalData).end().read().toString();
return this.computeHash(await this.fetchBytes(url, byteLimit));
}
}

@@ -43,3 +43,3 @@ import assert from 'node:assert';

const outHashed = await service.upsert(file, false, new HashNamingStrategy());
const hash = await AssetUtil.hashFile(pth);
const hash = await AssetUtil.computeHash(pth);
assert(outHashed.replace(/\//g, '').replace(/[.][^.]+$/, '') === hash);

@@ -55,3 +55,3 @@ }

const saved = await service.get(loc);
const { meta: saved } = await service.get(loc);

@@ -58,0 +58,0 @@ assert(file.contentType === saved.contentType);

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc