@mastra/lance 0.2.0 → 0.2.1-alpha.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.
@@ -2,6 +2,7 @@ import { ArrayOperator } from '@mastra/core/vector/filter';
2
2
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
3
3
  import { BasicOperator } from '@mastra/core/vector/filter';
4
4
  import type { BlacklistedRootOperators } from '@mastra/core/vector/filter';
5
+ import type { Connection } from '@lancedb/lancedb';
5
6
  import type { ConnectionOptions } from '@lancedb/lancedb';
6
7
  import type { CreateIndexParams } from '@mastra/core';
7
8
  import type { CreateTableOptions } from '@lancedb/lancedb';
@@ -10,6 +11,7 @@ import type { DeleteVectorParams } from '@mastra/core';
10
11
  import type { DescribeIndexParams } from '@mastra/core';
11
12
  import type { EvalRow } from '@mastra/core/storage';
12
13
  import type { IndexStats } from '@mastra/core';
14
+ import { LegacyEvalsStorage } from '@mastra/core/storage';
13
15
  import { LogicalOperator } from '@mastra/core/vector/filter';
14
16
  import type { LogicalOperatorValueMap } from '@mastra/core/vector/filter';
15
17
  import type { MastraMessageContentV2 } from '@mastra/core/agent';
@@ -17,6 +19,7 @@ import type { MastraMessageV1 } from '@mastra/core/memory';
17
19
  import type { MastraMessageV2 } from '@mastra/core/memory';
18
20
  import { MastraStorage } from '@mastra/core/storage';
19
21
  import { MastraVector } from '@mastra/core/vector';
22
+ import { MemoryStorage } from '@mastra/core/storage';
20
23
  import { NumericOperator } from '@mastra/core/vector/filter';
21
24
  import type { OperatorValueMap } from '@mastra/core/vector/filter';
22
25
  import type { PaginationInfo } from '@mastra/core/storage';
@@ -24,20 +27,37 @@ import type { QueryResult } from '@mastra/core';
24
27
  import type { QueryVectorParams } from '@mastra/core';
25
28
  import { RegexOperator } from '@mastra/core/vector/filter';
26
29
  import type { SchemaLike } from '@lancedb/lancedb';
30
+ import type { ScoreRowData } from '@mastra/core/scores';
31
+ import { ScoresStorage } from '@mastra/core/storage';
27
32
  import type { StorageColumn } from '@mastra/core/storage';
33
+ import type { StorageDomains } from '@mastra/core/storage';
28
34
  import type { StorageGetMessagesArg } from '@mastra/core/storage';
29
- import type { StorageGetTracesArg } from '@mastra/core/storage';
35
+ import type { StorageGetTracesPaginatedArg } from '@mastra/core/storage';
36
+ import type { StoragePagination } from '@mastra/core/storage';
37
+ import type { StorageResourceType } from '@mastra/core/storage';
30
38
  import type { StorageThreadType } from '@mastra/core/memory';
39
+ import { StoreOperations } from '@mastra/core/storage';
31
40
  import type { Table } from '@lancedb/lancedb';
32
41
  import type { TABLE_NAMES } from '@mastra/core/storage';
33
42
  import type { TableLike } from '@lancedb/lancedb';
34
43
  import type { Trace } from '@mastra/core/telemetry';
44
+ import { TracesStorage } from '@mastra/core/storage';
35
45
  import type { TraceType } from '@mastra/core/memory';
36
46
  import type { UpdateVectorParams } from '@mastra/core';
37
47
  import type { UpsertVectorParams } from '@mastra/core';
38
48
  import type { VectorFilter } from '@mastra/core/vector/filter';
39
49
  import type { WorkflowRuns } from '@mastra/core/storage';
50
+ import type { WorkflowRuns as WorkflowRuns_2 } from '@mastra/core';
40
51
  import type { WorkflowRunState } from '@mastra/core/workflows';
52
+ import type { WorkflowRunState as WorkflowRunState_2 } from '@mastra/core';
53
+ import { WorkflowsStorage } from '@mastra/core/storage';
54
+
55
+ export declare function getPrimaryKeys(tableName: TABLE_NAMES): string[];
56
+
57
+ export declare function getTableSchema({ tableName, client, }: {
58
+ tableName: TABLE_NAMES;
59
+ client: Connection;
60
+ }): Promise<SchemaLike>;
41
61
 
