@agentica/core 0.22.0 → 0.24.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.
Files changed (67) hide show
  1. package/README.md +11 -12
  2. package/lib/Agentica.d.ts +6 -1
  3. package/lib/Agentica.js +8 -3
  4. package/lib/Agentica.js.map +1 -1
  5. package/lib/MicroAgentica.d.ts +10 -0
  6. package/lib/MicroAgentica.js +11 -0
  7. package/lib/MicroAgentica.js.map +1 -1
  8. package/lib/context/AgenticaContext.d.ts +4 -0
  9. package/lib/functional/assertHttpController.d.ts +75 -0
  10. package/lib/functional/assertHttpController.js +9622 -0
  11. package/lib/functional/assertHttpController.js.map +1 -0
  12. package/lib/functional/assertHttpLlmApplication.d.ts +1 -0
  13. package/lib/functional/assertHttpLlmApplication.js +1 -0
  14. package/lib/functional/assertHttpLlmApplication.js.map +1 -1
  15. package/lib/functional/assertMcpController.d.ts +1 -1
  16. package/lib/functional/assertMcpController.js +1 -1
  17. package/lib/functional/assertMcpController.js.map +1 -1
  18. package/lib/functional/validateHttpController.d.ts +75 -0
  19. package/lib/functional/validateHttpController.js +7952 -0
  20. package/lib/functional/validateHttpController.js.map +1 -0
  21. package/lib/functional/validateHttpLlmApplication.d.ts +1 -0
  22. package/lib/functional/validateHttpLlmApplication.js +1 -0
  23. package/lib/functional/validateHttpLlmApplication.js.map +1 -1
  24. package/lib/functional/validateMcpController.d.ts +24 -0
  25. package/lib/functional/validateMcpController.js +3034 -0
  26. package/lib/functional/validateMcpController.js.map +1 -0
  27. package/lib/histories/AgenticaUserInputHistory.d.ts +13 -7
  28. package/lib/index.d.ts +3 -0
  29. package/lib/index.js +3 -0
  30. package/lib/index.js.map +1 -1
  31. package/lib/index.mjs +25981 -5985
  32. package/lib/index.mjs.map +1 -1
  33. package/lib/utils/ChatGptCompletionMessageUtil.spec.d.ts +1 -0
  34. package/lib/utils/ChatGptCompletionMessageUtil.spec.js +288 -0
  35. package/lib/utils/ChatGptCompletionMessageUtil.spec.js.map +1 -0
  36. package/lib/utils/ChatGptTokenUsageAggregator.spec.d.ts +1 -0
  37. package/lib/utils/ChatGptTokenUsageAggregator.spec.js +199 -0
  38. package/lib/utils/ChatGptTokenUsageAggregator.spec.js.map +1 -0
  39. package/lib/utils/Singleton.js +18 -0
  40. package/lib/utils/Singleton.js.map +1 -1
  41. package/lib/utils/Singleton.spec.d.ts +1 -0
  42. package/lib/utils/Singleton.spec.js +106 -0
  43. package/lib/utils/Singleton.spec.js.map +1 -0
  44. package/lib/utils/__map_take.spec.d.ts +1 -0
  45. package/lib/utils/__map_take.spec.js +108 -0
  46. package/lib/utils/__map_take.spec.js.map +1 -0
  47. package/package.json +1 -1
  48. package/src/Agentica.ts +16 -2
  49. package/src/MicroAgentica.ts +13 -0
  50. package/src/context/AgenticaContext.ts +5 -0
  51. package/src/functional/assertHttpController.ts +112 -0
  52. package/src/functional/assertHttpLlmApplication.ts +1 -0
  53. package/src/functional/assertMcpController.ts +1 -2
  54. package/src/functional/validateHttpController.ts +118 -0
  55. package/src/functional/validateHttpLlmApplication.ts +1 -1
  56. package/src/functional/validateMcpController.ts +56 -0
  57. package/src/histories/AgenticaUserInputHistory.ts +14 -8
  58. package/src/index.ts +3 -0
  59. package/src/utils/ChatGptCompletionMessageUtil.spec.ts +320 -0
  60. package/src/utils/ChatGptTokenUsageAggregator.spec.ts +226 -0
  61. package/src/utils/Singleton.spec.ts +138 -0
  62. package/src/utils/Singleton.ts +18 -0
  63. package/src/utils/__map_take.spec.ts +140 -0
  64. package/lib/utils/MathUtil.d.ts +0 -3
  65. package/lib/utils/MathUtil.js +0 -8
  66. package/lib/utils/MathUtil.js.map +0 -1
  67. package/src/utils/MathUtil.ts +0 -3
