@solidstarters/solid-core 1.2.165 → 1.2.168

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.
Files changed (80) hide show
  1. package/dist/config/iam.config.d.ts +2 -0
  2. package/dist/config/iam.config.d.ts.map +1 -1
  3. package/dist/config/iam.config.js +1 -0
  4. package/dist/config/iam.config.js.map +1 -1
  5. package/dist/decorators/error-codes-provider.decorator.d.ts +4 -0
  6. package/dist/decorators/error-codes-provider.decorator.d.ts.map +1 -0
  7. package/dist/decorators/error-codes-provider.decorator.js +12 -0
  8. package/dist/decorators/error-codes-provider.decorator.js.map +1 -0
  9. package/dist/dtos/post-chatter-message.dto.js.map +1 -1
  10. package/dist/entities/chatter-message.entity.js.map +1 -1
  11. package/dist/entities/security-rule.entity.js +0 -1
  12. package/dist/entities/security-rule.entity.js.map +1 -1
  13. package/dist/filters/http-exception.filter.d.ts +3 -2
  14. package/dist/filters/http-exception.filter.d.ts.map +1 -1
  15. package/dist/filters/http-exception.filter.js +23 -16
  16. package/dist/filters/http-exception.filter.js.map +1 -1
  17. package/dist/helpers/error-mapper.service.d.ts +12 -2
  18. package/dist/helpers/error-mapper.service.d.ts.map +1 -1
  19. package/dist/helpers/error-mapper.service.js +85 -72
  20. package/dist/helpers/error-mapper.service.js.map +1 -1
  21. package/dist/helpers/security.helper.d.ts +4 -2
  22. package/dist/helpers/security.helper.d.ts.map +1 -1
  23. package/dist/helpers/security.helper.js +38 -23
  24. package/dist/helpers/security.helper.js.map +1 -1
  25. package/dist/helpers/solid-core-error-codes-provider.service.d.ts +7 -0
  26. package/dist/helpers/solid-core-error-codes-provider.service.d.ts.map +1 -0
  27. package/dist/helpers/solid-core-error-codes-provider.service.js +67 -0
  28. package/dist/helpers/solid-core-error-codes-provider.service.js.map +1 -0
  29. package/dist/helpers/solid-registry.d.ts +5 -1
  30. package/dist/helpers/solid-registry.d.ts.map +1 -1
  31. package/dist/helpers/solid-registry.js +16 -0
  32. package/dist/helpers/solid-registry.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/interfaces.d.ts +16 -0
  38. package/dist/interfaces.d.ts.map +1 -1
  39. package/dist/interfaces.js.map +1 -1
  40. package/dist/repository/security-rule.repository.js +2 -2
  41. package/dist/repository/security-rule.repository.js.map +1 -1
  42. package/dist/seeders/seed-data/solid-core-metadata.json +2 -2
  43. package/dist/services/authentication.service.js +5 -4
  44. package/dist/services/authentication.service.js.map +1 -1
  45. package/dist/services/chatter-message.service.d.ts.map +1 -1
  46. package/dist/services/chatter-message.service.js.map +1 -1
  47. package/dist/services/model-metadata.service.js +1 -1
  48. package/dist/services/model-metadata.service.js.map +1 -1
  49. package/dist/services/setting.service.d.ts.map +1 -1
  50. package/dist/services/setting.service.js +2 -1
  51. package/dist/services/setting.service.js.map +1 -1
  52. package/dist/services/solid-introspect.service.d.ts +1 -0
  53. package/dist/services/solid-introspect.service.d.ts.map +1 -1
  54. package/dist/services/solid-introspect.service.js +14 -0
  55. package/dist/services/solid-introspect.service.js.map +1 -1
  56. package/dist/solid-core.module.d.ts.map +1 -1
  57. package/dist/solid-core.module.js +2 -0
  58. package/dist/solid-core.module.js.map +1 -1
  59. package/dist/tsconfig.tsbuildinfo +1 -1
  60. package/package.json +1 -1
  61. package/src/config/iam.config.ts +1 -0
  62. package/src/decorators/error-codes-provider.decorator.ts +9 -0
  63. package/src/dtos/post-chatter-message.dto.ts +1 -1
  64. package/src/entities/chatter-message.entity.ts +3 -3
  65. package/src/entities/security-rule.entity.ts +1 -1
  66. package/src/filters/http-exception.filter.ts +48 -23
  67. package/src/helpers/error-mapper.service.ts +117 -176
  68. package/src/helpers/security.helper.ts +95 -30
  69. package/src/helpers/solid-core-error-codes-provider.service.ts +63 -0
  70. package/src/helpers/solid-registry.ts +20 -1
  71. package/src/index.ts +1 -0
  72. package/src/interfaces.ts +36 -0
  73. package/src/repository/security-rule.repository.ts +2 -2
  74. package/src/seeders/seed-data/solid-core-metadata.json +2 -2
  75. package/src/services/authentication.service.ts +6 -6
  76. package/src/services/chatter-message.service.ts +373 -374
  77. package/src/services/model-metadata.service.ts +1 -1
  78. package/src/services/setting.service.ts +2 -1
  79. package/src/services/solid-introspect.service.ts +22 -0
  80. package/src/solid-core.module.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidstarters/solid-core",
