rpc-express-toolkit 4.2.1 → 4.3.3

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/src/logger.js CHANGED
@@ -54,9 +54,14 @@ class Logger {
54
54
  */
55
55
  safeStringify(obj) {
56
56
  try {
57
+ const seen = new WeakSet();
57
58
  return JSON.stringify(obj, (key, value) => {
58
59
  if (typeof value === 'bigint') return `${value.toString()}n`;
59
60
  if (value instanceof Date) return value.toISOString();
61
+ if (value && typeof value === 'object') {
62
+ if (seen.has(value)) return '[Circular]';
63
+ seen.add(value);
64
+ }
60
65
  return value;
61
66
  });
62
67
  } catch (e) {
@@ -156,7 +161,8 @@ class Logger {
156
161
  duration: `${duration}ms`,
157
162
  error: error.message,
158
163
  code: error.code,
159
- stack: error.stack,
164
+ stack:
165
+ this.levels[this.level] >= this.levels.debug ? error.stack : undefined,
160
166
  });
161
167
  }
162
168
 
@@ -175,14 +181,19 @@ class Logger {
175
181
  'secret',
176
182
  'auth',
177
183
  'authorization',
184
+ 'credential',
185
+ 'cookie',
186
+ 'session',
178
187
  ];
179
- const sanitized = Array.isArray(params) ? [...params] : { ...params };
188
+ const seen = new WeakSet();
180
189
 
181
190
  const sanitizeObj = (input) => {
182
191
  if (typeof input !== 'object' || input === null) return input;
192
+ if (seen.has(input)) return '[Circular]';
183
193
 
194
+ seen.add(input);
184
195
  const output = Array.isArray(input) ? [] : {};
185
- for (const [key, value] of Object.entries(input)) {
196
+ Object.entries(input).forEach(([key, value]) => {
186
197
  const isSensitive = sensitiveKeys.some((sensitive) =>
187
198
  key.toLowerCase().includes(sensitive)
188
199
  );
@@ -193,11 +204,12 @@ class Logger {
193
204
  } else {
194
205
  output[key] = value;
195
206
  }
196
- }
207
+ });
208
+ seen.delete(input);
197
209
  return output;
198
210
  };
199
211
 
200
- return sanitizeObj(sanitized);
212
+ return sanitizeObj(params);
201
213
  }
202
214
  }
203
215
 
