holosphere 1.1.20 → 2.0.0-alpha0

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 (146) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/README.md +483 -367
  5. package/bin/holosphere-activitypub.js +158 -0
  6. package/cleanup-test-data.js +204 -0
  7. package/examples/demo.html +1333 -0
  8. package/examples/example-bot.js +197 -0
  9. package/package.json +47 -87
  10. package/scripts/check-bundle-size.js +54 -0
  11. package/scripts/check-quest-ids.js +77 -0
  12. package/scripts/import-holons.js +578 -0
  13. package/scripts/publish-to-relay.js +101 -0
  14. package/scripts/read-example.js +186 -0
  15. package/scripts/relay-diagnostic.js +59 -0
  16. package/scripts/relay-example.js +179 -0
  17. package/scripts/resync-to-relay.js +245 -0
  18. package/scripts/revert-import.js +196 -0
  19. package/scripts/test-hybrid-mode.js +108 -0
  20. package/scripts/test-local-storage.js +63 -0
  21. package/scripts/test-nostr-direct.js +55 -0
  22. package/scripts/test-read-data.js +45 -0
  23. package/scripts/test-write-read.js +63 -0
  24. package/scripts/verify-import.js +95 -0
  25. package/scripts/verify-relay-data.js +139 -0
  26. package/src/ai/aggregation.js +319 -0
  27. package/src/ai/breakdown.js +511 -0
  28. package/src/ai/classifier.js +217 -0
  29. package/src/ai/council.js +228 -0
  30. package/src/ai/embeddings.js +279 -0
  31. package/src/ai/federation-ai.js +324 -0
  32. package/src/ai/h3-ai.js +955 -0
  33. package/src/ai/index.js +112 -0
  34. package/src/ai/json-ops.js +225 -0
  35. package/src/ai/llm-service.js +205 -0
  36. package/src/ai/nl-query.js +223 -0
  37. package/src/ai/relationships.js +353 -0
  38. package/src/ai/schema-extractor.js +218 -0
  39. package/src/ai/spatial.js +293 -0
  40. package/src/ai/tts.js +194 -0
  41. package/src/content/social-protocols.js +168 -0
  42. package/src/core/holosphere.js +273 -0
  43. package/src/crypto/secp256k1.js +259 -0
  44. package/src/federation/discovery.js +334 -0
  45. package/src/federation/hologram.js +1042 -0
  46. package/src/federation/registry.js +386 -0
  47. package/src/hierarchical/upcast.js +110 -0
  48. package/src/index.js +2669 -0
  49. package/src/schema/validator.js +91 -0
  50. package/src/spatial/h3-operations.js +110 -0
  51. package/src/storage/backend-factory.js +125 -0
  52. package/src/storage/backend-interface.js +142 -0
  53. package/src/storage/backends/activitypub/server.js +653 -0
  54. package/src/storage/backends/activitypub-backend.js +272 -0
  55. package/src/storage/backends/gundb-backend.js +233 -0
  56. package/src/storage/backends/nostr-backend.js +136 -0
  57. package/src/storage/filesystem-storage-browser.js +41 -0
  58. package/src/storage/filesystem-storage.js +138 -0
  59. package/src/storage/global-tables.js +81 -0
  60. package/src/storage/gun-async.js +281 -0
  61. package/src/storage/gun-wrapper.js +221 -0
  62. package/src/storage/indexeddb-storage.js +122 -0
  63. package/src/storage/key-storage-simple.js +76 -0
  64. package/src/storage/key-storage.js +136 -0
  65. package/src/storage/memory-storage.js +59 -0
  66. package/src/storage/migration.js +338 -0
  67. package/src/storage/nostr-async.js +811 -0
  68. package/src/storage/nostr-client.js +939 -0
  69. package/src/storage/nostr-wrapper.js +211 -0
  70. package/src/storage/outbox-queue.js +208 -0
  71. package/src/storage/persistent-storage.js +109 -0
  72. package/src/storage/sync-service.js +164 -0
  73. package/src/subscriptions/manager.js +142 -0
  74. package/test-ai-real-api.js +202 -0
  75. package/tests/unit/ai/aggregation.test.js +295 -0
  76. package/tests/unit/ai/breakdown.test.js +446 -0
  77. package/tests/unit/ai/classifier.test.js +294 -0
  78. package/tests/unit/ai/council.test.js +262 -0
  79. package/tests/unit/ai/embeddings.test.js +384 -0
  80. package/tests/unit/ai/federation-ai.test.js +344 -0
  81. package/tests/unit/ai/h3-ai.test.js +458 -0
  82. package/tests/unit/ai/index.test.js +304 -0
  83. package/tests/unit/ai/json-ops.test.js +307 -0
  84. package/tests/unit/ai/llm-service.test.js +390 -0
  85. package/tests/unit/ai/nl-query.test.js +383 -0
  86. package/tests/unit/ai/relationships.test.js +311 -0
  87. package/tests/unit/ai/schema-extractor.test.js +384 -0
  88. package/tests/unit/ai/spatial.test.js +279 -0
  89. package/tests/unit/ai/tts.test.js +279 -0
  90. package/tests/unit/content.test.js +332 -0
  91. package/tests/unit/contract/core.test.js +88 -0
  92. package/tests/unit/contract/crypto.test.js +198 -0
  93. package/tests/unit/contract/data.test.js +223 -0
  94. package/tests/unit/contract/federation.test.js +181 -0
  95. package/tests/unit/contract/hierarchical.test.js +113 -0
  96. package/tests/unit/contract/schema.test.js +114 -0
  97. package/tests/unit/contract/social.test.js +217 -0
  98. package/tests/unit/contract/spatial.test.js +110 -0
  99. package/tests/unit/contract/subscriptions.test.js +128 -0
  100. package/tests/unit/contract/utils.test.js +159 -0
  101. package/tests/unit/core.test.js +152 -0
  102. package/tests/unit/crypto.test.js +328 -0
  103. package/tests/unit/federation.test.js +234 -0
  104. package/tests/unit/gun-async.test.js +252 -0
  105. package/tests/unit/hierarchical.test.js +399 -0
  106. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  107. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  108. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  109. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  110. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  111. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  112. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  113. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  114. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  115. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  116. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  117. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  118. package/tests/unit/performance/benchmark.test.js +85 -0
  119. package/tests/unit/schema.test.js +213 -0
  120. package/tests/unit/spatial.test.js +158 -0
  121. package/tests/unit/storage.test.js +195 -0
  122. package/tests/unit/subscriptions.test.js +328 -0
  123. package/tests/unit/test-data-permanence-debug.js +197 -0
  124. package/tests/unit/test-data-permanence.js +340 -0
  125. package/tests/unit/test-key-persistence-fixed.js +148 -0
  126. package/tests/unit/test-key-persistence.js +172 -0
  127. package/tests/unit/test-relay-permanence.js +376 -0
  128. package/tests/unit/test-second-node.js +95 -0
  129. package/tests/unit/test-simple-write.js +89 -0
  130. package/vite.config.js +49 -0
  131. package/vitest.config.js +20 -0
  132. package/FEDERATION.md +0 -213
  133. package/compute.js +0 -298
  134. package/content.js +0 -980
  135. package/federation.js +0 -1234
  136. package/global.js +0 -736
  137. package/hexlib.js +0 -335
  138. package/hologram.js +0 -183
  139. package/holosphere-bundle.esm.js +0 -33256
  140. package/holosphere-bundle.js +0 -33287
  141. package/holosphere-bundle.min.js +0 -39
  142. package/holosphere.d.ts +0 -601
  143. package/holosphere.js +0 -719
  144. package/node.js +0 -246
  145. package/schema.js +0 -139
  146. package/utils.js +0 -302
