@agentuity/core 0.0.60 → 0.0.61
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/error.d.ts +90 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +258 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json.d.ts +1 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -2
- package/dist/json.js.map +1 -1
- package/dist/services/_util.d.ts +2 -6
- package/dist/services/_util.d.ts.map +1 -1
- package/dist/services/_util.js +56 -8
- package/dist/services/_util.js.map +1 -1
- package/dist/services/adapter.d.ts +2 -1
- package/dist/services/adapter.d.ts.map +1 -1
- package/dist/services/exception.d.ts +20 -6
- package/dist/services/exception.d.ts.map +1 -1
- package/dist/services/exception.js +2 -11
- package/dist/services/exception.js.map +1 -1
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/keyvalue.d.ts.map +1 -1
- package/dist/services/keyvalue.js +5 -1
- package/dist/services/keyvalue.js.map +1 -1
- package/dist/services/objectstore.d.ts.map +1 -1
- package/dist/services/objectstore.js +65 -26
- package/dist/services/objectstore.js.map +1 -1
- package/dist/services/stream.d.ts.map +1 -1
- package/dist/services/stream.js +25 -9
- package/dist/services/stream.js.map +1 -1
- package/dist/services/vector.d.ts.map +1 -1
- package/dist/services/vector.js +48 -30
- package/dist/services/vector.js.map +1 -1
- package/dist/workbench-config.d.ts +38 -0
- package/dist/workbench-config.d.ts.map +1 -1
- package/dist/workbench-config.js +7 -5
- package/dist/workbench-config.js.map +1 -1
- package/package.json +1 -1
- package/src/__test__/error.test.ts +431 -0
- package/src/error.ts +384 -0
- package/src/index.ts +1 -0
- package/src/json.ts +2 -2
- package/src/services/__test__/vector.test.ts +20 -14
- package/src/services/_util.ts +56 -18
- package/src/services/adapter.ts +3 -1
- package/src/services/exception.ts +7 -9
- package/src/services/index.ts +0 -1
- package/src/services/keyvalue.ts +6 -1
- package/src/services/objectstore.ts +79 -44
- package/src/services/stream.ts +45 -11
- package/src/services/vector.ts +99 -30
- package/src/workbench-config.ts +16 -5
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { StructuredError, isStructuredError } from '../error';
|
|
3
|
+
|
|
4
|
+
describe('StructuredError', () => {
|
|
5
|
+
describe('basic creation', () => {
|
|
6
|
+
test('should create error with tag', () => {
|
|
7
|
+
const NotFound = StructuredError('NotFound');
|
|
8
|
+
const error = new NotFound();
|
|
9
|
+
|
|
10
|
+
expect(error).toBeInstanceOf(Error);
|
|
11
|
+
expect(error._tag).toBe('NotFound');
|
|
12
|
+
expect(error.name).toBe('NotFound');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should create error with message', () => {
|
|
16
|
+
const NotFound = StructuredError('NotFound');
|
|
17
|
+
const error = new NotFound({ message: 'Resource not found' });
|
|
18
|
+
|
|
19
|
+
expect(error.message).toBe('Resource not found');
|
|
20
|
+
expect(error._tag).toBe('NotFound');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should create error with custom properties', () => {
|
|
24
|
+
const ValidationError = StructuredError('ValidationError')<{
|
|
25
|
+
field: string;
|
|
26
|
+
code: string;
|
|
27
|
+
}>();
|
|
28
|
+
const error = new ValidationError({ field: 'email', code: 'INVALID_FORMAT' });
|
|
29
|
+
|
|
30
|
+
expect(error.field).toBe('email');
|
|
31
|
+
expect(error.code).toBe('INVALID_FORMAT');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('should create error with message and custom properties', () => {
|
|
35
|
+
const ValidationError = StructuredError('ValidationError')<{
|
|
36
|
+
field: string;
|
|
37
|
+
value: string;
|
|
38
|
+
}>();
|
|
39
|
+
const error = new ValidationError({
|
|
40
|
+
message: 'Validation failed',
|
|
41
|
+
field: 'email',
|
|
42
|
+
value: 'invalid-email',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(error.message).toBe('Validation failed');
|
|
46
|
+
expect(error.field).toBe('email');
|
|
47
|
+
expect(error.value).toBe('invalid-email');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('stack trace', () => {
|
|
52
|
+
test('should capture stack trace', () => {
|
|
53
|
+
const TestError = StructuredError('TestError');
|
|
54
|
+
const error = new TestError({ message: 'test' });
|
|
55
|
+
|
|
56
|
+
expect(error.stack).toBeDefined();
|
|
57
|
+
expect(error.stack).toContain('TestError');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('cause chaining', () => {
|
|
62
|
+
test('should store cause error', () => {
|
|
63
|
+
const RootError = StructuredError('RootError');
|
|
64
|
+
const WrapperError = StructuredError('WrapperError');
|
|
65
|
+
|
|
66
|
+
const root = new RootError({ message: 'Root cause' });
|
|
67
|
+
const wrapper = new WrapperError({ message: 'Wrapped', cause: root });
|
|
68
|
+
|
|
69
|
+
expect(wrapper.cause).toBe(root);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('should store non-Error cause', () => {
|
|
73
|
+
const AppError = StructuredError('AppError');
|
|
74
|
+
const error = new AppError({ message: 'Failed', cause: 'string cause' });
|
|
75
|
+
|
|
76
|
+
expect(error.cause).toBe('string cause');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('should handle object cause', () => {
|
|
80
|
+
const AppError = StructuredError('AppError');
|
|
81
|
+
const error = new AppError({
|
|
82
|
+
message: 'Failed',
|
|
83
|
+
cause: { code: 500, reason: 'Server error' },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(error.cause).toEqual({ code: 500, reason: 'Server error' });
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('plainArgs', () => {
|
|
91
|
+
test('should return plain args without message and cause', () => {
|
|
92
|
+
const AppError = StructuredError('AppError')<{ id: number; status: string }>();
|
|
93
|
+
const error = new AppError({
|
|
94
|
+
message: 'Error message',
|
|
95
|
+
cause: new Error('cause'),
|
|
96
|
+
id: 123,
|
|
97
|
+
status: 'failed',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(error.plainArgs).toEqual({ id: 123, status: 'failed' });
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should return undefined when no args provided', () => {
|
|
104
|
+
const AppError = StructuredError('AppError');
|
|
105
|
+
const error = new AppError();
|
|
106
|
+
|
|
107
|
+
expect(error.plainArgs).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('should return undefined when only message provided', () => {
|
|
111
|
+
const AppError = StructuredError('AppError');
|
|
112
|
+
const error = new AppError({ message: 'test' });
|
|
113
|
+
|
|
114
|
+
expect(error.plainArgs).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('_tag protection', () => {
|
|
119
|
+
test('should not allow _tag override from args', () => {
|
|
120
|
+
const AppError = StructuredError('AppError');
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
const error = new AppError({ _tag: 'DifferentTag' } as any);
|
|
123
|
+
|
|
124
|
+
expect(error._tag).toBe('AppError');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('toJSON', () => {
|
|
129
|
+
test('should serialize basic error', () => {
|
|
130
|
+
const AppError = StructuredError('AppError');
|
|
131
|
+
const error = new AppError({ message: 'test' });
|
|
132
|
+
const json = error.toJSON();
|
|
133
|
+
|
|
134
|
+
expect(json.name).toBe('AppError');
|
|
135
|
+
expect(json.message).toBe('test');
|
|
136
|
+
expect(json.stack).toBeDefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('should include custom properties', () => {
|
|
140
|
+
const AppError = StructuredError('AppError')<{ id: number; status: string }>();
|
|
141
|
+
const error = new AppError({ message: 'test', id: 123, status: 'failed' });
|
|
142
|
+
const json = error.toJSON();
|
|
143
|
+
|
|
144
|
+
expect(json.id).toBe(123);
|
|
145
|
+
expect(json.status).toBe('failed');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('should serialize cause as Error', () => {
|
|
149
|
+
const RootError = StructuredError('RootError');
|
|
150
|
+
const WrapperError = StructuredError('WrapperError');
|
|
151
|
+
|
|
152
|
+
const root = new RootError({ message: 'Root' });
|
|
153
|
+
const wrapper = new WrapperError({ message: 'Wrapper', cause: root });
|
|
154
|
+
const json = wrapper.toJSON();
|
|
155
|
+
|
|
156
|
+
expect(json.cause).toBeDefined();
|
|
157
|
+
expect(json.cause.name).toBe('RootError');
|
|
158
|
+
expect(json.cause.message).toBe('Root');
|
|
159
|
+
expect(json.cause.stack).toBeDefined();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should serialize non-Error cause as-is', () => {
|
|
163
|
+
const AppError = StructuredError('AppError');
|
|
164
|
+
const error = new AppError({ message: 'test', cause: { code: 500 } });
|
|
165
|
+
const json = error.toJSON();
|
|
166
|
+
|
|
167
|
+
expect(json.cause).toEqual({ code: 500 });
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('prettyPrint', () => {
|
|
172
|
+
test('should print basic error', () => {
|
|
173
|
+
const AppError = StructuredError('AppError');
|
|
174
|
+
const error = new AppError({ message: 'Something went wrong' });
|
|
175
|
+
const output = error.prettyPrint();
|
|
176
|
+
|
|
177
|
+
expect(output).toContain('AppError');
|
|
178
|
+
expect(output).toContain('Something went wrong');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('should print error with custom args', () => {
|
|
182
|
+
const ValidationError = StructuredError('ValidationError')<{
|
|
183
|
+
field: string;
|
|
184
|
+
code: string;
|
|
185
|
+
}>();
|
|
186
|
+
const error = new ValidationError({
|
|
187
|
+
message: 'Invalid input',
|
|
188
|
+
field: 'email',
|
|
189
|
+
code: 'INVALID',
|
|
190
|
+
});
|
|
191
|
+
const output = error.prettyPrint();
|
|
192
|
+
|
|
193
|
+
expect(output).toContain('ValidationError');
|
|
194
|
+
expect(output).toContain('Invalid input');
|
|
195
|
+
expect(output).toContain('field');
|
|
196
|
+
expect(output).toContain('email');
|
|
197
|
+
expect(output).toContain('code');
|
|
198
|
+
expect(output).toContain('INVALID');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should print cause chain', () => {
|
|
202
|
+
const RootError = StructuredError('RootError');
|
|
203
|
+
const MiddleError = StructuredError('MiddleError');
|
|
204
|
+
const TopError = StructuredError('TopError');
|
|
205
|
+
|
|
206
|
+
const root = new RootError({ message: 'Root cause' });
|
|
207
|
+
const middle = new MiddleError({ message: 'Middle error', cause: root });
|
|
208
|
+
const top = new TopError({ message: 'Top error', cause: middle });
|
|
209
|
+
|
|
210
|
+
const output = top.prettyPrint();
|
|
211
|
+
|
|
212
|
+
expect(output).toContain('TopError');
|
|
213
|
+
expect(output).toContain('Top error');
|
|
214
|
+
expect(output).toContain('MiddleError');
|
|
215
|
+
expect(output).toContain('Middle error');
|
|
216
|
+
expect(output).toContain('RootError');
|
|
217
|
+
expect(output).toContain('Root cause');
|
|
218
|
+
expect(output).toContain('-- caused by --');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('should handle non-Error cause', () => {
|
|
222
|
+
const AppError = StructuredError('AppError');
|
|
223
|
+
const error = new AppError({
|
|
224
|
+
message: 'Failed',
|
|
225
|
+
cause: { code: 500, reason: 'Internal' },
|
|
226
|
+
});
|
|
227
|
+
const output = error.prettyPrint();
|
|
228
|
+
|
|
229
|
+
expect(output).toContain('AppError');
|
|
230
|
+
expect(output).toContain('Failed');
|
|
231
|
+
expect(output).toContain('cause:');
|
|
232
|
+
expect(output).toContain('500');
|
|
233
|
+
expect(output).toContain('Internal');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('should prevent infinite loops with circular causes', () => {
|
|
237
|
+
const AppError = StructuredError('AppError');
|
|
238
|
+
const error1 = new AppError({ message: 'Error 1' });
|
|
239
|
+
const error2 = new AppError({ message: 'Error 2', cause: error1 });
|
|
240
|
+
|
|
241
|
+
// Create circular reference by modifying the internal symbol
|
|
242
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
243
|
+
(error1 as any)[Symbol.for('@@RichError:cause')] = error2;
|
|
244
|
+
|
|
245
|
+
const output = error1.prettyPrint();
|
|
246
|
+
|
|
247
|
+
// Should complete without hanging due to visited Set
|
|
248
|
+
expect(output).toBeDefined();
|
|
249
|
+
expect(output).toContain('AppError');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('toString', () => {
|
|
254
|
+
test('should call prettyPrint', () => {
|
|
255
|
+
const AppError = StructuredError('AppError');
|
|
256
|
+
const error = new AppError({ message: 'test' });
|
|
257
|
+
const str = error.toString();
|
|
258
|
+
|
|
259
|
+
expect(str).toContain('AppError');
|
|
260
|
+
expect(str).toContain('test');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('JSON.stringify with circular references', () => {
|
|
265
|
+
test('should handle circular object references in custom properties', () => {
|
|
266
|
+
const AppError = StructuredError('AppError')<{ data: unknown }>();
|
|
267
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
268
|
+
const circular: any = { name: 'test' };
|
|
269
|
+
circular.self = circular;
|
|
270
|
+
|
|
271
|
+
const error = new AppError({ message: 'test', data: circular });
|
|
272
|
+
const json = error.toJSON();
|
|
273
|
+
|
|
274
|
+
// Should complete without throwing
|
|
275
|
+
expect(json).toBeDefined();
|
|
276
|
+
expect(json.data).toBeDefined();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('error inheritance', () => {
|
|
281
|
+
test('should be instanceof Error', () => {
|
|
282
|
+
const AppError = StructuredError('AppError');
|
|
283
|
+
const error = new AppError();
|
|
284
|
+
|
|
285
|
+
expect(error instanceof Error).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('should work with try-catch', () => {
|
|
289
|
+
const AppError = StructuredError('AppError');
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
throw new AppError({ message: 'test' });
|
|
293
|
+
} catch (e) {
|
|
294
|
+
expect(e).toBeInstanceOf(Error);
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
296
|
+
expect((e as any)._tag).toBe('AppError');
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe('multiple tagged error types', () => {
|
|
302
|
+
test('should distinguish between different tagged errors', () => {
|
|
303
|
+
const NotFoundError = StructuredError('NotFoundError');
|
|
304
|
+
const ValidationError = StructuredError('ValidationError');
|
|
305
|
+
|
|
306
|
+
const notFound = new NotFoundError({ message: 'Not found' });
|
|
307
|
+
const validation = new ValidationError({ message: 'Invalid' });
|
|
308
|
+
|
|
309
|
+
expect(notFound._tag).toBe('NotFoundError');
|
|
310
|
+
expect(validation._tag).toBe('ValidationError');
|
|
311
|
+
expect(notFound._tag).not.toBe(validation._tag);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('typed shape', () => {
|
|
316
|
+
test('should enforce typed shape with generic (tag auto-inferred)', () => {
|
|
317
|
+
const ValidationError = StructuredError('ValidationError')<{
|
|
318
|
+
field: string;
|
|
319
|
+
code: string;
|
|
320
|
+
}>();
|
|
321
|
+
|
|
322
|
+
const error = new ValidationError({
|
|
323
|
+
field: 'email',
|
|
324
|
+
code: 'INVALID_FORMAT',
|
|
325
|
+
message: 'Invalid email format',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
expect(error._tag).toBe('ValidationError');
|
|
329
|
+
expect(error.field).toBe('email');
|
|
330
|
+
expect(error.code).toBe('INVALID_FORMAT');
|
|
331
|
+
expect(error.message).toBe('Invalid email format');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('should make shape properties readonly on instance', () => {
|
|
335
|
+
const ApiError = StructuredError('ApiError')<{ status: number; endpoint: string }>();
|
|
336
|
+
|
|
337
|
+
const error = new ApiError({ status: 404, endpoint: '/api/users' });
|
|
338
|
+
|
|
339
|
+
expect(error.status).toBe(404);
|
|
340
|
+
expect(error.endpoint).toBe('/api/users');
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('should allow message and cause with typed shape', () => {
|
|
344
|
+
const DbError = StructuredError('DbError')<{ query: string; table: string }>();
|
|
345
|
+
const rootCause = new Error('Connection timeout');
|
|
346
|
+
|
|
347
|
+
const error = new DbError({
|
|
348
|
+
query: 'SELECT * FROM users',
|
|
349
|
+
table: 'users',
|
|
350
|
+
message: 'Database query failed',
|
|
351
|
+
cause: rootCause,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(error.query).toBe('SELECT * FROM users');
|
|
355
|
+
expect(error.table).toBe('users');
|
|
356
|
+
expect(error.message).toBe('Database query failed');
|
|
357
|
+
expect(error.cause).toBe(rootCause);
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
describe('isStructuredError', () => {
|
|
362
|
+
test('should return true for StructuredError instances', () => {
|
|
363
|
+
const AppError = StructuredError('AppError');
|
|
364
|
+
const error = new AppError({ message: 'test' });
|
|
365
|
+
|
|
366
|
+
expect(isStructuredError(error)).toBe(true);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('should return true for StructuredError with typed shape', () => {
|
|
370
|
+
const ValidationError = StructuredError('ValidationError')<{
|
|
371
|
+
field: string;
|
|
372
|
+
code: string;
|
|
373
|
+
}>();
|
|
374
|
+
const error = new ValidationError({ field: 'email', code: 'INVALID' });
|
|
375
|
+
|
|
376
|
+
expect(isStructuredError(error)).toBe(true);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('should return false for regular Error', () => {
|
|
380
|
+
const error = new Error('test');
|
|
381
|
+
|
|
382
|
+
expect(isStructuredError(error)).toBe(false);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('should return false for plain object with _tag', () => {
|
|
386
|
+
const obj = { _tag: 'FakeError', message: 'test' };
|
|
387
|
+
|
|
388
|
+
expect(isStructuredError(obj)).toBe(false);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('should return false for null', () => {
|
|
392
|
+
expect(isStructuredError(null)).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('should return false for undefined', () => {
|
|
396
|
+
expect(isStructuredError(undefined)).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('should return false for non-object values', () => {
|
|
400
|
+
expect(isStructuredError('error')).toBe(false);
|
|
401
|
+
expect(isStructuredError(123)).toBe(false);
|
|
402
|
+
expect(isStructuredError(true)).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('should work as type guard', () => {
|
|
406
|
+
const AppError = StructuredError('AppError')<{ id: number }>();
|
|
407
|
+
const error: unknown = new AppError({ id: 123 });
|
|
408
|
+
|
|
409
|
+
if (isStructuredError(error)) {
|
|
410
|
+
// TypeScript should know error has _tag
|
|
411
|
+
expect(error._tag).toBe('AppError');
|
|
412
|
+
// Should be able to access RichError methods
|
|
413
|
+
expect(error.prettyPrint).toBeDefined();
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test('should identify multiple different StructuredError types', () => {
|
|
418
|
+
const NotFoundError = StructuredError('NotFoundError');
|
|
419
|
+
const ValidationError = StructuredError('ValidationError');
|
|
420
|
+
const DbError = StructuredError('DbError');
|
|
421
|
+
|
|
422
|
+
const notFound = new NotFoundError();
|
|
423
|
+
const validation = new ValidationError();
|
|
424
|
+
const dbError = new DbError();
|
|
425
|
+
|
|
426
|
+
expect(isStructuredError(notFound)).toBe(true);
|
|
427
|
+
expect(isStructuredError(validation)).toBe(true);
|
|
428
|
+
expect(isStructuredError(dbError)).toBe(true);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
});
|