@ram_28/kf-ai-sdk 1.0.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 (126) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +840 -0
  3. package/dist/api/client.d.ts +78 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/datetime.d.ts +21 -0
  6. package/dist/api/datetime.d.ts.map +1 -0
  7. package/dist/api/index.d.ts +7 -0
  8. package/dist/api/index.d.ts.map +1 -0
  9. package/dist/api/metadata.d.ts +75 -0
  10. package/dist/api/metadata.d.ts.map +1 -0
  11. package/dist/components/hooks/index.d.ts +8 -0
  12. package/dist/components/hooks/index.d.ts.map +1 -0
  13. package/dist/components/hooks/useFilter/index.d.ts +5 -0
  14. package/dist/components/hooks/useFilter/index.d.ts.map +1 -0
  15. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +33 -0
  16. package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +1 -0
  17. package/dist/components/hooks/useFilter/types.d.ts +137 -0
  18. package/dist/components/hooks/useFilter/types.d.ts.map +1 -0
  19. package/dist/components/hooks/useFilter/useFilter.d.ts +3 -0
  20. package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -0
  21. package/dist/components/hooks/useFilter/validation.utils.d.ts +38 -0
  22. package/dist/components/hooks/useFilter/validation.utils.d.ts.map +1 -0
  23. package/dist/components/hooks/useForm/apiClient.d.ts +71 -0
  24. package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -0
  25. package/dist/components/hooks/useForm/expressionValidator.utils.d.ts +28 -0
  26. package/dist/components/hooks/useForm/expressionValidator.utils.d.ts.map +1 -0
  27. package/dist/components/hooks/useForm/index.d.ts +6 -0
  28. package/dist/components/hooks/useForm/index.d.ts.map +1 -0
  29. package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts +88 -0
  30. package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts.map +1 -0
  31. package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts +28 -0
  32. package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts.map +1 -0
  33. package/dist/components/hooks/useForm/schemaParser.utils.d.ts +29 -0
  34. package/dist/components/hooks/useForm/schemaParser.utils.d.ts.map +1 -0
  35. package/dist/components/hooks/useForm/types.d.ts +412 -0
  36. package/dist/components/hooks/useForm/types.d.ts.map +1 -0
  37. package/dist/components/hooks/useForm/useForm.d.ts +3 -0
  38. package/dist/components/hooks/useForm/useForm.d.ts.map +1 -0
  39. package/dist/components/hooks/useKanban/apiClient.d.ts +99 -0
  40. package/dist/components/hooks/useKanban/apiClient.d.ts.map +1 -0
  41. package/dist/components/hooks/useKanban/context.d.ts +4 -0
  42. package/dist/components/hooks/useKanban/context.d.ts.map +1 -0
  43. package/dist/components/hooks/useKanban/dragDropManager.d.ts +27 -0
  44. package/dist/components/hooks/useKanban/dragDropManager.d.ts.map +1 -0
  45. package/dist/components/hooks/useKanban/index.d.ts +6 -0
  46. package/dist/components/hooks/useKanban/index.d.ts.map +1 -0
  47. package/dist/components/hooks/useKanban/types.d.ts +438 -0
  48. package/dist/components/hooks/useKanban/types.d.ts.map +1 -0
  49. package/dist/components/hooks/useKanban/useKanban.d.ts +3 -0
  50. package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -0
  51. package/dist/components/hooks/useKanban/useKanbanSimple.d.ts +62 -0
  52. package/dist/components/hooks/useKanban/useKanbanSimple.d.ts.map +1 -0
  53. package/dist/components/hooks/useTable/index.d.ts +3 -0
  54. package/dist/components/hooks/useTable/index.d.ts.map +1 -0
  55. package/dist/components/hooks/useTable/types.d.ts +107 -0
  56. package/dist/components/hooks/useTable/types.d.ts.map +1 -0
  57. package/dist/components/hooks/useTable/useTable.d.ts +8 -0
  58. package/dist/components/hooks/useTable/useTable.d.ts.map +1 -0
  59. package/dist/components/index.d.ts +3 -0
  60. package/dist/components/index.d.ts.map +1 -0
  61. package/dist/components/ui/index.d.ts +2 -0
  62. package/dist/components/ui/index.d.ts.map +1 -0
  63. package/dist/components/ui/kanban/Kanban.d.ts +12 -0
  64. package/dist/components/ui/kanban/Kanban.d.ts.map +1 -0
  65. package/dist/components/ui/kanban/index.d.ts +2 -0
  66. package/dist/components/ui/kanban/index.d.ts.map +1 -0
  67. package/dist/index.cjs +45 -0
  68. package/dist/index.d.ts +5 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.mjs +6522 -0
  71. package/dist/types/base-fields.d.ts +182 -0
  72. package/dist/types/base-fields.d.ts.map +1 -0
  73. package/dist/types/common.d.ts +238 -0
  74. package/dist/types/common.d.ts.map +1 -0
  75. package/dist/types/index.d.ts +3 -0
  76. package/dist/types/index.d.ts.map +1 -0
  77. package/dist/utils/cn.d.ts +7 -0
  78. package/dist/utils/cn.d.ts.map +1 -0
  79. package/dist/utils/formatting.d.ts +52 -0
  80. package/dist/utils/formatting.d.ts.map +1 -0
  81. package/dist/utils/index.d.ts +3 -0
  82. package/dist/utils/index.d.ts.map +1 -0
  83. package/package.json +98 -0
  84. package/sdk/api/client.ts +447 -0
  85. package/sdk/api/datetime.ts +33 -0
  86. package/sdk/api/index.ts +61 -0
  87. package/sdk/api/metadata.ts +148 -0
  88. package/sdk/components/hooks/index.ts +34 -0
  89. package/sdk/components/hooks/useFilter/index.ts +37 -0
  90. package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +298 -0
  91. package/sdk/components/hooks/useFilter/types.ts +158 -0
  92. package/sdk/components/hooks/useFilter/useFilter.llm.txt +497 -0
  93. package/sdk/components/hooks/useFilter/useFilter.ts +494 -0
  94. package/sdk/components/hooks/useFilter/validation.utils.ts +401 -0
  95. package/sdk/components/hooks/useForm/apiClient.ts +441 -0
  96. package/sdk/components/hooks/useForm/expressionValidator.utils.ts +444 -0
  97. package/sdk/components/hooks/useForm/index.ts +64 -0
  98. package/sdk/components/hooks/useForm/optimizedExpressionValidator.utils.ts +482 -0
  99. package/sdk/components/hooks/useForm/ruleClassifier.utils.ts +424 -0
  100. package/sdk/components/hooks/useForm/schemaParser.utils.ts +519 -0
  101. package/sdk/components/hooks/useForm/types.ts +630 -0
  102. package/sdk/components/hooks/useForm/useForm.llm.txt +340 -0
  103. package/sdk/components/hooks/useForm/useForm.ts +821 -0
  104. package/sdk/components/hooks/useKanban/apiClient.ts +494 -0
  105. package/sdk/components/hooks/useKanban/context.ts +14 -0
  106. package/sdk/components/hooks/useKanban/dragDropManager.ts +529 -0
  107. package/sdk/components/hooks/useKanban/index.ts +63 -0
  108. package/sdk/components/hooks/useKanban/types.ts +606 -0
  109. package/sdk/components/hooks/useKanban/useKanban.llm.txt +482 -0
  110. package/sdk/components/hooks/useKanban/useKanban.ts +725 -0
  111. package/sdk/components/hooks/useKanban/useKanbanSimple.ts +389 -0
  112. package/sdk/components/hooks/useTable/index.ts +5 -0
  113. package/sdk/components/hooks/useTable/types.ts +154 -0
  114. package/sdk/components/hooks/useTable/useTable.llm.txt +344 -0
  115. package/sdk/components/hooks/useTable/useTable.ts +413 -0
  116. package/sdk/components/index.ts +15 -0
  117. package/sdk/components/ui/index.ts +2 -0
  118. package/sdk/components/ui/kanban/Kanban.tsx +134 -0
  119. package/sdk/components/ui/kanban/index.ts +11 -0
  120. package/sdk/index.ts +13 -0
  121. package/sdk/types/base-fields.ts +221 -0
  122. package/sdk/types/common.ts +306 -0
  123. package/sdk/types/index.ts +5 -0
  124. package/sdk/utils/cn.ts +10 -0
  125. package/sdk/utils/formatting.ts +212 -0
  126. package/sdk/utils/index.ts +5 -0
