Comparing version 0.2.1 to 0.2.2
@@ -87,2 +87,5 @@ "use strict"; | ||
} | ||
throw(error) { | ||
throw error; | ||
} | ||
} | ||
@@ -89,0 +92,0 @@ exports.CliContext = CliContext; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.VERSION = void 0; | ||
exports.VERSION = "0.2.0"; | ||
exports.VERSION = "0.2.1"; | ||
//# sourceMappingURL=consts.js.map |
@@ -23,2 +23,25 @@ "use strict"; | ||
} | ||
findRegistryValue(id) { | ||
return this.registry.get(id); | ||
} | ||
putRegistryValue(value) { | ||
this.registry.set(value.id, value); | ||
} | ||
cloneWith(id, ...args) { | ||
const container = new Container(id); | ||
for (const arg of args) { | ||
const serviceValue = this.findRegistryValue(arg); | ||
if (!serviceValue) { | ||
throw this.createErrorWithIdentifier(arg, 'Identifier %id not exists.'); | ||
} | ||
container.register(serviceValue); | ||
} | ||
return container; | ||
} | ||
/** | ||
* create error with identifier | ||
* @param id container id | ||
* @param message error message | ||
* @returns | ||
*/ | ||
createErrorWithIdentifier(id, message) { | ||
@@ -36,2 +59,7 @@ let idstr = 'unk'; | ||
} | ||
/** | ||
* add or replace | ||
* @param {ContainerValue} data | ||
* @param {boolean} replace | ||
*/ | ||
register(data, replace = false) { | ||
@@ -54,8 +82,30 @@ if (this.registry.has(data.id) && !replace) { | ||
} | ||
delete(id) { | ||
this.registry.delete(id); | ||
this.resolvedRegistry.delete(id); | ||
} | ||
has(id) { | ||
return this.registry.has(id); | ||
} | ||
/** | ||
* only set if not exists | ||
* @param data | ||
* @returns | ||
*/ | ||
set(data) { | ||
return this.register(data); | ||
} | ||
/** | ||
* set or replace | ||
* @param data | ||
* @returns | ||
*/ | ||
put(data) { | ||
return this.register(data, true); | ||
} | ||
/** | ||
* value scope, for static values | ||
* @param id | ||
* @returns | ||
*/ | ||
value(id) { | ||
@@ -66,3 +116,3 @@ const data = this.registry.get(id); | ||
} | ||
if (!data.value) { | ||
if (!data.value || data.scope != ContainerScope.Value) { | ||
throw this.createErrorWithIdentifier(id, 'Identifier(%id) does not have a value.'); | ||
@@ -72,20 +122,7 @@ } | ||
} | ||
valueResolver(data) { | ||
const value = data.value; | ||
if (value) { | ||
if ((0, type_js_1.isClass)(value)) { | ||
return new value(); | ||
} | ||
if ((0, type_js_1.isFunction)(value)) { | ||
return value(); | ||
} | ||
return value; | ||
} | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
factoryResolver(data) { | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* check if container already have resolved value | ||
* @param id | ||
* @returns | ||
*/ | ||
hasResolved(id) { | ||
@@ -116,5 +153,38 @@ const data = this.registry.get(id); | ||
} | ||
valueResolver(data) { | ||
const value = data.value; | ||
if (value) { | ||
if ((0, type_js_1.isClass)(value)) { | ||
return new value(); | ||
} | ||
if ((0, type_js_1.isFunction)(value)) { | ||
return value(); | ||
} | ||
return value; | ||
} | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* unused | ||
* @param data | ||
* @returns | ||
*/ | ||
factoryResolver(data) { | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* use to resolve async deps | ||
* @param id | ||
* @returns | ||
*/ | ||
resolveAsync(id) { | ||
return this.resolve(id); | ||
} | ||
/** | ||
* resolve service with given identifier | ||
* @param id | ||
* @returns | ||
*/ | ||
resolve(id) { | ||
@@ -152,2 +222,8 @@ const data = this.registry.get(id); | ||
} | ||
/** | ||
* does not consider/respect scope, always return fresh value if possible, | ||
* as static value will be always be stale | ||
* @param id | ||
* @returns | ||
*/ | ||
fresh(id) { | ||
@@ -160,8 +236,23 @@ const data = this.registry.get(id); | ||
} | ||
/** | ||
* call a function with given deps | ||
* @param cb | ||
* @param deps | ||
* @returns | ||
*/ | ||
call(cb, deps = []) { | ||
return cb(...deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* call a async function or when you have async deps | ||
* @param cb | ||
* @param deps | ||
* @returns | ||
*/ | ||
async callAsync(cb, deps = []) { | ||
return await cb(...await Promise.all(deps.map(dep => this.resolveAsync(dep)))); | ||
} | ||
/** | ||
* pre-resolve all Singleton deps | ||
*/ | ||
preresolve() { | ||
@@ -175,2 +266,6 @@ for (const id of this.registry.keys()) { | ||
} | ||
/** | ||
* each context will have different registry | ||
* @returns | ||
*/ | ||
ctxRegistry() { | ||
@@ -181,6 +276,6 @@ if (!this.asyncLocalStorage) { | ||
const store = this.asyncLocalStorage.getStore(); | ||
if (!store['contextRegistryKey']) { | ||
return store['contextRegistryKey'] = new Map; | ||
if (!store[Container.contextRegistryKey]) { | ||
return store[Container.contextRegistryKey] = new Map; | ||
} | ||
return store['contextRegistryKey']; | ||
return store[Container.contextRegistryKey]; | ||
} | ||
@@ -187,0 +282,0 @@ setAsyncLocalStorage(asyncLocalStorage) { |
@@ -8,2 +8,3 @@ "use strict"; | ||
const consts_js_1 = require("./consts.js"); | ||
const result_js_1 = require("../support/result.js"); | ||
class HttpApp { | ||
@@ -72,3 +73,3 @@ opts; | ||
if (route) { | ||
// execute route | ||
// execute route, it will handover control to next middleware | ||
await route.getComposedHandler()(ctx, next); | ||
@@ -85,9 +86,17 @@ return; | ||
// } | ||
const status = err.statusCode || err.status || err.code || 500; | ||
const message = status < 500 || err.expose ? err.message : 'Internal server error.'; | ||
const data = { message }; | ||
if (status == 422 && err.errors) { | ||
data.errors = err.errors; | ||
err.status = err.statusCode || err.status || err.code || 500; | ||
const message = err.status < 500 || err.expose ? err.message : 'Internal server error.'; | ||
const resultError = result_js_1.ResultError.try(err); | ||
if (ctx.accepts('html')) { | ||
if (this.viewProvider) { | ||
await this.viewProvider.renderError(ctx, resultError); | ||
} | ||
} | ||
ctx.json(data, status); | ||
else { | ||
const data = { message }; | ||
if (err.status == 422 && err.errors) { | ||
data.errors = err.errors; | ||
} | ||
ctx.json(data, err.status); | ||
} | ||
// if (ctx.accepts('json')) { | ||
@@ -94,0 +103,0 @@ // ctx.json(data, status); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.HttpRequestQuery = exports.HttpRequestHeaders = void 0; | ||
exports.HttpCookies = exports.HttpRequestQuery = exports.HttpRequestHeaders = void 0; | ||
class HttpRequestHeaders { | ||
@@ -70,2 +70,25 @@ headers; | ||
exports.HttpRequestQuery = HttpRequestQuery; | ||
class HttpCookies { | ||
raw; | ||
cookies = {}; | ||
constructor(raw) { | ||
this.raw = raw; | ||
const carray = raw.split(';'); | ||
for (const citem of carray) { | ||
let [name, value, ...rest] = citem.split('='); | ||
name = name?.trim(); | ||
if (name) { | ||
} | ||
// const value = rest.join(`=`).trim(); | ||
this.cookies[name] = { value, rest }; | ||
} | ||
} | ||
get(name) { | ||
return this.cookies[name]?.value; | ||
} | ||
set(name, value, opts) { | ||
this.cookies[name] = { value, ...opts }; | ||
} | ||
} | ||
exports.HttpCookies = HttpCookies; | ||
//# sourceMappingURL=context.js.map |
@@ -29,2 +29,3 @@ "use strict"; | ||
headers; | ||
cookies; | ||
params = {}; | ||
@@ -40,2 +41,3 @@ willRender = false; | ||
}); | ||
this.cookies = ctx.cookies; | ||
} | ||
@@ -102,5 +104,16 @@ get host() { | ||
} | ||
abort(status, message) { | ||
this.ctx.status = status; | ||
this.ctx.body = { message: message || 'Something went wrong.' }; | ||
abort(...args) { | ||
if (args.length == 1) { | ||
if (typeof args[0] == 'number') { | ||
this.ctx.status = args[0]; | ||
this.ctx.body = { message: 'Something went wrong.' }; | ||
return; | ||
} | ||
const err = args[0]; | ||
this.ctx.status = err.status; | ||
this.ctx.body = { message: err.message, ...(err.data || {}) }; | ||
return; | ||
} | ||
this.ctx.status = args[0]; | ||
this.ctx.body = { message: args[1] || 'Something went wrong.', ...(args[2] || {}) }; | ||
} | ||
@@ -112,3 +125,9 @@ /** | ||
reply(response) { | ||
this._reply = response; | ||
if (response.headers) { | ||
for (const key in response.headers) { | ||
this.ctx.set(key, response.headers[key]); | ||
} | ||
} | ||
this.ctx.status = response.status; | ||
this.ctx.body = response.body; | ||
} | ||
@@ -120,2 +139,5 @@ stream(stream, mime = 'application/octet-stream') { | ||
} | ||
redirect(url, alt) { | ||
this.ctx.redirect(url, alt); | ||
} | ||
async view(template, data = {}, status = 200) { | ||
@@ -127,3 +149,3 @@ const view = this.value(consts_js_1.HTTP_KEY_VIEW_PROVIDER); | ||
this.ctx.status = status; | ||
this.ctx.body = await view.render(template, data); | ||
this.ctx.body = await view.render(this, template, data); | ||
this.willRender = true; | ||
@@ -130,0 +152,0 @@ } |
@@ -15,2 +15,5 @@ "use strict"; | ||
} | ||
setStatus(status) { | ||
this.status = status; | ||
} | ||
setHeader(key, value) { | ||
@@ -17,0 +20,0 @@ this._headers[key] = value; |
@@ -14,2 +14,4 @@ "use strict"; | ||
path = ''; | ||
middlewares = []; | ||
composedHandler = null; | ||
constructor(methods, path, handler, metadata = {}) { | ||
@@ -107,3 +109,6 @@ this.methods = methods; | ||
clone() { | ||
return new HttpRoute(Array.isArray(this.methods) ? [...this.methods] : '*', this.path, this.handler, { ...this.metadata }); | ||
const route = new HttpRoute(Array.isArray(this.methods) ? [...this.methods] : '*', this.path, this.handler, { ...this.metadata }); | ||
route.middleware(...this.middlewares); | ||
route.as(this._name); | ||
return route; | ||
} | ||
@@ -129,7 +134,18 @@ /** | ||
middleware(...middlewares) { | ||
this.handler = (Array.isArray(this.handler) ? [...middlewares, ...this.handler] : [...middlewares, this.handler]); | ||
for (const cb of middlewares) { | ||
// if (!this.middlewares.includes(cb)) { | ||
this.middlewares.push(cb); | ||
// } | ||
} | ||
return this; | ||
} | ||
getComposedHandler() { | ||
return (0, compose_js_1.composeAsync)((Array.isArray(this.handler) ? this.handler : [this.handler])); | ||
if (this.composedHandler) { | ||
return this.composedHandler; | ||
} | ||
this.composedHandler = (0, compose_js_1.composeAsync)([ | ||
...this.middlewares, | ||
...(Array.isArray(this.handler) ? this.handler : [this.handler]) | ||
]); | ||
return this.composedHandler; | ||
} | ||
@@ -136,0 +152,0 @@ } |
@@ -117,3 +117,3 @@ "use strict"; | ||
routePath = prefix + routePath; | ||
if (routePath.endsWith('/')) { | ||
if (routePath != '/' && routePath.endsWith('/')) { | ||
// remove trailing slash | ||
@@ -161,3 +161,2 @@ routePath = routePath.slice(0, routePath.length - 1); | ||
if (what instanceof HttpRouter) { | ||
console.log(`merge single in(${this._prefix})`, what._prefix); | ||
this.mergeRouters([what]); | ||
@@ -195,3 +194,3 @@ return; | ||
find(method, path, params = {}) { | ||
if (path.endsWith('/')) { | ||
if (path != '/' && path.endsWith('/')) { | ||
// ignore trailing slash(/) | ||
@@ -198,0 +197,0 @@ path = path.slice(0, path.length - 1); |
@@ -17,6 +17,9 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./asset.js"), exports); | ||
__exportStar(require("./compose.js"), exports); | ||
__exportStar(require("./date.js"), exports); | ||
__exportStar(require("./file.js"), exports); | ||
__exportStar(require("./mime.js"), exports); | ||
__exportStar(require("./result.js"), exports); | ||
__exportStar(require("./type.js"), exports); | ||
//# sourceMappingURL=index.js.map |
@@ -9,2 +9,14 @@ "use strict"; | ||
switch (ext) { | ||
case '.css': | ||
type = 'text/css'; | ||
break; | ||
case '.js': | ||
type = 'text/javascript'; | ||
break; | ||
case '.json': | ||
type = 'application/json'; | ||
break; | ||
case '.html': | ||
type = 'text/html'; | ||
break; | ||
case '.jpg': | ||
@@ -11,0 +23,0 @@ case '.jpeg': |
@@ -33,5 +33,14 @@ "use strict"; | ||
} | ||
metadata() { | ||
return this._data; | ||
get status() { | ||
return this.code; | ||
} | ||
get data() { | ||
return this._data || {}; | ||
} | ||
metadata(data) { | ||
if (data) { | ||
this._data = data; | ||
} | ||
return this._data || {}; | ||
} | ||
static from(error, data) { | ||
@@ -53,2 +62,27 @@ if (error instanceof ResultError) { | ||
} | ||
static try(error, data) { | ||
if (error instanceof ResultError) { | ||
return error; | ||
} | ||
if (error instanceof Error) { | ||
let code = 500; | ||
if (data && typeof data['code'] != "undefined") { | ||
code = data['code']; | ||
} | ||
else if (error.code) { | ||
code = error.code; | ||
} | ||
return new ResultError(error.message, code || 500, data); | ||
} | ||
return new ResultError(error.message, error.code, error.data); | ||
} | ||
toHttpResponse() { | ||
return { | ||
status: this.status, | ||
body: { | ||
message: this.expose ? this.message : 'Internal server error.', | ||
...(this.data || {}), | ||
} | ||
}; | ||
} | ||
toJSON() { | ||
@@ -58,2 +92,3 @@ return { | ||
code: this.code, | ||
...(this.data || {}), | ||
}; | ||
@@ -60,0 +95,0 @@ } |
@@ -11,2 +11,3 @@ "use strict"; | ||
headers; | ||
cookies; | ||
params = {}; | ||
@@ -19,2 +20,3 @@ willRender = false; | ||
body: undefined, | ||
redirect: undefined, | ||
}; | ||
@@ -35,2 +37,3 @@ _reply = null; | ||
}); | ||
this.cookies = new context_js_1.HttpCookies(""); | ||
} | ||
@@ -124,5 +127,16 @@ modify(ctx) { | ||
} | ||
abort(status, message) { | ||
this.response.status = status; | ||
this.response.body = { message: message || 'Something went wrong.' }; | ||
abort(...args) { | ||
if (args.length == 1) { | ||
if (typeof args[0] == 'number') { | ||
this.response.status = args[0]; | ||
this.response.body = { message: 'Something went wrong.' }; | ||
return; | ||
} | ||
const err = args[0]; | ||
this.response.status = err.status; | ||
this.response.body = { message: err.message }; | ||
return; | ||
} | ||
this.response.status = args[0]; | ||
this.response.body = { message: args[1] || 'Something went wrong.', data: args[2] }; | ||
} | ||
@@ -141,2 +155,5 @@ /** | ||
} | ||
redirect(url, alt) { | ||
this.response.redirect = { url, alt }; | ||
} | ||
async view(template, data = {}, status = 200) { | ||
@@ -148,3 +165,3 @@ const view = this.value(consts_js_1.HTTP_KEY_VIEW_PROVIDER); | ||
this.response.status = status; | ||
this.response.body = await view.render(template, data); | ||
this.response.body = await view.render(this, template, data); | ||
this.willRender = true; | ||
@@ -151,0 +168,0 @@ } |
@@ -84,2 +84,5 @@ import * as util from 'node:util'; | ||
} | ||
throw(error) { | ||
throw error; | ||
} | ||
} | ||
@@ -86,0 +89,0 @@ function parseFlag(flag) { |
@@ -1,2 +0,2 @@ | ||
export const VERSION = "0.2.0"; | ||
export const VERSION = "0.2.1"; | ||
//# sourceMappingURL=consts.js.map |
@@ -20,2 +20,25 @@ import { AsyncLocalStorage } from "async_hooks"; | ||
} | ||
findRegistryValue(id) { | ||
return this.registry.get(id); | ||
} | ||
putRegistryValue(value) { | ||
this.registry.set(value.id, value); | ||
} | ||
cloneWith(id, ...args) { | ||
const container = new Container(id); | ||
for (const arg of args) { | ||
const serviceValue = this.findRegistryValue(arg); | ||
if (!serviceValue) { | ||
throw this.createErrorWithIdentifier(arg, 'Identifier %id not exists.'); | ||
} | ||
container.register(serviceValue); | ||
} | ||
return container; | ||
} | ||
/** | ||
* create error with identifier | ||
* @param id container id | ||
* @param message error message | ||
* @returns | ||
*/ | ||
createErrorWithIdentifier(id, message) { | ||
@@ -33,2 +56,7 @@ let idstr = 'unk'; | ||
} | ||
/** | ||
* add or replace | ||
* @param {ContainerValue} data | ||
* @param {boolean} replace | ||
*/ | ||
register(data, replace = false) { | ||
@@ -51,8 +79,30 @@ if (this.registry.has(data.id) && !replace) { | ||
} | ||
delete(id) { | ||
this.registry.delete(id); | ||
this.resolvedRegistry.delete(id); | ||
} | ||
has(id) { | ||
return this.registry.has(id); | ||
} | ||
/** | ||
* only set if not exists | ||
* @param data | ||
* @returns | ||
*/ | ||
set(data) { | ||
return this.register(data); | ||
} | ||
/** | ||
* set or replace | ||
* @param data | ||
* @returns | ||
*/ | ||
put(data) { | ||
return this.register(data, true); | ||
} | ||
/** | ||
* value scope, for static values | ||
* @param id | ||
* @returns | ||
*/ | ||
value(id) { | ||
@@ -63,3 +113,3 @@ const data = this.registry.get(id); | ||
} | ||
if (!data.value) { | ||
if (!data.value || data.scope != ContainerScope.Value) { | ||
throw this.createErrorWithIdentifier(id, 'Identifier(%id) does not have a value.'); | ||
@@ -69,20 +119,7 @@ } | ||
} | ||
valueResolver(data) { | ||
const value = data.value; | ||
if (value) { | ||
if (isClass(value)) { | ||
return new value(); | ||
} | ||
if (isFunction(value)) { | ||
return value(); | ||
} | ||
return value; | ||
} | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
factoryResolver(data) { | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* check if container already have resolved value | ||
* @param id | ||
* @returns | ||
*/ | ||
hasResolved(id) { | ||
@@ -113,5 +150,38 @@ const data = this.registry.get(id); | ||
} | ||
valueResolver(data) { | ||
const value = data.value; | ||
if (value) { | ||
if (isClass(value)) { | ||
return new value(); | ||
} | ||
if (isFunction(value)) { | ||
return value(); | ||
} | ||
return value; | ||
} | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* unused | ||
* @param data | ||
* @returns | ||
*/ | ||
factoryResolver(data) { | ||
const factory = data.factory; | ||
return factory.apply(null, data.deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* use to resolve async deps | ||
* @param id | ||
* @returns | ||
*/ | ||
resolveAsync(id) { | ||
return this.resolve(id); | ||
} | ||
/** | ||
* resolve service with given identifier | ||
* @param id | ||
* @returns | ||
*/ | ||
resolve(id) { | ||
@@ -149,2 +219,8 @@ const data = this.registry.get(id); | ||
} | ||
/** | ||
* does not consider/respect scope, always return fresh value if possible, | ||
* as static value will be always be stale | ||
* @param id | ||
* @returns | ||
*/ | ||
fresh(id) { | ||
@@ -157,8 +233,23 @@ const data = this.registry.get(id); | ||
} | ||
/** | ||
* call a function with given deps | ||
* @param cb | ||
* @param deps | ||
* @returns | ||
*/ | ||
call(cb, deps = []) { | ||
return cb(...deps.map(dep => this.resolve(dep))); | ||
} | ||
/** | ||
* call a async function or when you have async deps | ||
* @param cb | ||
* @param deps | ||
* @returns | ||
*/ | ||
async callAsync(cb, deps = []) { | ||
return await cb(...await Promise.all(deps.map(dep => this.resolveAsync(dep)))); | ||
} | ||
/** | ||
* pre-resolve all Singleton deps | ||
*/ | ||
preresolve() { | ||
@@ -172,2 +263,6 @@ for (const id of this.registry.keys()) { | ||
} | ||
/** | ||
* each context will have different registry | ||
* @returns | ||
*/ | ||
ctxRegistry() { | ||
@@ -178,6 +273,6 @@ if (!this.asyncLocalStorage) { | ||
const store = this.asyncLocalStorage.getStore(); | ||
if (!store['contextRegistryKey']) { | ||
return store['contextRegistryKey'] = new Map; | ||
if (!store[Container.contextRegistryKey]) { | ||
return store[Container.contextRegistryKey] = new Map; | ||
} | ||
return store['contextRegistryKey']; | ||
return store[Container.contextRegistryKey]; | ||
} | ||
@@ -184,0 +279,0 @@ setAsyncLocalStorage(asyncLocalStorage) { |
@@ -5,2 +5,3 @@ import { AsyncLocalStorage } from 'node:async_hooks'; | ||
import { HTTP_KEY_REQ_ID, HTTP_KEY_VIEW_PROVIDER } from './consts.js'; | ||
import { ResultError } from '../support/result.js'; | ||
export class HttpApp { | ||
@@ -69,3 +70,3 @@ opts; | ||
if (route) { | ||
// execute route | ||
// execute route, it will handover control to next middleware | ||
await route.getComposedHandler()(ctx, next); | ||
@@ -82,9 +83,17 @@ return; | ||
// } | ||
const status = err.statusCode || err.status || err.code || 500; | ||
const message = status < 500 || err.expose ? err.message : 'Internal server error.'; | ||
const data = { message }; | ||
if (status == 422 && err.errors) { | ||
data.errors = err.errors; | ||
err.status = err.statusCode || err.status || err.code || 500; | ||
const message = err.status < 500 || err.expose ? err.message : 'Internal server error.'; | ||
const resultError = ResultError.try(err); | ||
if (ctx.accepts('html')) { | ||
if (this.viewProvider) { | ||
await this.viewProvider.renderError(ctx, resultError); | ||
} | ||
} | ||
ctx.json(data, status); | ||
else { | ||
const data = { message }; | ||
if (err.status == 422 && err.errors) { | ||
data.errors = err.errors; | ||
} | ||
ctx.json(data, err.status); | ||
} | ||
// if (ctx.accepts('json')) { | ||
@@ -91,0 +100,0 @@ // ctx.json(data, status); |
@@ -65,2 +65,24 @@ export class HttpRequestHeaders { | ||
} | ||
export class HttpCookies { | ||
raw; | ||
cookies = {}; | ||
constructor(raw) { | ||
this.raw = raw; | ||
const carray = raw.split(';'); | ||
for (const citem of carray) { | ||
let [name, value, ...rest] = citem.split('='); | ||
name = name?.trim(); | ||
if (name) { | ||
} | ||
// const value = rest.join(`=`).trim(); | ||
this.cookies[name] = { value, rest }; | ||
} | ||
} | ||
get(name) { | ||
return this.cookies[name]?.value; | ||
} | ||
set(name, value, opts) { | ||
this.cookies[name] = { value, ...opts }; | ||
} | ||
} | ||
//# sourceMappingURL=context.js.map |
@@ -25,2 +25,3 @@ import { HttpRequestHeaders, HttpRequestQuery } from './context.js'; | ||
headers; | ||
cookies; | ||
params = {}; | ||
@@ -36,2 +37,3 @@ willRender = false; | ||
}); | ||
this.cookies = ctx.cookies; | ||
} | ||
@@ -98,5 +100,16 @@ get host() { | ||
} | ||
abort(status, message) { | ||
this.ctx.status = status; | ||
this.ctx.body = { message: message || 'Something went wrong.' }; | ||
abort(...args) { | ||
if (args.length == 1) { | ||
if (typeof args[0] == 'number') { | ||
this.ctx.status = args[0]; | ||
this.ctx.body = { message: 'Something went wrong.' }; | ||
return; | ||
} | ||
const err = args[0]; | ||
this.ctx.status = err.status; | ||
this.ctx.body = { message: err.message, ...(err.data || {}) }; | ||
return; | ||
} | ||
this.ctx.status = args[0]; | ||
this.ctx.body = { message: args[1] || 'Something went wrong.', ...(args[2] || {}) }; | ||
} | ||
@@ -108,3 +121,9 @@ /** | ||
reply(response) { | ||
this._reply = response; | ||
if (response.headers) { | ||
for (const key in response.headers) { | ||
this.ctx.set(key, response.headers[key]); | ||
} | ||
} | ||
this.ctx.status = response.status; | ||
this.ctx.body = response.body; | ||
} | ||
@@ -116,2 +135,5 @@ stream(stream, mime = 'application/octet-stream') { | ||
} | ||
redirect(url, alt) { | ||
this.ctx.redirect(url, alt); | ||
} | ||
async view(template, data = {}, status = 200) { | ||
@@ -123,3 +145,3 @@ const view = this.value(HTTP_KEY_VIEW_PROVIDER); | ||
this.ctx.status = status; | ||
this.ctx.body = await view.render(template, data); | ||
this.ctx.body = await view.render(this, template, data); | ||
this.willRender = true; | ||
@@ -126,0 +148,0 @@ } |
@@ -12,2 +12,5 @@ export class HttpResponse { | ||
} | ||
setStatus(status) { | ||
this.status = status; | ||
} | ||
setHeader(key, value) { | ||
@@ -14,0 +17,0 @@ this._headers[key] = value; |
@@ -11,2 +11,4 @@ import { composeAsync } from '../../support/compose.js'; | ||
path = ''; | ||
middlewares = []; | ||
composedHandler = null; | ||
constructor(methods, path, handler, metadata = {}) { | ||
@@ -104,3 +106,6 @@ this.methods = methods; | ||
clone() { | ||
return new HttpRoute(Array.isArray(this.methods) ? [...this.methods] : '*', this.path, this.handler, { ...this.metadata }); | ||
const route = new HttpRoute(Array.isArray(this.methods) ? [...this.methods] : '*', this.path, this.handler, { ...this.metadata }); | ||
route.middleware(...this.middlewares); | ||
route.as(this._name); | ||
return route; | ||
} | ||
@@ -126,9 +131,20 @@ /** | ||
middleware(...middlewares) { | ||
this.handler = (Array.isArray(this.handler) ? [...middlewares, ...this.handler] : [...middlewares, this.handler]); | ||
for (const cb of middlewares) { | ||
// if (!this.middlewares.includes(cb)) { | ||
this.middlewares.push(cb); | ||
// } | ||
} | ||
return this; | ||
} | ||
getComposedHandler() { | ||
return composeAsync((Array.isArray(this.handler) ? this.handler : [this.handler])); | ||
if (this.composedHandler) { | ||
return this.composedHandler; | ||
} | ||
this.composedHandler = composeAsync([ | ||
...this.middlewares, | ||
...(Array.isArray(this.handler) ? this.handler : [this.handler]) | ||
]); | ||
return this.composedHandler; | ||
} | ||
} | ||
//# sourceMappingURL=route.js.map |
@@ -114,3 +114,3 @@ import { HttpRoute } from './route.js'; | ||
routePath = prefix + routePath; | ||
if (routePath.endsWith('/')) { | ||
if (routePath != '/' && routePath.endsWith('/')) { | ||
// remove trailing slash | ||
@@ -158,3 +158,2 @@ routePath = routePath.slice(0, routePath.length - 1); | ||
if (what instanceof HttpRouter) { | ||
console.log(`merge single in(${this._prefix})`, what._prefix); | ||
this.mergeRouters([what]); | ||
@@ -192,3 +191,3 @@ return; | ||
find(method, path, params = {}) { | ||
if (path.endsWith('/')) { | ||
if (path != '/' && path.endsWith('/')) { | ||
// ignore trailing slash(/) | ||
@@ -195,0 +194,0 @@ path = path.slice(0, path.length - 1); |
@@ -0,5 +1,8 @@ | ||
export * from './asset.js'; | ||
export * from './compose.js'; | ||
export * from './date.js'; | ||
export * from './file.js'; | ||
export * from './mime.js'; | ||
export * from './result.js'; | ||
export * from './type.js'; | ||
//# sourceMappingURL=index.js.map |
@@ -6,2 +6,14 @@ import * as path from 'node:path'; | ||
switch (ext) { | ||
case '.css': | ||
type = 'text/css'; | ||
break; | ||
case '.js': | ||
type = 'text/javascript'; | ||
break; | ||
case '.json': | ||
type = 'application/json'; | ||
break; | ||
case '.html': | ||
type = 'text/html'; | ||
break; | ||
case '.jpg': | ||
@@ -8,0 +20,0 @@ case '.jpeg': |
@@ -29,5 +29,14 @@ export class Result { | ||
} | ||
metadata() { | ||
return this._data; | ||
get status() { | ||
return this.code; | ||
} | ||
get data() { | ||
return this._data || {}; | ||
} | ||
metadata(data) { | ||
if (data) { | ||
this._data = data; | ||
} | ||
return this._data || {}; | ||
} | ||
static from(error, data) { | ||
@@ -49,2 +58,27 @@ if (error instanceof ResultError) { | ||
} | ||
static try(error, data) { | ||
if (error instanceof ResultError) { | ||
return error; | ||
} | ||
if (error instanceof Error) { | ||
let code = 500; | ||
if (data && typeof data['code'] != "undefined") { | ||
code = data['code']; | ||
} | ||
else if (error.code) { | ||
code = error.code; | ||
} | ||
return new ResultError(error.message, code || 500, data); | ||
} | ||
return new ResultError(error.message, error.code, error.data); | ||
} | ||
toHttpResponse() { | ||
return { | ||
status: this.status, | ||
body: { | ||
message: this.expose ? this.message : 'Internal server error.', | ||
...(this.data || {}), | ||
} | ||
}; | ||
} | ||
toJSON() { | ||
@@ -54,2 +88,3 @@ return { | ||
code: this.code, | ||
...(this.data || {}), | ||
}; | ||
@@ -56,0 +91,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { HttpRequestHeaders, HttpRequestQuery } from '../../http/context.js'; | ||
import { HttpCookies, HttpRequestHeaders, HttpRequestQuery } from '../../http/context.js'; | ||
import { HTTP_KEY_VIEW_PROVIDER } from '../../http/consts.js'; | ||
@@ -8,2 +8,3 @@ import { TestHttpError } from './error.js'; | ||
headers; | ||
cookies; | ||
params = {}; | ||
@@ -16,2 +17,3 @@ willRender = false; | ||
body: undefined, | ||
redirect: undefined, | ||
}; | ||
@@ -32,2 +34,3 @@ _reply = null; | ||
}); | ||
this.cookies = new HttpCookies(""); | ||
} | ||
@@ -121,5 +124,16 @@ modify(ctx) { | ||
} | ||
abort(status, message) { | ||
this.response.status = status; | ||
this.response.body = { message: message || 'Something went wrong.' }; | ||
abort(...args) { | ||
if (args.length == 1) { | ||
if (typeof args[0] == 'number') { | ||
this.response.status = args[0]; | ||
this.response.body = { message: 'Something went wrong.' }; | ||
return; | ||
} | ||
const err = args[0]; | ||
this.response.status = err.status; | ||
this.response.body = { message: err.message }; | ||
return; | ||
} | ||
this.response.status = args[0]; | ||
this.response.body = { message: args[1] || 'Something went wrong.', data: args[2] }; | ||
} | ||
@@ -138,2 +152,5 @@ /** | ||
} | ||
redirect(url, alt) { | ||
this.response.redirect = { url, alt }; | ||
} | ||
async view(template, data = {}, status = 200) { | ||
@@ -145,3 +162,3 @@ const view = this.value(HTTP_KEY_VIEW_PROVIDER); | ||
this.response.status = status; | ||
this.response.body = await view.render(template, data); | ||
this.response.body = await view.render(this, template, data); | ||
this.willRender = true; | ||
@@ -148,0 +165,0 @@ } |
{ | ||
"name": "astad", | ||
"version": "0.2.1", | ||
"version": "0.2.2", | ||
"description": "astad is a nodejs framework for api and web development", | ||
@@ -58,3 +58,3 @@ "main": "./cjs/index.js", | ||
"compile": "npx tsc -p tsconfig.json && npx tsc -p cjs.tsconfig.json && node fixup.js", | ||
"test": "NODE_OPTIONS=\"--loader ts-node/esm --experimental-specifier-resolution=node\" tap ./src/http/router/*.spec.ts -- --ts" | ||
"test": "./test" | ||
}, | ||
@@ -72,3 +72,3 @@ "repository": { | ||
], | ||
"author": "Harcharan Singh <harcharan1628@gmail.com>", | ||
"author": "Harcharan Singh <bitnbytesio@gmail.com>", | ||
"license": "ISC", | ||
@@ -88,2 +88,2 @@ "bugs": { | ||
} | ||
} | ||
} |
@@ -228,2 +228,5 @@ # Astad | ||
- templating | ||
- testing | ||
- testing | ||
## Bugs | ||
- handle duplicate middlewares in router, should only have unique middlewares |
@@ -34,2 +34,3 @@ /// <reference types="node" /> | ||
confirm(question: string): Promise<unknown>; | ||
throw(error: Error): void; | ||
} | ||
@@ -111,3 +112,3 @@ export interface ICliParsedFlag { | ||
default: T; | ||
value: T; | ||
value?: T; | ||
type: Boolean | Number | String; | ||
@@ -114,0 +115,0 @@ } |
@@ -33,16 +33,92 @@ /// <reference types="node" /> | ||
constructor(id: ContainerIdentifer, asyncLocalStorage?: AsyncLocalStorage<any> | undefined); | ||
findRegistryValue(id: ContainerValueIdentifier): ContainerValue | undefined; | ||
putRegistryValue(value: ContainerValue): void; | ||
cloneWith(id: ContainerIdentifer, ...args: ContainerValueIdentifier[]): Container; | ||
/** | ||
* create error with identifier | ||
* @param id container id | ||
* @param message error message | ||
* @returns | ||
*/ | ||
createErrorWithIdentifier(id: ContainerValueIdentifier, message: string): Error; | ||
/** | ||
* add or replace | ||
* @param {ContainerValue} data | ||
* @param {boolean} replace | ||
*/ | ||
register(data: ContainerValue, replace?: boolean): void; | ||
delete(id: ContainerValueIdentifier): void; | ||
has(id: ContainerValueIdentifier): boolean; | ||
/** | ||
* only set if not exists | ||
* @param data | ||
* @returns | ||
*/ | ||
set(data: ContainerValue): void; | ||
/** | ||
* set or replace | ||
* @param data | ||
* @returns | ||
*/ | ||
put(data: ContainerValue): void; | ||
/** | ||
* value scope, for static values | ||
* @param id | ||
* @returns | ||
*/ | ||
value<T>(id: ContainerValueIdentifier): T; | ||
/** | ||
* check if container already have resolved value | ||
* @param id | ||
* @returns | ||
*/ | ||
hasResolved(id: ContainerValueIdentifier): boolean; | ||
protected valueResolver(data: ContainerValue): any; | ||
/** | ||
* unused | ||
* @param data | ||
* @returns | ||
*/ | ||
protected factoryResolver(data: ContainerValue): any; | ||
hasResolved(id: ContainerValueIdentifier): boolean; | ||
/** | ||
* use to resolve async deps | ||
* @param id | ||
* @returns | ||
*/ | ||
resolveAsync<T>(id: ContainerIdentifer): Promise<T>; | ||
/** | ||
* resolve service with given identifier | ||
* @param id | ||
* @returns | ||
*/ | ||
resolve<T>(id: ContainerValueIdentifier): T; | ||
/** | ||
* does not consider/respect scope, always return fresh value if possible, | ||
* as static value will be always be stale | ||
* @param id | ||
* @returns | ||
*/ | ||
fresh<T>(id: ContainerValueIdentifier): T; | ||
/** | ||
* call a function with given deps | ||
* @param cb | ||
* @param deps | ||
* @returns | ||
*/ | ||
call(cb: Function, deps?: Array<ContainerIdentifer>): any; | ||
/** | ||
* call a async function or when you have async deps | ||
* @param cb | ||
* @param deps | ||
* @returns | ||
*/ | ||
callAsync(cb: Function, deps?: Array<ContainerIdentifer>): Promise<any>; | ||
/** | ||
* pre-resolve all Singleton deps | ||
*/ | ||
preresolve(): void; | ||
/** | ||
* each context will have different registry | ||
* @returns | ||
*/ | ||
protected ctxRegistry(): Map<ContainerValueIdentifier, any>; | ||
@@ -49,0 +125,0 @@ setAsyncLocalStorage(asyncLocalStorage: AsyncLocalStorage<any>): void; |
@@ -57,5 +57,5 @@ /// <reference types="node" /> | ||
export interface IHttpMiddleware { | ||
handle(ctx: any, next: any): Promise<any>; | ||
handle(ctx: IHttpContext, next: any): Promise<any>; | ||
} | ||
export type HttpMiddlewareCallback = (ctx: any, next: any) => Promise<any>; | ||
export type HttpMiddlewareCallback = (ctx: IHttpContext, next: any) => Promise<any>; | ||
export type HttpAppMiddleware = HttpMiddlewareCallback | IHttpMiddleware; | ||
@@ -62,0 +62,0 @@ export type HttpCorrelationIdGenerator = () => { |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import * as internal from 'node:stream'; | ||
import { HttpResponse } from './response.js'; | ||
import { IHttpError, IHttpResponse } from './response.js'; | ||
export interface IHttpCookieOpts { | ||
maxAge?: number; | ||
expires?: Date; | ||
path?: string; | ||
domain?: string; | ||
secure?: boolean; | ||
httpOnly?: boolean; | ||
sameSite?: 'strict' | 'lax' | 'none' | boolean; | ||
signed?: boolean; | ||
overwrite?: boolean; | ||
} | ||
export interface IHttpCookies { | ||
get(name: string): string | undefined; | ||
set(name: string, value: string, options?: IHttpCookieOpts): any; | ||
} | ||
export interface IHttpContext { | ||
query: HttpRequestQuery; | ||
headers: HttpRequestHeaders; | ||
cookies: IHttpCookies; | ||
params: Record<string, string>; | ||
@@ -29,5 +45,7 @@ body: any; | ||
abort(status: number, message?: string): void; | ||
reply(response: HttpResponse): void; | ||
abort(error: IHttpError): void; | ||
reply(response: IHttpResponse): void; | ||
view(template: any, data?: any, status?: number): Promise<any>; | ||
stream(stream: internal.Readable, mime?: string): void; | ||
redirect(url: string, alt?: string): void; | ||
set<T = any>(key: any, value: T): T; | ||
@@ -59,1 +77,8 @@ value<T = any>(key: any): T | undefined; | ||
} | ||
export declare class HttpCookies { | ||
readonly raw: string; | ||
cookies: Record<string, any>; | ||
constructor(raw: string); | ||
get(name: string): any; | ||
set(name: string, value: string, opts: IHttpCookieOpts): void; | ||
} |
@@ -6,4 +6,4 @@ /// <reference types="node" /> | ||
import * as internal from 'node:stream'; | ||
import { HttpRequestHeaders, HttpRequestQuery, IHttpContext } from './context.js'; | ||
import { HttpResponse } from './response.js'; | ||
import { HttpRequestHeaders, HttpRequestQuery, IHttpContext, IHttpCookies } from './context.js'; | ||
import { IHttpError, IHttpResponse } from './response.js'; | ||
export type HttpKoaMiddlewareCallback = (ctx: any, next: any) => Promise<any> | any; | ||
@@ -25,6 +25,7 @@ export interface IHttpKoa { | ||
headers: HttpRequestHeaders; | ||
cookies: IHttpCookies; | ||
params: {}; | ||
willRender: boolean; | ||
state: Record<any, any>; | ||
protected _reply: HttpResponse | null; | ||
protected _reply: IHttpResponse | null; | ||
constructor(ctx: any); | ||
@@ -50,2 +51,3 @@ get host(): string; | ||
throw(status: number, message: string): never; | ||
abort(error: IHttpError): void; | ||
abort(status: number, message?: string): void; | ||
@@ -56,4 +58,5 @@ /** | ||
*/ | ||
reply(response: HttpResponse): void; | ||
reply(response: IHttpResponse): void; | ||
stream(stream: internal.Readable, mime?: string): void; | ||
redirect(url: string, alt?: string): void; | ||
view(template: string, data?: {}, status?: number): Promise<void>; | ||
@@ -60,0 +63,0 @@ shouldRender(): boolean; |
type ResponseHeaders = Record<string, string | string[]>; | ||
export interface IHttpError { | ||
status: number; | ||
message: string; | ||
data?: any; | ||
} | ||
export interface IHttpResponse<T = any> { | ||
body?: T; | ||
status: number; | ||
headers?: Record<string, string>; | ||
} | ||
export declare class HttpResponse { | ||
protected data: any; | ||
readonly status: number; | ||
protected status: number; | ||
protected _headers: ResponseHeaders; | ||
constructor(data: any, status: number); | ||
get body(): any; | ||
setStatus(status: number): void; | ||
setHeader(key: string, value: string): void; | ||
@@ -9,0 +20,0 @@ setHeaders(headers: ResponseHeaders): void; |
@@ -7,7 +7,9 @@ /// <reference types="node" /> | ||
private metadata; | ||
private _static; | ||
private regex; | ||
private paramKeys; | ||
private _name; | ||
protected _static: boolean; | ||
protected regex: RegExp | null; | ||
protected paramKeys: Array<string>; | ||
protected _name: string; | ||
protected path: string; | ||
protected middlewares: RouteMiddlewareCallback[]; | ||
protected composedHandler: any; | ||
constructor(methods: HTTP_METHOD[] | '*', path: string, handler: T, metadata?: NodeJS.Dict<any>); | ||
@@ -52,3 +54,3 @@ getPath(): string; | ||
middleware(...middlewares: RouteMiddlewareCallback[]): this; | ||
getComposedHandler<T>(): (context: T, next: () => Promise<any>) => Promise<any>; | ||
getComposedHandler<T = RouteMiddlewareCallback>(): T; | ||
} |
@@ -0,3 +1,6 @@ | ||
import { IHttpContext } from "./context.js"; | ||
import { IHttpError } from "./response.js"; | ||
export interface IViewEngine { | ||
render(template: string, data: any): Promise<any>; | ||
render(ctx: IHttpContext, template: string, data: any): Promise<any>; | ||
renderError(ctx: IHttpContext, error: IHttpError): Promise<any>; | ||
} |
@@ -0,4 +1,7 @@ | ||
export * from './asset.js'; | ||
export * from './compose.js'; | ||
export * from './date.js'; | ||
export * from './file.js'; | ||
export * from './mime.js'; | ||
export * from './result.js'; | ||
export * from './type.js'; |
@@ -7,4 +7,4 @@ type ResultErrorInput = IResultErrorAttributes | ResultError | Error; | ||
error<T = ResultError>(): T; | ||
static ok<V>(value: V): Result<V, V>; | ||
static error(error: ResultErrorInput): Result<ResultError, ResultError>; | ||
static ok<V = any>(value: V): Result<V, ResultErrorInput>; | ||
static error<V = ResultError>(error: ResultErrorInput): Result<V, ResultErrorInput>; | ||
} | ||
@@ -25,4 +25,13 @@ export interface IResultErrorAttributes { | ||
constructor(message: string, code: any, _data?: {}); | ||
metadata(): {}; | ||
get status(): any; | ||
get data(): {}; | ||
metadata(data?: any): {}; | ||
static from(error: ResultErrorInput, data?: IResultErrorData): ResultError; | ||
static try(error: ResultErrorInput, data?: IResultErrorData): ResultError; | ||
toHttpResponse(): { | ||
status: any; | ||
body: { | ||
message: string; | ||
}; | ||
}; | ||
toJSON(): { | ||
@@ -29,0 +38,0 @@ message: string; |
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import * as internal from 'node:stream'; | ||
import { HttpRequestHeaders, HttpRequestQuery, IHttpContext } from '../../http/context.js'; | ||
import { HttpResponse } from '../../http/response.js'; | ||
import { HttpRequestHeaders, HttpRequestQuery, IHttpContext, IHttpCookies } from '../../http/context.js'; | ||
import { IHttpResponse } from '../../http/response.js'; | ||
import { IHttpFile, ITestHttpContext, ITestHttpResponse } from './contracts.js'; | ||
@@ -11,2 +11,3 @@ export declare class TestHttpContext implements IHttpContext { | ||
headers: HttpRequestHeaders; | ||
cookies: IHttpCookies; | ||
params: {}; | ||
@@ -16,3 +17,3 @@ willRender: boolean; | ||
response: ITestHttpResponse; | ||
protected _reply: HttpResponse | null; | ||
protected _reply: IHttpResponse | null; | ||
parseHeaders(headers: any): any; | ||
@@ -42,3 +43,3 @@ constructor(ctx: ITestHttpContext); | ||
throw(status: number, message: string): never; | ||
abort(status: number, message?: string): void; | ||
abort(...args: any): void; | ||
/** | ||
@@ -48,4 +49,5 @@ * ununsed | ||
*/ | ||
reply(response: HttpResponse): void; | ||
reply(response: IHttpResponse): void; | ||
stream(stream: internal.Readable, mime?: string): void; | ||
redirect(url: string, alt?: string | undefined): void; | ||
view(template: string, data?: {}, status?: number): Promise<void>; | ||
@@ -52,0 +54,0 @@ shouldRender(): boolean; |
@@ -26,2 +26,6 @@ /// <reference types="node" /> | ||
body: T; | ||
redirect?: { | ||
url: string; | ||
alt?: string; | ||
}; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
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
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
397291
266
6873
231