42
62
  declare interface HNSWConfig {
43
63
  m?: number;
@@ -112,6 +132,7 @@ declare interface LanceQueryVectorParams extends QueryVectorParams<LanceVectorFi
112
132
  }
113
133
 
114
134
  declare class LanceStorage extends MastraStorage {
135
+ stores: StorageDomains;
115
136
  private lanceClient;
116
137
  /**
117
138
  * Creates a new instance of LanceStorage
@@ -136,7 +157,6 @@ declare class LanceStorage extends MastraStorage {
136
157
  * ```
137
158
  */
138
159
  static create(name: string, uri: string, options?: ConnectionOptions): Promise<LanceStorage>;
139
- private getPrimaryKeys;
140
160
  /**
141
161
  * @internal
142
162
  * Private constructor to enforce using the create factory method
@@ -146,76 +166,29 @@ declare class LanceStorage extends MastraStorage {
146
166
  tableName: TABLE_NAMES;
147
167
  schema: Record<string, StorageColumn>;
148
168
  }): Promise<void>;
149
- private translateSchema;
150
- /**
151
- * Drop a table if it exists
152
- * @param tableName Name of the table to drop
153
- */
154
- dropTable(tableName: TABLE_NAMES): Promise<void>;
155
- /**
156
- * Get table schema
157
- * @param tableName Name of the table
158
- * @returns Table schema
159
- */
160
- getTableSchema(tableName: TABLE_NAMES): Promise<SchemaLike>;
161
- protected getDefaultValue(type: StorageColumn['type']): string;
162
- /**
163
- * Alters table schema to add columns if they don't exist
164
- * @param tableName Name of the table
165
- * @param schema Schema of the table
166
- * @param ifNotExists Array of column names to add if they don't exist
167
- */
169
+ dropTable({ tableName }: {
170
+ tableName: TABLE_NAMES;
171
+ }): Promise<void>;
168
172
  alterTable({ tableName, schema, ifNotExists, }: {
169
- tableName: string;
173
+ tableName: TABLE_NAMES;
170
174
  schema: Record<string, StorageColumn>;
171
175
  ifNotExists: string[];
172
176
  }): Promise<void>;
173
177
  clearTable({ tableName }: {
174
178
  tableName: TABLE_NAMES;
175
179
  }): Promise<void>;
176
- /**
177
- * Insert a single record into a table. This function overwrites the existing record if it exists. Use this function for inserting records into tables with custom schemas.
178
- * @param tableName The name of the table to insert into.
179
- * @param record The record to insert.
180
- */
181
180
  insert({ tableName, record }: {
182
- tableName: string;
181
+ tableName: TABLE_NAMES;
183
182
  record: Record<string, any>;
184
183
  }): Promise<void>;
185
- /**
186
- * Insert multiple records into a table. This function overwrites the existing records if they exist. Use this function for inserting records into tables with custom schemas.
187
- * @param tableName The name of the table to insert into.
188
- * @param records The records to insert.
189
- */
190
184
  batchInsert({ tableName, records }: {
191
- tableName: string;
185
+ tableName: TABLE_NAMES;
192
186
  records: Record<string, any>[];
193
187
  }): Promise<void>;
194
- /**
195
- * Load a record from the database by its key(s)
196
- * @param tableName The name of the table to query
197
- * @param keys Record of key-value pairs to use for lookup
198
- * @throws Error if invalid types are provided for keys
199
- * @returns The loaded record with proper type conversions, or null if not found
200
- */
201
188
  load({ tableName, keys }: {
202
189
  tableName: TABLE_NAMES;
203
190
  keys: Record<string, any>;
204
191
  }): Promise<any>;
205
- /**
206
- * Validates that key types match the schema definition
207
- * @param keys The keys to validate
208
- * @param tableSchema The table schema to validate against
209
- * @throws Error if a key has an incompatible type
210
- */
211
- private validateKeyTypes;
212
- /**
213
- * Process a database result with appropriate type conversions based on the table schema
214
- * @param rawResult The raw result object from the database
215
- * @param tableSchema The schema of the table containing type information
216
- * @returns Processed result with correct data types
217
- */
218
- private processResultWithTypeConversion;
219
192
  getThreadById({ threadId }: {
220
193
  threadId: string;
221
194
  }): Promise<StorageThreadType | null>;
@@ -238,6 +211,23 @@ declare class LanceStorage extends MastraStorage {
238
211
  deleteThread({ threadId }: {
239
212
  threadId: string;
240
213
  }): Promise<void>;
214
+ get supports(): {
215
+ selectByIncludeResourceScope: boolean;
216
+ resourceWorkingMemory: boolean;
217
+ hasColumn: boolean;
218
+ createTable: boolean;
219
+ };
220
+ getResourceById({ resourceId }: {
221
+ resourceId: string;
222
+ }): Promise<StorageResourceType | null>;
223
+ saveResource({ resource }: {
224
+ resource: StorageResourceType;
225
+ }): Promise<StorageResourceType>;
226
+ updateResource({ resourceId, workingMemory, metadata, }: {
227
+ resourceId: string;
228
+ workingMemory?: string;
229
+ metadata?: Record<string, unknown>;
230
+ }): Promise<StorageResourceType>;
241
231
  /**
242
232
  * Processes messages to include context messages based on withPreviousMessages and withNextMessages
243
233
  * @param records - The sorted array of records to process
@@ -259,24 +249,55 @@ declare class LanceStorage extends MastraStorage {
259
249
  messages: MastraMessageV2[];
260
250
  format: 'v2';
261
251
  }): Promise<MastraMessageV2[]>;
262
- saveTrace({ trace }: {
263
- trace: TraceType;
264
- }): Promise<TraceType>;
265
- getTraceById({ traceId }: {
252
+ getThreadsByResourceIdPaginated(args: {
253
+ resourceId: string;
254
+ page: number;
255
+ perPage: number;
256
+ }): Promise<PaginationInfo & {
257
+ threads: StorageThreadType[];
258
+ }>;
259
+ getMessagesPaginated(args: StorageGetMessagesArg & {
260
+ format?: 'v1' | 'v2';
261
+ }): Promise<PaginationInfo & {
262
+ messages: MastraMessageV1[] | MastraMessageV2[];
263
+ }>;
264
+ updateMessages(_args: {
265
+ messages: Partial<Omit<MastraMessageV2, 'createdAt'>> & {
266
+ id: string;
267
+ content?: {
268
+ metadata?: MastraMessageContentV2['metadata'];
269
+ content?: MastraMessageContentV2['content'];
270
+ };
271
+ }[];
272
+ }): Promise<MastraMessageV2[]>;
273
+ getTraceById(args: {
266
274
  traceId: string;
267
275
  }): Promise<TraceType>;
268
- getTraces({ name, scope, page, perPage, attributes, }: {
276
+ getTraces(args: {
269
277
  name?: string;
270
278
  scope?: string;
271
279
  page: number;
272
280
  perPage: number;
273
281
  attributes?: Record<string, string>;
274
- }): Promise<TraceType[]>;
275
- saveEvals({ evals }: {
276
- evals: EvalRow[];
277
- }): Promise<EvalRow[]>;
282
+ }): Promise<Trace[]>;
283
+ getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & {
284
+ traces: Trace[];
285
+ }>;
278
286
  getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]>;
279
- private parseWorkflowRun;
287
+ getEvals(options: {
288
+ agentName?: string;
289
+ type?: 'test' | 'live';
290
+ page?: number;
291
+ perPage?: number;
292
+ fromDate?: Date;
293
+ toDate?: Date;
294
+ dateRange?: {
295
+ start?: Date;
296
+ end?: Date;
297
+ };
298
+ }): Promise<PaginationInfo & {
299
+ evals: EvalRow[];
300
+ }>;
280
301
  getWorkflowRuns(args?: {
281
302
  namespace?: string;
282
303
  workflowName?: string;
@@ -285,11 +306,6 @@ declare class LanceStorage extends MastraStorage {
285
306
  limit?: number;
286
307
  offset?: number;
287
308
  }): Promise<WorkflowRuns>;
288
- /**
289
- * Retrieve a single workflow run by its runId.
290
- * @param args The ID of the workflow run to retrieve
291
- * @returns The workflow run object or null if not found
292
- */
293
309
  getWorkflowRunById(args: {
294
310
  runId: string;
295
311
  workflowName?: string;
@@ -309,28 +325,34 @@ declare class LanceStorage extends MastraStorage {
309
325
  workflowName: string;
310
326
  runId: string;
311
327
  }): Promise<WorkflowRunState | null>;
312
- getTracesPaginated(_args: StorageGetTracesArg): Promise<PaginationInfo & {
313
- traces: Trace[];
328
+ getScoreById({ id: _id }: {
329
+ id: string;
330
+ }): Promise<ScoreRowData | null>;
331
+ getScoresByScorerId({ scorerId, pagination, }: {
332
+ scorerId: string;
333
+ pagination: StoragePagination;
334
+ }): Promise<{
335
+ pagination: PaginationInfo;
336
+ scores: ScoreRowData[];
314
337
  }>;
315
- getThreadsByResourceIdPaginated(_args: {
316
- resourceId: string;
317
- page?: number;
318
- perPage?: number;
319
- }): Promise<PaginationInfo & {
320
- threads: StorageThreadType[];
338
+ saveScore(_score: ScoreRowData): Promise<{
339
+ score: ScoreRowData;
321
340
  }>;
322
- getMessagesPaginated(_args: StorageGetMessagesArg): Promise<PaginationInfo & {
323
- messages: MastraMessageV1[] | MastraMessageV2[];
341
+ getScoresByRunId({ runId, pagination, }: {
342
+ runId: string;
343
+ pagination: StoragePagination;
344
+ }): Promise<{
345
+ pagination: PaginationInfo;
346
+ scores: ScoreRowData[];
347
+ }>;
348
+ getScoresByEntityId({ entityId, entityType, pagination, }: {
349
+ pagination: StoragePagination;
350
+ entityId: string;
351
+ entityType: string;
352
+ }): Promise<{
353
+ pagination: PaginationInfo;
354
+ scores: ScoreRowData[];
324
355
  }>;
325
- updateMessages(_args: {
326
- messages: Partial<Omit<MastraMessageV2, 'createdAt'>> & {
327
- id: string;
328
- content?: {
329
- metadata?: MastraMessageContentV2['metadata'];
330
- content?: MastraMessageContentV2['content'];
331
- };
332
- }[];
333
- }): Promise<MastraMessageV2[]>;
334
356
  }
335
357
  export { LanceStorage }
336
358
  export { LanceStorage as LanceStorage_alias_1 }
@@ -406,4 +428,253 @@ declare class LanceVectorStore extends MastraVector<LanceVectorFilter> {
406
428
  export { LanceVectorStore }
407
429
  export { LanceVectorStore as LanceVectorStore_alias_1 }
408
430
 
431
+ export declare function processResultWithTypeConversion(rawResult: Record<string, any> | Record<string, any>[], tableSchema: SchemaLike): Record<string, any> | Record<string, any>[];
432
+
433
+ export declare class StoreLegacyEvalsLance extends LegacyEvalsStorage {
434
+ private client;
435
+ constructor({ client }: {
436
+ client: Connection;
437
+ });
438
+ getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]>;
439
+ getEvals(options: {
440
+ agentName?: string;
441
+ type?: 'test' | 'live';
442
+ page?: number;
443
+ perPage?: number;
444
+ fromDate?: Date;
445
+ toDate?: Date;
446
+ dateRange?: {
447
+ start?: Date;
448
+ end?: Date;
449
+ };
450
+ }): Promise<PaginationInfo & {
451
+ evals: EvalRow[];
452
+ }>;
453
+ }
454
+
455
+ export declare class StoreMemoryLance extends MemoryStorage {
456
+ private client;
457
+ private operations;
458
+ constructor({ client, operations }: {
459
+ client: Connection;
460
+ operations: StoreOperationsLance;
461
+ });
462
+ getThreadById({ threadId }: {
463
+ threadId: string;
464
+ }): Promise<StorageThreadType | null>;
465
+ getThreadsByResourceId({ resourceId }: {
466
+ resourceId: string;
467
+ }): Promise<StorageThreadType[]>;
468
+ /**
469
+ * Saves a thread to the database. This function doesn't overwrite existing threads.
470
+ * @param thread - The thread to save
471
+ * @returns The saved thread
472
+ */
473
+ saveThread({ thread }: {
474
+ thread: StorageThreadType;
475
+ }): Promise<StorageThreadType>;
476
+ updateThread({ id, title, metadata, }: {
477
+ id: string;
478
+ title: string;
479
+ metadata: Record<string, unknown>;
480
+ }): Promise<StorageThreadType>;
481
+ deleteThread({ threadId }: {
482
+ threadId: string;
483
+ }): Promise<void>;
484
+ getMessages(args: StorageGetMessagesArg & {
485
+ format?: 'v1';
486
+ }): Promise<MastraMessageV1[]>;
487
+ getMessages(args: StorageGetMessagesArg & {
488
+ format: 'v2';
489
+ }): Promise<MastraMessageV2[]>;
490
+ saveMessages(args: {
491
+ messages: MastraMessageV1[];
492
+ format?: undefined | 'v1';
493
+ }): Promise<MastraMessageV1[]>;
494
+ saveMessages(args: {
495
+ messages: MastraMessageV2[];
496
+ format: 'v2';
497
+ }): Promise<MastraMessageV2[]>;
498
+ getThreadsByResourceIdPaginated(args: {
499
+ resourceId: string;
500
+ page?: number;
501
+ perPage?: number;
502
+ }): Promise<PaginationInfo & {
503
+ threads: StorageThreadType[];
504
+ }>;
505
+ /**
506
+ * Processes messages to include context messages based on withPreviousMessages and withNextMessages
507
+ * @param records - The sorted array of records to process
508
+ * @param include - The array of include specifications with context parameters
509
+ * @returns The processed array with context messages included
510
+ */
511
+ private processMessagesWithContext;
512
+ getMessagesPaginated(args: StorageGetMessagesArg & {
513
+ format?: 'v1' | 'v2';
514
+ }): Promise<PaginationInfo & {
515
+ messages: MastraMessageV1[] | MastraMessageV2[];
516
+ }>;
517
+ /**
518
+ * Parse message data from LanceDB record format to MastraMessageV2 format
519
+ */
520
+ private parseMessageData;
521
+ updateMessages(args: {
522
+ messages: Partial<Omit<MastraMessageV2, 'createdAt'>> & {
523
+ id: string;
524
+ content?: {
525
+ metadata?: MastraMessageContentV2['metadata'];
526
+ content?: MastraMessageContentV2['content'];
527
+ };
528
+ }[];
529
+ }): Promise<MastraMessageV2[]>;
530
+ getResourceById({ resourceId }: {
531
+ resourceId: string;
532
+ }): Promise<StorageResourceType | null>;
533
+ saveResource({ resource }: {
534
+ resource: StorageResourceType;
535
+ }): Promise<StorageResourceType>;
536
+ updateResource({ resourceId, workingMemory, metadata, }: {
537
+ resourceId: string;
538
+ workingMemory?: string;
539
+ metadata?: Record<string, unknown>;
540
+ }): Promise<StorageResourceType>;
541
+ }
542
+
543
+ export declare class StoreOperationsLance extends StoreOperations {
544
+ client: Connection;
545
+ constructor({ client }: {
546
+ client: Connection;
547
+ });
548
+ protected getDefaultValue(type: StorageColumn['type']): string;
549
+ hasColumn(tableName: TABLE_NAMES, columnName: string): Promise<boolean>;
550
+ private translateSchema;
551
+ createTable({ tableName, schema, }: {
552
+ tableName: TABLE_NAMES;
553
+ schema: Record<string, StorageColumn>;
554
+ }): Promise<void>;
555
+ dropTable({ tableName }: {
556
+ tableName: TABLE_NAMES;
557
+ }): Promise<void>;
558
+ alterTable({ tableName, schema, ifNotExists, }: {
559
+ tableName: string;
560
+ schema: Record<string, StorageColumn>;
561
+ ifNotExists: string[];
562
+ }): Promise<void>;
563
+ clearTable({ tableName }: {
564
+ tableName: TABLE_NAMES;
565
+ }): Promise<void>;
566
+ insert({ tableName, record }: {
567
+ tableName: string;
568
+ record: Record<string, any>;
569
+ }): Promise<void>;
570
+ batchInsert({ tableName, records }: {
571
+ tableName: string;
572
+ records: Record<string, any>[];
573
+ }): Promise<void>;
574
+ load({ tableName, keys }: {
575
+ tableName: TABLE_NAMES;
576
+ keys: Record<string, any>;
577
+ }): Promise<any>;
578
+ }
579
+
580
+ export declare class StoreScoresLance extends ScoresStorage {
581
+ private client;
582
+ constructor({ client }: {
583
+ client: Connection;
584
+ });
585
+ saveScore(score: ScoreRowData): Promise<{
586
+ score: ScoreRowData;
587
+ }>;
588
+ getScoreById({ id }: {
589
+ id: string;
590
+ }): Promise<ScoreRowData | null>;
591
+ getScoresByScorerId({ scorerId, pagination, }: {
592
+ scorerId: string;
593
+ pagination: StoragePagination;
594
+ }): Promise<{
595
+ pagination: PaginationInfo;
596
+ scores: ScoreRowData[];
597
+ }>;
598
+ getScoresByRunId({ runId, pagination, }: {
599
+ runId: string;
600
+ pagination: StoragePagination;
601
+ }): Promise<{
602
+ pagination: PaginationInfo;
603
+ scores: ScoreRowData[];
604
+ }>;
605
+ getScoresByEntityId({ entityId, entityType, pagination, }: {
606
+ pagination: StoragePagination;
607
+ entityId: string;
608
+ entityType: string;
609
+ }): Promise<{
610
+ pagination: PaginationInfo;
611
+ scores: ScoreRowData[];
612
+ }>;
613
+ }
614
+
615
+ export declare class StoreTracesLance extends TracesStorage {
616
+ private client;
617
+ private operations;
618
+ constructor({ client, operations }: {
619
+ client: Connection;
620
+ operations: StoreOperationsLance;
621
+ });
622
+ saveTrace({ trace }: {
623
+ trace: TraceType;
624
+ }): Promise<TraceType>;
625
+ getTraceById({ traceId }: {
626
+ traceId: string;
627
+ }): Promise<TraceType>;
628
+ getTraces({ name, scope, page, perPage, attributes, }: {
629
+ name?: string;
630
+ scope?: string;
631
+ page: number;
632
+ perPage: number;
633
+ attributes?: Record<string, string>;
634
+ }): Promise<Trace[]>;
635
+ getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & {
636
+ traces: Trace[];
637
+ }>;
638
+ batchTraceInsert({ records }: {
639
+ records: Record<string, any>[];
640
+ }): Promise<void>;
641
+ }
642
+
643
+ export declare class StoreWorkflowsLance extends WorkflowsStorage {
644
+ client: Connection;
645
+ constructor({ client }: {
646
+ client: Connection;
647
+ });
648
+ persistWorkflowSnapshot({ workflowName, runId, snapshot, }: {
649
+ workflowName: string;
650
+ runId: string;
651
+ snapshot: WorkflowRunState_2;
652
+ }): Promise<void>;
653
+ loadWorkflowSnapshot({ workflowName, runId, }: {
654
+ workflowName: string;
655
+ runId: string;
656
+ }): Promise<WorkflowRunState_2 | null>;
657
+ getWorkflowRunById(args: {
658
+ runId: string;
659
+ workflowName?: string;
660
+ }): Promise<{
661
+ workflowName: string;
662
+ runId: string;
663
+ snapshot: any;
664
+ createdAt: Date;
665
+ updatedAt: Date;
666
+ } | null>;
667
+ getWorkflowRuns(args?: {
668
+ namespace?: string;
669
+ resourceId?: string;
670
+ workflowName?: string;
671
+ fromDate?: Date;
672
+ toDate?: Date;
673
+ limit?: number;
674
+ offset?: number;
675
+ }): Promise<WorkflowRuns_2>;
676
+ }
677
+
678
+ export declare function validateKeyTypes(keys: Record<string, any>, tableSchema: SchemaLike): void;
679
+
409
680
  export { }