@@ -0,0 +1,304 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import AI, {
3
+ LLMService,
4
+ LLM,
5
+ SchemaExtractor,
6
+ JSONOps,
7
+ Embeddings,
8
+ Council,
9
+ TTS,
10
+ VOICES,
11
+ MODELS,
12
+ NLQuery,
13
+ Classifier,
14
+ SpatialAnalysis,
15
+ SmartAggregation,
16
+ FederationAdvisor,
17
+ RelationshipDiscovery,
18
+ TaskBreakdown,
19
+ H3AI,
20
+ createAIServices
21
+ } from '../../../src/ai/index.js';
22
+
23
+ // Mock OpenAI
24
+ vi.mock('openai', () => ({
25
+ default: vi.fn().mockImplementation(() => ({
26
+ chat: {
27
+ completions: {
28
+ create: vi.fn()
29
+ }
30
+ },
31
+ embeddings: {
32
+ create: vi.fn()
33
+ },
34
+ audio: {
35
+ speech: {
36
+ create: vi.fn()
37
+ }
38
+ }
39
+ }))
40
+ }));
41
+
42
+ describe('Unit: AI Module Index', () => {
43
+ describe('Named Exports', () => {
44
+ it('should export LLMService', () => {
45
+ expect(LLMService).toBeDefined();
46
+ expect(typeof LLMService).toBe('function');
47
+ });
48
+
49
+ it('should export LLM as alias for LLMService', () => {
50
+ expect(LLM).toBeDefined();
51
+ });
52
+
53
+ it('should export SchemaExtractor', () => {
54
+ expect(SchemaExtractor).toBeDefined();
55
+ expect(typeof SchemaExtractor).toBe('function');
56
+ });
57
+
58
+ it('should export JSONOps', () => {
59
+ expect(JSONOps).toBeDefined();
60
+ expect(typeof JSONOps).toBe('function');
61
+ });
62
+
63
+ it('should export Embeddings', () => {
64
+ expect(Embeddings).toBeDefined();
65
+ expect(typeof Embeddings).toBe('function');
66
+ });
67
+
68
+ it('should export Council', () => {
69
+ expect(Council).toBeDefined();
70
+ expect(typeof Council).toBe('function');
71
+ });
72
+
73
+ it('should export TTS', () => {
74
+ expect(TTS).toBeDefined();
75
+ expect(typeof TTS).toBe('function');
76
+ });
77
+
78
+ it('should export VOICES constant', () => {
79
+ expect(VOICES).toBeDefined();
80
+ expect(VOICES.ALLOY).toBe('alloy');
81
+ expect(VOICES.ECHO).toBe('echo');
82
+ expect(VOICES.FABLE).toBe('fable');
83
+ expect(VOICES.ONYX).toBe('onyx');
84
+ expect(VOICES.NOVA).toBe('nova');
85
+ expect(VOICES.SHIMMER).toBe('shimmer');
86
+ });
87
+
88
+ it('should export MODELS constant', () => {
89
+ expect(MODELS).toBeDefined();
90
+ expect(MODELS.TTS_1).toBe('tts-1');
91
+ expect(MODELS.TTS_1_HD).toBe('tts-1-hd');
92
+ });
93
+
94
+ it('should export NLQuery', () => {
95
+ expect(NLQuery).toBeDefined();
96
+ expect(typeof NLQuery).toBe('function');
97
+ });
98
+
99
+ it('should export Classifier', () => {
100
+ expect(Classifier).toBeDefined();
101
+ expect(typeof Classifier).toBe('function');
102
+ });
103
+
104
+ it('should export SpatialAnalysis', () => {
105
+ expect(SpatialAnalysis).toBeDefined();
106
+ expect(typeof SpatialAnalysis).toBe('function');
107
+ });
108
+
109
+ it('should export SmartAggregation', () => {
110
+ expect(SmartAggregation).toBeDefined();
111
+ expect(typeof SmartAggregation).toBe('function');
112
+ });
113
+
114
+ it('should export FederationAdvisor', () => {
115
+ expect(FederationAdvisor).toBeDefined();
116
+ expect(typeof FederationAdvisor).toBe('function');
117
+ });
118
+
119
+ it('should export RelationshipDiscovery', () => {
120
+ expect(RelationshipDiscovery).toBeDefined();
121
+ expect(typeof RelationshipDiscovery).toBe('function');
122
+ });
123
+
124
+ it('should export TaskBreakdown', () => {
125
+ expect(TaskBreakdown).toBeDefined();
126
+ expect(typeof TaskBreakdown).toBe('function');
127
+ });
128
+
129
+ it('should export H3AI', () => {
130
+ expect(H3AI).toBeDefined();
131
+ expect(typeof H3AI).toBe('function');
132
+ });
133
+
134
+ it('should export createAIServices function', () => {
135
+ expect(createAIServices).toBeDefined();
136
+ expect(typeof createAIServices).toBe('function');
137
+ });
138
+ });
139
+
140
+ describe('Default Export', () => {
141
+ it('should have all service classes', () => {
142
+ expect(AI.LLMService).toBe(LLMService);
143
+ expect(AI.SchemaExtractor).toBe(SchemaExtractor);
144
+ expect(AI.JSONOps).toBe(JSONOps);
145
+ expect(AI.Embeddings).toBe(Embeddings);
146
+ expect(AI.Council).toBe(Council);
147
+ expect(AI.TTS).toBe(TTS);
148
+ expect(AI.NLQuery).toBe(NLQuery);
149
+ expect(AI.Classifier).toBe(Classifier);
150
+ expect(AI.SpatialAnalysis).toBe(SpatialAnalysis);
151
+ expect(AI.SmartAggregation).toBe(SmartAggregation);
152
+ expect(AI.FederationAdvisor).toBe(FederationAdvisor);
153
+ expect(AI.RelationshipDiscovery).toBe(RelationshipDiscovery);
154
+ expect(AI.TaskBreakdown).toBe(TaskBreakdown);
155
+ expect(AI.H3AI).toBe(H3AI);
156
+ });
157
+
158
+ it('should have constants', () => {
159
+ expect(AI.VOICES).toBe(VOICES);
160
+ expect(AI.MODELS).toBe(MODELS);
161
+ });
162
+
163
+ it('should have createAIServices', () => {
164
+ expect(AI.createAIServices).toBe(createAIServices);
165
+ });
166
+ });
167
+
168
+ describe('createAIServices', () => {
169
+ let services;
170
+
171
+ beforeEach(() => {
172
+ vi.clearAllMocks();
173
+ services = createAIServices('test-api-key');
174
+ });
175
+
176
+ it('should create LLM service', () => {
177
+ expect(services.llm).toBeInstanceOf(LLMService);
178
+ });
179
+
180
+ it('should create OpenAI client', () => {
181
+ expect(services.openai).toBeDefined();
182
+ });
183
+
184
+ it('should create Embeddings service', () => {
185
+ expect(services.embeddings).toBeInstanceOf(Embeddings);
186
+ });
187
+
188
+ it('should create SchemaExtractor service', () => {
189
+ expect(services.schemaExtractor).toBeInstanceOf(SchemaExtractor);
190
+ });
191
+
192
+ it('should create JSONOps service', () => {
193
+ expect(services.jsonOps).toBeInstanceOf(JSONOps);
194
+ });
195
+
196
+ it('should create Council service', () => {
197
+ expect(services.council).toBeInstanceOf(Council);
198
+ });
199
+
200
+ it('should create TTS service', () => {
201
+ expect(services.tts).toBeInstanceOf(TTS);
202
+ });
203
+
204
+ it('should create NLQuery service', () => {
205
+ expect(services.nlQuery).toBeInstanceOf(NLQuery);
206
+ });
207
+
208
+ it('should create Classifier service', () => {
209
+ expect(services.classifier).toBeInstanceOf(Classifier);
210
+ });
211
+
212
+ it('should create SpatialAnalysis service', () => {
213
+ expect(services.spatial).toBeInstanceOf(SpatialAnalysis);
214
+ });
215
+
216
+ it('should create SmartAggregation service', () => {
217
+ expect(services.aggregation).toBeInstanceOf(SmartAggregation);
218
+ });
219
+
220
+ it('should create FederationAdvisor service', () => {
221
+ expect(services.federationAdvisor).toBeInstanceOf(FederationAdvisor);
222
+ });
223
+
224
+ it('should create RelationshipDiscovery service', () => {
225
+ expect(services.relationships).toBeInstanceOf(RelationshipDiscovery);
226
+ });
227
+
228
+ it('should create TaskBreakdown service', () => {
229
+ expect(services.taskBreakdown).toBeInstanceOf(TaskBreakdown);
230
+ });
231
+
232
+ it('should create H3AI service', () => {
233
+ expect(services.h3ai).toBeInstanceOf(H3AI);
234
+ });
235
+
236
+ it('should share LLM instance across services', () => {
237
+ const llm = services.llm;
238
+
239
+ // Services that use LLM should have the same instance
240
+ expect(services.schemaExtractor.llm).toBe(llm);
241
+ expect(services.jsonOps.llm).toBe(llm);
242
+ expect(services.council.llm).toBe(llm);
243
+ expect(services.nlQuery.llm).toBe(llm);
244
+ expect(services.classifier.llm).toBe(llm);
245
+ expect(services.spatial.llm).toBe(llm);
246
+ expect(services.aggregation.llm).toBe(llm);
247
+ expect(services.federationAdvisor.llm).toBe(llm);
248
+ expect(services.relationships.llm).toBe(llm);
249
+ expect(services.taskBreakdown.llm).toBe(llm);
250
+ expect(services.h3ai.llm).toBe(llm);
251
+ });
252
+
253
+ it('should accept HoloSphere instance', () => {
254
+ const mockHolosphere = { put: vi.fn(), getAll: vi.fn() };
255
+ const servicesWithHS = createAIServices('test-key', mockHolosphere);
256
+
257
+ expect(servicesWithHS.embeddings.holosphere).toBe(mockHolosphere);
258
+ expect(servicesWithHS.nlQuery.holosphere).toBe(mockHolosphere);
259
+ expect(servicesWithHS.classifier.holosphere).toBe(mockHolosphere);
260
+ expect(servicesWithHS.spatial.holosphere).toBe(mockHolosphere);
261
+ expect(servicesWithHS.aggregation.holosphere).toBe(mockHolosphere);
262
+ expect(servicesWithHS.federationAdvisor.holosphere).toBe(mockHolosphere);
263
+ expect(servicesWithHS.relationships.holosphere).toBe(mockHolosphere);
264
+ expect(servicesWithHS.taskBreakdown.holosphere).toBe(mockHolosphere);
265
+ expect(servicesWithHS.h3ai.holosphere).toBe(mockHolosphere);
266
+ });
267
+
268
+ it('should pass LLM options', () => {
269
+ const servicesWithOptions = createAIServices('test-key', null, {
270
+ llm: { model: 'gpt-4', temperature: 0.5 }
271
+ });
272
+
273
+ expect(servicesWithOptions.llm.model).toBe('gpt-4');
274
+ expect(servicesWithOptions.llm.temperature).toBe(0.5);
275
+ });
276
+
277
+ it('should share embeddings with federation and relationship services', () => {
278
+ expect(services.federationAdvisor.embeddings).toBe(services.embeddings);
279
+ expect(services.relationships.embeddings).toBe(services.embeddings);
280
+ });
281
+ });
282
+
283
+ describe('Service Integration', () => {
284
+ it('should allow instantiating services individually', () => {
285
+ const llm = new LLMService('test-key');
286
+ const schemaExtractor = new SchemaExtractor(llm);
287
+ const council = new Council(llm);
288
+
289
+ expect(schemaExtractor.llm).toBe(llm);
290
+ expect(council.llm).toBe(llm);
291
+ });
292
+
293
+ it('should allow creating partial service sets', () => {
294
+ const llm = new LLMService('test-key');
295
+
296
+ // Only create needed services
297
+ const jsonOps = new JSONOps(llm);
298
+ const classifier = new Classifier(llm);
299
+
300
+ expect(jsonOps).toBeInstanceOf(JSONOps);
301
+ expect(classifier).toBeInstanceOf(Classifier);
302
+ });
303
+ });
304
+ });
@@ -0,0 +1,307 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { JSONOps } from '../../../src/ai/json-ops.js';
3
+
4
+ describe('Unit: JSONOps', () => {
5
+ let jsonOps;
6
+ let mockLLM;
7
+
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+
11
+ mockLLM = {
12
+ getJSON: vi.fn()
13
+ };
14
+
15
+ jsonOps = new JSONOps(mockLLM);
16
+ });
17
+
18
+ describe('Constructor', () => {
19
+ it('should initialize with LLM service', () => {
20
+ expect(jsonOps.llm).toBe(mockLLM);
21
+ });
22
+ });
23
+
24
+ describe('add', () => {
25
+ it('should semantically merge two objects', async () => {
26
+ mockLLM.getJSON.mockResolvedValue({
27
+ name: 'Combined Name',
28
+ description: 'Merged description from both',
29
+ tags: ['tag1', 'tag2', 'tag3']
30
+ });
31
+
32
+ const obj1 = { name: 'Name 1', tags: ['tag1'] };
33
+ const obj2 = { description: 'Description', tags: ['tag2', 'tag3'] };
34
+
35
+ const result = await jsonOps.add(obj1, obj2);
36
+
37
+ expect(result.name).toBe('Combined Name');
38
+ expect(result.tags).toContain('tag1');
39
+ expect(mockLLM.getJSON).toHaveBeenCalledWith(
40
+ expect.stringContaining('merge'),
41
+ expect.stringContaining('Object 1'),
42
+ expect.objectContaining({ temperature: 0.2 })
43
+ );
44
+ });
45
+
46
+ it('should prefer second object when option set', async () => {
47
+ mockLLM.getJSON.mockResolvedValue({ value: 'from obj2' });
48
+
49
+ await jsonOps.add({ value: 'from obj1' }, { value: 'from obj2' }, { preferSecond: true });
50
+
51
+ const call = mockLLM.getJSON.mock.calls[0];
52
+ expect(call[0]).toContain('prefer the second object');
53
+ });
54
+ });
55
+
56
+ describe('subtract', () => {
57
+ it('should remove concepts from first object based on second', async () => {
58
+ mockLLM.getJSON.mockResolvedValue({
59
+ name: 'Remaining',
60
+ uniqueField: 'kept'
61
+ });
62
+
63
+ const obj1 = { name: 'Remaining', description: 'To remove', uniqueField: 'kept' };
64
+ const obj2 = { description: 'Similar description' };
65
+
66
+ const result = await jsonOps.subtract(obj1, obj2);
67
+
68
+ expect(result.name).toBe('Remaining');
69
+ expect(result.uniqueField).toBe('kept');
70
+ expect(result.description).toBeUndefined();
71
+ });
72
+ });
73
+
74
+ describe('union', () => {
75
+ it('should create semantic union with deduplication', async () => {
76
+ mockLLM.getJSON.mockResolvedValue({
77
+ items: ['unique1', 'unique2', 'common'],
78
+ field1: 'from obj1',
79
+ field2: 'from obj2'
80
+ });
81
+
82
+ const obj1 = { items: ['unique1', 'common'], field1: 'from obj1' };
83
+ const obj2 = { items: ['unique2', 'common'], field2: 'from obj2' };
84
+
85
+ const result = await jsonOps.union(obj1, obj2);
86
+
87
+ expect(result.items).toHaveLength(3);
88
+ expect(result.field1).toBeDefined();
89
+ expect(result.field2).toBeDefined();
90
+ });
91
+ });
92
+
93
+ describe('difference', () => {
94
+ it('should find differences between objects', async () => {
95
+ mockLLM.getJSON.mockResolvedValue({
96
+ added: { newField: 'new value' },
97
+ removed: { oldField: 'old value' },
98
+ changed: { status: { old: 'active', new: 'completed' } }
99
+ });
100
+
101
+ const obj1 = { oldField: 'old value', status: 'active' };
102
+ const obj2 = { newField: 'new value', status: 'completed' };
103
+
104
+ const result = await jsonOps.difference(obj1, obj2);
105
+
106
+ expect(result.added.newField).toBe('new value');
107
+ expect(result.removed.oldField).toBe('old value');
108
+ expect(result.changed.status.old).toBe('active');
109
+ expect(result.changed.status.new).toBe('completed');
110
+ });
111
+ });
112
+
113
+ describe('concatenate', () => {
114
+ it('should concatenate objects by field type', async () => {
115
+ mockLLM.getJSON.mockResolvedValue({
116
+ text: 'First part. Second part.',
117
+ items: [1, 2, 3, 4],
118
+ count: 15,
119
+ active: true
120
+ });
121
+
122
+ const obj1 = { text: 'First part.', items: [1, 2], count: 5, active: false };
123
+ const obj2 = { text: 'Second part.', items: [3, 4], count: 10, active: true };
124
+
125
+ const result = await jsonOps.concatenate(obj1, obj2);
126
+
127
+ expect(result.text).toContain('First');
128
+ expect(result.text).toContain('Second');
129
+ expect(result.items).toHaveLength(4);
130
+ expect(result.count).toBe(15);
131
+ expect(result.active).toBe(true);
132
+ });
133
+ });
134
+
135
+ describe('intersection', () => {
136
+ it('should find common elements between objects', async () => {
137
+ mockLLM.getJSON.mockResolvedValue({
138
+ commonField: 'shared value',
139
+ sharedTags: ['common']
140
+ });
141
+
142
+ const obj1 = { commonField: 'shared value', unique1: 'only in 1', sharedTags: ['common', 'unique1'] };
143
+ const obj2 = { commonField: 'shared value', unique2: 'only in 2', sharedTags: ['common', 'unique2'] };
144
+
145
+ const result = await jsonOps.intersection(obj1, obj2);
146
+
147
+ expect(result.commonField).toBe('shared value');
148
+ expect(result.sharedTags).toContain('common');
149
+ });
150
+ });
151
+
152
+ describe('transform', () => {
153
+ it('should transform object to new structure', async () => {
154
+ mockLLM.getJSON.mockResolvedValue({
155
+ fullName: 'John Doe',
156
+ contact: {
157
+ email: 'john@example.com',
158
+ phone: '555-1234'
159
+ }
160
+ });
161
+
162
+ const obj = {
163
+ firstName: 'John',
164
+ lastName: 'Doe',
165
+ email: 'john@example.com',
166
+ phone: '555-1234'
167
+ };
168
+
169
+ const result = await jsonOps.transform(obj, 'Combine names into fullName, group contact info');
170
+
171
+ expect(result.fullName).toBe('John Doe');
172
+ expect(result.contact.email).toBe('john@example.com');
173
+ });
174
+
175
+ it('should include target structure in prompt', async () => {
176
+ mockLLM.getJSON.mockResolvedValue({});
177
+
178
+ await jsonOps.transform({ data: 'test' }, 'Convert to array format');
179
+
180
+ const call = mockLLM.getJSON.mock.calls[0];
181
+ expect(call[0]).toContain('Convert to array format');
182
+ });
183
+ });
184
+
185
+ describe('simplify', () => {
186
+ it('should simplify nested object structure', async () => {
187
+ mockLLM.getJSON.mockResolvedValue({
188
+ name: 'Simple Name',
189
+ value: 42,
190
+ tags: ['a', 'b']
191
+ });
192
+
193
+ const obj = {
194
+ nested: {
195
+ deeply: {
196
+ name: 'Simple Name'
197
+ }
198
+ },
199
+ meta: {
200
+ data: {
201
+ value: 42
202
+ }
203
+ },
204
+ emptyField: null,
205
+ tags: ['a', 'b', '']
206
+ };
207
+
208
+ const result = await jsonOps.simplify(obj);
209
+
210
+ expect(result.name).toBe('Simple Name');
211
+ expect(result.emptyField).toBeUndefined();
212
+ });
213
+
214
+ it('should keep empty values when option set', async () => {
215
+ mockLLM.getJSON.mockResolvedValue({ emptyField: null });
216
+
217
+ await jsonOps.simplify({ emptyField: null }, { keepEmpty: true });
218
+
219
+ const call = mockLLM.getJSON.mock.calls[0];
220
+ expect(call[0]).toContain('unless specified to keep');
221
+ });
222
+ });
223
+
224
+ describe('apply', () => {
225
+ it('should apply natural language operation to object', async () => {
226
+ mockLLM.getJSON.mockResolvedValue({
227
+ title: 'Uppercase Title',
228
+ status: 'ACTIVE'
229
+ });
230
+
231
+ const obj = { title: 'uppercase title', status: 'active' };
232
+ const result = await jsonOps.apply(obj, 'Convert all string values to uppercase');
233
+
234
+ expect(result.title).toBe('Uppercase Title');
235
+ expect(result.status).toBe('ACTIVE');
236
+ });
237
+
238
+ it('should include operation in prompt', async () => {
239
+ mockLLM.getJSON.mockResolvedValue({});
240
+
241
+ await jsonOps.apply({ data: 1 }, 'Double all numbers');
242
+
243
+ const call = mockLLM.getJSON.mock.calls[0];
244
+ expect(call[0]).toContain('Double all numbers');
245
+ });
246
+
247
+ it('should handle complex operations', async () => {
248
+ mockLLM.getJSON.mockResolvedValue({
249
+ items: [
250
+ { name: 'Item 1', active: true },
251
+ { name: 'Item 2', active: true }
252
+ ]
253
+ });
254
+
255
+ const obj = {
256
+ items: [
257
+ { name: 'Item 1', active: false },
258
+ { name: 'Item 2', active: false }
259
+ ]
260
+ };
261
+
262
+ const result = await jsonOps.apply(obj, 'Set all items to active');
263
+
264
+ expect(result.items[0].active).toBe(true);
265
+ expect(result.items[1].active).toBe(true);
266
+ });
267
+ });
268
+
269
+ describe('Integration scenarios', () => {
270
+ it('should chain operations', async () => {
271
+ // First merge
272
+ mockLLM.getJSON.mockResolvedValueOnce({
273
+ name: 'Merged',
274
+ tags: ['a', 'b', 'c']
275
+ });
276
+
277
+ const merged = await jsonOps.add(
278
+ { name: 'First', tags: ['a'] },
279
+ { name: 'Second', tags: ['b', 'c'] }
280
+ );
281
+
282
+ // Then simplify
283
+ mockLLM.getJSON.mockResolvedValueOnce({
284
+ name: 'Merged',
285
+ tagCount: 3
286
+ });
287
+
288
+ const simplified = await jsonOps.transform(merged, 'Replace tags array with tagCount');
289
+
290
+ expect(simplified.tagCount).toBe(3);
291
+ });
292
+
293
+ it('should handle empty objects', async () => {
294
+ mockLLM.getJSON.mockResolvedValue({});
295
+
296
+ const result = await jsonOps.add({}, {});
297
+ expect(result).toEqual({});
298
+ });
299
+
300
+ it('should handle arrays as root', async () => {
301
+ mockLLM.getJSON.mockResolvedValue([1, 2, 3, 4, 5]);
302
+
303
+ const result = await jsonOps.union([1, 2, 3], [3, 4, 5]);
304
+ expect(result).toEqual([1, 2, 3, 4, 5]);
305
+ });
306
+ });
307
+ });