@restura/core 1.1.1 → 1.3.0

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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SerializerFn } from 'pino';
1
+ import { TransportTargetOptions, SerializerFn, DestinationStream } from 'pino';
2
2
  import { z } from 'zod';
3
3
  import { UUID } from 'crypto';
4
4
  import * as express from 'express';
@@ -32,14 +32,11 @@ declare const loggerConfigSchema: z.ZodObject<{
32
32
  silly: "silly";
33
33
  trace: "trace";
34
34
  }>>;
35
- transports: z.ZodOptional<z.ZodArray<z.ZodObject<{
36
- target: z.ZodString;
37
- level: z.ZodOptional<z.ZodString>;
38
- options: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
39
- }, z.core.$strip>>>;
35
+ transports: z.ZodOptional<z.ZodArray<z.ZodCustom<TransportTargetOptions<Record<string, any>>, TransportTargetOptions<Record<string, any>>>>>;
40
36
  serializers: z.ZodOptional<z.ZodObject<{
41
37
  err: z.ZodOptional<z.ZodCustom<ErrorSerializerFactory, ErrorSerializerFactory>>;
42
38
  }, z.core.$strip>>;
39
+ stream: z.ZodOptional<z.ZodCustom<DestinationStream, DestinationStream>>;
43
40
  }, z.core.$strip>;
44
41
  type LoggerConfigSchema = z.infer<typeof loggerConfigSchema>;
45
42
 
@@ -1073,6 +1070,20 @@ declare abstract class SqlEngine {
1073
1070
  protected abstract generateOrderBy(req: RsRequest<unknown>, routeData: StandardRouteData): string;
1074
1071
  protected abstract generateWhereClause(req: RsRequest<unknown>, where: WhereData[], routeData: StandardRouteData, sqlParams: string[]): string;
1075
1072
  protected replaceParamKeywords(value: string | number, routeData: RouteData, req: RsRequest<unknown>, sqlParams: string[]): string | number;
1073
+ /**
1074
+ * Replaces local parameter keywords (e.g., $paramName) in SQL query strings with '?' placeholders
1075
+ * and adds the corresponding parameter values to the sqlParams array for parameterized queries.
1076
+ *
1077
+ * Validates that each parameter keyword exists in the route's request schema before processing.
1078
+ * If the value is not a string or routeData has no request schema, returns the value unchanged.
1079
+ *
1080
+ * @param value - The string or number value that may contain parameter keywords (e.g., "$userId")
1081
+ * @param routeData - The route data containing the request schema for parameter validation
1082
+ * @param req - The request object containing the actual parameter values in req.data
1083
+ * @param sqlParams - Array to which parameter values are appended (modified by reference)
1084
+ * @returns The value with parameter keywords replaced by '?' placeholders, or the original value if unchanged
1085
+ * @throws {RsError} If a parameter keyword is found that doesn't exist in the route's request schema
1086
+ */
1076
1087
  protected replaceLocalParamKeywords(value: string | number, routeData: RouteData, req: RsRequest<unknown>, sqlParams: string[]): string | number;
1077
1088
  protected replaceGlobalParamKeywords(value: string | number, routeData: RouteData, req: RsRequest<unknown>, sqlParams: string[]): string | number;
1078
1089
  abstract generateDatabaseSchemaFromSchema(schema: ResturaSchema): string;
package/dist/index.js CHANGED
@@ -12,21 +12,20 @@ var __decorateClass = (decorators, target, key, kind) => {
12
12
  // src/logger/logger.ts
13
13
  import { config } from "@restura/internal";
14
14
  import pino from "pino";
15
+ import pinoPretty from "pino-pretty";
15
16
 
16
17
  // src/logger/loggerConfigSchema.ts
17
18
  import { z } from "zod";
18
19
  var loggerConfigSchema = z.object({
19
20
  level: z.enum(["fatal", "error", "warn", "info", "debug", "silly", "trace"]).default("info"),
20
- transports: z.array(
21
- z.object({
22
- target: z.string(),
23
- level: z.string().optional(),
24
- options: z.record(z.string(), z.unknown()).optional()
25
- })
26
- ).optional(),
21
+ transports: z.array(z.custom()).optional(),
27
22
  serializers: z.object({
28
23
  err: z.custom().optional()
29
- }).optional()
24
+ }).optional(),
25
+ stream: z.custom().optional()
26
+ }).refine((data) => !(data.transports && data.stream), {
27
+ message: "You must provide either a transports array or a stream object, but not both",
28
+ path: ["transports"]
30
29
  });
