@axiom-lattice/gateway 2.1.19 → 2.1.21
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +20 -0
- package/LOGGER_CONFIG.md +135 -0
- package/README.md +28 -1
- package/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +397 -155
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +397 -149
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/controllers/skills.ts +474 -0
- package/src/index.ts +112 -17
- package/src/routes/index.ts +61 -1
- package/src/schemas/index.ts +453 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/gateway",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.21",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"pino-roll": "^3.1.0",
|
|
33
33
|
"redis": "^5.0.1",
|
|
34
34
|
"uuid": "^9.0.1",
|
|
35
|
-
"@axiom-lattice/core": "2.1.
|
|
36
|
-
"@axiom-lattice/protocols": "2.1.
|
|
37
|
-
"@axiom-lattice/queue-redis": "1.0.
|
|
35
|
+
"@axiom-lattice/core": "2.1.16",
|
|
36
|
+
"@axiom-lattice/protocols": "2.1.10",
|
|
37
|
+
"@axiom-lattice/queue-redis": "1.0.9"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/jest": "^29.5.14",
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import { getStoreLattice } from "@axiom-lattice/core";
|
|
3
|
+
import { validateSkillName } from "@axiom-lattice/core";
|
|
4
|
+
import type {
|
|
5
|
+
Skill,
|
|
6
|
+
CreateSkillRequest,
|
|
7
|
+
} from "@axiom-lattice/protocols";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Skills Controller
|
|
11
|
+
* Handles skill-related CRUD operations
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Skill list response interface
|
|
16
|
+
*/
|
|
17
|
+
interface SkillListResponse {
|
|
18
|
+
success: boolean;
|
|
19
|
+
message: string;
|
|
20
|
+
data: {
|
|
21
|
+
records: Skill[];
|
|
22
|
+
total: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Skill response interface
|
|
28
|
+
*/
|
|
29
|
+
interface SkillResponse {
|
|
30
|
+
success: boolean;
|
|
31
|
+
message: string;
|
|
32
|
+
data?: Skill;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Skill update request body interface
|
|
37
|
+
*/
|
|
38
|
+
interface SkillUpdateBody {
|
|
39
|
+
name?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
license?: string;
|
|
42
|
+
compatibility?: string;
|
|
43
|
+
metadata?: Record<string, string>;
|
|
44
|
+
content?: string;
|
|
45
|
+
subSkills?: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Serialize Skill object for JSON response
|
|
50
|
+
* Converts Date objects to ISO strings
|
|
51
|
+
* Explicitly creates a plain object to ensure all fields are serializable
|
|
52
|
+
*/
|
|
53
|
+
function serializeSkill(skill: Skill): any {
|
|
54
|
+
// Explicitly create a plain object with all fields
|
|
55
|
+
// This ensures Fastify can properly serialize the response
|
|
56
|
+
const serialized: any = {
|
|
57
|
+
id: skill.id,
|
|
58
|
+
name: skill.name,
|
|
59
|
+
description: skill.description,
|
|
60
|
+
license: skill.license,
|
|
61
|
+
compatibility: skill.compatibility,
|
|
62
|
+
metadata: skill.metadata || {},
|
|
63
|
+
content: skill.content,
|
|
64
|
+
subSkills: skill.subSkills,
|
|
65
|
+
createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : (skill.createdAt ? new Date(skill.createdAt).toISOString() : new Date().toISOString()),
|
|
66
|
+
updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : (skill.updatedAt ? new Date(skill.updatedAt).toISOString() : new Date().toISOString()),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Remove undefined fields to avoid serialization issues
|
|
70
|
+
Object.keys(serialized).forEach((key) => {
|
|
71
|
+
if (serialized[key] === undefined) {
|
|
72
|
+
delete serialized[key];
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return serialized;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get list of all skills
|
|
81
|
+
*/
|
|
82
|
+
export async function getSkillList(
|
|
83
|
+
request: FastifyRequest,
|
|
84
|
+
reply: FastifyReply
|
|
85
|
+
): Promise<SkillListResponse> {
|
|
86
|
+
try {
|
|
87
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
88
|
+
const skillStore = storeLattice.store;
|
|
89
|
+
const skills = await skillStore.getAllSkills();
|
|
90
|
+
|
|
91
|
+
// Serialize skills to convert Date objects to ISO strings
|
|
92
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
message: "Successfully retrieved skill list",
|
|
97
|
+
data: {
|
|
98
|
+
records: serializedSkills,
|
|
99
|
+
total: serializedSkills.length,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
} catch (error: any) {
|
|
103
|
+
return reply.status(500).send({
|
|
104
|
+
success: false,
|
|
105
|
+
message: `Failed to retrieve skills: ${error.message}`,
|
|
106
|
+
data: {
|
|
107
|
+
records: [],
|
|
108
|
+
total: 0,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get a single skill by ID
|
|
116
|
+
*/
|
|
117
|
+
export async function getSkill(
|
|
118
|
+
request: FastifyRequest<{ Params: { id: string } }>,
|
|
119
|
+
reply: FastifyReply
|
|
120
|
+
): Promise<SkillResponse> {
|
|
121
|
+
try {
|
|
122
|
+
const { id } = request.params;
|
|
123
|
+
|
|
124
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
125
|
+
const skillStore = storeLattice.store;
|
|
126
|
+
const skill = await skillStore.getSkillById(id);
|
|
127
|
+
|
|
128
|
+
if (!skill) {
|
|
129
|
+
return reply.status(404).send({
|
|
130
|
+
success: false,
|
|
131
|
+
message: "Skill not found",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
message: "Successfully retrieved skill",
|
|
138
|
+
data: serializeSkill(skill),
|
|
139
|
+
};
|
|
140
|
+
} catch (error: any) {
|
|
141
|
+
return reply.status(500).send({
|
|
142
|
+
success: false,
|
|
143
|
+
message: `Failed to retrieve skill: ${error.message}`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Create a new skill
|
|
150
|
+
*/
|
|
151
|
+
export async function createSkill(
|
|
152
|
+
request: FastifyRequest<{ Body: CreateSkillRequest }>,
|
|
153
|
+
reply: FastifyReply
|
|
154
|
+
): Promise<SkillResponse> {
|
|
155
|
+
try {
|
|
156
|
+
const data = request.body;
|
|
157
|
+
|
|
158
|
+
// Validate required fields
|
|
159
|
+
if (!data.name) {
|
|
160
|
+
return reply.status(400).send({
|
|
161
|
+
success: false,
|
|
162
|
+
message: "name is required",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!data.description) {
|
|
167
|
+
return reply.status(400).send({
|
|
168
|
+
success: false,
|
|
169
|
+
message: "description is required",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Validate name format
|
|
174
|
+
try {
|
|
175
|
+
validateSkillName(data.name);
|
|
176
|
+
} catch (error: any) {
|
|
177
|
+
return reply.status(400).send({
|
|
178
|
+
success: false,
|
|
179
|
+
message: error.message || "Invalid skill name format",
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ID must equal name (name is used for path addressing)
|
|
184
|
+
const id = (request.body as any).id || data.name;
|
|
185
|
+
|
|
186
|
+
if (id !== data.name) {
|
|
187
|
+
return reply.status(400).send({
|
|
188
|
+
success: false,
|
|
189
|
+
message: `id "${id}" must equal name "${data.name}" (name is used for path addressing)`,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
194
|
+
const skillStore = storeLattice.store;
|
|
195
|
+
|
|
196
|
+
// Check if skill already exists
|
|
197
|
+
const exists = await skillStore.hasSkill(id);
|
|
198
|
+
if (exists) {
|
|
199
|
+
return reply.status(409).send({
|
|
200
|
+
success: false,
|
|
201
|
+
message: `Skill with id "${id}" already exists`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Create skill
|
|
206
|
+
const newSkill = await skillStore.createSkill(id, data);
|
|
207
|
+
|
|
208
|
+
return reply.status(201).send({
|
|
209
|
+
success: true,
|
|
210
|
+
message: "Successfully created skill",
|
|
211
|
+
data: serializeSkill(newSkill),
|
|
212
|
+
});
|
|
213
|
+
} catch (error: any) {
|
|
214
|
+
return reply.status(500).send({
|
|
215
|
+
success: false,
|
|
216
|
+
message: `Failed to create skill: ${error.message}`,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Update an existing skill by ID
|
|
223
|
+
*/
|
|
224
|
+
export async function updateSkill(
|
|
225
|
+
request: FastifyRequest<{
|
|
226
|
+
Params: { id: string };
|
|
227
|
+
Body: SkillUpdateBody;
|
|
228
|
+
}>,
|
|
229
|
+
reply: FastifyReply
|
|
230
|
+
): Promise<SkillResponse> {
|
|
231
|
+
try {
|
|
232
|
+
const { id } = request.params;
|
|
233
|
+
const updates = request.body;
|
|
234
|
+
|
|
235
|
+
// Validate name format if name is being updated
|
|
236
|
+
if (updates.name !== undefined) {
|
|
237
|
+
try {
|
|
238
|
+
validateSkillName(updates.name);
|
|
239
|
+
} catch (error: any) {
|
|
240
|
+
return reply.status(400).send({
|
|
241
|
+
success: false,
|
|
242
|
+
message: error.message || "Invalid skill name format",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
248
|
+
const skillStore = storeLattice.store;
|
|
249
|
+
|
|
250
|
+
// Check if skill exists
|
|
251
|
+
const exists = await skillStore.hasSkill(id);
|
|
252
|
+
if (!exists) {
|
|
253
|
+
return reply.status(404).send({
|
|
254
|
+
success: false,
|
|
255
|
+
message: "Skill not found",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Update skill
|
|
260
|
+
const updatedSkill = await skillStore.updateSkill(id, updates);
|
|
261
|
+
|
|
262
|
+
if (!updatedSkill) {
|
|
263
|
+
return reply.status(500).send({
|
|
264
|
+
success: false,
|
|
265
|
+
message: "Failed to update skill",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
message: "Successfully updated skill",
|
|
272
|
+
data: serializeSkill(updatedSkill),
|
|
273
|
+
};
|
|
274
|
+
} catch (error: any) {
|
|
275
|
+
return reply.status(500).send({
|
|
276
|
+
success: false,
|
|
277
|
+
message: `Failed to update skill: ${error.message}`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Delete a skill by ID
|
|
284
|
+
*/
|
|
285
|
+
export async function deleteSkill(
|
|
286
|
+
request: FastifyRequest<{ Params: { id: string } }>,
|
|
287
|
+
reply: FastifyReply
|
|
288
|
+
): Promise<{ success: boolean; message: string }> {
|
|
289
|
+
try {
|
|
290
|
+
const { id } = request.params;
|
|
291
|
+
|
|
292
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
293
|
+
const skillStore = storeLattice.store;
|
|
294
|
+
|
|
295
|
+
// Check if skill exists
|
|
296
|
+
const exists = await skillStore.hasSkill(id);
|
|
297
|
+
if (!exists) {
|
|
298
|
+
return reply.status(404).send({
|
|
299
|
+
success: false,
|
|
300
|
+
message: "Skill not found",
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Delete the skill
|
|
305
|
+
const deleted = await skillStore.deleteSkill(id);
|
|
306
|
+
|
|
307
|
+
if (!deleted) {
|
|
308
|
+
return reply.status(500).send({
|
|
309
|
+
success: false,
|
|
310
|
+
message: "Failed to delete skill",
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success: true,
|
|
316
|
+
message: "Successfully deleted skill",
|
|
317
|
+
};
|
|
318
|
+
} catch (error: any) {
|
|
319
|
+
return reply.status(500).send({
|
|
320
|
+
success: false,
|
|
321
|
+
message: `Failed to delete skill: ${error.message}`,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Search skills by metadata
|
|
328
|
+
*/
|
|
329
|
+
export async function searchSkillsByMetadata(
|
|
330
|
+
request: FastifyRequest<{
|
|
331
|
+
Querystring: { key: string; value: string };
|
|
332
|
+
}>,
|
|
333
|
+
reply: FastifyReply
|
|
334
|
+
): Promise<SkillListResponse> {
|
|
335
|
+
try {
|
|
336
|
+
const { key, value } = request.query;
|
|
337
|
+
|
|
338
|
+
if (!key || !value) {
|
|
339
|
+
return reply.status(400).send({
|
|
340
|
+
success: false,
|
|
341
|
+
message: "key and value query parameters are required",
|
|
342
|
+
data: {
|
|
343
|
+
records: [],
|
|
344
|
+
total: 0,
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
350
|
+
const skillStore = storeLattice.store;
|
|
351
|
+
const skills = await skillStore.searchByMetadata(key, value);
|
|
352
|
+
|
|
353
|
+
// Serialize skills to convert Date objects to ISO strings
|
|
354
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
success: true,
|
|
358
|
+
message: "Successfully searched skills",
|
|
359
|
+
data: {
|
|
360
|
+
records: serializedSkills,
|
|
361
|
+
total: serializedSkills.length,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
} catch (error: any) {
|
|
365
|
+
return reply.status(500).send({
|
|
366
|
+
success: false,
|
|
367
|
+
message: `Failed to search skills: ${error.message}`,
|
|
368
|
+
data: {
|
|
369
|
+
records: [],
|
|
370
|
+
total: 0,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Filter skills by compatibility
|
|
378
|
+
*/
|
|
379
|
+
export async function filterSkillsByCompatibility(
|
|
380
|
+
request: FastifyRequest<{
|
|
381
|
+
Querystring: { compatibility: string };
|
|
382
|
+
}>,
|
|
383
|
+
reply: FastifyReply
|
|
384
|
+
): Promise<SkillListResponse> {
|
|
385
|
+
try {
|
|
386
|
+
const { compatibility } = request.query;
|
|
387
|
+
|
|
388
|
+
if (!compatibility) {
|
|
389
|
+
return reply.status(400).send({
|
|
390
|
+
success: false,
|
|
391
|
+
message: "compatibility query parameter is required",
|
|
392
|
+
data: {
|
|
393
|
+
records: [],
|
|
394
|
+
total: 0,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
400
|
+
const skillStore = storeLattice.store;
|
|
401
|
+
const skills = await skillStore.filterByCompatibility(compatibility);
|
|
402
|
+
|
|
403
|
+
// Serialize skills to convert Date objects to ISO strings
|
|
404
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
message: "Successfully filtered skills",
|
|
409
|
+
data: {
|
|
410
|
+
records: serializedSkills,
|
|
411
|
+
total: serializedSkills.length,
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
} catch (error: any) {
|
|
415
|
+
return reply.status(500).send({
|
|
416
|
+
success: false,
|
|
417
|
+
message: `Failed to filter skills: ${error.message}`,
|
|
418
|
+
data: {
|
|
419
|
+
records: [],
|
|
420
|
+
total: 0,
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Filter skills by license
|
|
428
|
+
*/
|
|
429
|
+
export async function filterSkillsByLicense(
|
|
430
|
+
request: FastifyRequest<{
|
|
431
|
+
Querystring: { license: string };
|
|
432
|
+
}>,
|
|
433
|
+
reply: FastifyReply
|
|
434
|
+
): Promise<SkillListResponse> {
|
|
435
|
+
try {
|
|
436
|
+
const { license } = request.query;
|
|
437
|
+
|
|
438
|
+
if (!license) {
|
|
439
|
+
return reply.status(400).send({
|
|
440
|
+
success: false,
|
|
441
|
+
message: "license query parameter is required",
|
|
442
|
+
data: {
|
|
443
|
+
records: [],
|
|
444
|
+
total: 0,
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const storeLattice = getStoreLattice("default", "skill");
|
|
450
|
+
const skillStore = storeLattice.store;
|
|
451
|
+
const skills = await skillStore.filterByLicense(license);
|
|
452
|
+
|
|
453
|
+
// Serialize skills to convert Date objects to ISO strings
|
|
454
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
success: true,
|
|
458
|
+
message: "Successfully filtered skills",
|
|
459
|
+
data: {
|
|
460
|
+
records: serializedSkills,
|
|
461
|
+
total: serializedSkills.length,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
} catch (error: any) {
|
|
465
|
+
return reply.status(500).send({
|
|
466
|
+
success: false,
|
|
467
|
+
message: `Failed to filter skills: ${error.message}`,
|
|
468
|
+
data: {
|
|
469
|
+
records: [],
|
|
470
|
+
total: 0,
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,25 +2,56 @@ import fastify from "fastify";
|
|
|
2
2
|
import cors from "@fastify/cors";
|
|
3
3
|
import sensible from "@fastify/sensible";
|
|
4
4
|
import { registerLatticeRoutes } from "./routes";
|
|
5
|
-
// 导入自定义 Logger 类
|
|
6
|
-
import { Logger } from "./logger/Logger";
|
|
7
5
|
import { configureSwagger } from "./swagger";
|
|
8
6
|
import {
|
|
9
7
|
setQueueServiceType,
|
|
10
8
|
QueueServiceType,
|
|
11
9
|
} from "./services/queue_service";
|
|
12
10
|
import { AgentTaskConsumer } from "./services/agent_task_consumer";
|
|
11
|
+
import {
|
|
12
|
+
registerLoggerLattice,
|
|
13
|
+
getLoggerLattice,
|
|
14
|
+
loggerLatticeManager,
|
|
15
|
+
} from "@axiom-lattice/core";
|
|
16
|
+
import {
|
|
17
|
+
LoggerType,
|
|
18
|
+
LoggerConfig,
|
|
19
|
+
PinoFileOptions,
|
|
20
|
+
} from "@axiom-lattice/protocols";
|
|
13
21
|
|
|
14
22
|
process.on("unhandledRejection", (reason, promise) => {
|
|
15
23
|
console.error("未处理的Promise拒绝:", reason);
|
|
16
24
|
// 可以在这里进行日志记录或其他处理
|
|
17
25
|
});
|
|
18
26
|
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
// Default logger configuration
|
|
28
|
+
const DEFAULT_LOGGER_CONFIG: LoggerConfig = {
|
|
29
|
+
name: "default",
|
|
30
|
+
description: "Default logger for lattice-gateway service",
|
|
31
|
+
type: LoggerType.PINO,
|
|
32
|
+
serviceName: "lattice/gateway",
|
|
33
|
+
loggerName: "lattice/gateway",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Initialize logger with default config (can be overridden in start function)
|
|
37
|
+
let loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
|
|
38
|
+
let logger = loggerLattice.client;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize logger lattice with given configuration
|
|
42
|
+
*/
|
|
43
|
+
function initializeLogger(config: LoggerConfig) {
|
|
44
|
+
// Remove existing logger if it exists
|
|
45
|
+
if (loggerLatticeManager.hasLattice("default")) {
|
|
46
|
+
loggerLatticeManager.removeLattice("default");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Register logger with provided config
|
|
50
|
+
registerLoggerLattice("default", config);
|
|
51
|
+
|
|
52
|
+
// Get and return logger lattice instance
|
|
53
|
+
return getLoggerLattice("default");
|
|
54
|
+
}
|
|
24
55
|
|
|
25
56
|
// 创建 Fastify 应用
|
|
26
57
|
const app = fastify({
|
|
@@ -28,20 +59,45 @@ const app = fastify({
|
|
|
28
59
|
bodyLimit: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024, // Default 50MB, configurable via BODY_LIMIT env var
|
|
29
60
|
});
|
|
30
61
|
|
|
31
|
-
//
|
|
62
|
+
// Add custom logging hooks
|
|
32
63
|
app.addHook("onRequest", (request, reply, done) => {
|
|
64
|
+
// Convert headers to strings (Fastify headers can be string | string[])
|
|
65
|
+
const getHeaderValue = (
|
|
66
|
+
header: string | string[] | undefined
|
|
67
|
+
): string | undefined => {
|
|
68
|
+
if (Array.isArray(header)) {
|
|
69
|
+
return header[0];
|
|
70
|
+
}
|
|
71
|
+
return header;
|
|
72
|
+
};
|
|
73
|
+
|
|
33
74
|
const context = {
|
|
34
|
-
"x-tenant-id": request.headers["x-tenant-id"],
|
|
35
|
-
"x-request-id": request.headers["x-request-id"],
|
|
75
|
+
"x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
|
|
76
|
+
"x-request-id": getHeaderValue(request.headers["x-request-id"]),
|
|
36
77
|
};
|
|
78
|
+
// Update logger context for this request
|
|
79
|
+
if (loggerLattice.updateContext) {
|
|
80
|
+
loggerLattice.updateContext(context);
|
|
81
|
+
}
|
|
37
82
|
done();
|
|
38
83
|
});
|
|
39
84
|
|
|
40
85
|
app.addHook("onResponse", (request, reply, done) => {
|
|
86
|
+
// Convert headers to strings (Fastify headers can be string | string[])
|
|
87
|
+
const getHeaderValue = (
|
|
88
|
+
header: string | string[] | undefined
|
|
89
|
+
): string | undefined => {
|
|
90
|
+
if (Array.isArray(header)) {
|
|
91
|
+
return header[0];
|
|
92
|
+
}
|
|
93
|
+
return header;
|
|
94
|
+
};
|
|
95
|
+
|
|
41
96
|
const context = {
|
|
42
|
-
"x-tenant-id": request.headers["x-tenant-id"],
|
|
43
|
-
"x-request-id": request.headers["x-request-id"],
|
|
97
|
+
"x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
|
|
98
|
+
"x-request-id": getHeaderValue(request.headers["x-request-id"]),
|
|
44
99
|
};
|
|
100
|
+
loggerLattice.info(`${request.method} ${request.url} - ${reply.statusCode}`);
|
|
45
101
|
done();
|
|
46
102
|
});
|
|
47
103
|
|
|
@@ -61,11 +117,21 @@ app.register(cors, {
|
|
|
61
117
|
});
|
|
62
118
|
app.register(sensible);
|
|
63
119
|
|
|
64
|
-
//
|
|
120
|
+
// Error handler
|
|
65
121
|
app.setErrorHandler((error, request, reply) => {
|
|
122
|
+
// Convert headers to strings (Fastify headers can be string | string[])
|
|
123
|
+
const getHeaderValue = (
|
|
124
|
+
header: string | string[] | undefined
|
|
125
|
+
): string | undefined => {
|
|
126
|
+
if (Array.isArray(header)) {
|
|
127
|
+
return header[0];
|
|
128
|
+
}
|
|
129
|
+
return header;
|
|
130
|
+
};
|
|
131
|
+
|
|
66
132
|
const context = {
|
|
67
|
-
"x-tenant-id": request.headers["x-tenant-id"],
|
|
68
|
-
"x-request-id": request.headers["x-request-id"],
|
|
133
|
+
"x-tenant-id": getHeaderValue(request.headers["x-tenant-id"]),
|
|
134
|
+
"x-request-id": getHeaderValue(request.headers["x-request-id"]),
|
|
69
135
|
};
|
|
70
136
|
logger.error(
|
|
71
137
|
`请求错误: ${request.method} ${request.url} error:${error.message}`,
|
|
@@ -82,8 +148,20 @@ app.setErrorHandler((error, request, reply) => {
|
|
|
82
148
|
});
|
|
83
149
|
});
|
|
84
150
|
|
|
85
|
-
//
|
|
86
|
-
|
|
151
|
+
// Logger lattice will be decorated in start() function
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Logger configuration for gateway
|
|
155
|
+
*/
|
|
156
|
+
export interface GatewayLoggerConfig {
|
|
157
|
+
name?: string;
|
|
158
|
+
description?: string;
|
|
159
|
+
type?: LoggerType;
|
|
160
|
+
serviceName?: string;
|
|
161
|
+
loggerName?: string;
|
|
162
|
+
file?: string | PinoFileOptions;
|
|
163
|
+
context?: Record<string, any>;
|
|
164
|
+
}
|
|
87
165
|
|
|
88
166
|
// Gateway configuration interface
|
|
89
167
|
export interface LatticeGatewayConfig {
|
|
@@ -92,11 +170,28 @@ export interface LatticeGatewayConfig {
|
|
|
92
170
|
type: QueueServiceType;
|
|
93
171
|
defaultStartPollingQueue: boolean;
|
|
94
172
|
};
|
|
173
|
+
loggerConfig?: Partial<GatewayLoggerConfig>; // Optional logger configuration to override defaults
|
|
95
174
|
}
|
|
96
175
|
|
|
97
176
|
// Start server
|
|
98
177
|
const start = async (config?: LatticeGatewayConfig) => {
|
|
99
178
|
try {
|
|
179
|
+
// Initialize or update logger configuration if provided
|
|
180
|
+
if (config?.loggerConfig) {
|
|
181
|
+
const loggerConfig: LoggerConfig = {
|
|
182
|
+
...DEFAULT_LOGGER_CONFIG,
|
|
183
|
+
...config.loggerConfig,
|
|
184
|
+
// Merge file config if provided
|
|
185
|
+
file: config.loggerConfig.file || DEFAULT_LOGGER_CONFIG.file,
|
|
186
|
+
};
|
|
187
|
+
loggerLattice = initializeLogger(loggerConfig);
|
|
188
|
+
logger = loggerLattice.client;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Decorate app with logger lattice (only once, in start function)
|
|
192
|
+
// Access via: request.server.loggerLattice or app.loggerLattice
|
|
193
|
+
app.decorate("loggerLattice", loggerLattice);
|
|
194
|
+
|
|
100
195
|
const target_port = config?.port || Number(process.env.PORT) || 4001;
|
|
101
196
|
|
|
102
197
|
await app.listen({ port: target_port, host: "0.0.0.0" });
|