holosphere 2.0.0-alpha1 → 2.0.0-alpha4

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 (154) hide show
  1. package/dist/2019-D2OG2idw.js +6680 -0
  2. package/dist/2019-D2OG2idw.js.map +1 -0
  3. package/dist/2019-EION3wKo.cjs +8 -0
  4. package/dist/2019-EION3wKo.cjs.map +1 -0
  5. package/dist/_commonjsHelpers-C37NGDzP.cjs +2 -0
  6. package/dist/_commonjsHelpers-C37NGDzP.cjs.map +1 -0
  7. package/dist/_commonjsHelpers-CUmg6egw.js +7 -0
  8. package/dist/_commonjsHelpers-CUmg6egw.js.map +1 -0
  9. package/dist/browser-BSniCNqO.js +3058 -0
  10. package/dist/browser-BSniCNqO.js.map +1 -0
  11. package/dist/browser-Cq59Ij19.cjs +2 -0
  12. package/dist/browser-Cq59Ij19.cjs.map +1 -0
  13. package/dist/cjs/holosphere.cjs +2 -0
  14. package/dist/cjs/holosphere.cjs.map +1 -0
  15. package/dist/esm/holosphere.js +53 -0
  16. package/dist/esm/holosphere.js.map +1 -0
  17. package/dist/index-BB_vVJgv.cjs +5 -0
  18. package/dist/index-BB_vVJgv.cjs.map +1 -0
  19. package/dist/index-CBitK71M.cjs +12 -0
  20. package/dist/index-CBitK71M.cjs.map +1 -0
  21. package/dist/index-CV0eOogK.js +37423 -0
  22. package/dist/index-CV0eOogK.js.map +1 -0
  23. package/dist/index-Cz-PLCUR.js +15104 -0
  24. package/dist/index-Cz-PLCUR.js.map +1 -0
  25. package/dist/indexeddb-storage-CRsZyB2f.cjs +2 -0
  26. package/dist/indexeddb-storage-CRsZyB2f.cjs.map +1 -0
  27. package/dist/indexeddb-storage-DZaGlY_a.js +132 -0
  28. package/dist/indexeddb-storage-DZaGlY_a.js.map +1 -0
  29. package/dist/memory-storage-BkUi6sZG.js +51 -0
  30. package/dist/memory-storage-BkUi6sZG.js.map +1 -0
  31. package/dist/memory-storage-C0DuUsdY.cjs +2 -0
  32. package/dist/memory-storage-C0DuUsdY.cjs.map +1 -0
  33. package/dist/secp256k1-0kPdAVkK.cjs +12 -0
  34. package/dist/secp256k1-0kPdAVkK.cjs.map +1 -0
  35. package/dist/secp256k1-DN4FVXcv.js +1890 -0
  36. package/dist/secp256k1-DN4FVXcv.js.map +1 -0
  37. package/docs/CONTRACTS.md +797 -0
  38. package/docs/FOSDEM_PROPOSAL.md +388 -0
  39. package/docs/LOCALFIRST.md +266 -0
  40. package/docs/contracts/api-interface.md +793 -0
  41. package/docs/data-model.md +476 -0
  42. package/docs/gun-async-usage.md +338 -0
  43. package/docs/plan.md +349 -0
  44. package/docs/quickstart.md +674 -0
  45. package/docs/research.md +362 -0
  46. package/docs/spec.md +244 -0
  47. package/docs/storage-backends.md +326 -0
  48. package/docs/tasks.md +947 -0
  49. package/examples/demo.html +47 -0
  50. package/package.json +10 -5
  51. package/src/contracts/abis/Appreciative.json +1280 -0
  52. package/src/contracts/abis/AppreciativeFactory.json +101 -0
  53. package/src/contracts/abis/Bundle.json +1435 -0
  54. package/src/contracts/abis/BundleFactory.json +106 -0
  55. package/src/contracts/abis/Holon.json +881 -0
  56. package/src/contracts/abis/Holons.json +330 -0
  57. package/src/contracts/abis/Managed.json +1262 -0
  58. package/src/contracts/abis/ManagedFactory.json +149 -0
  59. package/src/contracts/abis/Membrane.json +261 -0
  60. package/src/contracts/abis/Splitter.json +1624 -0
  61. package/src/contracts/abis/SplitterFactory.json +220 -0
  62. package/src/contracts/abis/TestToken.json +321 -0
  63. package/src/contracts/abis/Zoned.json +1461 -0
  64. package/src/contracts/abis/ZonedFactory.json +154 -0
  65. package/src/contracts/chain-manager.js +375 -0
  66. package/src/contracts/deployer.js +443 -0
  67. package/src/contracts/event-listener.js +507 -0
  68. package/src/contracts/holon-contracts.js +344 -0
  69. package/src/contracts/index.js +83 -0
  70. package/src/contracts/networks.js +224 -0
  71. package/src/contracts/operations.js +670 -0
  72. package/src/contracts/queries.js +589 -0
  73. package/src/core/holosphere.js +453 -1
  74. package/src/crypto/nostr-utils.js +263 -0
  75. package/src/federation/handshake.js +455 -0
  76. package/src/federation/hologram.js +1 -1
  77. package/src/hierarchical/upcast.js +6 -5
  78. package/src/index.js +463 -1939
  79. package/src/lib/ai-methods.js +308 -0
  80. package/src/lib/contract-methods.js +293 -0
  81. package/src/lib/errors.js +23 -0
  82. package/src/lib/federation-methods.js +238 -0
  83. package/src/lib/index.js +26 -0
  84. package/src/spatial/h3-operations.js +2 -2
  85. package/src/storage/backends/gundb-backend.js +377 -46
  86. package/src/storage/global-tables.js +28 -1
  87. package/src/storage/gun-auth.js +303 -0
  88. package/src/storage/gun-federation.js +776 -0
  89. package/src/storage/gun-references.js +198 -0
  90. package/src/storage/gun-schema.js +291 -0
  91. package/src/storage/gun-wrapper.js +347 -31
  92. package/src/storage/indexeddb-storage.js +49 -11
  93. package/src/storage/memory-storage.js +5 -0
  94. package/src/storage/nostr-async.js +45 -23
  95. package/src/storage/nostr-client.js +11 -5
  96. package/src/storage/persistent-storage.js +6 -1
  97. package/src/storage/unified-storage.js +119 -0
  98. package/src/subscriptions/manager.js +1 -1
  99. package/types/index.d.ts +133 -0
  100. package/tests/unit/ai/aggregation.test.js +0 -295
  101. package/tests/unit/ai/breakdown.test.js +0 -446
  102. package/tests/unit/ai/classifier.test.js +0 -294
  103. package/tests/unit/ai/council.test.js +0 -262
  104. package/tests/unit/ai/embeddings.test.js +0 -384
  105. package/tests/unit/ai/federation-ai.test.js +0 -344
  106. package/tests/unit/ai/h3-ai.test.js +0 -458
  107. package/tests/unit/ai/index.test.js +0 -304
  108. package/tests/unit/ai/json-ops.test.js +0 -307
  109. package/tests/unit/ai/llm-service.test.js +0 -390
  110. package/tests/unit/ai/nl-query.test.js +0 -383
  111. package/tests/unit/ai/relationships.test.js +0 -311
  112. package/tests/unit/ai/schema-extractor.test.js +0 -384
  113. package/tests/unit/ai/spatial.test.js +0 -279
  114. package/tests/unit/ai/tts.test.js +0 -279
  115. package/tests/unit/content.test.js +0 -332
  116. package/tests/unit/contract/core.test.js +0 -88
  117. package/tests/unit/contract/crypto.test.js +0 -198
  118. package/tests/unit/contract/data.test.js +0 -223
  119. package/tests/unit/contract/federation.test.js +0 -181
  120. package/tests/unit/contract/hierarchical.test.js +0 -113
  121. package/tests/unit/contract/schema.test.js +0 -114
  122. package/tests/unit/contract/social.test.js +0 -217
  123. package/tests/unit/contract/spatial.test.js +0 -110
  124. package/tests/unit/contract/subscriptions.test.js +0 -128
  125. package/tests/unit/contract/utils.test.js +0 -159
  126. package/tests/unit/core.test.js +0 -152
  127. package/tests/unit/crypto.test.js +0 -328
  128. package/tests/unit/federation.test.js +0 -234
  129. package/tests/unit/gun-async.test.js +0 -252
  130. package/tests/unit/hierarchical.test.js +0 -399
  131. package/tests/unit/integration/scenario-01-geographic-storage.test.js +0 -74
  132. package/tests/unit/integration/scenario-02-federation.test.js +0 -76
  133. package/tests/unit/integration/scenario-03-subscriptions.test.js +0 -102
  134. package/tests/unit/integration/scenario-04-validation.test.js +0 -129
  135. package/tests/unit/integration/scenario-05-hierarchy.test.js +0 -125
  136. package/tests/unit/integration/scenario-06-social.test.js +0 -135
  137. package/tests/unit/integration/scenario-07-persistence.test.js +0 -130
  138. package/tests/unit/integration/scenario-08-authorization.test.js +0 -161
  139. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +0 -139
  140. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +0 -357
  141. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +0 -410
  142. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +0 -719
  143. package/tests/unit/performance/benchmark.test.js +0 -85
  144. package/tests/unit/schema.test.js +0 -213
  145. package/tests/unit/spatial.test.js +0 -158
  146. package/tests/unit/storage.test.js +0 -195
  147. package/tests/unit/subscriptions.test.js +0 -328
  148. package/tests/unit/test-data-permanence-debug.js +0 -197
  149. package/tests/unit/test-data-permanence.js +0 -340
  150. package/tests/unit/test-key-persistence-fixed.js +0 -148
  151. package/tests/unit/test-key-persistence.js +0 -172
  152. package/tests/unit/test-relay-permanence.js +0 -376
  153. package/tests/unit/test-second-node.js +0 -95
  154. package/tests/unit/test-simple-write.js +0 -89