package/src/middleware.js CHANGED
File without changes
@@ -0,0 +1,97 @@
1
+ const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
2
+
3
+ function isObject(value) {
4
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
5
+ }
6
+
7
+ function isValidId(id) {
8
+ return id === null || typeof id === 'string' || typeof id === 'number';
9
+ }
10
+
11
+ function isValidParams(params) {
12
+ return Array.isArray(params) || isObject(params);
13
+ }
14
+
15
+ function validationError(message, id = null) {
16
+ return {
17
+ valid: false,
18
+ responseId: id,
19
+ error: {
20
+ code: -32600,
21
+ message,
22
+ },
23
+ };
24
+ }
25
+
26
+ function validateEnvelope(request) {
27
+ if (!isObject(request)) {
28
+ return validationError('Invalid Request: request must be an object');
29
+ }
30
+
31
+ const hasId = hasOwn(request, 'id');
32
+ const responseId = hasId && isValidId(request.id) ? request.id : null;
33
+
34
+ if (request.jsonrpc !== '2.0') {
35
+ return validationError(
36
+ "Invalid Request: 'jsonrpc' must be '2.0'.",
37
+ responseId
38
+ );
39
+ }
40
+
41
+ if (typeof request.method !== 'string') {
42
+ return validationError(
43
+ "Invalid Request: 'method' must be a string.",
44
+ responseId
45
+ );
46
+ }
47
+
48
+ if (request.method.length === 0) {
49
+ return validationError(
50
+ "Invalid Request: 'method' must not be empty.",
51
+ responseId
52
+ );
53
+ }
54
+
55
+ if (hasOwn(request, 'params') && !isValidParams(request.params)) {
56
+ return validationError(
57
+ "Invalid Request: 'params' must be an object or array.",
58
+ responseId
59
+ );
60
+ }
61
+
62
+ if (hasId && !isValidId(request.id)) {
63
+ return validationError(
64
+ "Invalid Request: 'id' must be a string, number, or null.",
65
+ null
66
+ );
67
+ }
68
+
69
+ return {
70
+ valid: true,
71
+ hasId,
72
+ isNotification: !hasId,
73
+ responseId,
74
+ };
75
+ }
76
+
77
+ function addBatchIndex(error, batchIndex) {
78
+ const data =
79
+ error.data !== undefined && isObject(error.data) ? error.data : {};
80
+
81
+ return {
82
+ ...error,
83
+ data: {
84
+ ...data,
85
+ ...(error.data !== undefined && !isObject(error.data)
86
+ ? { value: error.data }
87
+ : {}),
88
+ batchIndex,
89
+ },
90
+ };
91
+ }
92
+
93
+ module.exports = {
94
+ addBatchIndex,
95
+ hasOwn,
96
+ validateEnvelope,
97
+ };
package/src/safe.d.ts CHANGED
File without changes
package/src/safe.js CHANGED
File without changes
package/src/validation.js CHANGED
@@ -1,222 +1,222 @@
1
- /* eslint max-classes-per-file: off */
2
- const Ajv = require('ajv');
3
- const addFormats = require('ajv-formats');
4
-
5
- /**
6
- * JSON-RPC 2.0 Schema Validation Error
7
- */
8
- class SchemaValidator {
9
- constructor(options = {}) {
10
- this.ajv = new Ajv({
11
- allErrors: true,
12
- removeAdditional: options.removeAdditional || false,
13
- useDefaults: options.useDefaults !== false,
14
- coerceTypes: options.coerceTypes || false,
15
- strict: false, // Allow unknown keywords like exposeSchema in method options
16
- ...options.ajvOptions,
17
- });
18
-
19
- addFormats(this.ajv);
20
-
21
- // Add custom formats
22
- this.addCustomFormats();
23
- }
24
-
25
- /**
26
- * Add custom format validators
27
- */
28
- addCustomFormats() {
29
- // BigInt format
30
- this.ajv.addFormat('bigint', {
31
- type: 'string',
32
- validate: (data) => /^-?\d+n?$/.test(data),
33
- });
34
-
35
- // ObjectId format (MongoDB style)
36
- this.ajv.addFormat('objectid', {
37
- type: 'string',
38
- validate: (data) => /^[0-9a-fA-F]{24}$/.test(data),
39
- });
40
- }
41
-
42
- /**
43
- * Validate parameters against a schema
44
- * @param {any} params
45
- * @param {Object} schema
46
- * @returns {Object} validation result
47
- */
48
- validate(params, schema) {
49
- const validator = this.ajv.compile(schema);
50
- const valid = validator(params);
51
-
52
- return {
53
- valid,
54
- errors: valid ? null : validator.errors,
55
- data: params,
56
- };
57
- }
58
-
59
- /**
60
- * Create a validation middleware
61
- * @param {Object} schema
62
- * @returns {Function}
63
- */
64
- middleware(schema) {
65
- return async (context) => {
66
- const { params } = context;
67
- const result = this.validate(params, schema);
68
-
69
- if (!result.valid) {
70
- const error = new Error('Validation failed');
71
- error.code = -32602; // Invalid params
72
- error.data = {
73
- validationErrors: result.errors.map((err) => ({
74
- field: err.instancePath || err.schemaPath,
75
- message: err.message,
76
- value: err.data,
77
- })),
78
- };
79
- throw error;
80
- }
81
-
82
- // Update params with validated/coerced data
83
- context.params = result.data;
84
- return context;
85
- };
86
- }
87
-
88
- /**
89
- * Add a custom keyword
90
- * @param {string} name
91
- * @param {Object} definition
92
- */
93
- addKeyword(name, definition) {
94
- this.ajv.addKeyword(name, definition);
95
- }
96
- }
97
-
98
- /**
99
- * Common schema definitions
100
- */
101
- const commonSchemas = {
102
- // Pagination parameters
103
- pagination: {
104
- type: 'object',
105
- properties: {
106
- page: { type: 'integer', minimum: 1, default: 1 },
107
- limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
108
- sort: { type: 'string' },
109
- order: { type: 'string', enum: ['asc', 'desc'], default: 'asc' },
110
- },
111
- },
112
-
113
- // User identification
114
- userId: {
115
- oneOf: [
116
- { type: 'string', format: 'objectid' },
117
- { type: 'string', format: 'uuid' },
118
- { type: 'integer', minimum: 1 },
119
- ],
120
- },
121
-
122
- // Email validation
123
- email: {
124
- type: 'string',
125
- format: 'email',
126
- maxLength: 255,
127
- },
128
-
129
- // Password requirements
130
- password: {
131
- type: 'string',
132
- minLength: 8,
133
- maxLength: 128,
134
- pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)',
135
- },
136
-
137
- // BigInt validation
138
- bigintString: {
139
- type: 'string',
140
- format: 'bigint',
141
- },
142
-
143
- // Date validation
144
- dateString: {
145
- type: 'string',
146
- format: 'date-time',
147
- },
148
- };
149
-
150
- /**
151
- * Schema builder helper
152
- */
153
- class SchemaBuilder {
154
- constructor() {
155
- this.schema = {
156
- type: 'object',
157
- properties: {},
158
- required: [],
159
- };
160
- }
161
-
162
- /**
163
- * Add a property to the schema
164
- * @param {string} name
165
- * @param {Object} definition
166
- * @param {boolean} required
167
- * @returns {SchemaBuilder}
168
- */
169
- property(name, definition, required = false) {
170
- this.schema.properties[name] = definition;
171
- if (required) {
172
- this.schema.required.push(name);
173
- }
174
- return this;
175
- }
176
-
177
- /**
178
- * Add multiple properties
179
- * @param {Object} properties
180
- * @returns {SchemaBuilder}
181
- */
182
- properties(properties) {
183
- Object.entries(properties).forEach(([name, definition]) => {
184
- this.property(name, definition);
185
- });
186
- return this;
187
- }
188
-
189
- /**
190
- * Set required fields
191
- * @param {string[]} fields
192
- * @returns {SchemaBuilder}
193
- */
194
- required(fields) {
195
- this.schema.required = [...new Set([...this.schema.required, ...fields])];
196
- return this;
197
- }
198
-
199
- /**
200
- * Add additional properties setting
201
- * @param {boolean} allowed
202
- * @returns {SchemaBuilder}
203
- */
204
- additionalProperties(allowed) {
205
- this.schema.additionalProperties = allowed;
206
- return this;
207
- }
208
-
209
- /**
210
- * Build and return the schema
211
- * @returns {Object}
212
- */
213
- build() {
214
- return this.schema;
215
- }
216
- }
217
-
218
- module.exports = {
219
- SchemaValidator,
220
- commonSchemas,
221
- SchemaBuilder,
222
- };
1
+ /* eslint max-classes-per-file: off */
2
+ const Ajv = require('ajv');
3
+ const addFormats = require('ajv-formats');
4
+
5
+ /**
6
+ * JSON-RPC 2.0 Schema Validation Error
7
+ */
8
+ class SchemaValidator {
9
+ constructor(options = {}) {
10
+ this.ajv = new Ajv({
11
+ allErrors: true,
12
+ removeAdditional: options.removeAdditional || false,
13
+ useDefaults: options.useDefaults !== false,
14
+ coerceTypes: options.coerceTypes || false,
15
+ strict: false, // Allow unknown keywords like exposeSchema in method options
16
+ ...options.ajvOptions,
17
+ });
18
+
19
+ addFormats(this.ajv);
20
+
21
+ // Add custom formats
22
+ this.addCustomFormats();
23
+ }
24
+
25
+ /**
26
+ * Add custom format validators
27
+ */
28
+ addCustomFormats() {
29
+ // BigInt format
30
+ this.ajv.addFormat('bigint', {
31
+ type: 'string',
32
+ validate: (data) => /^-?\d+n?$/.test(data),
33
+ });
34
+
35
+ // ObjectId format (MongoDB style)
36
+ this.ajv.addFormat('objectid', {
37
+ type: 'string',
38
+ validate: (data) => /^[0-9a-fA-F]{24}$/.test(data),
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Validate parameters against a schema
44
+ * @param {any} params
45
+ * @param {Object} schema
46
+ * @returns {Object} validation result
47
+ */
48
+ validate(params, schema) {
49
+ const validator = this.ajv.compile(schema);
50
+ const valid = validator(params);
51
+
52
+ return {
53
+ valid,
54
+ errors: valid ? null : validator.errors,
55
+ data: params,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Create a validation middleware
61
+ * @param {Object} schema
62
+ * @returns {Function}
63
+ */
64
+ middleware(schema) {
65
+ return async (context) => {
66
+ const { params } = context;
67
+ const result = this.validate(params, schema);
68
+
69
+ if (!result.valid) {
70
+ const error = new Error('Validation failed');
71
+ error.code = -32602; // Invalid params
72
+ error.data = {
73
+ validationErrors: result.errors.map((err) => ({
74
+ field: err.instancePath || err.schemaPath,
75
+ message: err.message,
76
+ value: err.data,
77
+ })),
78
+ };
79
+ throw error;
80
+ }
81
+
82
+ // Update params with validated/coerced data
83
+ context.params = result.data;
84
+ return context;
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Add a custom keyword
90
+ * @param {string} name
91
+ * @param {Object} definition
92
+ */
93
+ addKeyword(name, definition) {
94
+ this.ajv.addKeyword(name, definition);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Common schema definitions
100
+ */
101
+ const commonSchemas = {
102
+ // Pagination parameters
103
+ pagination: {
104
+ type: 'object',
105
+ properties: {
106
+ page: { type: 'integer', minimum: 1, default: 1 },
107
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
108
+ sort: { type: 'string' },
109
+ order: { type: 'string', enum: ['asc', 'desc'], default: 'asc' },
110
+ },
111
+ },
112
+
113
+ // User identification
114
+ userId: {
115
+ oneOf: [
116
+ { type: 'string', format: 'objectid' },
117
+ { type: 'string', format: 'uuid' },
118
+ { type: 'integer', minimum: 1 },
119
+ ],
120
+ },
121
+
122
+ // Email validation
123
+ email: {
124
+ type: 'string',
125
+ format: 'email',
126
+ maxLength: 255,
127
+ },
128
+
129
+ // Password requirements
130
+ password: {
131
+ type: 'string',
132
+ minLength: 8,
133
+ maxLength: 128,
134
+ pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)',
135
+ },
136
+
137
+ // BigInt validation
138
+ bigintString: {
139
+ type: 'string',
140
+ format: 'bigint',
141
+ },
142
+
143
+ // Date validation
144
+ dateString: {
145
+ type: 'string',
146
+ format: 'date-time',
147
+ },
148
+ };
149
+
150
+ /**
151
+ * Schema builder helper
152
+ */
153
+ class SchemaBuilder {
154
+ constructor() {
155
+ this.schema = {
156
+ type: 'object',
157
+ properties: {},
158
+ required: [],
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Add a property to the schema
164
+ * @param {string} name
165
+ * @param {Object} definition
166
+ * @param {boolean} required
167
+ * @returns {SchemaBuilder}
168
+ */
169
+ property(name, definition, required = false) {
170
+ this.schema.properties[name] = definition;
171
+ if (required) {
172
+ this.schema.required.push(name);
173
+ }
174
+ return this;
175
+ }
176
+
177
+ /**
178
+ * Add multiple properties
179
+ * @param {Object} properties
180
+ * @returns {SchemaBuilder}
181
+ */
182
+ properties(properties) {
183
+ Object.entries(properties).forEach(([name, definition]) => {
184
+ this.property(name, definition);
185
+ });
186
+ return this;
187
+ }
188
+
189
+ /**
190
+ * Set required fields
191
+ * @param {string[]} fields
192
+ * @returns {SchemaBuilder}
193
+ */
194
+ required(fields) {
195
+ this.schema.required = [...new Set([...this.schema.required, ...fields])];
196
+ return this;
197
+ }
198
+
199
+ /**
200
+ * Add additional properties setting
201
+ * @param {boolean} allowed
202
+ * @returns {SchemaBuilder}
203
+ */
204
+ additionalProperties(allowed) {
205
+ this.schema.additionalProperties = allowed;
206
+ return this;
207
+ }
208
+
209
+ /**
210
+ * Build and return the schema
211
+ * @returns {Object}
212
+ */
213
+ build() {
214
+ return this.schema;
215
+ }
216
+ }
217
+
218
+ module.exports = {
219
+ SchemaValidator,
220
+ commonSchemas,
221
+ SchemaBuilder,
222
+ };