@@ -0,0 +1,494 @@
1
+ // ============================================================
2
+ // KANBAN API CLIENT
3
+ // ============================================================
4
+ // Backend integration for kanban operations using existing API patterns
5
+ // Follows the same structure as useForm and useTable API clients
6
+
7
+ import { api } from "../../../api";
8
+ import type {
9
+ ListOptions,
10
+ ListResponse,
11
+ CreateUpdateResponse,
12
+ DeleteResponse,
13
+ CountResponse
14
+ } from "../../../types/common";
15
+ import type {
16
+ KanbanCard,
17
+ KanbanColumn,
18
+ BulkCardUpdateRequest,
19
+ BulkColumnUpdateRequest
20
+ } from "./types";
21
+
22
+ // ============================================================
23
+ // COLUMN API OPERATIONS
24
+ // ============================================================
25
+
26
+ /**
27
+ * Fetch columns from backend with optional filtering and sorting
28
+ */
29
+ export async function fetchColumns<T>(
30
+ source: string,
31
+ options?: ListOptions
32
+ ): Promise<KanbanColumn<T>[]> {
33
+ const client = api<KanbanColumn<T>>(source);
34
+
35
+ // Build API options for columns query
36
+ const apiOptions: ListOptions = {
37
+ ...options,
38
+ // Default sort by position if no sort specified - using correct API format
39
+ Sort: options?.Sort || [{ position: "ASC" }]
40
+ };
41
+
42
+ const response: ListResponse<KanbanColumn<T>> = await client.list(apiOptions);
43
+
44
+ // Initialize empty cards array for each column
45
+ // Cards will be fetched separately and merged
46
+ return response.Data.map(column => ({
47
+ ...column,
48
+ cards: []
49
+ }));
50
+ }
51
+
52
+ /**
53
+ * Create a new column
54
+ */
55
+ export async function createColumn<T>(
56
+ source: string,
57
+ column: Partial<KanbanColumn<T>>
58
+ ): Promise<string> {
59
+ const client = api<KanbanColumn<T>>(source);
60
+
61
+ // Exclude cards array from creation payload
62
+ const { cards, ...columnData } = column;
63
+
64
+ const response: CreateUpdateResponse = await client.create(columnData);
65
+ return response._id;
66
+ }
67
+
68
+ /**
69
+ * Update an existing column
70
+ */
71
+ export async function updateColumn<T>(
72
+ source: string,
73
+ id: string,
74
+ updates: Partial<KanbanColumn<T>>
75
+ ): Promise<void> {
76
+ const client = api<KanbanColumn<T>>(source);
77
+
78
+ // Exclude cards array from update payload
79
+ const { cards, ...columnUpdates } = updates;
80
+
81
+ await client.update(id, columnUpdates);
82
+ }
83
+
84
+ /**
85
+ * Delete a column
86
+ */
87
+ export async function deleteColumn(
88
+ source: string,
89
+ id: string
90
+ ): Promise<void> {
91
+ const client = api(source);
92
+ await client.delete(id);
93
+ }
94
+
95
+ /**
96
+ * Reorder multiple columns
97
+ */
98
+ export async function reorderColumns<T>(
99
+ source: string,
100
+ columnIds: string[]
101
+ ): Promise<void> {
102
+ const client = api<KanbanColumn<T>>(source);
103
+
104
+ // Update position for each column
105
+ const updates = columnIds.map((id, index) => ({
106
+ columnId: id,
107
+ data: { position: index }
108
+ }));
109
+
110
+ // Perform bulk update
111
+ await Promise.all(
112
+ updates.map(update =>
113
+ client.update(update.columnId, update.data)
114
+ )
115
+ );
116
+ }
117
+
118
+ // ============================================================
119
+ // CARD API OPERATIONS
120
+ // ============================================================
121
+
122
+ /**
123
+ * Fetch cards from backend with optional filtering and sorting
124
+ */
125
+ export async function fetchCards<T>(
126
+ source: string,
127
+ options?: ListOptions
128
+ ): Promise<KanbanCard<T>[]> {
129
+ const client = api<KanbanCard<T>>(source);
130
+
131
+ // Build API options for cards query
132
+ const apiOptions: ListOptions = {
133
+ ...options,
134
+ // Default sort by column and position if no sort specified - using correct API format
135
+ Sort: options?.Sort || [
136
+ { columnId: "ASC" },
137
+ { position: "ASC" }
138
+ ]
139
+ };
140
+
141
+ const response: ListResponse<KanbanCard<T>> = await client.list(apiOptions);
142
+ return response.Data;
143
+ }
144
+
145
+ /**
146
+ * Create a new card
147
+ */
148
+ export async function createCard<T>(
149
+ source: string,
150
+ card: Partial<KanbanCard<T>> & { columnId: string }
151
+ ): Promise<string> {
152
+ const client = api<KanbanCard<T>>(source);
153
+
154
+ const response: CreateUpdateResponse = await client.create(card);
155
+ return response._id;
156
+ }
157
+
158
+ /**
159
+ * Update an existing card
160
+ */
161
+ export async function updateCard<T>(
162
+ source: string,
163
+ id: string,
164
+ updates: Partial<KanbanCard<T>>
165
+ ): Promise<void> {
166
+ const client = api<KanbanCard<T>>(source);
167
+ await client.update(id, updates);
168
+ }
169
+
170
+ /**
171
+ * Delete a card
172
+ */
173
+ export async function deleteCard(
174
+ source: string,
175
+ id: string
176
+ ): Promise<void> {
177
+ const client = api(source);
178
+ await client.delete(id);
179
+ }
180
+
181
+ /**
182
+ * Move a card to a different column or position
183
+ */
184
+ export async function moveCard<T>(
185
+ source: string,
186
+ cardId: string,
187
+ toColumnId: string,
188
+ position?: number
189
+ ): Promise<void> {
190
+ const client = api<KanbanCard<T>>(source);
191
+
192
+ const updates: any = {
193
+ columnId: toColumnId,
194
+ ...(position !== undefined && { position })
195
+ };
196
+
197
+ await client.update(cardId, updates);
198
+ }
199
+
200
+ /**
201
+ * Reorder cards within a column
202
+ */
203
+ export async function reorderCards<T>(
204
+ source: string,
205
+ cardIds: string[],
206
+ columnId: string
207
+ ): Promise<void> {
208
+ const client = api<KanbanCard<T>>(source);
209
+
210
+ // Update position for each card
211
+ const updates = cardIds.map((id, index) => ({
212
+ cardId: id,
213
+ data: { position: index, columnId } as any
214
+ }));
215
+
216
+ // Perform bulk update
217
+ await Promise.all(
218
+ updates.map(update =>
219
+ client.update(update.cardId, update.data)
220
+ )
221
+ );
222
+ }
223
+
224
+ // ============================================================
225
+ // BULK OPERATIONS
226
+ // ============================================================
227
+
228
+ /**
229
+ * Bulk update multiple cards
230
+ */
231
+ export async function bulkUpdateCards<T>(
232
+ source: string,
233
+ request: BulkCardUpdateRequest<T>
234
+ ): Promise<void> {
235
+ const client = api<KanbanCard<T>>(source);
236
+
237
+ await Promise.all(
238
+ request.updates.map(update =>
239
+ client.update(update.cardId, update.data)
240
+ )
241
+ );
242
+ }
243
+
244
+ /**
245
+ * Bulk update multiple columns
246
+ */
247
+ export async function bulkUpdateColumns<T>(
248
+ source: string,
249
+ request: BulkColumnUpdateRequest<T>
250
+ ): Promise<void> {
251
+ const client = api<KanbanColumn<T>>(source);
252
+
253
+ await Promise.all(
254
+ request.updates.map(update =>
255
+ client.update(update.columnId, update.data)
256
+ )
257
+ );
258
+ }
259
+
260
+ /**
261
+ * Move multiple cards to a new column
262
+ */
263
+ export async function bulkMoveCards<T>(
264
+ cardSource: string,
265
+ cardIds: string[],
266
+ toColumnId: string,
267
+ startPosition = 0
268
+ ): Promise<void> {
269
+ const client = api<KanbanCard<T>>(cardSource);
270
+
271
+ const updates = cardIds.map((cardId, index) => ({
272
+ cardId,
273
+ data: {
274
+ columnId: toColumnId,
275
+ position: startPosition + index
276
+ } as any
277
+ }));
278
+
279
+ await Promise.all(
280
+ updates.map(update =>
281
+ client.update(update.cardId, update.data)
282
+ )
283
+ );
284
+ }
285
+
286
+ // ============================================================
287
+ // QUERY HELPERS
288
+ // ============================================================
289
+
290
+ /**
291
+ * Get count of cards in a specific column
292
+ */
293
+ export async function getCardCount(
294
+ source: string,
295
+ columnId: string
296
+ ): Promise<number> {
297
+ const client = api(source);
298
+
299
+ const options: ListOptions = {
300
+ Filter: {
301
+ Operator: "And",
302
+ Condition: [
303
+ {
304
+ Operator: "EQ",
305
+ LHSField: "columnId",
306
+ RHSValue: columnId,
307
+ RHSType: "Constant"
308
+ }
309
+ ]
310
+ }
311
+ };
312
+
313
+ const response: CountResponse = await client.count(options);
314
+ return response.Count;
315
+ }
316
+
317
+ /**
318
+ * Get total count of all cards across columns
319
+ */
320
+ export async function getTotalCardCount(
321
+ source: string,
322
+ options?: ListOptions
323
+ ): Promise<number> {
324
+ const client = api(source);
325
+ const response: CountResponse = await client.count(options);
326
+ return response.Count;
327
+ }
328
+
329
+ /**
330
+ * Search cards across all columns
331
+ */
332
+ export async function searchCards<T>(
333
+ source: string,
334
+ searchQuery: string,
335
+ additionalOptions?: ListOptions
336
+ ): Promise<KanbanCard<T>[]> {
337
+ const client = api<KanbanCard<T>>(source);
338
+
339
+ const options: ListOptions = {
340
+ ...additionalOptions,
341
+ Search: searchQuery,
342
+ Sort: additionalOptions?.Sort || [
343
+ { columnId: "ASC" },
344
+ { position: "ASC" }
345
+ ]
346
+ };
347
+
348
+ const response: ListResponse<KanbanCard<T>> = await client.list(options);
349
+ return response.Data;
350
+ }
351
+
352
+ // ============================================================
353
+ // DATA PROCESSING HELPERS
354
+ // ============================================================
355
+
356
+ /**
357
+ * Merge cards into their respective columns
358
+ */
359
+ export function mergeCardsIntoColumns<T>(
360
+ columns: KanbanColumn<T>[],
361
+ cards: KanbanCard<T>[]
362
+ ): KanbanColumn<T>[] {
363
+ // Group cards by columnId
364
+ const cardsByColumn = cards.reduce((acc, card) => {
365
+ if (!acc[card.columnId]) {
366
+ acc[card.columnId] = [];
367
+ }
368
+ acc[card.columnId].push(card);
369
+ return acc;
370
+ }, {} as Record<string, KanbanCard<T>[]>);
371
+
372
+ // Assign cards to their respective columns
373
+ return columns.map(column => ({
374
+ ...column,
375
+ cards: (cardsByColumn[column._id] || [])
376
+ .sort((a, b) => a.position - b.position)
377
+ }));
378
+ }
379
+
380
+ /**
381
+ * Calculate optimal position for inserting a new card
382
+ */
383
+ export function calculateCardPosition<T>(
384
+ column: KanbanColumn<T>,
385
+ insertIndex?: number
386
+ ): number {
387
+ if (column.cards.length === 0) {
388
+ return 0;
389
+ }
390
+
391
+ if (insertIndex === undefined || insertIndex >= column.cards.length) {
392
+ // Insert at end
393
+ const lastCard = column.cards[column.cards.length - 1];
394
+ return lastCard.position + 1;
395
+ }
396
+
397
+ if (insertIndex === 0) {
398
+ // Insert at beginning
399
+ const firstCard = column.cards[0];
400
+ return Math.max(0, firstCard.position - 1);
401
+ }
402
+
403
+ // Insert between cards
404
+ const prevCard = column.cards[insertIndex - 1];
405
+ const nextCard = column.cards[insertIndex];
406
+ return Math.floor((prevCard.position + nextCard.position) / 2);
407
+ }
408
+
409
+ /**
410
+ * Calculate optimal position for inserting a new column
411
+ */
412
+ export function calculateColumnPosition<T>(
413
+ columns: KanbanColumn<T>[],
414
+ insertIndex?: number
415
+ ): number {
416
+ if (columns.length === 0) {
417
+ return 0;
418
+ }
419
+
420
+ if (insertIndex === undefined || insertIndex >= columns.length) {
421
+ // Insert at end
422
+ const lastColumn = columns[columns.length - 1];
423
+ return lastColumn.position + 1;
424
+ }
425
+
426
+ if (insertIndex === 0) {
427
+ // Insert at beginning
428
+ const firstColumn = columns[0];
429
+ return Math.max(0, firstColumn.position - 1);
430
+ }
431
+
432
+ // Insert between columns
433
+ const prevColumn = columns[insertIndex - 1];
434
+ const nextColumn = columns[insertIndex];
435
+ return Math.floor((prevColumn.position + nextColumn.position) / 2);
436
+ }
437
+
438
+ /**
439
+ * Recalculate positions to ensure proper ordering
440
+ */
441
+ export function normalizePositions<T extends { position: number }>(
442
+ items: T[]
443
+ ): T[] {
444
+ return items
445
+ .sort((a, b) => a.position - b.position)
446
+ .map((item, index) => ({
447
+ ...item,
448
+ position: index
449
+ }));
450
+ }
451
+
452
+ // ============================================================
453
+ // ERROR HANDLING
454
+ // ============================================================
455
+
456
+ /**
457
+ * Handle API errors with context
458
+ */
459
+ export function handleKanbanApiError(
460
+ error: unknown,
461
+ operation: string,
462
+ entityType: 'card' | 'column',
463
+ entityId?: string
464
+ ): Error {
465
+ const baseMessage = `Failed to ${operation} ${entityType}`;
466
+ const context = entityId ? ` (ID: ${entityId})` : '';
467
+
468
+ if (error instanceof Error) {
469
+ return new Error(`${baseMessage}${context}: ${error.message}`);
470
+ }
471
+
472
+ return new Error(`${baseMessage}${context}: Unknown error`);
473
+ }
474
+
475
+ /**
476
+ * Validate API response structure
477
+ */
478
+ export function validateApiResponse<T>(
479
+ response: any,
480
+ expectedType: 'list' | 'single' | 'create' | 'delete'
481
+ ): response is ListResponse<T> | T | CreateUpdateResponse | DeleteResponse {
482
+ switch (expectedType) {
483
+ case 'list':
484
+ return response && Array.isArray(response.Data);
485
+ case 'single':
486
+ return response && typeof response === 'object';
487
+ case 'create':
488
+ return response && typeof response._id === 'string';
489
+ case 'delete':
490
+ return response && response.status === 'success';
491
+ default:
492
+ return false;
493
+ }
494
+ }
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext } from "react";
2
+ import { UseKanbanReturn } from "./types";
3
+
4
+ export const KanbanContext = createContext<UseKanbanReturn<any> | null>(null);
5
+
6
+ export function useKanbanContext<T extends Record<string, any> = any>() {
7
+ const context = useContext(KanbanContext);
8
+ if (!context) {
9
+ throw new Error(
10
+ "Kanban components must be used within a KanbanBoard component"
11
+ );
12
+ }
13
+ return context as UseKanbanReturn<T>;
14
+ }