@@ -1,390 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { LLMService } from '../../../src/ai/llm-service.js';
3
-
4
- // Mock OpenAI
5
- vi.mock('openai', () => ({
6
- default: vi.fn().mockImplementation(() => ({
7
- chat: {
8
- completions: {
9
- create: vi.fn()
10
- }
11
- }
12
- }))
13
- }));
14
-
15
- describe('Unit: LLMService', () => {
16
- let llm;
17
- let mockOpenAI;
18
-
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
- llm = new LLMService('test-api-key');
22
- mockOpenAI = llm.openai;
23
- });
24
-
25
- describe('Constructor', () => {
26
- it('should throw error if API key is not provided', () => {
27
- expect(() => new LLMService()).toThrow('OpenAI API key is required');
28
- expect(() => new LLMService('')).toThrow('OpenAI API key is required');
29
- expect(() => new LLMService(null)).toThrow('OpenAI API key is required');
30
- });
31
-
32
- it('should initialize with default options', () => {
33
- const service = new LLMService('test-key');
34
- expect(service.model).toBe('gpt-4o-mini');
35
- expect(service.maxTokens).toBe(2000);
36
- expect(service.temperature).toBe(0.7);
37
- });
38
-
39
- it('should accept custom options', () => {
40
- const service = new LLMService('test-key', {
41
- model: 'gpt-4',
42
- maxTokens: 1000,
43
- temperature: 0.5
44
- });
45
- expect(service.model).toBe('gpt-4');
46
- expect(service.maxTokens).toBe(1000);
47
- expect(service.temperature).toBe(0.5);
48
- });
49
- });
50
-
51
- describe('sendMessage', () => {
52
- it('should send message with system and user prompts', async () => {
53
- mockOpenAI.chat.completions.create.mockResolvedValue({
54
- choices: [{ message: { content: 'Test response' } }]
55
- });
56
-
57
- const result = await llm.sendMessage('You are helpful', 'Hello');
58
-
59
- expect(mockOpenAI.chat.completions.create).toHaveBeenCalledWith({
60
- model: 'gpt-4o-mini',
61
- messages: [
62
- { role: 'system', content: 'You are helpful' },
63
- { role: 'user', content: 'Hello' }
64
- ],
65
- max_tokens: 2000,
66
- temperature: 0.7
67
- });
68
- expect(result).toBe('Test response');
69
- });
70
-
71
- it('should allow option overrides', async () => {
72
- mockOpenAI.chat.completions.create.mockResolvedValue({
73
- choices: [{ message: { content: 'Response' } }]
74
- });
75
-
76
- await llm.sendMessage('System', 'User', {
77
- model: 'gpt-4',
78
- maxTokens: 500,
79
- temperature: 0.3
80
- });
81
-
82
- expect(mockOpenAI.chat.completions.create).toHaveBeenCalledWith(
83
- expect.objectContaining({
84
- model: 'gpt-4',
85
- max_tokens: 500,
86
- temperature: 0.3
87
- })
88
- );
89
- });
90
-
91
- it('should handle empty response', async () => {
92
- mockOpenAI.chat.completions.create.mockResolvedValue({
93
- choices: [{ message: { content: null } }]
94
- });
95
-
96
- const result = await llm.sendMessage('System', 'User');
97
- expect(result).toBe('');
98
- });
99
-
100
- it('should throw error on API failure', async () => {
101
- mockOpenAI.chat.completions.create.mockRejectedValue(new Error('API Error'));
102
-
103
- await expect(llm.sendMessage('System', 'User'))
104
- .rejects.toThrow('LLM request failed: API Error');
105
- });
106
-
107
- it('should trim whitespace from response', async () => {
108
- mockOpenAI.chat.completions.create.mockResolvedValue({
109
- choices: [{ message: { content: ' Response with spaces ' } }]
110
- });
111
-
112
- const result = await llm.sendMessage('System', 'User');
113
- expect(result).toBe('Response with spaces');
114
- });
115
- });
116
-
117
- describe('chat', () => {
118
- it('should send conversation messages', async () => {
119
- mockOpenAI.chat.completions.create.mockResolvedValue({
120
- choices: [{ message: { content: 'Chat response' } }]
121
- });
122
-
123
- const messages = [
124
- { role: 'system', content: 'You are helpful' },
125
- { role: 'user', content: 'Hello' },
126
- { role: 'assistant', content: 'Hi there!' },
127
- { role: 'user', content: 'How are you?' }
128
- ];
129
-
130
- const result = await llm.chat(messages);
131
-
132
- expect(mockOpenAI.chat.completions.create).toHaveBeenCalledWith(
133
- expect.objectContaining({ messages })
134
- );
135
- expect(result).toBe('Chat response');
136
- });
137
-
138
- it('should throw error on chat failure', async () => {
139
- mockOpenAI.chat.completions.create.mockRejectedValue(new Error('Chat failed'));
140
-
141
- await expect(llm.chat([]))
142
- .rejects.toThrow('LLM chat failed: Chat failed');
143
- });
144
- });
145
-
146
- describe('summarize', () => {
147
- it('should summarize text', async () => {
148
- mockOpenAI.chat.completions.create.mockResolvedValue({
149
- choices: [{ message: { content: 'Summary of text' } }]
150
- });
151
-
152
- const result = await llm.summarize('Long text to summarize');
153
-
154
- expect(mockOpenAI.chat.completions.create).toHaveBeenCalledWith(
155
- expect.objectContaining({
156
- temperature: 0.5
157
- })
158
- );
159
- expect(result).toBe('Summary of text');
160
- });
161
-
162
- it('should include maxLength in prompt when specified', async () => {
163
- mockOpenAI.chat.completions.create.mockResolvedValue({
164
- choices: [{ message: { content: 'Short summary' } }]
165
- });
166
-
167
- await llm.summarize('Text', { maxLength: 50 });
168
-
169
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
170
- expect(call.messages[0].content).toContain('50 words');
171
- });
172
-
173
- it('should include style in prompt when specified', async () => {
174
- mockOpenAI.chat.completions.create.mockResolvedValue({
175
- choices: [{ message: { content: 'Bullet summary' } }]
176
- });
177
-
178
- await llm.summarize('Text', { style: 'bullet points' });
179
-
180
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
181
- expect(call.messages[0].content).toContain('bullet points');
182
- });
183
- });
184
-
185
- describe('analyze', () => {
186
- it('should analyze text from specified perspective', async () => {
187
- mockOpenAI.chat.completions.create.mockResolvedValue({
188
- choices: [{ message: { content: 'Sentiment analysis result' } }]
189
- });
190
-
191
- const result = await llm.analyze('Happy text', 'sentiment');
192
-
193
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
194
- expect(call.messages[0].content).toContain('sentiment');
195
- expect(call.temperature).toBe(0.3);
196
- expect(result).toBe('Sentiment analysis result');
197
- });
198
- });
199
-
200
- describe('extractKeywords', () => {
201
- it('should extract keywords as JSON array', async () => {
202
- mockOpenAI.chat.completions.create.mockResolvedValue({
203
- choices: [{ message: { content: '["keyword1", "keyword2", "keyword3"]' } }]
204
- });
205
-
206
- const result = await llm.extractKeywords('Some text about keywords');
207
-
208
- expect(result).toEqual(['keyword1', 'keyword2', 'keyword3']);
209
- });
210
-
211
- it('should handle non-JSON response by extracting array', async () => {
212
- mockOpenAI.chat.completions.create.mockResolvedValue({
213
- choices: [{ message: { content: 'Here are keywords: ["a", "b"]' } }]
214
- });
215
-
216
- const result = await llm.extractKeywords('Text');
217
- expect(result).toEqual(['a', 'b']);
218
- });
219
-
220
- it('should fall back to comma splitting if JSON fails', async () => {
221
- mockOpenAI.chat.completions.create.mockResolvedValue({
222
- choices: [{ message: { content: 'keyword1, keyword2, keyword3' } }]
223
- });
224
-
225
- const result = await llm.extractKeywords('Text');
226
- expect(result).toEqual(['keyword1', 'keyword2', 'keyword3']);
227
- });
228
-
229
- it('should respect maxKeywords parameter', async () => {
230
- mockOpenAI.chat.completions.create.mockResolvedValue({
231
- choices: [{ message: { content: '["a", "b", "c"]' } }]
232
- });
233
-
234
- await llm.extractKeywords('Text', 5);
235
-
236
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
237
- expect(call.messages[0].content).toContain('5');
238
- });
239
- });
240
-
241
- describe('categorize', () => {
242
- it('should categorize text into one of the categories', async () => {
243
- mockOpenAI.chat.completions.create.mockResolvedValue({
244
- choices: [{ message: { content: '{"category": "sports", "confidence": 0.9, "reasoning": "Text is about sports"}' } }]
245
- });
246
-
247
- const result = await llm.categorize('Football game', ['sports', 'politics', 'tech']);
248
-
249
- expect(result).toEqual({
250
- category: 'sports',
251
- confidence: 0.9,
252
- reasoning: 'Text is about sports'
253
- });
254
- });
255
-
256
- it('should handle non-JSON response', async () => {
257
- mockOpenAI.chat.completions.create.mockResolvedValue({
258
- choices: [{ message: { content: 'The category is {"category": "tech", "confidence": 0.8, "reasoning": "about tech"}' } }]
259
- });
260
-
261
- const result = await llm.categorize('AI text', ['tech']);
262
- expect(result.category).toBe('tech');
263
- });
264
-
265
- it('should fall back to first category on parse failure', async () => {
266
- mockOpenAI.chat.completions.create.mockResolvedValue({
267
- choices: [{ message: { content: 'Unable to parse' } }]
268
- });
269
-
270
- const result = await llm.categorize('Text', ['first', 'second']);
271
- expect(result.category).toBe('first');
272
- expect(result.confidence).toBe(0.5);
273
- });
274
- });
275
-
276
- describe('translate', () => {
277
- it('should translate text to target language', async () => {
278
- mockOpenAI.chat.completions.create.mockResolvedValue({
279
- choices: [{ message: { content: 'Bonjour le monde' } }]
280
- });
281
-
282
- const result = await llm.translate('Hello world', 'French');
283
-
284
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
285
- expect(call.messages[0].content).toContain('French');
286
- expect(result).toBe('Bonjour le monde');
287
- });
288
- });
289
-
290
- describe('generateQuestions', () => {
291
- it('should generate questions as JSON array', async () => {
292
- mockOpenAI.chat.completions.create.mockResolvedValue({
293
- choices: [{ message: { content: '["Question 1?", "Question 2?"]' } }]
294
- });
295
-
296
- const result = await llm.generateQuestions('Some content');
297
- expect(result).toEqual(['Question 1?', 'Question 2?']);
298
- });
299
-
300
- it('should handle non-JSON by extracting array', async () => {
301
- mockOpenAI.chat.completions.create.mockResolvedValue({
302
- choices: [{ message: { content: 'Questions: ["Q1?", "Q2?"]' } }]
303
- });
304
-
305
- const result = await llm.generateQuestions('Content');
306
- expect(result).toEqual(['Q1?', 'Q2?']);
307
- });
308
-
309
- it('should fall back to line splitting for questions', async () => {
310
- mockOpenAI.chat.completions.create.mockResolvedValue({
311
- choices: [{ message: { content: 'What is this?\nWhy does it matter?' } }]
312
- });
313
-
314
- const result = await llm.generateQuestions('Content');
315
- expect(result).toContain('What is this?');
316
- expect(result).toContain('Why does it matter?');
317
- });
318
-
319
- it('should respect count parameter', async () => {
320
- mockOpenAI.chat.completions.create.mockResolvedValue({
321
- choices: [{ message: { content: '["Q1?", "Q2?", "Q3?"]' } }]
322
- });
323
-
324
- await llm.generateQuestions('Content', 3);
325
-
326
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
327
- expect(call.messages[0].content).toContain('3');
328
- });
329
- });
330
-
331
- describe('getJSON', () => {
332
- it('should return parsed JSON from response', async () => {
333
- mockOpenAI.chat.completions.create.mockResolvedValue({
334
- choices: [{ message: { content: '{"key": "value", "number": 42}' } }]
335
- });
336
-
337
- const result = await llm.getJSON('System prompt', 'User message');
338
- expect(result).toEqual({ key: 'value', number: 42 });
339
- });
340
-
341
- it('should extract JSON from non-pure JSON response', async () => {
342
- mockOpenAI.chat.completions.create.mockResolvedValue({
343
- choices: [{ message: { content: 'Here is the result: {"data": [1, 2, 3]}' } }]
344
- });
345
-
346
- const result = await llm.getJSON('System', 'User');
347
- expect(result).toEqual({ data: [1, 2, 3] });
348
- });
349
-
350
- it('should extract JSON array from response', async () => {
351
- mockOpenAI.chat.completions.create.mockResolvedValue({
352
- choices: [{ message: { content: 'Result: [1, 2, 3]' } }]
353
- });
354
-
355
- const result = await llm.getJSON('System', 'User');
356
- expect(result).toEqual([1, 2, 3]);
357
- });
358
-
359
- it('should throw error on invalid JSON', async () => {
360
- mockOpenAI.chat.completions.create.mockResolvedValue({
361
- choices: [{ message: { content: 'Not valid JSON at all' } }]
362
- });
363
-
364
- await expect(llm.getJSON('System', 'User'))
365
- .rejects.toThrow('Failed to parse JSON response');
366
- });
367
-
368
- it('should use low temperature by default', async () => {
369
- mockOpenAI.chat.completions.create.mockResolvedValue({
370
- choices: [{ message: { content: '{}' } }]
371
- });
372
-
373
- await llm.getJSON('System', 'User');
374
-
375
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
376
- expect(call.temperature).toBe(0.2);
377
- });
378
-
379
- it('should append JSON instruction to system prompt', async () => {
380
- mockOpenAI.chat.completions.create.mockResolvedValue({
381
- choices: [{ message: { content: '{}' } }]
382
- });
383
-
384
- await llm.getJSON('Original prompt', 'User');
385
-
386
- const call = mockOpenAI.chat.completions.create.mock.calls[0][0];
387
- expect(call.messages[0].content).toContain('Return ONLY valid JSON');
388
- });
389
- });
390
- });