@carno.js/core 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,11 @@ exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = expo
4
4
  exports.createParamDecorator = createParamDecorator;
5
5
  const Metadata_1 = require("../../domain/Metadata");
6
6
  const constants_1 = require("../../constants");
7
+ const PARAM_TYPE_KEY = "__carnoParamType";
8
+ const markParamType = (func, paramType) => {
9
+ func[PARAM_TYPE_KEY] = paramType;
10
+ return func;
11
+ };
7
12
  const createMethodDecorator = (methodType) => {
8
13
  return (path = "") => {
9
14
  return (target, propertyKey) => {
@@ -27,16 +32,30 @@ const createMethodDecorator = (methodType) => {
27
32
  function createParamDecorator(func) {
28
33
  return (data) => (target, propertyKey, parameterIndex) => {
29
34
  const existingArgs = Metadata_1.Metadata.get(constants_1.ROUTE_PARAM, target.constructor, propertyKey) || {};
30
- existingArgs[parameterIndex] = { fun: func, param: data };
35
+ existingArgs[parameterIndex] = {
36
+ fun: func,
37
+ param: data,
38
+ type: func[PARAM_TYPE_KEY],
39
+ };
31
40
  Metadata_1.Metadata.set(constants_1.ROUTE_PARAM, existingArgs, target.constructor, propertyKey);
32
41
  };
33
42
  }
34
- exports.Body = createParamDecorator((context, data) => data ? context.body[data] : context.body || {});
35
- exports.Query = createParamDecorator((context, data) => data ? context.query[data] : context.query || {});
36
- exports.Param = createParamDecorator((context, data) => data ? context.param[data] : null);
37
- exports.Req = createParamDecorator((context) => context.req);
38
- exports.Headers = createParamDecorator((context, data) => data ? (context.headers.has(data) ? context.headers.get(data) : undefined) : context.headers || {});
39
- exports.Locals = createParamDecorator((context) => context.locals || {});
43
+ const bodyResolver = markParamType((context, data) => data ? context.body[data] : context.body || {}, "body");
44
+ const queryResolver = markParamType((context, data) => data ? context.query[data] : context.query || {}, "query");
45
+ const paramResolver = markParamType((context, data) => data ? context.param[data] : null, "param");
46
+ const reqResolver = markParamType((context) => context.req, "req");
47
+ const headersResolver = markParamType((context, data) => data
48
+ ? context.headers.has(data)
49
+ ? context.headers.get(data)
50
+ : undefined
51
+ : context.headers || {}, "headers");
52
+ const localsResolver = markParamType((context) => context.locals || {}, "locals");
53
+ exports.Body = createParamDecorator(bodyResolver);
54
+ exports.Query = createParamDecorator(queryResolver);
55
+ exports.Param = createParamDecorator(paramResolver);
56
+ exports.Req = createParamDecorator(reqResolver);
57
+ exports.Headers = createParamDecorator(headersResolver);
58
+ exports.Locals = createParamDecorator(localsResolver);
40
59
  exports.Get = createMethodDecorator("GET");
41
60
  exports.Post = createMethodDecorator("POST");
42
61
  exports.Put = createMethodDecorator("PUT");
@@ -12,6 +12,9 @@ export declare class MethodInvoker {
12
12
  private cacheMethodInfo;
13
13
  private setCachedMethod;
14
14
  private resolveMethodServices;
15
+ private ensureBodyParsed;
16
+ private hasBodyParam;
17
+ private isBodyParam;
15
18
  private resolveService;
16
19
  private validateAndTransform;
17
20
  }
@@ -15,6 +15,7 @@ class MethodInvoker {
15
15
  }
16
16
  async invoke(instance, methodName, locals, context, invokeCallback) {
17
17
  const methodInfo = this.getMethodInfo(instance, methodName);
18
+ await this.ensureBodyParsed(methodInfo, context);
18
19
  const services = this.resolveMethodServices(methodInfo, context, locals, invokeCallback);
19
20
  return instance[methodName](...services);
20
21
  }
@@ -29,7 +30,8 @@ class MethodInvoker {
29
30
  cacheMethodInfo(instance, methodName) {
30
31
  const args = (0, getMethodArgTypes_1.getMethodArgTypes)(instance, methodName);
31
32
  const params = Metadata_1.Metadata.getParamDecoratorFunc(instance, methodName);
32
- const methodInfo = { args, params };
33
+ const needsBody = this.hasBodyParam(params);
34
+ const methodInfo = { args, params, needsBody };
33
35
  this.setCachedMethod(instance, methodName, methodInfo);
34
36
  return methodInfo;
35
37
  }
@@ -47,6 +49,30 @@ class MethodInvoker {
47
49
  }
48
50
  return services;
49
51
  }
52
+ async ensureBodyParsed(methodInfo, context) {
53
+ if (!methodInfo.needsBody) {
54
+ return;
55
+ }
56
+ if (context.isBodyParsed()) {
57
+ return;
58
+ }
59
+ await context.getBody();
60
+ }
61
+ hasBodyParam(params) {
62
+ if (!params) {
63
+ return false;
64
+ }
65
+ return Object.values(params).some((param) => this.isBodyParam(param));
66
+ }
67
+ isBodyParam(param) {
68
+ if (param?.type === "body") {
69
+ return true;
70
+ }
71
+ if (!param?.fun) {
72
+ return false;
73
+ }
74
+ return String(param.fun).includes("context.body");
75
+ }
50
76
  resolveService(token, param, context, locals, invokeCallback) {
51
77
  if (!param) {
52
78
  return invokeCallback((0, getClassOrSymbol_1.getClassOrSymbol)(token), locals);
@@ -59,7 +85,8 @@ class MethodInvoker {
59
85
  validateAndTransform(token, value) {
60
86
  const obj = (0, class_transformer_1.plainToInstance)(token, value);
61
87
  const errors = (0, class_validator_1.validateSync)(obj, this.applicationConfig.validation);
62
- // todo: deve retornar apenas os erros e não o objeto class-validator inteiro.
88
+ // todo: deve retornar apenas os erros e no o objeto class-validator intei
89
+ // ro.
63
90
  if (errors.length > 0) {
64
91
  throw new HttpException_1.HttpException(errors, 400);
65
92
  }
@@ -9,12 +9,12 @@ class HttpException {
9
9
  this.initMessage();
10
10
  }
11
11
  initMessage() {
12
- if ((0, utils_1.isObject)(this.response)) {
13
- this.message = this.response;
14
- }
15
- else {
16
- this.message = this.response;
12
+ const formatted = (0, utils_1.formatValidationErrors)(this.response);
13
+ if ((0, utils_1.isObject)(formatted)) {
14
+ this.message = formatted;
15
+ return;
17
16
  }
17
+ this.message = formatted;
18
18
  }
19
19
  getResponse() {
20
20
  return this.message;
@@ -1,12 +1,14 @@
1
1
  import { type ValidatorOptions } from 'class-validator';
2
2
  import type { Context } from '../domain/Context';
3
3
  import type { ParamResolver, AsyncParamResolver } from './CompiledRoute';
4
+ export type ParamDecoratorType = 'body' | 'query' | 'param' | 'headers' | 'req' | 'locals';
4
5
  export interface ParamDecoratorMeta {
5
6
  fun: (context: Context, data?: any) => any;
6
7
  param?: any;
8
+ type?: ParamDecoratorType;
7
9
  }
8
10
  export interface ParamInfo {
9
- type: 'body' | 'query' | 'param' | 'headers' | 'req' | 'locals' | 'di';
11
+ type: ParamDecoratorType | 'di';
10
12
  key?: string;
11
13
  needsValidation: boolean;
12
14
  token?: any;
@@ -12,28 +12,46 @@ function analyzeParamDecorator(decoratorMeta, token) {
12
12
  if (!decoratorMeta) {
13
13
  return { type: 'di', needsValidation: false, token };
14
14
  }
15
- const funcStr = decoratorMeta.fun.toString();
15
+ const paramType = resolveParamType(decoratorMeta);
16
16
  const key = decoratorMeta.param;
17
17
  const needsValidation = typeof token === 'function' && (0, ValidationCache_1.isValidatable)(token);
18
+ if (paramType) {
19
+ return {
20
+ type: paramType,
21
+ key,
22
+ needsValidation,
23
+ token,
24
+ };
25
+ }
26
+ return { type: 'di', needsValidation: false, token };
27
+ }
28
+ function resolveParamType(decoratorMeta) {
29
+ if (decoratorMeta.type) {
30
+ return decoratorMeta.type;
31
+ }
32
+ return inferTypeFromSource(decoratorMeta.fun);
33
+ }
34
+ function inferTypeFromSource(resolver) {
35
+ const funcStr = resolver.toString();
18
36
  if (funcStr.includes('context.body')) {
19
- return { type: 'body', key, needsValidation, token };
37
+ return 'body';
20
38
  }
21
39
  if (funcStr.includes('context.query')) {
22
- return { type: 'query', key, needsValidation, token };
40
+ return 'query';
23
41
  }
24
42
  if (funcStr.includes('context.param')) {
25
- return { type: 'param', key, needsValidation, token };
43
+ return 'param';
26
44
  }
27
45
  if (funcStr.includes('context.headers')) {
28
- return { type: 'headers', key, needsValidation, token };
46
+ return 'headers';
29
47
  }
30
48
  if (funcStr.includes('context.req')) {
31
- return { type: 'req', needsValidation: false, token };
49
+ return 'req';
32
50
  }
33
51
  if (funcStr.includes('context.locals')) {
34
- return { type: 'locals', needsValidation: false, token };
52
+ return 'locals';
35
53
  }
36
- return { type: 'di', needsValidation: false, token };
54
+ return null;
37
55
  }
38
56
  function createValidationResolver(extractFn, token, validationConfig) {
39
57
  return (context) => {
@@ -179,6 +179,10 @@ class Memoirist {
179
179
  }
180
180
  if (node.params?.store === oldStore) {
181
181
  node.params.store = newStore;
182
+ const paramName = node.params.names.get(oldStore);
183
+ if (paramName) {
184
+ node.params.names.set(newStore, paramName);
185
+ }
182
186
  this.updateHistoryStore(method, path, newStore);
183
187
  return true;
184
188
  }
@@ -0,0 +1,5 @@
1
+ export type ValidationIssue = {
2
+ field: string;
3
+ messages: string[];
4
+ };
5
+ export declare function formatValidationErrors(value: unknown): unknown;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatValidationErrors = formatValidationErrors;
4
+ function formatValidationErrors(value) {
5
+ if (!shouldFormatErrors(value)) {
6
+ return value;
7
+ }
8
+ return collectIssues(value);
9
+ }
10
+ function shouldFormatErrors(value) {
11
+ if (!Array.isArray(value)) {
12
+ return false;
13
+ }
14
+ if (value.length === 0) {
15
+ return false;
16
+ }
17
+ return isValidationError(value[0]);
18
+ }
19
+ function isValidationError(value) {
20
+ if (!value) {
21
+ return false;
22
+ }
23
+ if (typeof value !== "object") {
24
+ return false;
25
+ }
26
+ return hasValidationShape(value);
27
+ }
28
+ function hasValidationShape(value) {
29
+ if ("constraints" in value) {
30
+ return true;
31
+ }
32
+ if ("property" in value) {
33
+ return true;
34
+ }
35
+ if ("children" in value) {
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ function collectIssues(errors) {
41
+ const issues = [];
42
+ for (const error of errors) {
43
+ appendIssues(error, "", issues);
44
+ }
45
+ return issues;
46
+ }
47
+ function appendIssues(error, prefix, issues) {
48
+ const field = buildField(prefix, error.property);
49
+ appendConstraints(error, field, issues);
50
+ appendChildren(error, field, issues);
51
+ }
52
+ function appendConstraints(error, field, issues) {
53
+ const constraints = error.constraints;
54
+ if (!constraints) {
55
+ return;
56
+ }
57
+ const messages = Object.values(constraints);
58
+ if (messages.length === 0) {
59
+ return;
60
+ }
61
+ issues.push({ field, messages });
62
+ }
63
+ function appendChildren(error, prefix, issues) {
64
+ const children = error.children;
65
+ if (!children || children.length === 0) {
66
+ return;
67
+ }
68
+ for (const child of children) {
69
+ appendIssues(child, prefix, issues);
70
+ }
71
+ }
72
+ function buildField(prefix, property) {
73
+ if (!property) {
74
+ return prefix || "body";
75
+ }
76
+ if (!prefix) {
77
+ return property;
78
+ }
79
+ return `${prefix}.${property}`;
80
+ }
@@ -13,3 +13,4 @@ export * from './isPrimitiveType';
13
13
  export * from './setValue';
14
14
  export * from './isClass';
15
15
  export * from './methodsOf';
16
+ export * from './formatValidationErrors';
@@ -29,3 +29,4 @@ __exportStar(require("./isPrimitiveType"), exports);
29
29
  __exportStar(require("./setValue"), exports);
30
30
  __exportStar(require("./isClass"), exports);
31
31
  __exportStar(require("./methodsOf"), exports);
32
+ __exportStar(require("./formatValidationErrors"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carno.js/core",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Carno.js is a framework for building web applications object oriented with TypeScript and Bun.sh",
5
5
  "keywords": [
6
6
  "bun",
@@ -51,5 +51,5 @@
51
51
  "publishConfig": {
52
52
  "access": "public"
53
53
  },
54
- "gitHead": "64eb08eb95a337f93062486b545e27ab9b30a643"
54
+ "gitHead": "2793d412b70bb432d2c825bae58522ccac87baff"
55
55
  }