31
30
 
32
31
  // src/logger/logger.ts
@@ -41,38 +40,54 @@ var logLevelMap = {
41
40
  trace: "trace"
42
41
  };
43
42
  var currentLogLevel = logLevelMap[loggerConfig.level];
44
- var defaultTransports = [
45
- {
46
- target: "pino-pretty",
47
- options: {
48
- colorize: true,
49
- translateTime: "yyyy-mm-dd HH:MM:ss.l",
50
- ignore: "pid,hostname,_meta",
51
- // _meta allows a user to pass in metadata for JSON but not print it to the console
52
- messageFormat: "{msg}",
53
- levelFirst: true,
54
- customColors: "error:red,warn:yellow,info:green,debug:blue,trace:magenta"
55
- }
56
- }
57
- ];
58
- var baseErrSerializer = pino.stdSerializers.err;
43
+ var defaultStream = pinoPretty({
44
+ colorize: true,
45
+ translateTime: "yyyy-mm-dd HH:MM:ss.l",
46
+ ignore: "pid,hostname,_meta",
47
+ // _meta allows a user to pass in metadata for JSON but not print it to the console
48
+ messageFormat: "{msg}",
49
+ levelFirst: true,
50
+ customColors: "error:red,warn:yellow,info:green,debug:blue,trace:magenta",
51
+ destination: process.stdout
52
+ });
53
+ function isAxiosError(error) {
54
+ const isObject = (error2) => error2 !== null && typeof error2 === "object";
55
+ return isObject(error) && "isAxiosError" in error && error.isAxiosError === true;
56
+ }
57
+ var baseSerializer = pino.stdSerializers.err;
58
+ var defaultSerializer = (error) => {
59
+ if (isAxiosError(error)) {
60
+ const err = error;
61
+ return {
62
+ type: "AxiosError",
63
+ message: err.message,
64
+ stack: err.stack,
65
+ url: err.config?.url,
66
+ method: err.config?.method?.toUpperCase(),
67
+ status: err.response?.status,
68
+ responseData: err.response?.data
69
+ };
70
+ }
71
+ return baseSerializer(error);
72
+ };
59
73
  var errorSerializer = (() => {
60
74
  try {
61
- return loggerConfig.serializers?.err ? loggerConfig.serializers.err(baseErrSerializer) : baseErrSerializer;
75
+ return loggerConfig.serializers?.err ? loggerConfig.serializers.err(baseSerializer) : defaultSerializer;
62
76
  } catch (error) {
63
77
  console.error("Failed to initialize custom error serializer, falling back to default", error);
64
- return baseErrSerializer;
78
+ return defaultSerializer;
65
79
  }
66
80
  })();
