hermes-client-typescript 1.8.95 → 1.8.97
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.
- package/README.md +174 -78
- package/dist/client.d.ts +15 -42
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +50 -334
- package/dist/client.js.map +1 -1
- package/dist/converters.d.ts +12 -0
- package/dist/converters.d.ts.map +1 -0
- package/dist/converters.js +287 -0
- package/dist/converters.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hermes TypeScript client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed Node.js client for the
|
|
4
|
+
[Hermes](https://github.com/SpaceFrontiers/hermes) gRPC search server.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
@@ -8,7 +9,7 @@ TypeScript client for [Hermes](https://github.com/SpaceFrontiers/hermes) search
|
|
|
8
9
|
pnpm add hermes-client-typescript
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
##
|
|
12
|
+
## Quick start
|
|
12
13
|
|
|
13
14
|
```typescript
|
|
14
15
|
import { HermesClient } from "hermes-client-typescript";
|
|
@@ -16,109 +17,204 @@ import { HermesClient } from "hermes-client-typescript";
|
|
|
16
17
|
const client = new HermesClient("localhost:50051");
|
|
17
18
|
client.connect();
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
await client.createIndex(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
try {
|
|
21
|
+
await client.createIndex(
|
|
22
|
+
"articles",
|
|
23
|
+
`
|
|
24
|
+
index articles {
|
|
25
|
+
field title: text<simple> [indexed, stored]
|
|
26
|
+
field body: text<simple> [indexed, stored]
|
|
27
|
+
}
|
|
28
|
+
`,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const [indexedCount, errorCount, errors] = await client.indexDocuments(
|
|
32
|
+
"articles",
|
|
33
|
+
[
|
|
34
|
+
{ title: "Hello", body: "First article" },
|
|
35
|
+
{ title: "Hermes", body: "Fast search" },
|
|
36
|
+
],
|
|
37
|
+
);
|
|
38
|
+
if (errorCount) throw new Error(JSON.stringify(errors));
|
|
39
|
+
console.log(`Indexed ${indexedCount} documents`);
|
|
40
|
+
|
|
41
|
+
await client.commit("articles");
|
|
42
|
+
|
|
43
|
+
const results = await client.search("articles", {
|
|
44
|
+
query: { match: { field: "title", text: "hello" } },
|
|
45
|
+
fieldsToLoad: ["title", "body"],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
for (const hit of results.hits) {
|
|
49
|
+
console.log(hit.address, hit.score, hit.fields);
|
|
26
50
|
}
|
|
27
|
-
`,
|
|
28
|
-
);
|
|
29
51
|
|
|
30
|
-
|
|
31
|
-
await client.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
term: ["title", "hello"],
|
|
40
|
-
});
|
|
41
|
-
for (const hit of results.hits) {
|
|
42
|
-
console.log(hit.docId, hit.score, hit.fields);
|
|
52
|
+
if (results.hits.length > 0) {
|
|
53
|
+
const document = await client.getDocument(
|
|
54
|
+
"articles",
|
|
55
|
+
results.hits[0].address,
|
|
56
|
+
);
|
|
57
|
+
console.log(document?.fields);
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
client.close();
|
|
43
61
|
}
|
|
62
|
+
```
|
|
44
63
|
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
Call `connect()` before the first RPC and `close()` when the client is no
|
|
65
|
+
longer needed.
|
|
66
|
+
|
|
67
|
+
## Index management
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
await client.createIndex("articles", schema);
|
|
71
|
+
const names = await client.listIndexes();
|
|
72
|
+
const info = await client.getIndexInfo("articles");
|
|
73
|
+
|
|
74
|
+
await client.forceMerge("articles");
|
|
75
|
+
await client.reorder("articles");
|
|
76
|
+
await client.retrainVectorIndex("articles");
|
|
77
|
+
await client.deleteIndex("articles");
|
|
47
78
|
```
|
|
48
79
|
|
|
49
|
-
|
|
80
|
+
Newly indexed documents become searchable after `commit()`.
|
|
50
81
|
|
|
51
|
-
###
|
|
82
|
+
### Batch and streaming indexing
|
|
52
83
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
```typescript
|
|
85
|
+
const [indexed, errorCount, errors] = await client.indexDocuments("articles", [
|
|
86
|
+
{ title: "One", tags: ["search", "typescript"] },
|
|
87
|
+
{ title: "Two", tags: ["grpc"] },
|
|
88
|
+
]);
|
|
57
89
|
|
|
58
|
-
|
|
90
|
+
async function* documents() {
|
|
91
|
+
for (let number = 0; number < 10_000; number += 1) {
|
|
92
|
+
yield { title: `Document ${number}` };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
59
95
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- `indexDocumentsStream(indexName, asyncIterable)` — Stream documents for indexing
|
|
63
|
-
- `commit(indexName)` — Commit pending changes, returns total doc count
|
|
64
|
-
- `forceMerge(indexName)` — Force merge segments
|
|
96
|
+
const streamed = await client.indexDocumentsStream("articles", documents());
|
|
97
|
+
```
|
|
65
98
|
|
|
66
|
-
|
|
99
|
+
Repeated arrays become repeated field entries. Flat numeric arrays are dense
|
|
100
|
+
vectors. Sparse vectors use arrays of `[dimension, weight]` pairs inside an
|
|
101
|
+
outer repeated-value array—for example, `[[[1, 0.5], [8, 0.25]]]` for one
|
|
102
|
+
sparse vector. The outer array is required because `[[1, 0.5], [8, 0.25]]` is
|
|
103
|
+
the legacy shape for two dense vectors.
|
|
67
104
|
|
|
68
|
-
|
|
69
|
-
- `getDocument(indexName, docId)` — Get document by ID
|
|
105
|
+
## Searching
|
|
70
106
|
|
|
71
|
-
|
|
107
|
+
`search()` accepts a `SearchRequest`. Its `query` is a discriminated union, so
|
|
108
|
+
exactly one query variant is selected:
|
|
72
109
|
|
|
73
110
|
```typescript
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
term:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
// Exact term
|
|
112
|
+
await client.search("articles", {
|
|
113
|
+
query: { term: { field: "title", term: "hermes" } },
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Recursive Boolean query
|
|
117
|
+
await client.search("articles", {
|
|
118
|
+
query: {
|
|
119
|
+
boolean: {
|
|
120
|
+
must: [{ match: { field: "body", text: "fast search" } }],
|
|
121
|
+
mustNot: [{ term: { field: "title", term: "draft" } }],
|
|
122
|
+
},
|
|
82
123
|
},
|
|
124
|
+
});
|
|
83
125
|
|
|
84
|
-
|
|
85
|
-
|
|
126
|
+
// Dense retrieval with reranking
|
|
127
|
+
await client.search("articles", {
|
|
128
|
+
query: {
|
|
129
|
+
denseVector: {
|
|
130
|
+
field: "embedding",
|
|
131
|
+
vector: [0.1, 0.2, 0.3],
|
|
132
|
+
nprobe: 16,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
reranker: {
|
|
136
|
+
field: "embedding",
|
|
137
|
+
vector: [0.1, 0.2, 0.3],
|
|
138
|
+
},
|
|
139
|
+
candidateLimit: 20,
|
|
140
|
+
limit: 10,
|
|
141
|
+
fieldsToLoad: ["title"],
|
|
142
|
+
});
|
|
86
143
|
|
|
87
|
-
|
|
88
|
-
|
|
144
|
+
// Hybrid union fusion
|
|
145
|
+
await client.search("articles", {
|
|
146
|
+
query: {
|
|
147
|
+
fusion: {
|
|
148
|
+
method: "rrf",
|
|
149
|
+
rrfK: 60,
|
|
150
|
+
queries: [
|
|
151
|
+
{
|
|
152
|
+
query: {
|
|
153
|
+
sparseVector: {
|
|
154
|
+
field: "sparseEmbedding",
|
|
155
|
+
indices: [1, 5],
|
|
156
|
+
values: [0.8, 0.2],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
query: {
|
|
162
|
+
denseVector: {
|
|
163
|
+
field: "embedding",
|
|
164
|
+
vector: [0.1, 0.2, 0.3],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
89
173
|
|
|
90
|
-
|
|
91
|
-
|
|
174
|
+
Supported variants are `term`, `match`, `boolean`, `sparseVector`,
|
|
175
|
+
`denseVector`, `binaryDenseVector`, `boost`, `range`, `prefix`, `all`, and
|
|
176
|
+
`fusion`.
|
|
92
177
|
|
|
93
|
-
|
|
94
|
-
limit: 10,
|
|
95
|
-
offset: 0,
|
|
96
|
-
fieldsToLoad: ["title", "body"],
|
|
97
|
-
combiner: "sum", // "sum" | "max" | "avg"
|
|
178
|
+
`getDocument()` takes the full address returned by a search hit:
|
|
98
179
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
180
|
+
```typescript
|
|
181
|
+
const document = await client.getDocument("articles", hit.address);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
It returns `null` when the server responds with gRPC `NOT_FOUND`.
|
|
185
|
+
|
|
186
|
+
## Deadlines
|
|
187
|
+
|
|
188
|
+
Every RPC accepts an optional trailing deadline in milliseconds. A per-call
|
|
189
|
+
value overrides the client default:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const client = new HermesClient("localhost:50051", {
|
|
193
|
+
defaultTimeoutMs: 5_000,
|
|
102
194
|
});
|
|
195
|
+
|
|
196
|
+
await client.search("articles", { query: { all: {} } }, 500);
|
|
197
|
+
await client.forceMerge("articles", 3_600_000);
|
|
103
198
|
```
|
|
104
199
|
|
|
200
|
+
Expired calls reject with a gRPC `DEADLINE_EXCEEDED` error.
|
|
201
|
+
|
|
105
202
|
## Development
|
|
106
203
|
|
|
107
204
|
```bash
|
|
108
|
-
pnpm install
|
|
109
|
-
pnpm
|
|
110
|
-
pnpm run build # compile TypeScript
|
|
205
|
+
pnpm install --frozen-lockfile
|
|
206
|
+
pnpm check
|
|
111
207
|
```
|
|
112
208
|
|
|
113
|
-
|
|
209
|
+
`pnpm check` compiles the strict TypeScript sources and runs the pure converter
|
|
210
|
+
unit tests. After changing `hermes-proto/hermes.proto`, regenerate and check in
|
|
211
|
+
the generated source:
|
|
114
212
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`DEADLINE_EXCEEDED`.
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
const client = new HermesClient("localhost:50051", { defaultTimeoutMs: 5000 });
|
|
122
|
-
await client.search("articles", { query: { ... } }, 500); // per-call override
|
|
123
|
-
await client.forceMerge("articles", 3_600_000); // long op, long deadline
|
|
213
|
+
```bash
|
|
214
|
+
pnpm generate
|
|
215
|
+
pnpm check
|
|
124
216
|
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT
|
package/dist/client.d.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Async Hermes client implementation.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Search types mirror the protobuf API. Serialization details live in
|
|
5
|
+
* converters.ts so this class remains focused on connection and RPC lifecycle.
|
|
6
6
|
*/
|
|
7
|
-
import type { DocAddress, Document,
|
|
7
|
+
import type { DocAddress, Document, IndexInfo, SearchRequest, SearchResponse } from "./types";
|
|
8
8
|
export interface HermesClientOptions {
|
|
9
9
|
/**
|
|
10
10
|
* Default per-RPC deadline in milliseconds, applied to every call unless
|
|
11
|
-
* overridden by the call's `timeoutMs` argument. Undefined
|
|
12
|
-
*
|
|
11
|
+
* overridden by the call's `timeoutMs` argument. Undefined means no
|
|
12
|
+
* deadline. Expired calls reject with gRPC DEADLINE_EXCEEDED.
|
|
13
13
|
*/
|
|
14
14
|
defaultTimeoutMs?: number;
|
|
15
15
|
}
|
|
16
16
|
export declare class HermesClient {
|
|
17
|
-
private address;
|
|
17
|
+
private readonly address;
|
|
18
|
+
private readonly defaultTimeoutMs?;
|
|
18
19
|
private channel;
|
|
19
20
|
private indexClient;
|
|
20
21
|
private searchClient;
|
|
21
|
-
private defaultTimeoutMs?;
|
|
22
22
|
constructor(address?: string, options?: HermesClientOptions);
|
|
23
|
-
/** Per-call options with the effective deadline (call override > default). */
|
|
24
|
-
private callOptions;
|
|
25
23
|
/** Connect to the server. */
|
|
26
24
|
connect(): void;
|
|
27
25
|
/** Close the connection. */
|
|
28
26
|
close(): void;
|
|
27
|
+
/** Per-call options with the effective deadline (call override > default). */
|
|
28
|
+
private callOptions;
|
|
29
29
|
private ensureConnected;
|
|
30
30
|
/** Create a new index. */
|
|
31
31
|
createIndex(indexName: string, schema: string, timeoutMs?: number): Promise<boolean>;
|
|
@@ -35,58 +35,31 @@ export declare class HermesClient {
|
|
|
35
35
|
listIndexes(timeoutMs?: number): Promise<string[]>;
|
|
36
36
|
/** Get information about an index. */
|
|
37
37
|
getIndexInfo(indexName: string, timeoutMs?: number): Promise<IndexInfo>;
|
|
38
|
-
/** Index multiple documents
|
|
39
|
-
indexDocuments(indexName: string, documents: Record<string,
|
|
38
|
+
/** Index multiple documents. Returns [indexedCount, errorCount, errors]. */
|
|
39
|
+
indexDocuments(indexName: string, documents: Record<string, unknown>[], timeoutMs?: number): Promise<[number, number, Array<{
|
|
40
40
|
index: number;
|
|
41
41
|
error: string;
|
|
42
42
|
}>]>;
|
|
43
43
|
/** Index a single document. */
|
|
44
|
-
indexDocument(indexName: string, document: Record<string,
|
|
44
|
+
indexDocument(indexName: string, document: Record<string, unknown>, timeoutMs?: number): Promise<void>;
|
|
45
45
|
/** Stream documents for indexing. Returns number of indexed documents. */
|
|
46
|
-
indexDocumentsStream(indexName: string, documents: AsyncIterable<Record<string,
|
|
46
|
+
indexDocumentsStream(indexName: string, documents: AsyncIterable<Record<string, unknown>>, timeoutMs?: number): Promise<number>;
|
|
47
47
|
/** Commit pending changes. Returns total number of documents. */
|
|
48
48
|
commit(indexName: string, timeoutMs?: number): Promise<number>;
|
|
49
49
|
/** Force merge all segments. Returns number of segments after merge. */
|
|
50
50
|
forceMerge(indexName: string, timeoutMs?: number): Promise<number>;
|
|
51
51
|
/** Retrain vector index centroids/codebooks from current data. */
|
|
52
52
|
retrainVectorIndex(indexName: string, timeoutMs?: number): Promise<boolean>;
|
|
53
|
-
/** Reorder BMP blocks by SimHash similarity
|
|
53
|
+
/** Reorder BMP blocks by SimHash similarity. */
|
|
54
54
|
reorder(indexName: string, timeoutMs?: number): Promise<number>;
|
|
55
55
|
/**
|
|
56
56
|
* Search for documents.
|
|
57
57
|
*
|
|
58
58
|
* @example
|
|
59
|
-
* // Term query
|
|
60
|
-
* await client.search("articles", {
|
|
61
|
-
* query: { term: { field: "title", term: "hello" } },
|
|
62
|
-
* });
|
|
63
|
-
*
|
|
64
|
-
* // Match query (full-text, tokenized server-side)
|
|
65
59
|
* await client.search("articles", {
|
|
66
|
-
* query: { match: { field: "title", text: "
|
|
67
|
-
* });
|
|
68
|
-
*
|
|
69
|
-
* // Sparse vector query with server-side tokenization + pruning
|
|
70
|
-
* await client.search("docs", {
|
|
71
|
-
* query: { sparseVector: { field: "embedding", text: "machine learning", pruning: 0.5 } },
|
|
60
|
+
* query: { match: { field: "title", text: "search engine" } },
|
|
72
61
|
* fieldsToLoad: ["title"],
|
|
73
62
|
* });
|
|
74
|
-
*
|
|
75
|
-
* // Dense vector query
|
|
76
|
-
* await client.search("docs", {
|
|
77
|
-
* query: { denseVector: { field: "embedding", vector: [0.1, 0.2, ...], nprobe: 10 } },
|
|
78
|
-
* reranker: { field: "embedding", vector: [0.1, 0.2, ...] },
|
|
79
|
-
* limit: 50,
|
|
80
|
-
* candidateLimit: 100,
|
|
81
|
-
* });
|
|
82
|
-
*
|
|
83
|
-
* // Boolean query
|
|
84
|
-
* await client.search("articles", {
|
|
85
|
-
* query: { boolean: {
|
|
86
|
-
* must: [{ match: { field: "title", text: "hello" } }],
|
|
87
|
-
* should: [{ match: { field: "body", text: "world" } }],
|
|
88
|
-
* }},
|
|
89
|
-
* });
|
|
90
63
|
*/
|
|
91
64
|
search(indexName: string, request: SearchRequest, timeoutMs?: number): Promise<SearchResponse>;
|
|
92
65
|
/** Get a document by address. Returns null if not found. */
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoBH,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,SAAS,EAET,aAAa,EACb,cAAc,EAEf,MAAM,SAAS,CAAC;AAKjB,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAS;IAC3C,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,YAAY,CAA6B;gBAG/C,OAAO,GAAE,MAA0B,EACnC,OAAO,GAAE,mBAAwB;IAMnC,6BAA6B;IAC7B,OAAO,IAAI,IAAI;IAUf,4BAA4B;IAC5B,KAAK,IAAI,IAAI;IASb,8EAA8E;IAC9E,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,eAAe;IAMvB,0BAA0B;IACpB,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC;IASnB,uBAAuB;IACjB,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC;IASnB,sCAAsC;IAChC,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IASxD,sCAAsC;IAChC,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,CAAC;IAoBrB,4EAA4E;IACtE,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACpC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAkBrE,+BAA+B;IACzB,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC;IAIhB,0EAA0E;IACpE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACjD,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC;IAmBlB,iEAAiE;IAC3D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASpE,wEAAwE;IAClE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASxE,kEAAkE;IAC5D,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC;IASnB,gDAAgD;IAC1C,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASrE;;;;;;;;OAQG;IACG,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC;IAoD1B,4DAA4D;IACtD,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,UAAU,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;CAkC5B"}
|