efarmz-slackbot-data 1.0.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/.clever.json +12 -0
- package/.dockerignore +13 -0
- package/.env.example +28 -0
- package/.github/workflows/deploy-production.yaml +34 -0
- package/.prettierrc +6 -0
- package/.tasks/F1-bootstrap.md +110 -0
- package/.tasks/F2-domain-layer.md +173 -0
- package/.tasks/F3-application-layer.md +166 -0
- package/.tasks/F4-infrastructure-layer.md +229 -0
- package/.tasks/F5-config-main.md +160 -0
- package/.tasks/F6-schemas-deployment.md +129 -0
- package/CLAUDE.md +163 -0
- package/Dockerfile +15 -0
- package/PRD.md +119 -0
- package/docs/schemas/.gitkeep +0 -0
- package/docs/schemas/_guidelines.md +89 -0
- package/docs/schemas/efarmz_db.md +759 -0
- package/docs/schemas/example.md +16 -0
- package/eslint.config.mjs +18 -0
- package/package.json +54 -0
- package/releaserc.json +15 -0
- package/src/.gitkeep +0 -0
- package/src/application/agent/.gitkeep +0 -0
- package/src/application/agent/AgentContext.test.ts +263 -0
- package/src/application/agent/AgentContext.ts +93 -0
- package/src/application/agent/AgentLoop.test.ts +275 -0
- package/src/application/agent/AgentLoop.ts +101 -0
- package/src/application/agent/AgentRunResult.ts +11 -0
- package/src/application/agent/LLMMessage.ts +16 -0
- package/src/application/agent/tools/RunSqlTool.ts +23 -0
- package/src/application/formatting/.gitkeep +0 -0
- package/src/application/formatting/CsvRenderer.test.ts +162 -0
- package/src/application/formatting/CsvRenderer.ts +34 -0
- package/src/application/formatting/MonospaceTableRenderer.test.ts +129 -0
- package/src/application/formatting/MonospaceTableRenderer.ts +58 -0
- package/src/application/formatting/RenderedResponse.ts +7 -0
- package/src/application/formatting/ResponseRenderer.test.ts +159 -0
- package/src/application/formatting/ResponseRenderer.ts +39 -0
- package/src/application/formatting/ScalarRenderer.test.ts +36 -0
- package/src/application/formatting/ScalarRenderer.ts +12 -0
- package/src/application/usecases/.gitkeep +0 -0
- package/src/application/usecases/AnswerQuestion.test.ts +362 -0
- package/src/application/usecases/AnswerQuestion.ts +69 -0
- package/src/application/usecases/ParseQuestion.test.ts +39 -0
- package/src/application/usecases/ParseQuestion.ts +9 -0
- package/src/config/.gitkeep +0 -0
- package/src/config/Container.test.ts +35 -0
- package/src/config/Container.ts +74 -0
- package/src/config/constants.ts +9 -0
- package/src/config/env.test.ts +103 -0
- package/src/config/env.ts +41 -0
- package/src/domain/entities/.gitkeep +0 -0
- package/src/domain/entities/Conversation.test.ts +69 -0
- package/src/domain/entities/Conversation.ts +26 -0
- package/src/domain/entities/ConversationMessage.test.ts +49 -0
- package/src/domain/entities/ConversationMessage.ts +18 -0
- package/src/domain/entities/index.ts +2 -0
- package/src/domain/errors/.gitkeep +0 -0
- package/src/domain/errors/AgentLoopExceededError.ts +12 -0
- package/src/domain/errors/DomainError.test.ts +106 -0
- package/src/domain/errors/DomainError.ts +11 -0
- package/src/domain/errors/InvalidSqlError.ts +15 -0
- package/src/domain/errors/LLMError.ts +15 -0
- package/src/domain/errors/SchemaLoadError.ts +15 -0
- package/src/domain/errors/SqlExecutionError.ts +15 -0
- package/src/domain/errors/index.ts +15 -0
- package/src/domain/ports/.gitkeep +0 -0
- package/src/domain/ports/AdminLogger.ts +16 -0
- package/src/domain/ports/ConversationRepository.ts +10 -0
- package/src/domain/ports/LLMProvider.ts +33 -0
- package/src/domain/ports/Logger.ts +8 -0
- package/src/domain/ports/SchemaCatalog.ts +5 -0
- package/src/domain/ports/SlackMessenger.ts +8 -0
- package/src/domain/ports/SqlExecutor.ts +8 -0
- package/src/domain/ports/SqlValidator.ts +5 -0
- package/src/domain/ports/index.ts +17 -0
- package/src/domain/value-objects/.gitkeep +0 -0
- package/src/domain/value-objects/LLMProviderName.ts +6 -0
- package/src/domain/value-objects/QueryResult.test.ts +51 -0
- package/src/domain/value-objects/QueryResult.ts +18 -0
- package/src/domain/value-objects/Question.test.ts +59 -0
- package/src/domain/value-objects/Question.ts +22 -0
- package/src/domain/value-objects/QuestionFlags.test.ts +59 -0
- package/src/domain/value-objects/QuestionFlags.ts +18 -0
- package/src/domain/value-objects/ResponseRendering.ts +7 -0
- package/src/domain/value-objects/SqlQuery.test.ts +40 -0
- package/src/domain/value-objects/SqlQuery.ts +12 -0
- package/src/domain/value-objects/ThreadId.test.ts +68 -0
- package/src/domain/value-objects/ThreadId.ts +27 -0
- package/src/domain/value-objects/index.ts +13 -0
- package/src/infrastructure/llm/.gitkeep +0 -0
- package/src/infrastructure/llm/AnthropicLLMProvider.test.ts +229 -0
- package/src/infrastructure/llm/AnthropicLLMProvider.ts +45 -0
- package/src/infrastructure/llm/index.ts +4 -0
- package/src/infrastructure/llm/mappers/AnthropicMessageMapper.test.ts +173 -0
- package/src/infrastructure/llm/mappers/AnthropicMessageMapper.ts +34 -0
- package/src/infrastructure/llm/prompts/SystemPromptBuilder.test.ts +41 -0
- package/src/infrastructure/llm/prompts/SystemPromptBuilder.ts +31 -0
- package/src/infrastructure/llm/prompts/ToolDefinitions.ts +7 -0
- package/src/infrastructure/logging/.gitkeep +0 -0
- package/src/infrastructure/logging/PinoLogger.test.ts +59 -0
- package/src/infrastructure/logging/PinoLogger.ts +28 -0
- package/src/infrastructure/logging/index.ts +1 -0
- package/src/infrastructure/persistence/.gitkeep +0 -0
- package/src/infrastructure/persistence/InMemoryConversationRepository.test.ts +325 -0
- package/src/infrastructure/persistence/InMemoryConversationRepository.ts +69 -0
- package/src/infrastructure/persistence/PostgresPoolFactory.ts +11 -0
- package/src/infrastructure/persistence/PostgresSqlExecutor.test.ts +130 -0
- package/src/infrastructure/persistence/PostgresSqlExecutor.ts +34 -0
- package/src/infrastructure/persistence/index.ts +3 -0
- package/src/infrastructure/schemas/.gitkeep +0 -0
- package/src/infrastructure/schemas/FileSystemSchemaCatalog.test.ts +163 -0
- package/src/infrastructure/schemas/FileSystemSchemaCatalog.ts +35 -0
- package/src/infrastructure/schemas/index.ts +4 -0
- package/src/infrastructure/slack/.gitkeep +0 -0
- package/src/infrastructure/slack/BoltSlackMessenger.test.ts +59 -0
- package/src/infrastructure/slack/BoltSlackMessenger.ts +36 -0
- package/src/infrastructure/slack/SlackAdminLogger.test.ts +54 -0
- package/src/infrastructure/slack/SlackAdminLogger.ts +27 -0
- package/src/infrastructure/slack/SlackApp.ts +9 -0
- package/src/infrastructure/slack/handlers/AppMentionHandler.ts +52 -0
- package/src/infrastructure/slack/handlers/DirectMessageHandler.ts +65 -0
- package/src/infrastructure/slack/index.ts +5 -0
- package/src/infrastructure/sql/.gitkeep +0 -0
- package/src/infrastructure/sql/RegexSqlValidator.test.ts +242 -0
- package/src/infrastructure/sql/RegexSqlValidator.ts +53 -0
- package/src/infrastructure/sql/index.ts +1 -0
- package/src/main.ts +19 -0
- package/tsconfig.json +23 -0
- package/vitest.config.ts +15 -0
- package/vitest.setup.ts +23 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
import AnthropicMessageMapper from "./AnthropicMessageMapper.js";
|
|
4
|
+
import type { LLMRequest } from "@/domain/ports/LLMProvider.js";
|
|
5
|
+
|
|
6
|
+
describe(`AnthropicMessageMapper`, () => {
|
|
7
|
+
describe(`toSDK`, () => {
|
|
8
|
+
it(`should convert user message to SDK format`, () => {
|
|
9
|
+
const messages: LLMRequest["messages"] = [
|
|
10
|
+
{ role: `user`, content: `What is the data?` },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const result = AnthropicMessageMapper.toSDK(messages);
|
|
14
|
+
|
|
15
|
+
expect(result).toHaveLength(1);
|
|
16
|
+
expect(result[0]).toEqual({
|
|
17
|
+
role: `user`,
|
|
18
|
+
content: `What is the data?`,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it(`should convert multiple messages preserving order`, () => {
|
|
23
|
+
const messages: LLMRequest["messages"] = [
|
|
24
|
+
{ role: `user`, content: `Question 1` },
|
|
25
|
+
{ role: `assistant`, content: `Answer 1` },
|
|
26
|
+
{ role: `user`, content: `Question 2` },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const result = AnthropicMessageMapper.toSDK(messages);
|
|
30
|
+
|
|
31
|
+
expect(result).toHaveLength(3);
|
|
32
|
+
expect(result[0]?.content).toBe(`Question 1`);
|
|
33
|
+
expect(result[1]?.content).toBe(`Answer 1`);
|
|
34
|
+
expect(result[2]?.content).toBe(`Question 2`);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe(`fromSDK`, () => {
|
|
39
|
+
it(`should extract text content from response`, () => {
|
|
40
|
+
const response: Anthropic.Message = {
|
|
41
|
+
id: `msg_123`,
|
|
42
|
+
type: `message`,
|
|
43
|
+
role: `assistant`,
|
|
44
|
+
content: [
|
|
45
|
+
{ type: `text`, text: `Here is the answer` },
|
|
46
|
+
],
|
|
47
|
+
model: `claude-3-5-sonnet-20241022`,
|
|
48
|
+
stop_reason: `end_turn`,
|
|
49
|
+
stop_sequence: null,
|
|
50
|
+
usage: { input_tokens: 10, output_tokens: 20 },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = AnthropicMessageMapper.fromSDK(response);
|
|
54
|
+
|
|
55
|
+
expect(result.content).toBe(`Here is the answer`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it(`should extract tool uses from response`, () => {
|
|
59
|
+
const response: Anthropic.Message = {
|
|
60
|
+
id: `msg_123`,
|
|
61
|
+
type: `message`,
|
|
62
|
+
role: `assistant`,
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: `tool_use`,
|
|
66
|
+
id: `tool_123`,
|
|
67
|
+
name: `run_sql`,
|
|
68
|
+
input: { sql: `SELECT * FROM users` },
|
|
69
|
+
} as unknown as Anthropic.ToolUseBlock,
|
|
70
|
+
],
|
|
71
|
+
model: `claude-3-5-sonnet-20241022`,
|
|
72
|
+
stop_reason: `tool_use`,
|
|
73
|
+
stop_sequence: null,
|
|
74
|
+
usage: { input_tokens: 10, output_tokens: 20 },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const result = AnthropicMessageMapper.fromSDK(response);
|
|
78
|
+
|
|
79
|
+
expect(result.toolUses).toHaveLength(1);
|
|
80
|
+
expect(result.toolUses[0]).toEqual({
|
|
81
|
+
id: `tool_123`,
|
|
82
|
+
name: `run_sql`,
|
|
83
|
+
input: { sql: `SELECT * FROM users` },
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it(`should set stop reason correctly`, () => {
|
|
88
|
+
const response: Anthropic.Message = {
|
|
89
|
+
id: `msg_123`,
|
|
90
|
+
type: `message`,
|
|
91
|
+
role: `assistant`,
|
|
92
|
+
content: [{ type: `text`, text: `Done` }],
|
|
93
|
+
model: `claude-3-5-sonnet-20241022`,
|
|
94
|
+
stop_reason: `end_turn`,
|
|
95
|
+
stop_sequence: null,
|
|
96
|
+
usage: { input_tokens: 10, output_tokens: 20 },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = AnthropicMessageMapper.fromSDK(response);
|
|
100
|
+
|
|
101
|
+
expect(result.stopReason).toBe(`end_turn`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it(`should extract token counts`, () => {
|
|
105
|
+
const response: Anthropic.Message = {
|
|
106
|
+
id: `msg_123`,
|
|
107
|
+
type: `message`,
|
|
108
|
+
role: `assistant`,
|
|
109
|
+
content: [{ type: `text`, text: `Response` }],
|
|
110
|
+
model: `claude-3-5-sonnet-20241022`,
|
|
111
|
+
stop_reason: `end_turn`,
|
|
112
|
+
stop_sequence: null,
|
|
113
|
+
usage: { input_tokens: 100, output_tokens: 50 },
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const result = AnthropicMessageMapper.fromSDK(response);
|
|
117
|
+
|
|
118
|
+
expect(result.inputTokens).toBe(100);
|
|
119
|
+
expect(result.outputTokens).toBe(50);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it(`should handle mixed text and tool use content`, () => {
|
|
123
|
+
const response: Anthropic.Message = {
|
|
124
|
+
id: `msg_123`,
|
|
125
|
+
type: `message`,
|
|
126
|
+
role: `assistant`,
|
|
127
|
+
content: [
|
|
128
|
+
{ type: `text`, text: `Let me query the data:` },
|
|
129
|
+
{
|
|
130
|
+
type: `tool_use`,
|
|
131
|
+
id: `tool_456`,
|
|
132
|
+
name: `run_sql`,
|
|
133
|
+
input: { sql: `SELECT count(*) FROM data` },
|
|
134
|
+
} as unknown as Anthropic.ToolUseBlock,
|
|
135
|
+
],
|
|
136
|
+
model: `claude-3-5-sonnet-20241022`,
|
|
137
|
+
stop_reason: `tool_use`,
|
|
138
|
+
stop_sequence: null,
|
|
139
|
+
usage: { input_tokens: 50, output_tokens: 30 },
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = AnthropicMessageMapper.fromSDK(response);
|
|
143
|
+
|
|
144
|
+
expect(result.content).toBe(`Let me query the data:`);
|
|
145
|
+
expect(result.toolUses).toHaveLength(1);
|
|
146
|
+
expect(result.toolUses[0]?.name).toBe(`run_sql`);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it(`should return empty content when no text block present`, () => {
|
|
150
|
+
const response: Anthropic.Message = {
|
|
151
|
+
id: `msg_123`,
|
|
152
|
+
type: `message`,
|
|
153
|
+
role: `assistant`,
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: `tool_use`,
|
|
157
|
+
id: `tool_789`,
|
|
158
|
+
name: `run_sql`,
|
|
159
|
+
input: {},
|
|
160
|
+
} as unknown as Anthropic.ToolUseBlock,
|
|
161
|
+
],
|
|
162
|
+
model: `claude-3-5-sonnet-20241022`,
|
|
163
|
+
stop_reason: `tool_use`,
|
|
164
|
+
stop_sequence: null,
|
|
165
|
+
usage: { input_tokens: 10, output_tokens: 20 },
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const result = AnthropicMessageMapper.fromSDK(response);
|
|
169
|
+
|
|
170
|
+
expect(result.content).toBe(``);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import type {
|
|
3
|
+
LLMRequest,
|
|
4
|
+
LLMResponse,
|
|
5
|
+
} from "@/domain/ports/LLMProvider.js";
|
|
6
|
+
|
|
7
|
+
function toSDK(messages: LLMRequest["messages"]): Anthropic.MessageParam[] {
|
|
8
|
+
return messages.map((m) => ({
|
|
9
|
+
role: m.role,
|
|
10
|
+
content: m.content,
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function fromSDK(response: Anthropic.Message): LLMResponse {
|
|
15
|
+
const textBlock = response.content.find((c) => c.type === `text`);
|
|
16
|
+
const toolBlocks = response.content.filter((c) => c.type === `tool_use`);
|
|
17
|
+
|
|
18
|
+
const content = textBlock ? textBlock.text : ``;
|
|
19
|
+
const toolUses = toolBlocks.map((t) => ({
|
|
20
|
+
id: t.id,
|
|
21
|
+
name: t.name,
|
|
22
|
+
input: (t.input ?? {}) as Record<string, unknown>,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
content,
|
|
27
|
+
toolUses,
|
|
28
|
+
stopReason: response.stop_reason as LLMResponse["stopReason"],
|
|
29
|
+
inputTokens: response.usage.input_tokens,
|
|
30
|
+
outputTokens: response.usage.output_tokens,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default { toSDK, fromSDK };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import SystemPromptBuilder from "./SystemPromptBuilder.js";
|
|
3
|
+
|
|
4
|
+
describe(`SystemPromptBuilder`, () => {
|
|
5
|
+
it(`should build system prompt with two blocks`, () => {
|
|
6
|
+
const schemaDoc = `Database schema documentation`;
|
|
7
|
+
const result = SystemPromptBuilder.build(schemaDoc);
|
|
8
|
+
|
|
9
|
+
expect(result).toHaveLength(2);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it(`should have header as first block without cache control`, () => {
|
|
13
|
+
const schemaDoc = `Database schema documentation`;
|
|
14
|
+
const result = SystemPromptBuilder.build(schemaDoc);
|
|
15
|
+
|
|
16
|
+
const firstBlock = result[0];
|
|
17
|
+
expect(firstBlock?.type).toBe(`text`);
|
|
18
|
+
expect(firstBlock?.text).toContain(`data analyst assistant for efarmz`);
|
|
19
|
+
expect(firstBlock?.cache_control).toBeUndefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it(`should have schema as second block with ephemeral cache control`, () => {
|
|
23
|
+
const schemaDoc = `Database schema documentation`;
|
|
24
|
+
const result = SystemPromptBuilder.build(schemaDoc);
|
|
25
|
+
|
|
26
|
+
const secondBlock = result[1];
|
|
27
|
+
expect(secondBlock?.type).toBe(`text`);
|
|
28
|
+
expect(secondBlock?.text).toBe(schemaDoc);
|
|
29
|
+
expect(secondBlock?.cache_control).toEqual({ type: `ephemeral` });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it(`should include instructions in header`, () => {
|
|
33
|
+
const schemaDoc = `test schema`;
|
|
34
|
+
const result = SystemPromptBuilder.build(schemaDoc);
|
|
35
|
+
|
|
36
|
+
const headerText = result[0]?.text;
|
|
37
|
+
expect(headerText).toContain(`run_sql tool`);
|
|
38
|
+
expect(headerText).toContain(`SELECT`);
|
|
39
|
+
expect(headerText).toContain(`French`);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const SYSTEM_PROMPT_HEADER = `You are a data analyst assistant for efarmz, responding in Slack.
|
|
2
|
+
You answer questions by querying PostgreSQL databases using the run_sql tool.
|
|
3
|
+
|
|
4
|
+
Rules:
|
|
5
|
+
- ALWAYS use the run_sql tool to query data before answering
|
|
6
|
+
- Only SELECT queries are allowed
|
|
7
|
+
- Respond in French with natural, conversational language
|
|
8
|
+
- Use Slack mrkdwn formatting: *bold* (single asterisk), _italic_, \`code\`
|
|
9
|
+
- Use bullet points with • for lists
|
|
10
|
+
- Use relevant emojis (📊 for data, 📦 for orders, 👥 for customers, 📅 for dates, etc.)
|
|
11
|
+
- For a single value, write a full sentence: "Il y a eu *494* commandes cette semaine."
|
|
12
|
+
- For multiple rows, write a short intro then list results with •
|
|
13
|
+
- Format numbers with thousands separators and appropriate units
|
|
14
|
+
- Keep responses concise and readable`;
|
|
15
|
+
|
|
16
|
+
function build(schemaDoc: string): Array<{
|
|
17
|
+
type: string;
|
|
18
|
+
text: string;
|
|
19
|
+
cache_control?: { type: string };
|
|
20
|
+
}> {
|
|
21
|
+
return [
|
|
22
|
+
{ type: `text`, text: SYSTEM_PROMPT_HEADER },
|
|
23
|
+
{
|
|
24
|
+
type: `text`,
|
|
25
|
+
text: schemaDoc,
|
|
26
|
+
cache_control: { type: `ephemeral` },
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default { build };
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import PinoLogger from "./PinoLogger.js";
|
|
3
|
+
|
|
4
|
+
describe(`PinoLogger`, () => {
|
|
5
|
+
it(`should instantiate with default log level (info)`, () => {
|
|
6
|
+
const logger = new PinoLogger();
|
|
7
|
+
expect(logger).toBeDefined();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it(`should instantiate with custom log level (debug)`, () => {
|
|
11
|
+
const logger = new PinoLogger(`debug`);
|
|
12
|
+
expect(logger).toBeDefined();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it(`should instantiate with custom log level (warn)`, () => {
|
|
16
|
+
const logger = new PinoLogger(`warn`);
|
|
17
|
+
expect(logger).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it(`should call debug method without throwing`, () => {
|
|
21
|
+
const logger = new PinoLogger();
|
|
22
|
+
expect(() => logger.debug(`test debug`)).not.toThrow();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it(`should call debug method with meta without throwing`, () => {
|
|
26
|
+
const logger = new PinoLogger();
|
|
27
|
+
expect(() => logger.debug(`test debug`, { key: `value` })).not.toThrow();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it(`should call info method without throwing`, () => {
|
|
31
|
+
const logger = new PinoLogger();
|
|
32
|
+
expect(() => logger.info(`test info`)).not.toThrow();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it(`should call info method with meta without throwing`, () => {
|
|
36
|
+
const logger = new PinoLogger();
|
|
37
|
+
expect(() => logger.info(`test info`, { key: `value` })).not.toThrow();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it(`should call warn method without throwing`, () => {
|
|
41
|
+
const logger = new PinoLogger();
|
|
42
|
+
expect(() => logger.warn(`test warn`)).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it(`should call warn method with meta without throwing`, () => {
|
|
46
|
+
const logger = new PinoLogger();
|
|
47
|
+
expect(() => logger.warn(`test warn`, { key: `value` })).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it(`should call error method without throwing`, () => {
|
|
51
|
+
const logger = new PinoLogger();
|
|
52
|
+
expect(() => logger.error(`test error`)).not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it(`should call error method with meta without throwing`, () => {
|
|
56
|
+
const logger = new PinoLogger();
|
|
57
|
+
expect(() => logger.error(`test error`, { key: `value` })).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
import type Logger from "@/domain/ports/Logger.js";
|
|
3
|
+
|
|
4
|
+
class PinoLogger implements Logger {
|
|
5
|
+
private readonly logger: pino.Logger;
|
|
6
|
+
|
|
7
|
+
constructor(level: string = `info`) {
|
|
8
|
+
this.logger = pino({ level });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
debug(msg: string, meta?: object): void {
|
|
12
|
+
this.logger.debug(meta ?? {}, msg);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
info(msg: string, meta?: object): void {
|
|
16
|
+
this.logger.info(meta ?? {}, msg);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
warn(msg: string, meta?: object): void {
|
|
20
|
+
this.logger.warn(meta ?? {}, msg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
error(msg: string, meta?: object): void {
|
|
24
|
+
this.logger.error(meta ?? {}, msg);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default PinoLogger;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PinoLogger } from "./PinoLogger.js";
|
|
File without changes
|