67
- var pinoLogger = pino({
68
- level: currentLogLevel,
69
- transport: {
70
- targets: loggerConfig.transports ?? defaultTransports
81
+ var pinoLogger = pino(
82
+ {
83
+ level: currentLogLevel,
84
+ ...loggerConfig.transports ? { transport: { targets: loggerConfig.transports } } : {},
85
+ serializers: {
86
+ err: errorSerializer
87
+ }
71
88
  },
72
- serializers: {
73
- err: errorSerializer
74
- }
75
- });
89
+ loggerConfig.stream ? loggerConfig.stream : defaultStream
90
+ );
76
91
  function buildContext(args) {
77
92
  const ctx = {};
78
93
  const prims = [];
@@ -2027,6 +2042,20 @@ var SqlEngine = class {
2027
2042
  returnValue = this.replaceGlobalParamKeywords(returnValue, routeData, req, sqlParams);
2028
2043
  return returnValue;
2029
2044
  }
2045
+ /**
2046
+ * Replaces local parameter keywords (e.g., $paramName) in SQL query strings with '?' placeholders
2047
+ * and adds the corresponding parameter values to the sqlParams array for parameterized queries.
2048
+ *
2049
+ * Validates that each parameter keyword exists in the route's request schema before processing.
2050
+ * If the value is not a string or routeData has no request schema, returns the value unchanged.
2051
+ *
2052
+ * @param value - The string or number value that may contain parameter keywords (e.g., "$userId")
2053
+ * @param routeData - The route data containing the request schema for parameter validation
2054
+ * @param req - The request object containing the actual parameter values in req.data
2055
+ * @param sqlParams - Array to which parameter values are appended (modified by reference)
2056
+ * @returns The value with parameter keywords replaced by '?' placeholders, or the original value if unchanged
2057
+ * @throws {RsError} If a parameter keyword is found that doesn't exist in the route's request schema
2058
+ */
2030
2059
  replaceLocalParamKeywords(value, routeData, req, sqlParams) {
2031
2060
  if (!routeData.request) return value;
2032
2061
  const data = req.data;
@@ -2605,7 +2634,7 @@ var PsqlEngine = class extends SqlEngine {
2605
2634
  }
2606
2635
  async executeUpdateRequest(req, routeData, schema) {
2607
2636
  const sqlParams = [];
2608
- const { id, ...bodyNoId } = req.body;
2637
+ const { id, baseModifiedOn, ...bodyNoId } = req.body;
2609
2638
  const table = schema.database.find((item) => {
2610
2639
  return item.name === routeData.table;
2611
2640
  });
@@ -2621,9 +2650,37 @@ var PsqlEngine = class extends SqlEngine {
2621
2650
  bodyNoId[assignmentEscaped] = Number(assignment.value);
2622
2651
  else bodyNoId[assignmentEscaped] = assignment.value;
2623
2652
  }
2624
- const whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
2653
+ let whereClause = this.generateWhereClause(req, routeData.where, routeData, sqlParams);
2654
+ const originalWhereClause = whereClause;
2655
+ const originalSqlParams = [...sqlParams];
2656
+ if (baseModifiedOn) {
2657
+ const replacedBaseModifiedOn = this.replaceParamKeywords(baseModifiedOn, routeData, req, sqlParams);
2658
+ const modifiedOnCheck = whereClause ? `${whereClause} AND "modifiedOn" = ?` : `"modifiedOn" = ?`;
2659
+ sqlParams.push(replacedBaseModifiedOn.toString());
2660
+ whereClause = modifiedOnCheck;
2661
+ }
2625
2662
  const query = updateObjectQuery(routeData.table, bodyNoId, whereClause);
2626
- await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
2663
+ try {
2664
+ await this.psqlConnectionPool.queryOne(query, [...sqlParams], req.requesterDetails);
2665
+ } catch (error) {
2666
+ if (!baseModifiedOn || !(error instanceof RsError) || error.err !== "NOT_FOUND") throw error;
2667
+ let isConflict = false;
2668
+ try {
2669
+ await this.psqlConnectionPool.queryOne(
2670
+ `SELECT 1 FROM "${routeData.table}" ${originalWhereClause};`,
2671
+ originalSqlParams,
2672
+ req.requesterDetails
2673
+ );
2674
+ isConflict = true;
2675
+ } catch {
2676
+ }
2677
+ if (isConflict)
2678
+ throw new RsError(
2679
+ "CONFLICT",
2680
+ "The record has been modified since the baseModifiedOn value was provided."
2681
+ );
2682
+ throw error;
2683
+ }
2627
2684
  return this.executeGetRequest(req, routeData, schema);
2628
2685
  }
2629
2686
  async executeDeleteRequest(req, routeData, schema) {