@tussle/core
Advanced tools
Comparing version 0.0.5 to 0.1.0
@@ -1,30 +0,26 @@ | ||
import type { Observable } from 'rxjs'; | ||
import type { TussleIncomingRequest } from '@tussle/spec/interface/request'; | ||
import type { TussleStorageService } from '@tussle/spec/interface/storage'; | ||
import type { TusProtocolExtension } from '@tussle/spec/interface/tus'; | ||
import type { TussleIncomingRequest } from '@tussle/spec/interface/request'; | ||
import type { TussleStorageService, TussleStorageCreateFileResponse, TussleStorageCreateFileParams } from '@tussle/spec/interface/storage'; | ||
import { TussleCoreHooks } from './hooks'; | ||
import { Observable } from 'rxjs'; | ||
export interface TussleConfig { | ||
maxSizeBytes?: number; | ||
storage: TussleStorageService | Record<'default' | string, TussleStorageService>; | ||
hooks?: Partial<TussleCoreHooks>; | ||
storage: TussleStorageService | (<Req>(ctx: TussleIncomingRequest<Req>) => Promise<TussleStorageService>); | ||
handlers?: Partial<RequestHandler>; | ||
} | ||
declare type IncomingRequestMethod = TussleIncomingRequest<unknown>['request']['method']; | ||
declare type IncomingRequestHandler = <T>(core: Tussle, ctx: TussleIncomingRequest<T>) => Observable<TussleIncomingRequest<T>>; | ||
declare type RequestHandler = Record<IncomingRequestMethod, IncomingRequestHandler>; | ||
export declare class Tussle { | ||
private readonly cfg; | ||
readonly handlers: Partial<Record<IncomingRequestMethod, IncomingRequestHandler>>; | ||
constructor(cfg: TussleConfig); | ||
readonly handlers: Partial<RequestHandler>; | ||
readonly extensions: Partial<Record<TusProtocolExtension, boolean>>; | ||
readonly storage: Partial<Record<'default' | string, TussleStorageService>>; | ||
readonly hooks: Partial<TussleCoreHooks>; | ||
constructor(cfg: TussleConfig); | ||
handle<T>(ctx: TussleIncomingRequest<T>): Observable<TussleIncomingRequest<T>>; | ||
private chooseProtocolVersion; | ||
private readonly processRequestHeaders; | ||
private chooseProtocolVersion; | ||
private readonly process; | ||
private readonly postProcess; | ||
setHandler(method: IncomingRequestMethod, handler: IncomingRequestHandler): void; | ||
getStorage(name?: string): TussleStorageService; | ||
hook<K extends keyof TussleCoreHooks, H extends TussleCoreHooks[K]>(which: K, ctx: TussleIncomingRequest<unknown>, params: Parameters<H>[2]): ReturnType<H> | Observable<Parameters<H>[2]>; | ||
create(params: TussleStorageCreateFileParams, storeName?: string): Observable<TussleStorageCreateFileResponse>; | ||
private readonly selectStorageService; | ||
private readonly processRequest; | ||
private readonly postProcessRequest; | ||
readonly handle: <T>(source: Observable<TussleIncomingRequest<T>>) => Observable<TussleIncomingRequest<T>>; | ||
getStorage<R>(ctx: TussleIncomingRequest<R>): Promise<TussleStorageService>; | ||
} | ||
export {}; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -9,6 +6,3 @@ exports.Tussle = void 0; | ||
const operators_1 = require("rxjs/operators"); | ||
const create_1 = __importDefault(require("./handlers/create")); | ||
const patch_1 = __importDefault(require("./handlers/patch")); | ||
const head_1 = __importDefault(require("./handlers/head")); | ||
const options_1 = __importDefault(require("./handlers/options")); | ||
const handlers_1 = require("./handlers"); | ||
const supportedVersions = [ | ||
@@ -20,7 +14,5 @@ '1.0.0', | ||
this.cfg = cfg; | ||
this.handlers = {}; | ||
this.handlers = Object.assign(Object.assign({}, handlers_1.defaultHandlers), this.cfg.handlers); | ||
this.extensions = {}; | ||
this.hooks = {}; | ||
this.processRequestHeaders = () => operators_1.map((ctx) => { | ||
// Ensure meta property exists on incoming request context | ||
this.processRequestHeaders = (0, rxjs_1.pipe)((0, operators_1.map)((ctx) => { | ||
// Verify that the requested Tus protocol version is supported. | ||
@@ -32,3 +24,3 @@ if (ctx.request.method !== 'OPTIONS') { | ||
} | ||
// Set the negotiated protocol version in the context metadata | ||
// Set the negotiated protocol version in the context metadata. | ||
ctx.meta.tusVersion = version; | ||
@@ -38,6 +30,19 @@ } | ||
return ctx; | ||
}); | ||
this.process = () => operators_1.mergeMap((ctx) => { | ||
// only if no response was already attached by preprocessing | ||
if (!ctx.response) { | ||
})); | ||
this.selectStorageService = (0, rxjs_1.pipe)((0, operators_1.mergeMap)((ctx) => { | ||
if (isStorageService(this.cfg.storage)) { | ||
ctx.cfg.storage = this.cfg.storage; | ||
return (0, rxjs_1.of)(ctx); | ||
} | ||
else { | ||
return (0, rxjs_1.from)(this.cfg.storage(ctx)).pipe((0, operators_1.map)((storage) => { | ||
ctx.cfg.storage = storage; | ||
return ctx; | ||
})); | ||
} | ||
})); | ||
this.processRequest = (0, rxjs_1.pipe)((0, operators_1.mergeMap)((ctx) => { | ||
// only if no response was already attached by preprocessing and a | ||
// storage service has been linked to the incoming request. | ||
if (!ctx.response && ctx.cfg.storage) { | ||
const handler = this.handlers[ctx.request.method]; | ||
@@ -48,5 +53,5 @@ if (handler) { | ||
} | ||
return rxjs_1.of(ctx); // pass through | ||
}); | ||
this.postProcess = () => operators_1.map((ctx) => { | ||
return (0, rxjs_1.of)(ctx); // pass through | ||
})); | ||
this.postProcessRequest = (0, rxjs_1.pipe)((0, operators_1.map)((ctx) => { | ||
// Add any remaining response headers | ||
@@ -63,3 +68,3 @@ const extraHeaders = {}; | ||
// Include required Tus-Extension | ||
const supportedExtensions = 'creation,termination,checksum'; // TODO -- generate this | ||
const supportedExtensions = 'creation,checksum'; // TODO -- generate this | ||
if (supportedExtensions) { | ||
@@ -76,13 +81,5 @@ extraHeaders['Tus-Extension'] = supportedExtensions; | ||
return ctx; | ||
}); | ||
this.setHandler('POST', create_1.default); | ||
this.setHandler('PATCH', patch_1.default); | ||
this.setHandler('HEAD', head_1.default); | ||
this.setHandler('OPTIONS', options_1.default); | ||
this.storage = isStorageService(cfg.storage) ? { default: cfg.storage } : cfg.storage; | ||
this.hooks = cfg.hooks || {}; | ||
})); | ||
this.handle = (0, rxjs_1.pipe)(this.selectStorageService, this.processRequestHeaders, this.processRequest, this.postProcessRequest); | ||
} | ||
handle(ctx) { | ||
return rxjs_1.of(ctx).pipe(this.processRequestHeaders(), this.process(), this.postProcess()); | ||
} | ||
chooseProtocolVersion(ctx) { | ||
@@ -95,23 +92,7 @@ const clientVersion = ctx.request.getHeader('tus-resumable'); | ||
} | ||
setHandler(method, handler) { | ||
this.handlers[method] = handler.bind(this); | ||
getStorage(ctx) { | ||
return isStorageService(this.cfg.storage) ? | ||
Promise.resolve(this.cfg.storage) : | ||
this.cfg.storage(ctx); | ||
} | ||
getStorage(name = 'default') { | ||
const storage = this.storage[name]; | ||
if (!storage) { | ||
throw new Error('Unable to find storage: ' + name); | ||
} | ||
return storage; | ||
} | ||
hook(which, ctx, params) { | ||
const hook = this.hooks[which]; | ||
if (hook) { | ||
return hook(this, ctx, params); | ||
} | ||
return rxjs_1.of(params); | ||
} | ||
create(params, storeName = 'default') { | ||
const store = this.getStorage(storeName); | ||
return store.createFile(params); | ||
} | ||
} | ||
@@ -118,0 +99,0 @@ exports.Tussle = Tussle; |
@@ -1,6 +0,6 @@ | ||
import type { Observable } from 'rxjs'; | ||
import type { TussleIncomingRequest } from '@tussle/spec/interface/request'; | ||
import type { Tussle } from '../core'; | ||
export default function handleCreate<T>(core: Tussle, ctx: TussleIncomingRequest<T>): Observable<TussleIncomingRequest<T>>; | ||
declare const extractCreationHeaders: <T>(ctx: TussleIncomingRequest<T>) => { | ||
import { Observable } from 'rxjs'; | ||
export default function handleCreate<R>(core: Tussle, ctx: Readonly<TussleIncomingRequest<R>>): Observable<TussleIncomingRequest<R>>; | ||
export interface TussleCreationParams { | ||
id: string; | ||
@@ -12,4 +12,5 @@ path: string; | ||
uploadConcat: string | null; | ||
}; | ||
} | ||
declare const extractCreationHeaders: <T>(ctx: TussleIncomingRequest<T>) => TussleCreationParams; | ||
export declare type ExtractedCreateHeaders = ReturnType<typeof extractCreationHeaders>; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const js_base64_1 = require("js-base64"); | ||
const rxjs_1 = require("rxjs"); | ||
const operators_1 = require("rxjs/operators"); | ||
const js_base64_1 = require("js-base64"); | ||
function defaultPath(path, filename) { | ||
@@ -12,15 +13,22 @@ return [ | ||
} | ||
// If the before-create hook didn't set the location, for the file being | ||
// created, then generate a default based on the request path and metadata | ||
// filename. | ||
const ensureFilePath = (originalPath) => (0, operators_1.map)(params => { | ||
if (params.path === originalPath) { | ||
params.path = defaultPath(params.path, params.uploadMetadata.filename); | ||
} | ||
return params; | ||
}); | ||
function handleCreate(core, ctx) { | ||
const params = extractCreationHeaders(ctx); | ||
const originalPath = params.path; | ||
const params$ = core.hook('before-create', ctx, params).pipe(operators_1.map((params) => { | ||
// If the before-create hook didn't set the location, | ||
// for the file being created, then generate a default | ||
// based on the request path and metadata filename. | ||
if (params.path === originalPath) { | ||
params.path = defaultPath(params.path, params.uploadMetadata.filename); | ||
} | ||
return params; | ||
})); | ||
return params$.pipe(operators_1.switchMap((params) => core.create(params).pipe(operators_1.mergeMap((createdFile) => core.hook('after-create', ctx, createdFile)), operators_1.map((createdFile) => toResponse(ctx, createdFile))))); | ||
const store = ctx.cfg.storage; | ||
if (!store) { | ||
return (0, rxjs_1.throwError)('no storage service selected'); | ||
} | ||
else { | ||
const params$ = ctx.source.hook('before-create', ctx, params).pipe(ensureFilePath(originalPath)); | ||
return params$.pipe((0, operators_1.switchMap)((params) => store.createFile(params)), (0, operators_1.switchMap)((createdFile) => ctx.source.hook('after-create', ctx, createdFile)), (0, operators_1.map)((createdFile) => toResponse(ctx, createdFile))); | ||
} | ||
} | ||
@@ -38,3 +46,3 @@ exports.default = handleCreate; | ||
.map((value) => value.split(' ')) | ||
.map(([key, value]) => [key, value ? js_base64_1.decode(value) : value]) | ||
.map(([key, value]) => [key, value ? (0, js_base64_1.decode)(value) : value]) | ||
.reduce((acc, [key, value]) => { | ||
@@ -46,7 +54,2 @@ acc[key] = value; | ||
const uploadConcat = header('upload-concat') || null; | ||
// provide a default file location (this can be altered during | ||
// the 'before-create' hook, and then possibly altered further | ||
// by the current storage component. | ||
// const location = [path, encodeURIComponent(uploadMetadata.filename)].join('/'); | ||
// console.log('creation location is', location); | ||
return { | ||
@@ -66,3 +69,3 @@ id, | ||
status: 201, | ||
headers: Object.assign({ 'Location': createdFile.location, 'Tussle-Storage': 'b2' }, (_a = ctx.response) === null || _a === void 0 ? void 0 : _a.headers), | ||
headers: Object.assign({ 'Location': createdFile.location }, (_a = ctx.response) === null || _a === void 0 ? void 0 : _a.headers), | ||
}; | ||
@@ -72,3 +75,3 @@ } | ||
ctx.response = { | ||
status: 400, | ||
status: 400, // TODO - check this | ||
}; | ||
@@ -75,0 +78,0 @@ } |
@@ -1,5 +0,5 @@ | ||
import type { Observable } from "rxjs"; | ||
import { Observable } from "rxjs"; | ||
import type { TussleIncomingRequest } from '@tussle/spec/interface/request'; | ||
import type { Tussle } from '../core'; | ||
export default function handleHead<T>(core: Tussle, ctx: TussleIncomingRequest<T>): Observable<TussleIncomingRequest<T>>; | ||
export default function handleHead<Req>(core: Tussle, ctx: TussleIncomingRequest<Req>): Observable<TussleIncomingRequest<Req>>; | ||
declare const extractParamsFromHeaders: <T>(ctx: TussleIncomingRequest<T>) => { | ||
@@ -6,0 +6,0 @@ location: string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const rxjs_1 = require("rxjs"); | ||
const operators_1 = require("rxjs/operators"); | ||
function handleHead(core, ctx) { | ||
const store = core.getStorage('default'); | ||
const params = extractParamsFromHeaders(ctx); | ||
const params$ = core.hook('before-head', ctx, params); | ||
return params$.pipe(operators_1.switchMap((params) => store.getFileInfo(params).pipe(operators_1.map((fileInfo) => toResponse(ctx, fileInfo))))); | ||
const store = ctx.cfg.storage; | ||
if (!store) { | ||
return (0, rxjs_1.throwError)('no storage service selected'); | ||
} | ||
else { | ||
const params$ = ctx.source.hook('before-head', ctx, params); | ||
return params$.pipe((0, operators_1.switchMap)((params) => store.getFileInfo(params)), (0, operators_1.map)(fileInfo => toResponse(ctx, fileInfo))); | ||
} | ||
} | ||
@@ -18,8 +24,13 @@ exports.default = handleHead; | ||
const toResponse = (ctx, fileInfo) => { | ||
if (fileInfo.info) { | ||
const { info } = fileInfo; | ||
if (info) { | ||
const headers = { | ||
'Upload-Offset': (info.currentOffset || 0).toString(), | ||
}; | ||
if (typeof info.uploadLength === 'number') { | ||
headers['Upload-Length'] = info.uploadLength.toString(); | ||
} | ||
ctx.response = { | ||
status: 200, | ||
headers: { | ||
'Upload-Offset': fileInfo.info.currentOffset.toString(), | ||
}, | ||
headers, | ||
}; | ||
@@ -29,3 +40,3 @@ } | ||
ctx.response = { | ||
status: 410, | ||
status: 410, // GONE | ||
}; | ||
@@ -32,0 +43,0 @@ } |
import type { Observable } from 'rxjs'; | ||
import type { TussleIncomingRequest } from '@tussle/spec/interface/request'; | ||
import type { Tussle } from '../core'; | ||
export default function handleOptions<T>(core: Tussle, ctx: TussleIncomingRequest<T>): Observable<TussleIncomingRequest<T>>; | ||
declare const defaultResponse: { | ||
status: number; | ||
headers: { | ||
'access-control-allow-headers': string; | ||
'access-control-expose-headers': string; | ||
}; | ||
}; | ||
export default function handleOptions<Req>(core: Tussle, ctx: TussleIncomingRequest<Req>): Observable<TussleIncomingRequest<Req>>; | ||
export declare type OptionsDefaultResponse = typeof defaultResponse; | ||
export {}; |
@@ -14,5 +14,5 @@ "use strict"; | ||
function handleOptions(core, ctx) { | ||
const response$ = core.hook('before-options', ctx, Object.assign({}, defaultResponse)).pipe(operators_1.map((response) => (Object.assign(Object.assign({}, ctx), { response })))); | ||
const response$ = ctx.source.hook('before-options', ctx, Object.assign({}, defaultResponse)).pipe((0, operators_1.map)((response) => (Object.assign(Object.assign({}, ctx), { response })))); | ||
return response$; | ||
} | ||
exports.default = handleOptions; |
/// <reference types="node" /> | ||
import type { Observable } from 'rxjs'; | ||
import { Observable } from 'rxjs'; | ||
import type { Tussle } from '../core'; | ||
import type { TussleIncomingRequest } from '@tussle/spec/interface/request'; | ||
export default function handlePatch<T>(core: Tussle, ctx: TussleIncomingRequest<T>): Observable<TussleIncomingRequest<T>>; | ||
declare const extractPatchHeaders: (ctx: TussleIncomingRequest<unknown>) => { | ||
declare const extractPatchHeaders: <Req>(ctx: TussleIncomingRequest<Req>) => { | ||
contentType: string; | ||
getReadable: () => import("stream").Readable; | ||
getReadable: () => import("stream").Readable | ReadableStream<Uint8Array>; | ||
length: number; | ||
location: string; | ||
offset: number; | ||
request: TussleIncomingRequest<unknown>; | ||
request: TussleIncomingRequest<Req>; | ||
}; | ||
export declare type ExtractedPatchHeaders = ReturnType<typeof extractPatchHeaders>; | ||
export {}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const rxjs_1 = require("rxjs"); | ||
const rxjs_2 = require("rxjs"); | ||
const operators_1 = require("rxjs/operators"); | ||
function handlePatch(core, ctx) { | ||
const store = core.getStorage('default'); | ||
const params = extractPatchHeaders(ctx); | ||
const store = ctx.cfg.storage; | ||
if (!store) { | ||
return (0, rxjs_1.throwError)('no storage service selected'); | ||
} | ||
// PATCH requests MUST use Content-Type: application/offset+octet-stream | ||
@@ -14,6 +18,6 @@ if (params.contentType !== 'application/offset+octet-stream') { | ||
}; | ||
return rxjs_1.of(ctx); | ||
return (0, rxjs_2.of)(ctx); | ||
} | ||
const params$ = core.hook('before-patch', ctx, params); | ||
return params$.pipe(operators_1.switchMap((params) => store.patchFile(params).pipe(operators_1.mergeMap((patchedFile) => callOptionalHooks(core, ctx, patchedFile)), operators_1.map((patchedFile) => toResponse(ctx, patchedFile))))); | ||
const params$ = ctx.source.hook('before-patch', ctx, params); | ||
return params$.pipe((0, operators_1.switchMap)((params) => store.patchFile(params)), (0, operators_1.switchMap)((patchedFile) => callOptionalHooks(ctx, patchedFile)), (0, operators_1.map)((patchedFile) => toResponse(ctx, patchedFile))); | ||
} | ||
@@ -24,8 +28,8 @@ exports.default = handlePatch; | ||
} | ||
const callOptionalHooks = (core, ctx, patchedFile) => { | ||
const callOptionalHooks = (ctx, patchedFile) => { | ||
ctx.meta.storage = patchedFile.details; | ||
if (isComplete(patchedFile)) { | ||
return core.hook('after-complete', ctx, patchedFile); | ||
return ctx.source.hook('after-complete', ctx, patchedFile); | ||
} | ||
return rxjs_1.of(patchedFile); | ||
return (0, rxjs_2.of)(patchedFile); | ||
}; | ||
@@ -32,0 +36,0 @@ const extractPatchHeaders = (ctx) => { |
@@ -7,2 +7,3 @@ import { Tussle, TussleConfig } from './core'; | ||
import { TTLCache } from './util/ttlcache'; | ||
export { Tussle, TusProtocolExtension, TussleConfig, TussleIncomingRequest, TussleOutgoingRequest, TussleOutgoingResponse, TussleRequestService, TussleStateNamespace, TussleStorageService, TTLCache, }; | ||
import { TussleBaseMiddleware } from './middleware'; | ||
export { Tussle, TusProtocolExtension, TussleConfig, TussleIncomingRequest, TussleOutgoingRequest, TussleOutgoingResponse, TussleRequestService, TussleStateNamespace, TussleStorageService, TussleBaseMiddleware, TTLCache, }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.TTLCache = exports.TussleStateNamespace = exports.Tussle = void 0; | ||
exports.TTLCache = exports.TussleBaseMiddleware = exports.TussleStateNamespace = exports.Tussle = void 0; | ||
const core_1 = require("./core"); | ||
@@ -10,1 +10,3 @@ Object.defineProperty(exports, "Tussle", { enumerable: true, get: function () { return core_1.Tussle; } }); | ||
Object.defineProperty(exports, "TTLCache", { enumerable: true, get: function () { return ttlcache_1.TTLCache; } }); | ||
const middleware_1 = require("./middleware"); | ||
Object.defineProperty(exports, "TussleBaseMiddleware", { enumerable: true, get: function () { return middleware_1.TussleBaseMiddleware; } }); |
@@ -16,7 +16,2 @@ "use strict"; | ||
const item = this.state.getItem(key); | ||
item.then((item) => { | ||
if (!item) { | ||
console.error('MISSING', key); | ||
} | ||
}); | ||
return item; | ||
@@ -23,0 +18,0 @@ } |
@@ -13,2 +13,3 @@ export declare type TTLCacheType<T> = T extends TTLCache<infer U> ? U : never; | ||
constructor(ttl?: number, garbageCollectionInterval?: number, cache?: Record<string, TTLCacheItem<T>>, now?: () => number); | ||
onRelease(_key: string, _data: T): void; | ||
getOrCreate(key: string, create: () => Promise<T>): Promise<T>; | ||
@@ -18,5 +19,8 @@ getItem(key: string): T | null; | ||
setItem(key: string, data: null): null; | ||
removeItem(key: string): T | null; | ||
key(nth: number): string | null; | ||
private garbageCollect; | ||
private release; | ||
private asyncGarbageCollect; | ||
} | ||
export {}; |
@@ -25,2 +25,3 @@ "use strict"; | ||
} | ||
onRelease(_key, _data) { } | ||
getOrCreate(key, create) { | ||
@@ -58,6 +59,16 @@ return __awaiter(this, void 0, void 0, function* () { | ||
else { | ||
delete this.cache[key]; | ||
this.release(key); | ||
} | ||
return data; | ||
} | ||
removeItem(key) { | ||
const hit = this.cache[key]; | ||
if (hit) { | ||
delete this.cache[key]; | ||
} | ||
return hit.data; | ||
} | ||
key(nth) { | ||
return Object.keys(this.cache)[nth] || null; | ||
} | ||
garbageCollect() { | ||
@@ -68,6 +79,15 @@ const now = this.now(); | ||
if (isExpired(now, cache[key].atime, ttl)) { | ||
delete cache[key]; | ||
this.release(key); | ||
} | ||
} | ||
} | ||
release(key) { | ||
const item = this.cache[key]; | ||
if (item) { | ||
if (this.onRelease) { | ||
this.onRelease(key, item.data); | ||
} | ||
delete this.cache[key]; | ||
} | ||
} | ||
asyncGarbageCollect(now, force = false) { | ||
@@ -74,0 +94,0 @@ if (force || isExpired(now, this.lastGarbageCollection, this.garbageCollectionInterval)) { |
{ | ||
"name": "@tussle/core", | ||
"version": "0.0.5", | ||
"version": "0.1.0", | ||
"description": "Tussle tus daemon core module", | ||
@@ -19,7 +19,7 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"js-base64": "^3.6.0", | ||
"rxjs": "^6.6.3" | ||
"js-base64": "^3.7.2", | ||
"rxjs": "^6.6.7" | ||
}, | ||
"devDependencies": { | ||
"jest": "^26.6.3", | ||
"jest": "^27.5.1", | ||
"npm-run-all": "^4.1.5", | ||
@@ -35,3 +35,3 @@ "rimraf": "^3.0.2" | ||
}, | ||
"gitHead": "5c214317d3148878710064b8a175960d350368a2" | ||
"gitHead": "3b2ef13249ca16b6c4db1f3c761ee3eb92053445" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
28216
24
657
Updatedjs-base64@^3.7.2
Updatedrxjs@^6.6.7