3
- "version": "1.2.165",
3
+ "version": "1.2.168",
4
4
  "description": "This module is a NestJS module containing all the required core providers required by a Solid application",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,6 +22,7 @@ export const iamConfig = registerAs('iam', () => {
22
22
  callbackURL: process.env.IAM_GOOGLE_OAUTH_CALLBACK_URL,
23
23
  redirectURL: process.env.IAM_GOOGLE_OAUTH_REDIRECT_URL,
24
24
  },
25
+ iamAutoGeneratedPassword:process.env.IAM_AUTOGENERATED_PASSWORD || true
25
26
  };
26
27
  })
27
28
 
@@ -0,0 +1,9 @@
1
+ import 'reflect-metadata';
2
+
3
+ export const IS_ERROR_CODE_PROVIDER = 'IS_ERROR_CODE_PROVIDER';
4
+
5
+ export const ErrorCodeProvider = () => {
6
+ return (target: Function) => {
7
+ Reflect.defineMetadata(IS_ERROR_CODE_PROVIDER, true, target);
8
+ };
9
+ };
@@ -16,4 +16,4 @@ export class PostChatterMessageDto {
16
16
  @IsString()
17
17
  @IsOptional()
18
18
  messageSubType?: string;
19
- }
19
+ }
@@ -1,14 +1,14 @@
1
1
  import { CommonEntity } from 'src/entities/common.entity'
2
- import {Entity, Column, Index, JoinColumn, ManyToOne} from 'typeorm';
2
+ import { Entity, Column, Index, JoinColumn, ManyToOne } from 'typeorm';
3
3
  import { User } from 'src/entities/user.entity'
4
4
 
5
5
  @Entity("ss_chatter_message")