@@ -0,0 +1,320 @@
1
+ import type {
2
+ ChatCompletion,
3
+ ChatCompletionChunk,
4
+ ChatCompletionMessageToolCall,
5
+ } from "openai/resources";
6
+
7
+ import { ChatGptCompletionMessageUtil } from "./ChatGptCompletionMessageUtil";
8
+
9
+ describe("chatGptCompletionMessageUtil", () => {
10
+ describe("transformCompletionChunk", () => {
11
+ it("should transform string chunk to ChatCompletionChunk", () => {
12
+ const chunk = {
13
+ id: "test-id",
14
+ choices: [{
15
+ index: 0,
16
+ delta: { content: "Hello" },
17
+ }],
18
+ created: 1234567890,
19
+ model: "gpt-4",
20
+ object: "chat.completion.chunk",
21
+ };
22
+
23
+ const result = ChatGptCompletionMessageUtil.transformCompletionChunk(JSON.stringify(chunk));
24
+ expect(result).toEqual(chunk);
25
+ });
26
+
27
+ it("should transform Uint8Array chunk to ChatCompletionChunk", () => {
28
+ const chunk = {
29
+ id: "test-id",
30
+ choices: [{
31
+ index: 0,
32
+ delta: { content: "Hello" },
33
+ }],
34
+ created: 1234567890,
35
+ model: "gpt-4",
36
+ object: "chat.completion.chunk",
37
+ };
38
+
39
+ const uint8Array = new TextEncoder().encode(JSON.stringify(chunk));
40
+ const result = ChatGptCompletionMessageUtil.transformCompletionChunk(uint8Array);
41
+ expect(result).toEqual(chunk);
42
+ });
43
+
44
+ it("should handle invalid JSON", () => {
45
+ expect(() => {
46
+ ChatGptCompletionMessageUtil.transformCompletionChunk("invalid json");
47
+ }).toThrow();
48
+ });
49
+ });
50
+
51
+ describe("accumulate", () => {
52
+ it("should accumulate content from chunks", () => {
53
+ const origin: ChatCompletion = {
54
+ id: "test-id",
55
+ choices: [{
56
+ index: 0,
57
+ // @ts-expect-error - refusal is not required
58
+ message: { role: "assistant", content: "Hello" },
59
+ }],
60
+ created: 1234567890,
61
+ model: "gpt-4",
62
+ object: "chat.completion",
63
+ };
64
+
65
+ const chunk: ChatCompletionChunk = {
66
+ id: "test-id",
67
+ // @ts-expect-error - finish_reason is not required
68
+ choices: [{
69
+ index: 0,
70
+ delta: { content: " World" },
71
+ }],
72
+ created: 1234567890,
73
+ model: "gpt-4",
74
+ object: "chat.completion.chunk",
75
+ };
76
+
77
+ const result = ChatGptCompletionMessageUtil.accumulate(origin, chunk);
78
+ expect(result.choices[0]?.message.content).toBe("Hello World");
79
+ });
80
+
81
+ it("should accumulate tool calls", () => {
82
+ const origin: ChatCompletion = {
83
+ id: "test-id",
84
+ choices: [{
85
+ index: 0,
86
+ // @ts-expect-error - finish_reason is not required
87
+ message: {
88
+ role: "assistant",
89
+ content: null,
90
+ tool_calls: [{
91
+ id: "call_1",
92
+ type: "function",
93
+ function: {
94
+ name: "test",
95
+ arguments: "{\"arg\": \"value\"}",
96
+ },
97
+ }],
98
+ },
99
+ }],
100
+ created: 1234567890,
101
+ model: "gpt-4",
102
+ object: "chat.completion",
103
+ };
104
+
105
+ const chunk: ChatCompletionChunk = {
106
+ id: "test-id",
107
+ // @ts-expect-error - finish_reason is not required
108
+ choices: [{
109
+ index: 0,
110
+ delta: {
111
+ tool_calls: [{
112
+ index: 0,
113
+ id: "call_1",
114
+ function: {
115
+ name: "_function",
116
+ arguments: "{\"arg2\": \"value2\"}",
117
+ },
118
+ }],
119
+ },
120
+ }],
121
+ created: 1234567890,
122
+ model: "gpt-4",
123
+ object: "chat.completion.chunk",
124
+ };
125
+
126
+ const result = ChatGptCompletionMessageUtil.accumulate(origin, chunk);
127
+ expect(result.choices[0]?.message.tool_calls?.[0]?.function.name).toBe("test_function");
128
+ expect(result.choices[0]?.message.tool_calls?.[0]?.function.arguments).toBe("{\"arg\": \"value\"}{\"arg2\": \"value2\"}");
129
+ });
130
+
131
+ it("should handle usage aggregation", () => {
132
+ const origin: ChatCompletion = {
133
+ id: "test-id",
134
+ choices: [{
135
+ index: 0,
136
+ // @ts-expect-error - finish_reason is not required
137
+ message: { role: "assistant", content: "Hello" },
138
+ }],
139
+ created: 1234567890,
140
+ model: "gpt-4",
141
+ object: "chat.completion",
142
+ usage: {
143
+ prompt_tokens: 10,
144
+ completion_tokens: 5,
145
+ total_tokens: 15,
146
+ },
147
+ };
148
+
149
+ const chunk: ChatCompletionChunk = {
150
+ id: "test-id",
151
+ // @ts-expect-error - finish_reason is not required
152
+ choices: [{
153
+ index: 0,
154
+ delta: { content: " World" },
155
+ }],
156
+ created: 1234567890,
157
+ model: "gpt-4",
158
+ object: "chat.completion.chunk",
159
+ usage: {
160
+ prompt_tokens: 0,
161
+ completion_tokens: 6,
162
+ total_tokens: 6,
163
+ },
164
+ };
165
+
166
+ const result = ChatGptCompletionMessageUtil.accumulate(origin, chunk);
167
+ expect(result.usage).toEqual({
168
+ prompt_tokens: 10,
169
+ completion_tokens: 11,
170
+ total_tokens: 21,
171
+ completion_tokens_details: {
172
+ accepted_prediction_tokens: 0,
173
+ reasoning_tokens: 0,
174
+ rejected_prediction_tokens: 0,
175
+ },
176
+ prompt_tokens_details: {
177
+ audio_tokens: 0,
178
+ cached_tokens: 0,
179
+ },
180
+ });
181
+ });
182
+ });
183
+
184
+ describe("merge", () => {
185
+ it("should merge multiple chunks into completion", () => {
186
+ const chunks: ChatCompletionChunk[] = [
187
+ {
188
+ id: "test-id",
189
+ // @ts-expect-error - finish_reason is not required
190
+ choices: [{
191
+ index: 0,
192
+ delta: { content: "Hello" },
193
+ }],
194
+ created: 1234567890,
195
+ model: "gpt-4",
196
+ object: "chat.completion.chunk",
197
+ },
198
+ {
199
+ id: "test-id",
200
+ // @ts-expect-error - finish_reason is not required
201
+ choices: [{
202
+ index: 0,
203
+ delta: { content: " World" },
204
+ }],
205
+ created: 1234567890,
206
+ model: "gpt-4",
207
+ object: "chat.completion.chunk",
208
+ },
209
+ ];
210
+
211
+ const result = ChatGptCompletionMessageUtil.merge(chunks);
212
+ expect(result.choices[0]?.message.content).toBe("Hello World");
213
+ });
214
+
215
+ it("should throw error for empty chunks array", () => {
216
+ expect(() => {
217
+ ChatGptCompletionMessageUtil.merge([]);
218
+ }).toThrow("No chunks received");
219
+ });
220
+ });
221
+
222
+ describe("mergeChoice", () => {
223
+ it("should merge finish reason", () => {
224
+ const acc: ChatCompletion.Choice = {
225
+ index: 0,
226
+ // @ts-expect-error - finish_reason is not required
227
+ message: { role: "assistant", content: "Hello" },
228
+ };
229
+
230
+ const cur: ChatCompletionChunk.Choice = {
231
+ index: 0,
232
+ delta: {},
233
+ finish_reason: "stop",
234
+ };
235
+
236
+ const result = ChatGptCompletionMessageUtil.mergeChoice(acc, cur);
237
+ expect(result.finish_reason).toBe("stop");
238
+ });
239
+
240
+ it("should merge content", () => {
241
+ const acc: ChatCompletion.Choice = {
242
+ index: 0,
243
+ // @ts-expect-error - refusal is not required
244
+ message: { role: "assistant", content: "Hello" },
245
+ };
246
+
247
+ // @ts-expect-error - finish_reason is not required
248
+ const cur: ChatCompletionChunk.Choice = {
249
+ index: 0,
250
+ delta: { content: " World" },
251
+ };
252
+
253
+ const result = ChatGptCompletionMessageUtil.mergeChoice(acc, cur);
254
+ expect(result.message.content).toBe("Hello World");
255
+ });
256
+
257
+ it("should merge refusal", () => {
258
+ // @ts-expect-error - finish_reason is not required
259
+ const acc: ChatCompletion.Choice = {
260
+ index: 0,
261
+ message: { role: "assistant", content: null, refusal: "I cannot" },
262
+ };
263
+
264
+ // @ts-expect-error - finish_reason is not required
265
+ const cur: ChatCompletionChunk.Choice = {
266
+ index: 0,
267
+ delta: { refusal: " do that" },
268
+ };
269
+
270
+ const result = ChatGptCompletionMessageUtil.mergeChoice(acc, cur);
271
+ expect(result.message.refusal).toBe("I cannot do that");
272
+ });
273
+ });
274
+
275
+ describe("mergeToolCalls", () => {
276
+ it("should merge tool call function arguments", () => {
277
+ const acc: ChatCompletionMessageToolCall = {
278
+ id: "call_1",
279
+ type: "function",
280
+ function: {
281
+ name: "test",
282
+ arguments: "{\"arg\": \"value\"}",
283
+ },
284
+ };
285
+
286
+ const cur: ChatCompletionChunk.Choice.Delta.ToolCall = {
287
+ index: 0,
288
+ id: "call_1",
289
+ function: {
290
+ arguments: "{\"arg2\": \"value2\"}",
291
+ },
292
+ };
293
+
294
+ const result = ChatGptCompletionMessageUtil.mergeToolCalls(acc, cur);
295
+ expect(result.function.arguments).toBe("{\"arg\": \"value\"}{\"arg2\": \"value2\"}");
296
+ });
297
+
298
+ it("should merge tool call function name", () => {
299
+ const acc: ChatCompletionMessageToolCall = {
300
+ id: "call_1",
301
+ type: "function",
302
+ function: {
303
+ name: "test",
304
+ arguments: "",
305
+ },
306
+ };
307
+
308
+ const cur: ChatCompletionChunk.Choice.Delta.ToolCall = {
309
+ index: 0,
310
+ id: "call_1",
311
+ function: {
312
+ name: "_function",
313
+ },
314
+ };
315
+
316
+ const result = ChatGptCompletionMessageUtil.mergeToolCalls(acc, cur);
317
+ expect(result.function.name).toBe("test_function");
318
+ });
319
+ });
320
+ });
@@ -0,0 +1,226 @@
1
+ import type { CompletionUsage } from "openai/resources";
2
+
3
+ import { ChatGptTokenUsageAggregator } from "./ChatGptTokenUsageAggregator";
4
+
5
+ describe("chatGptTokenUsageAggregator", () => {
6
+ describe("sum", () => {
7
+ it("should sum basic token usage", () => {
8
+ const usage1: CompletionUsage = {
9
+ prompt_tokens: 10,
10
+ completion_tokens: 5,
11
+ total_tokens: 15,
12
+ };
13
+
14
+ const usage2: CompletionUsage = {
15
+ prompt_tokens: 20,
16
+ completion_tokens: 10,
17
+ total_tokens: 30,
18
+ };
19
+
20
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
21
+
22
+ expect(result).toEqual({
23
+ prompt_tokens: 30,
24
+ completion_tokens: 15,
25
+ total_tokens: 45,
26
+ completion_tokens_details: {
27
+ accepted_prediction_tokens: 0,
28
+ reasoning_tokens: 0,
29
+ rejected_prediction_tokens: 0,
30
+ },
31
+ prompt_tokens_details: {
32
+ audio_tokens: 0,
33
+ cached_tokens: 0,
34
+ },
35
+ });
36
+ });
37
+
38
+ it("should handle undefined values", () => {
39
+ const usage1: CompletionUsage = {
40
+ prompt_tokens: 10,
41
+ completion_tokens: 5,
42
+ total_tokens: 15,
43
+ };
44
+
45
+ const usage2: CompletionUsage = {
46
+ // @ts-expect-error - intended to be undefined
47
+ prompt_tokens: undefined,
48
+ // @ts-expect-error - intended to be undefined
49
+ completion_tokens: undefined,
50
+ // @ts-expect-error - intended to be undefined
51
+ total_tokens: undefined,
52
+ };
53
+
54
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
55
+
56
+ expect(result).toEqual({
57
+ prompt_tokens: 10,
58
+ completion_tokens: 5,
59
+ total_tokens: 15,
60
+ completion_tokens_details: {
61
+ accepted_prediction_tokens: 0,
62
+ reasoning_tokens: 0,
63
+ rejected_prediction_tokens: 0,
64
+ },
65
+ prompt_tokens_details: {
66
+ audio_tokens: 0,
67
+ cached_tokens: 0,
68
+ },
69
+ });
70
+ });
71
+
72
+ it("should sum completion token details", () => {
73
+ const usage1: CompletionUsage = {
74
+ prompt_tokens: 10,
75
+ completion_tokens: 5,
76
+ total_tokens: 15,
77
+ completion_tokens_details: {
78
+ accepted_prediction_tokens: 3,
79
+ reasoning_tokens: 1,
80
+ rejected_prediction_tokens: 1,
81
+ },
82
+ };
83
+
84
+ const usage2: CompletionUsage = {
85
+ prompt_tokens: 20,
86
+ completion_tokens: 10,
87
+ total_tokens: 30,
88
+ completion_tokens_details: {
89
+ accepted_prediction_tokens: 7,
90
+ reasoning_tokens: 2,
91
+ rejected_prediction_tokens: 1,
92
+ },
93
+ };
94
+
95
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
96
+
97
+ expect(result.completion_tokens_details).toEqual({
98
+ accepted_prediction_tokens: 10,
99
+ reasoning_tokens: 3,
100
+ rejected_prediction_tokens: 2,
101
+ });
102
+ });
103
+
104
+ it("should handle undefined completion token details", () => {
105
+ const usage1: CompletionUsage = {
106
+ prompt_tokens: 10,
107
+ completion_tokens: 5,
108
+ total_tokens: 15,
109
+ completion_tokens_details: {
110
+ accepted_prediction_tokens: 3,
111
+ reasoning_tokens: 1,
112
+ rejected_prediction_tokens: 1,
113
+ },
114
+ };
115
+
116
+ const usage2: CompletionUsage = {
117
+ prompt_tokens: 20,
118
+ completion_tokens: 10,
119
+ total_tokens: 30,
120
+ };
121
+
122
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
123
+
124
+ expect(result.completion_tokens_details).toEqual({
125
+ accepted_prediction_tokens: 3,
126
+ reasoning_tokens: 1,
127
+ rejected_prediction_tokens: 1,
128
+ });
129
+ });
130
+
131
+ it("should sum prompt token details", () => {
132
+ const usage1: CompletionUsage = {
133
+ prompt_tokens: 10,
134
+ completion_tokens: 5,
135
+ total_tokens: 15,
136
+ prompt_tokens_details: {
137
+ audio_tokens: 3,
138
+ cached_tokens: 2,
139
+ },
140
+ };
141
+
142
+ const usage2: CompletionUsage = {
143
+ prompt_tokens: 20,
144
+ completion_tokens: 10,
145
+ total_tokens: 30,
146
+ prompt_tokens_details: {
147
+ audio_tokens: 7,
148
+ cached_tokens: 3,
149
+ },
150
+ };
151
+
152
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
153
+
154
+ expect(result.prompt_tokens_details).toEqual({
155
+ audio_tokens: 10,
156
+ cached_tokens: 5,
157
+ });
158
+ });
159
+
160
+ it("should handle undefined prompt token details", () => {
161
+ const usage1: CompletionUsage = {
162
+ prompt_tokens: 10,
163
+ completion_tokens: 5,
164
+ total_tokens: 15,
165
+ prompt_tokens_details: {
166
+ audio_tokens: 3,
167
+ cached_tokens: 2,
168
+ },
169
+ };
170
+
171
+ const usage2: CompletionUsage = {
172
+ prompt_tokens: 20,
173
+ completion_tokens: 10,
174
+ total_tokens: 30,
175
+ };
176
+
177
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
178
+
179
+ expect(result.prompt_tokens_details).toEqual({
180
+ audio_tokens: 3,
181
+ cached_tokens: 2,
182
+ });
183
+ });
184
+
185
+ it("should handle all undefined values", () => {
186
+ const usage1: CompletionUsage = {
187
+ // @ts-expect-error - intended to be undefined
188
+ prompt_tokens: undefined,
189
+ // @ts-expect-error - intended to be undefined
190
+ completion_tokens: undefined,
191
+ // @ts-expect-error - intended to be undefined
192
+ total_tokens: undefined,
193
+ completion_tokens_details: undefined,
194
+ prompt_tokens_details: undefined,
195
+ };
196
+
197
+ const usage2: CompletionUsage = {
198
+ // @ts-expect-error - intended to be undefined
199
+ prompt_tokens: undefined,
200
+ // @ts-expect-error - intended to be undefined
201
+ completion_tokens: undefined,
202
+ // @ts-expect-error - intended to be undefined
203
+ total_tokens: undefined,
204
+ completion_tokens_details: undefined,
205
+ prompt_tokens_details: undefined,
206
+ };
207
+
208
+ const result = ChatGptTokenUsageAggregator.sum(usage1, usage2);
209
+
210
+ expect(result).toEqual({
211
+ prompt_tokens: 0,
212
+ completion_tokens: 0,
213
+ total_tokens: 0,
214
+ completion_tokens_details: {
215
+ accepted_prediction_tokens: 0,
216
+ reasoning_tokens: 0,
217
+ rejected_prediction_tokens: 0,
218
+ },
219
+ prompt_tokens_details: {
220
+ audio_tokens: 0,
221
+ cached_tokens: 0,
222
+ },
223
+ });
224
+ });
225
+ });
226
+ });
@@ -0,0 +1,138 @@
1
+ import { Singleton } from "./Singleton";
2
+
3
+ describe("singleton", () => {
4
+ describe("basic functionality", () => {
5
+ it("should create instance only once", () => {
6
+ const factory = () => ({ value: 42 });
7
+ const singleton = new Singleton(factory);
8
+
9
+ const instance1 = singleton.get();
10
+ const instance2 = singleton.get();
11
+
12
+ expect(instance1).toBe(instance2);
13
+ expect(instance1.value).toBe(42);
14
+ });
15
+
16
+ it("should create different instances for different singletons", () => {
17
+ const factory1 = () => ({ value: 42 });
18
+ const factory2 = () => ({ value: 24 });
19
+
20
+ const singleton1 = new Singleton(factory1);
21
+ const singleton2 = new Singleton(factory2);
22
+
23
+ const instance1 = singleton1.get();
24
+ const instance2 = singleton2.get();
25
+
26
+ expect(instance1).not.toBe(instance2);
27
+ expect(instance1.value).toBe(42);
28
+ expect(instance2.value).toBe(24);
29
+ });
30
+ });
31
+
32
+ describe("constructor arguments", () => {
33
+ it("should pass constructor arguments to factory", () => {
34
+ const factory = (value: number) => ({ value });
35
+ const singleton = new Singleton(factory);
36
+
37
+ const instance = singleton.get(42);
38
+ expect(instance.value).toBe(42);
39
+ });
40
+
41
+ it("should use same instance with same constructor arguments", () => {
42
+ const factory = (value: number) => ({ value });
43
+ const singleton = new Singleton(factory);
44
+
45
+ const instance1 = singleton.get(42);
46
+ const instance2 = singleton.get(42);
47
+
48
+ expect(instance1).toBe(instance2);
49
+ expect(instance1.value).toBe(42);
50
+ });
51
+
52
+ it("should return same instance even with different constructor arguments", () => {
53
+ const factory = (value: number) => ({ value });
54
+ const singleton = new Singleton(factory);
55
+
56
+ const instance1 = singleton.get(42);
57
+ const instance2 = singleton.get(24);
58
+
59
+ expect(instance1).toBe(instance2);
60
+ expect(instance1.value).toBe(42);
61
+ expect(instance2.value).toBe(42);
62
+ });
63
+ });
64
+
65
+ describe("complex object types", () => {
66
+ it("should handle complex objects", () => {
67
+ interface ComplexObject {
68
+ id: number;
69
+ data: { name: string; value: number };
70
+ timestamp: Date;
71
+ }
72
+
73
+ const factory = (id: number, name: string, value: number) => ({
74
+ id,
75
+ data: { name, value },
76
+ timestamp: new Date(),
77
+ });
78
+
79
+ const singleton = new Singleton<ComplexObject, [number, string, number]>(factory);
80
+ const instance = singleton.get(1, "test", 42);
81
+
82
+ expect(instance.id).toBe(1);
83
+ expect(instance.data.name).toBe("test");
84
+ expect(instance.data.value).toBe(42);
85
+ expect(instance.timestamp).toBeInstanceOf(Date);
86
+ });
87
+
88
+ it("should maintain same complex object instance", () => {
89
+ const factory = () => ({
90
+ data: new Map<string, number>([["key", 42]]),
91
+ array: [1, 2, 3],
92
+ });
93
+
94
+ const singleton = new Singleton(factory);
95
+ const instance1 = singleton.get();
96
+ const instance2 = singleton.get();
97
+
98
+ expect(instance1).toBe(instance2);
99
+ expect(instance1.data).toBe(instance2.data);
100
+ expect(instance1.array).toBe(instance2.array);
101
+ });
102
+ });
103
+
104
+ describe("edge cases", () => {
105
+ it("should handle null factory return", () => {
106
+ const factory = () => null;
107
+ const singleton = new Singleton(factory);
108
+
109
+ const instance = singleton.get();
110
+ expect(instance).toBeNull();
111
+ });
112
+
113
+ it("should handle undefined factory return", () => {
114
+ const factory = () => undefined;
115
+ const singleton = new Singleton(factory);
116
+
117
+ const instance = singleton.get();
118
+ expect(instance).toBeUndefined();
119
+ });
120
+
121
+ it("should handle primitive values", () => {
122
+ const factory = () => 42;
123
+ const singleton = new Singleton(factory);
124
+
125
+ const instance = singleton.get();
126
+ expect(instance).toBe(42);
127
+ });
128
+
129
+ it("should handle factory throwing error", () => {
130
+ const factory = () => {
131
+ throw new Error("Factory error");
132
+ };
133
+ const singleton = new Singleton(factory);
134
+
135
+ expect(() => singleton.get()).toThrow("Factory error");
136
+ });
137
+ });
138
+ });
@@ -5,6 +5,24 @@ const NOT_MOUNTED_YET = {};
5
5
 
6
6
  /**
7
7
  * @internal
8
+ *
9
+ * @description
10
+ * A singleton class that creates a single instance of a class.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const singleton = new Singleton((name: string) => new SomeClass(name));
15
+ * const instance = singleton.get("test");
16
+ * ```
17
+ *
18
+ * but next case is not work
19
+ * ```ts
20
+ * const singleton = new Singleton((name: string) => new SomeClass(name));
21
+ * const instance = singleton.get("test");
22
+ * const instance2 = singleton.get("test2");
23
+ *
24
+ * expect(instance).toBe(instance2); // true
25
+ * ```
8
26
  */
9
27
  export class Singleton<T, Args extends any[] = []> {
10
28
  private readonly closure_: (...args: Args) => T;