6
6
  export class ChatterMessage extends CommonEntity {
7
7
  @Index()
8
8
  @Column({ type: "varchar" })
9
- messageType: string;
9
+ messageType: string; // audit | custom
10
10
  @Column({ type: "varchar" })
11
- messageSubType: string;
11
+ messageSubType: string; // update | insert | delete | post_message
12
12
  @Column({ type: "text" })
13
13
  messageBody: string;
14
14
  @Index()
@@ -8,7 +8,7 @@ export class SecurityRule extends CommonEntity {
8
8
  @Index({ unique: true })
9
9
  @Column({ type: "varchar" })
10
10
  name: string;
11
- @Index({ unique: true })
11
+ // @Index({ unique: true })
12
12
  @Column({ type: "varchar" })
13
13
  description: string;
14
14
  @Index()
@@ -1,39 +1,64 @@
1
- import { ExceptionFilter, Catch, ArgumentsHost, Logger, Injectable } from '@nestjs/common';
2
- import { Response } from 'express';
1
+ // src/common/filters/http-exception.filter.ts
2
+ import {
3
+ ExceptionFilter,
4
+ Catch,
5
+ ArgumentsHost,
6
+ Logger,
7
+ Injectable,
8
+ HttpException,
9
+ } from '@nestjs/common';
10
+ import { Response, Request } from 'express';
3
11
  import { HttpStatusCodeMessages } from '../interceptors/logging.interceptor';
12
+ import { ErrorMapperService } from 'src/helpers/error-mapper.service';
13
+ import { ErrorCode } from 'src/interfaces';
14
+
4
15
 
5
16
  @Catch()
6
17
  @Injectable()
7
18
  export class HttpExceptionFilter implements ExceptionFilter {
8
19
  private readonly logger = new Logger(HttpExceptionFilter.name);
9
20
 
10
- constructor() {
21
+ constructor(private readonly errorMapper: ErrorMapperService) {
11
22
  this.logger.debug('HttpExceptionFilter initialized');
12
- }
23
+ }
13
24
 
14
25
  catch(exception: any, host: ArgumentsHost) {
15
26
  const ctx = host.switchToHttp();
16
27
  const response = ctx.getResponse<Response>();
17
- const request = ctx.getRequest();
18
- const status = exception.status || 500;
19
- const message = exception.message || 'Internal server error';
20
- const { method, url } = request;
21
-
22
- // Log the error here
23
- this.logger.error(`[${status || 500} ${HttpStatusCodeMessages[status] || 'Internal Server Error'}] ${method} ${url} - ${message}`);
24
- this.logger.error(exception.stack || 'No stack trace available');
25
-
26
- // Send the response to the client
27
- const errorJson = this.getErrorJson(status, message, exception.response);
28
- response.status(status).json(errorJson);
29
- }
28
+ const request = ctx.getRequest<Request>();
30
29
 
31
- private getErrorJson(status: number|string, message: string, additionalErrorData: unknown = {}): any {
32
- return {
33
- statusCode: status,
34
- message: [message],
35
- error: HttpStatusCodeMessages[status] || 'Internal Server Error',
36
- data: additionalErrorData
30
+ const isHttp = exception instanceof HttpException;
31
+ const explicitStatus = isHttp ? exception.getStatus() : undefined;
32
+
33
+ // Canonical code + static message
34
+ const code: ErrorCode = this.errorMapper.mapException(exception);
35
+ const defaultStatus = this.errorMapper.getHttpStatus(code);
36
+ const message = this.errorMapper.getMessage(code);
37
+
38
+ const status = explicitStatus ?? defaultStatus ?? 500;
39
+
40
+ // Logging
41
+ this.logger.error(
42
+ `[${status} ${HttpStatusCodeMessages[status] || 'Internal Server Error'}] ${request?.method} ${request?.url} - ${exception?.message || message} [code=${code}]`,
43
+ );
44
+ if (exception?.stack) {
45
+ this.logger.error(exception.stack);
37
46
  }
47
+
48
+ // Preserve any extra data the exception carried (optional)
49
+ const extra =
50
+ (isHttp && (exception.getResponse?.() as any)) ??
51
+ exception?.response ??
52
+ {};
53
+
54
+ // Keep your legacy shape; add canonical code
55
+ response.status(status).json({
56
+ statusCode: status,
57
+ statusCodeMessage: HttpStatusCodeMessages[status] || 'Internal Server Error',
58
+ // message: [message],
59
+ errorCode: code,
60
+ error: message,
61
+ data: extra,
62
+ });
38
63
  }
39
64
  }
@@ -1,214 +1,155 @@
1
- // src/common/errors/error-mapper.service.ts
2
- import { Injectable } from '@nestjs/common';
3
-
4
- export const ERROR_CODES = [
5
- 'bedrock-throttling-error',
6
- 'bedrock-access-denied',
7
- 'bedrock-input-too-long',
8
- 'bedrock-validation-error',
9
- 'bedrock-model-not-found',
10
- 'db-duplicate-key',
11
- 'db-foreign-key-error',
12
- 'metadata-extraction-date-parsing-failed',
13
- 'metadata-extraction-missing-s3-file',
14
- 'solidx-mcp-server-unavailable',
15
- 'unknown-error',
16
- ] as const;
17
-
18
- export type ErrorCode = typeof ERROR_CODES[number];
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { SolidRegistry } from 'src/helpers/solid-registry';
3
+ import { ErrorCode, ErrorMeta, ErrorRule, IErrorCodeProvider } from 'src/interfaces';
4
+
5
+ // export const ERROR_CODES = [
6
+ // 'db-duplicate-key',
7
+ // 'db-foreign-key-error',
8
+ // 'solidx-mcp-server-unavailable',
9
+ // 'unknown-error',
10
+ // ] as const;
11
+
12
+ // export type ErrorCode = typeof ERROR_CODES[number];
13
+
14
+ // type ErrorMeta = {
15
+ // message: string;
16
+ // httpStatus?: number;
17
+ // };
18
+
19
+ // const ERROR_MESSAGES: Record<ErrorCode, ErrorMeta> = {
20
+ // 'db-duplicate-key': {
21
+ // message: 'Duplicate key violation. A record with these values already exists.',
22
+ // httpStatus: 409,
23
+ // },
24
+ // 'db-foreign-key-error': {
25
+ // message: 'Foreign key constraint prevents this operation due to related records.',
26
+ // httpStatus: 409,
27
+ // },
28
+ // 'solidx-mcp-server-unavailable': {
29
+ // message: 'SolidX MCP server is unreachable. Please verify the MCP endpoint.',
30
+ // httpStatus: 503,
31
+ // },
32
+ // 'unknown-error': {
33
+ // message: 'An unexpected error occurred.',
34
+ // httpStatus: 500,
35
+ // },
36
+ // };
19
37
 
20
38
  @Injectable()
21
39
  export class ErrorMapperService {
22
- /**
23
- * Given an error/exception, return a mapped error code string.
24
- * Default: "unknown-error"
25
- */
26
- mapException(exc: unknown): ErrorCode {
27
- const combined = this.combineErrorText(exc);
40
+ private readonly logger = new Logger(ErrorMapperService.name);
28
41
 
29
- // AiInteraction - mcp server down.
30
- // {
31
- // "success": false,
32
- // "errors": [
33
- // "unhandled errors in a TaskGroup (1 sub-exception)"
34
- // ],
35
- // "error_trace": [
36
- // "Traceback (most recent call last):",
37
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/mcp/client/sse.py\", line 47, in sse_client\n async with aconnect_sse(\n ^^^^^^^^^^^^^",
38
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 210, in __aenter__\n return await anext(self.gen)\n ^^^^^^^^^^^^^^^^^^^^^",
39
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx_sse/_api.py\", line 69, in aconnect_sse\n async with client.stream(method, url, headers=headers, **kwargs) as response:\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
40
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 210, in __aenter__\n return await anext(self.gen)\n ^^^^^^^^^^^^^^^^^^^^^",
41
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1583, in stream\n response = await self.send(\n ^^^^^^^^^^^^^^^^",
42
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1629, in send\n response = await self._send_handling_auth(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
43
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1657, in _send_handling_auth\n response = await self._send_handling_redirects(\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
44
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1694, in _send_handling_redirects\n response = await self._send_single_request(request)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
45
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1730, in _send_single_request\n response = await transport.handle_async_request(request)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
46
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_transports/default.py\", line 393, in handle_async_request\n with map_httpcore_exceptions():\n ^^^^^^^^^^^^^^^^^^^^^^^^^",
47
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 158, in __exit__\n self.gen.throw(value)",
48
- // "File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_transports/default.py\", line 118, in map_httpcore_exceptions\n raise mapped_exc(message) from exc",
49
- // "httpx.ConnectError: All connection attempts failed",
50
- // "During handling of the above exception, another exception occurred:",
51
- // "+ Exception Group Traceback (most recent call last):",
52
- // "| File \"/Users/harishpatel/mcp/clients/solidx_mcp_client/client_sse_nochat.py\", line 239, in main\n | await client.connect_to_sse_server()",
53
- // "| File \"/Users/harishpatel/mcp/clients/solidx_mcp_client/client_sse_nochat.py\", line 49, in connect_to_sse_server\n | streams = await self._streams_context.__aenter__()\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
54
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 210, in __aenter__\n | return await anext(self.gen)\n | ^^^^^^^^^^^^^^^^^^^^^",
55
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/mcp/client/sse.py\", line 43, in sse_client\n | async with anyio.create_task_group() as tg:\n | ^^^^^^^^^^^^^^^^^^^^^^^^^",
56
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/anyio/_backends/_asyncio.py\", line 767, in __aexit__\n | raise BaseExceptionGroup(",
57
- // "| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)",
58
- // "+-+---------------- 1 ----------------",
59
- // "| Traceback (most recent call last):",
60
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_transports/default.py\", line 101, in map_httpcore_exceptions\n | yield",
61
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_transports/default.py\", line 394, in handle_async_request\n | resp = await self._pool.handle_async_request(req)\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
62
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_async/connection_pool.py\", line 256, in handle_async_request\n | raise exc from None",
63
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_async/connection_pool.py\", line 236, in handle_async_request\n | response = await connection.handle_async_request(\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
64
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_async/connection.py\", line 101, in handle_async_request\n | raise exc",
65
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_async/connection.py\", line 78, in handle_async_request\n | stream = await self._connect(request)\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
66
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_async/connection.py\", line 124, in _connect\n | stream = await self._network_backend.connect_tcp(**kwargs)\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
67
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_backends/auto.py\", line 31, in connect_tcp\n | return await self._backend.connect_tcp(\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
68
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_backends/anyio.py\", line 113, in connect_tcp\n | with map_exceptions(exc_map):\n | ^^^^^^^^^^^^^^^^^^^^^^^",
69
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 158, in __exit__\n | self.gen.throw(value)",
70
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpcore/_exceptions.py\", line 14, in map_exceptions\n | raise to_exc(exc) from exc",
71
- // "| httpcore.ConnectError: All connection attempts failed",
72
- // "| \n | The above exception was the direct cause of the following exception:\n |",
73
- // "| Traceback (most recent call last):",
74
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/mcp/client/sse.py\", line 47, in sse_client\n | async with aconnect_sse(\n | ^^^^^^^^^^^^^",
75
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 210, in __aenter__\n | return await anext(self.gen)\n | ^^^^^^^^^^^^^^^^^^^^^",
76
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx_sse/_api.py\", line 69, in aconnect_sse\n | async with client.stream(method, url, headers=headers, **kwargs) as response:\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
77
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 210, in __aenter__\n | return await anext(self.gen)\n | ^^^^^^^^^^^^^^^^^^^^^",
78
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1583, in stream\n | response = await self.send(\n | ^^^^^^^^^^^^^^^^",
79
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1629, in send\n | response = await self._send_handling_auth(\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
80
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1657, in _send_handling_auth\n | response = await self._send_handling_redirects(\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
81
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1694, in _send_handling_redirects\n | response = await self._send_single_request(request)\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
82
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_client.py\", line 1730, in _send_single_request\n | response = await transport.handle_async_request(request)\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
83
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_transports/default.py\", line 393, in handle_async_request\n | with map_httpcore_exceptions():\n | ^^^^^^^^^^^^^^^^^^^^^^^^^",
84
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/lib/python3.12/contextlib.py\", line 158, in __exit__\n | self.gen.throw(value)",
85
- // "| File \"/Users/harishpatel/.pyenv/versions/3.12.7/envs/solid_mcp_client/lib/python3.12/site-packages/httpx/_transports/default.py\", line 118, in map_httpcore_exceptions\n | raise mapped_exc(message) from exc",
86
- // "| httpx.ConnectError: All connection attempts failed",
87
- // "+------------------------------------"
88
- // ],
89
- // "request": "\"Can you do 1 + 1\""
90
- // }
91
- if (combined.includes("all connection attempts failed") && combined.includes("unhandled errors in a taskgroup (1 sub-exception)")) {
92
- return 'solidx-mcp-server-unavailable';
93
- }
94
-
95
- // --- Bedrock errors ---
96
- // Throttling: "ThrottlingException" or "Too many tokens"
97
- if (
98
- combined.includes('throttlingexception') ||
99
- combined.includes('too many tokens')
100
- ) {
101
- return 'bedrock-throttling-error';
102
- }
42
+ constructor(private readonly solidRegistry: SolidRegistry) { }
103
43
 
104
- if (combined.includes('accessdeniedexception')) {
105
- return 'bedrock-access-denied';
106
- }
107
-
108
- if (
109
- combined.includes('validationexception') &&
110
- combined.includes('input is too long')
111
- ) {
112
- return 'bedrock-input-too-long';
113
- }
44
+ /** Map an exception object (or string) to a canonical ErrorCode */
45
+ mapException(exc: unknown): ErrorCode {
46
+ const combined = this.combineErrorText(exc);
47
+ return this.matchCode(combined);
48
+ }
114
49
 
115
- if (combined.includes('validationexception')) {
116
- return 'bedrock-validation-error';
117
- }
50
+ /** Map plain message/trace to ErrorCode */
51
+ mapMessage(message: string, trace?: string): ErrorCode {
52
+ const combined = `${message ?? ''}\n${trace ?? ''}`.toLowerCase();
53
+ return this.matchCode(combined);
54
+ }
118
55
 
119
- if (combined.includes('modelnotfoundexception')) {
120
- return 'bedrock-model-not-found';
121
- }
56
+ /** Get static message for a given code */
57
+ getMessage(code: ErrorCode): string {
58
+ const meta = this.lookupMeta(code);
59
+ return (meta ?? { message: 'An unexpected error occurred.' }).message;
60
+ }
122
61
 
123
- // --- DB errors ---
124
- if (
125
- combined.includes('unique constraint') ||
126
- combined.includes('duplicate key')
127
- ) {
128
- return 'db-duplicate-key';
129
- }
62
+ /** Get default HTTP status for a code (falls back to 500) */
63
+ getHttpStatus(code: ErrorCode): number {
64
+ const meta = this.lookupMeta(code);
65
+ return meta?.httpStatus ?? 500;
66
+ }
130
67
 
131
- if (combined.includes('foreign key')) {
132
- return 'db-foreign-key-error';
68
+ // ---- internal helpers ----
69
+ private matchCode(combined: string): ErrorCode {
70
+ const rules = this.getAllRulesSorted();
71
+ for (const rule of rules) {
72
+ try {
73
+ if (rule.match(combined)) return rule.code;
74
+ } catch (e) {
75
+ // Defensive: bad provider shouldn't crash mapping
76
+ this.logger.warn(`Error rule threw in match(): code=${rule.code} provider? — ${e}`);
77
+ }
133
78
  }
79
+ return 'unknown-error';
80
+ }
134
81
 
135
- // --- OpenSearch errors ---
136
- // mapper_parsing_exception on specific fields
137
- if (
138
- combined.includes('mapper_parsing_exception') &&
139
- (combined.includes('failed to parse field [metadata.properties.dates]') ||
140
- combined.includes(
141
- 'failed to parse field [metadata.properties.date_authored]',
142
- ))
143
- ) {
144
- return 'metadata-extraction-date-parsing-failed';
82
+ private lookupMeta(code: ErrorCode): ErrorMeta | undefined {
83
+ // Prefer the first rule with that code
84
+ const rules = this.getAllRulesSorted();
85
+ const rule = rules.find((r) => r.code === code);
86
+ if (rule?.meta) return rule.meta;
87
+
88
+ // Optional: ask providers directly if they implement resolve()
89
+ const providers = this.getProviders();
90
+ for (const p of providers) {
91
+ if (p.resolve) {
92
+ const meta = p.resolve(code);
93
+ if (meta) return meta;
94
+ }
145
95
  }
96
+ return undefined;
97
+ }
146
98
 
147
- // --- S3 errors ---
148
- // NoSuchKey during GetObject
149
- if (combined.includes('nosuchkey') && combined.includes('getobject')) {
150
- return 'metadata-extraction-missing-s3-file';
99
+ private getAllRulesSorted(): ReadonlyArray<ErrorRule> {
100
+ const providers = this.getProviders();
101
+ const all: ErrorRule[] = [];
102
+ for (const p of providers) {
103
+ try {
104
+ const rules = p.rules() ?? [];
105
+ // Optional: namespace collision check can be added here if desired
106
+ all.push(...rules);
107
+ } catch (e) {
108
+ this.logger.warn(`ErrorCodeProvider.rules() failed for ${p.name?.()}: ${e}`);
109
+ }
151
110
  }
152
-
153
- // --- Catch-all ---
154
- return 'unknown-error';
111
+ // Sort by priority desc; default 0
112
+ return all.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
155
113
  }
156
114
 
157
- /**
158
- * Same mapping, but takes raw strings instead of an Exception object.
159
- */
160
- mapMessage(message: string, trace?: string): ErrorCode {
161
- const combined = `${message ?? ''}\n${trace ?? ''}`.toLowerCase();
162
- return this.mapException(combined);
115
+ private getProviders(): IErrorCodeProvider[] {
116
+ // convert InstanceWrapper instance
117
+ return this.solidRegistry
118
+ .getErrorCodeProviders()
119
+ .map((w) => w.instance)
120
+ .filter(Boolean) as IErrorCodeProvider[];
163
121
  }
164
122
 
165
- // ---- helpers ----
166
-
167
123
  private combineErrorText(exc: unknown): string {
168
- // If caller passed us a pre-lowered string (e.g. from mapMessage), use it
169
- if (typeof exc === 'string') {
170
- return exc.toLowerCase();
171
- }
124
+ if (typeof exc === 'string') return exc.toLowerCase();
172
125
 
173
- // Standard Error
174
126
  if (exc instanceof Error) {
175
127
  const message = exc.message ?? '';
176
- // Many libs set .stack to "Error: message\n<stack>"
177
- // We still include it in case upstream mutated it.
178
128
  const stack = exc.stack ?? '';
179
129
  return `${message}\n${stack}`.toLowerCase();
180
130
  }
181
131
 
182
- // Some SDKs throw objects (e.g., { name, message, code, $metadata, ... })
183
132
  if (exc && typeof exc === 'object') {
184
133
  try {
185
- const maybeAny = exc as Record<string, unknown>;
186
- const msg =
187
- String(maybeAny.message ?? '') ||
188
- String(maybeAny['Message'] ?? '') ||
189
- '';
190
- const name =
191
- String(maybeAny.name ?? '') ||
192
- String(maybeAny['__type'] ?? '') ||
193
- '';
194
- const stack = String((maybeAny as any).stack ?? '');
195
- // Also fold in a JSON snapshot as a last resort
196
- const json = safeJsonStringify(maybeAny);
134
+ const obj = exc as Record<string, unknown>;
135
+ const msg = String(obj.message ?? (obj as any)['Message'] ?? '');
136
+ const name = String(obj.name ?? (obj as any)['__type'] ?? '');
137
+ const stack = String((obj as any).stack ?? '');
138
+ const json = this.safeJsonStringify(obj);
197
139
  return `${name}\n${msg}\n${stack}\n${json}`.toLowerCase();
198
140
  } catch {
199
- // fall through
141
+ // ignore
200
142
  }
201
143
  }
202
144
 
203
- // Fallback
204
145
  return String(exc ?? '').toLowerCase();
205
146
  }
206
- }
207
147
 
208
- function safeJsonStringify(obj: unknown): string {
209
- try {
210
- return JSON.stringify(obj);
211
- } catch {
212
- return '';
148
+ private safeJsonStringify(obj: unknown): string {
149
+ try {
150
+ return JSON.stringify(obj);
151
+ } catch {
152
+ return '';
153
+ }
213
154
  }
214
155
  }