@lde/search-typesense 0.28.0 → 0.29.1

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.
@@ -19,11 +19,14 @@ export type BlueGreenRebuildOptions = RebuildOptions;
19
19
  * creates the versioned collection (`${name}_<timestamp>`) with the schema
20
20
  * derived from the {@link SearchType}.
21
21
  * - `write` streams documents into the fresh collection, batched across write
22
- * calls, each stamped with its `source` (the dataset IRI).
22
+ * calls, each recording the dataset it came from ({@link openDocuments}).
23
23
  * - `flush` on a **failed** dataset, and `reset` (the pipeline’s dump-fallback
24
- * discard), roll that dataset’s documents back out of the not-yet-live
25
- * collection by `source`, so a swap never ships a half-processed dataset;
26
- * a successful flush leaves the streamed documents in place.
24
+ * discard), roll that dataset back out of the not-yet-live collection, so a
25
+ * swap never ships a half-processed dataset; a successful flush leaves the
26
+ * streamed documents in place. On a canonically keyed collection the
27
+ * rollback drops the failed dataset’s *membership*, so a document another
28
+ * dataset also contributed stays – with a reference from that dataset that
29
+ * still resolves.
27
30
  * - `commit` imports the remainder, atomically repoints the `name` alias to
28
31
  * the new collection, drops the collection it superseded, and releases the
29
32
  * lock. Until commit, the live alias never points at a partial build.
@@ -50,10 +53,6 @@ export declare class BlueGreenRebuild<TDocument extends {
50
53
  */
51
54
  readonly collectionName: string;
52
55
  private readonly resolved;
53
- /** The column this collection carries its documents’ dataset IRI in: the
54
- * type’s declared dataset field, or the private `source` when it declares
55
- * none. Both the stamp and the per-dataset rollback filter read it. */
56
- private readonly sourceField;
57
56
  constructor(client: Client, searchType: SearchType, options?: BlueGreenRebuildOptions);
58
57
  openRun(context: RunContext): Promise<RunWriter<TDocument>>;
59
58
  /** The collection an alias currently points at, or `undefined` if unset. */
@@ -1 +1 @@
1
- {"version":3,"file":"blue-green-rebuild.d.ts","sourceRoot":"","sources":["../src/blue-green-rebuild.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAEV,UAAU,EACV,SAAS,EACT,MAAM,EACP,MAAM,eAAe,CAAC;AAUvB,OAAO,EAML,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAE9B;;yDAEyD;AACzD,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,gBAAgB,CAC3B,SAAS,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAChC,YAAW,MAAM,CAAC,SAAS,CAAC;IAiB1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAjB7B;;;;;;;OAOG;IACH,SAAgB,cAAc,EAAE,MAAM,CAAC;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD;;4EAEwE;IACxE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAGlB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACvC,OAAO,GAAE,uBAA4B;IAajC,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAwHjE,4EAA4E;YAC9D,WAAW;CAW1B"}
1
+ {"version":3,"file":"blue-green-rebuild.d.ts","sourceRoot":"","sources":["../src/blue-green-rebuild.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAEV,UAAU,EACV,SAAS,EACT,MAAM,EACP,MAAM,eAAe,CAAC;AASvB,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAE9B;;yDAEyD;AACzD,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,gBAAgB,CAC3B,SAAS,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAChC,YAAW,MAAM,CAAC,SAAS,CAAC;IAa1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAb7B;;;;;;;OAOG;IACH,SAAgB,cAAc,EAAE,MAAM,CAAC;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;gBAG/B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACvC,OAAO,GAAE,uBAA4B;IAUjC,OAAO,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAsGjE,4EAA4E;YAC9D,WAAW;CAW1B"}
@@ -1,8 +1,7 @@
1
1
  import { buildCollectionDefinition } from './collection-definition.js';
2
- import { BatchImporter } from './import.js';
3
2
  import { httpStatus, openLockedRun, releaseLock } from './lock.js';
4
- import { SOURCE_FIELD, provenanceField, sourceDocumentsFilter, } from './sweep.js';
5
- import { assertNoReservedFields, assertSweepableProvenanceField, deleteByFilter, resolveRebuildOptions, stampDocuments, } from './rebuild-support.js';
3
+ import { assertWritableType, openDocuments, withBookkeeping, } from './documents.js';
4
+ import { resolveRebuildOptions, } from './rebuild-support.js';
6
5
  /**
7
6
  * Blue/green Rebuild (build a fresh index alongside the live one, then swap to
8
7
  * it atomically) as a transactional `Writer`: each run builds the search
@@ -16,11 +15,14 @@ import { assertNoReservedFields, assertSweepableProvenanceField, deleteByFilter,
16
15
  * creates the versioned collection (`${name}_<timestamp>`) with the schema
17
16
  * derived from the {@link SearchType}.
18
17
  * - `write` streams documents into the fresh collection, batched across write
19
- * calls, each stamped with its `source` (the dataset IRI).
18
+ * calls, each recording the dataset it came from ({@link openDocuments}).
20
19
  * - `flush` on a **failed** dataset, and `reset` (the pipeline’s dump-fallback
21
- * discard), roll that dataset’s documents back out of the not-yet-live
22
- * collection by `source`, so a swap never ships a half-processed dataset;
23
- * a successful flush leaves the streamed documents in place.
20
+ * discard), roll that dataset back out of the not-yet-live collection, so a
21
+ * swap never ships a half-processed dataset; a successful flush leaves the
22
+ * streamed documents in place. On a canonically keyed collection the
23
+ * rollback drops the failed dataset’s *membership*, so a document another
24
+ * dataset also contributed stays – with a reference from that dataset that
25
+ * still resolves.
24
26
  * - `commit` imports the remainder, atomically repoints the `name` alias to
25
27
  * the new collection, drops the collection it superseded, and releases the
26
28
  * lock. Until commit, the live alias never points at a partial build.
@@ -45,19 +47,12 @@ export class BlueGreenRebuild {
45
47
  */
46
48
  collectionName;
47
49
  resolved;
48
- /** The column this collection carries its documents’ dataset IRI in: the
49
- * type’s declared dataset field, or the private `source` when it declares
50
- * none. Both the stamp and the per-dataset rollback filter read it. */
51
- sourceField;
52
50
  constructor(client, searchType, options = {}) {
53
51
  this.client = client;
54
52
  this.searchType = searchType;
55
- // `source` is stamped on every document for per-dataset rollback.
56
- assertNoReservedFields(searchType, [SOURCE_FIELD]);
57
- // Rollback filters by a known dataset IRI rather than enumerating the
58
- // indexed ones, so a declared field need not be facetable here.
59
- assertSweepableProvenanceField(searchType, { requireFacetable: false });
60
- this.sourceField = provenanceField(searchType);
53
+ // Rollback drops a known dataset rather than enumerating the indexed ones,
54
+ // so a declared dataset field need not be facetable here.
55
+ assertWritableType(searchType, { requireFacetable: false });
61
56
  this.resolved = resolveRebuildOptions(searchType, options);
62
57
  this.collectionName =
63
58
  this.resolved.definitionOptions.collectionNameFor(searchType);
@@ -65,7 +60,6 @@ export class BlueGreenRebuild {
65
60
  async openRun(context) {
66
61
  const { batchSize, lockTtlMs, definitionOptions } = this.resolved;
67
62
  const name = this.collectionName;
68
- const sourceField = this.sourceField;
69
63
  return openLockedRun(this.client, name, lockTtlMs, async () => {
70
64
  // Create the fresh (blue) collection up front, so a failure surfaces
71
65
  // before any dataset is processed. startedAt orders the versioned names;
@@ -87,30 +81,22 @@ export class BlueGreenRebuild {
87
81
  ...definitionOptions,
88
82
  collectionNameFor: versioned,
89
83
  });
90
- await this.client.collections().create({
91
- ...definition,
92
- // The private `source` only when the type declares no dataset field of
93
- // its own; a declared one is already in the definition and carries the
94
- // same IRI.
95
- fields: [
96
- ...(definition.fields ?? []),
97
- ...(sourceField === SOURCE_FIELD
98
- ? [{ name: SOURCE_FIELD, type: 'string' }]
99
- : []),
100
- ],
84
+ await this.client
85
+ .collections()
86
+ .create(withBookkeeping(definition, this.searchType));
87
+ const documents = openDocuments(this.client, collection, {
88
+ searchType: this.searchType,
89
+ runId: context.runId,
90
+ startedAt: context.startedAt,
91
+ batchSize,
101
92
  });
102
- const importer = new BatchImporter(this.client, collection, batchSize);
103
- // Drop a dataset’s streamed documents back out of the not-yet-live
104
- // collection (a failed dataset, or a reset before the dump re-run). Any
105
- // still buffered must land first, so the delete filter sees them.
106
- const rollback = async (dataset) => {
107
- await importer.flush();
108
- await deleteByFilter(this.client, collection, sourceDocumentsFilter(sourceField, dataset.iri.toString()));
109
- };
93
+ // Roll a dataset back out of the not-yet-live collection (a failed
94
+ // dataset, or a reset before the dump re-run). On a keyed collection
95
+ // that drops its membership rather than the documents themselves, so a
96
+ // dataset that succeeded keeps the ones it also contributed.
97
+ const rollback = async (dataset) => documents.dropAll(dataset);
110
98
  return {
111
- write: async (dataset, documents) => importer.add(stampDocuments(documents, {
112
- [sourceField]: dataset.iri.toString(),
113
- })),
99
+ write: async (dataset, items) => documents.add(dataset, items),
114
100
  flush: async (dataset, outcome) => {
115
101
  // A successful dataset keeps its streamed documents (they go live at
116
102
  // the swap); a failed one is rolled back so the swap never ships it.
@@ -120,7 +106,7 @@ export class BlueGreenRebuild {
120
106
  },
121
107
  reset: async (dataset) => rollback(dataset),
122
108
  commit: async () => {
123
- await importer.flush();
109
+ await documents.flush();
124
110
  // The alias swap is the commit point: once it lands the new
125
111
  // collection is live. Everything after it is best-effort cleanup that
126
112
  // must NOT fail the commit – a post-swap rejection would otherwise
@@ -145,7 +145,11 @@ function typesenseFields(searchType, field, defaultLocale, defaultSortingField,
145
145
  const nested = nestedTypeOf(searchType, field, schema);
146
146
  if (nested !== undefined) {
147
147
  return [
148
- ...nestedFields(field.name, field, nested, schema, defaultLocale),
148
+ ...nestedFields(field.name, field, nested, schema, defaultLocale, false,
149
+ // The declaring type is already on the path, exactly as the frame and
150
+ // the extraction seed theirs, so a lookup back to it is cut at the
151
+ // same depth here as it is there.
152
+ new Set([searchType.name])),
149
153
  ...identityCompanionFields(field, schema),
150
154
  ];
151
155
  }
@@ -287,10 +291,30 @@ function nestedFields(prefix, reference, nestedType, schema, defaultLocale, with
287
291
  }
288
292
  const deeper = nestedTypeOfNestedField(field, schema);
289
293
  if (deeper !== undefined && walked.has(deeper.name)) {
290
- // The cycle stops here, and so does the frame – nothing will ever be
291
- // stored under this field, so nothing is declared for it. Falling
292
- // through would declare it as a LEAF, whose scalar type is not what a
293
- // nested object would be stored as.
294
+ // The cycle stops here, and so does the frame – but a value is still
295
+ // stored: the extraction falls back to the target's key hop, and the
296
+ // projection writes the referent as an `{id}` object beside its identity
297
+ // leaf. So the BOUNDARY is declared and only the descent stops. Falling
298
+ // through instead would declare the field as a LEAF, whose scalar type
299
+ // is not what a nested object is stored as.
300
+ const boundary = nestedFieldName(prefix, field.name);
301
+ const boundaryArray = flattensToArray || field.array === true;
302
+ children.push({
303
+ name: boundary,
304
+ type: boundaryArray ? 'object[]' : 'object',
305
+ // Nothing under it is indexed – the key is a stored value, and the
306
+ // leaf a filter welds on sits beside the object, not inside it.
307
+ index: false,
308
+ optional: true,
309
+ }, {
310
+ // Always a Root Type, so always identified: `searchSchema` rejects
311
+ // inline cycles, so the only way to arrive at a type already on the
312
+ // path is a `local` lookup, whose target declares a class.
313
+ name: nestedFieldName(boundary, 'id'),
314
+ type: boundaryArray ? 'string[]' : 'string',
315
+ index: false,
316
+ optional: true,
317
+ }, ...nestedIdentityFields(prefix, field, schema));
294
318
  continue;
295
319
  }
296
320
  if (deeper !== undefined) {
@@ -0,0 +1,147 @@
1
+ import type { Client, CollectionCreateSchema } from 'typesense';
2
+ import type { CollectionFieldSchema } from 'typesense/lib/Typesense/Collection.js';
3
+ import type { Dataset } from '@lde/dataset';
4
+ import type { SearchSchema, SearchType } from '@lde/search';
5
+ /**
6
+ * Default ceiling on the distinct datasets a collection may hold before the
7
+ * membership sweep refuses to guess. The sweep needs the complete set to spot
8
+ * the ones that left, so a truncated facet would silently miss deletions.
9
+ */
10
+ export declare const DEFAULT_MAX_SWEEPABLE_SOURCES = 10000;
11
+ /** What {@link openDocuments} needs to know about the run it serves. */
12
+ export interface RunDocumentsOptions {
13
+ /** The type whose collection this is; its `key` chooses the regime. */
14
+ readonly searchType: SearchType;
15
+ /** The run’s id, stamped on documents of a single-dataset collection. */
16
+ readonly runId: string;
17
+ /** The run’s ISO 8601 start, the ordered stamp a keyed collection records –
18
+ * see {@link openDocuments}. */
19
+ readonly startedAt: string;
20
+ /** Documents per Typesense import request. */
21
+ readonly batchSize: number;
22
+ /** Most distinct datasets {@link RunDocuments.dropUnselected} may enumerate
23
+ * before it throws rather than risk missing one that left. */
24
+ readonly maxSweepableSources?: number;
25
+ }
26
+ /**
27
+ * One run’s writes into one collection, plus the four questions a writer can
28
+ * ask about what should leave it. Every one is phrased about *datasets*, never
29
+ * about filters or fields: which column carries membership, whether a document
30
+ * is owned or shared, and what a deletion physically is, are this module’s.
31
+ *
32
+ * The one ordering rule a caller must hold: {@link add} buffers, so anything
33
+ * that drops documents needs the buffer landed first. Each `drop` does that
34
+ * itself; a caller only needs {@link flush} before it finishes the run.
35
+ */
36
+ export interface RunDocuments<TDocument extends {
37
+ id: string;
38
+ }> {
39
+ /** Write a dataset’s documents, batched. */
40
+ add(dataset: Dataset, documents: AsyncIterable<TDocument>): Promise<void>;
41
+ /** Land whatever is still buffered. */
42
+ flush(): Promise<void>;
43
+ /**
44
+ * Drop what this dataset had and this run did not rewrite – the sweep after
45
+ * a dataset succeeds. A document another dataset still references survives,
46
+ * having lost only this one’s membership.
47
+ */
48
+ dropUnwritten(dataset: Dataset): Promise<void>;
49
+ /**
50
+ * Drop only what this run wrote for the dataset, keeping its earlier
51
+ * documents – the pipeline’s dump-fallback discard.
52
+ */
53
+ dropWrittenThisRun(dataset: Dataset): Promise<void>;
54
+ /** Drop everything this dataset contributed, whenever it was written. */
55
+ dropAll(dataset: Dataset): Promise<void>;
56
+ /**
57
+ * Drop what belongs to datasets outside the run’s selection: the registry
58
+ * membership sweep. Enumerating what the collection holds is this module’s
59
+ * business, and so is refusing to sweep when it cannot see all of it.
60
+ *
61
+ * @param selectedDatasets Every dataset IRI the run’s selector produced,
62
+ * including datasets skipped as unchanged – selection is membership.
63
+ */
64
+ dropUnselected(selectedDatasets: Iterable<string>): Promise<void>;
65
+ }
66
+ /**
67
+ * Open a run’s documents on a collection.
68
+ *
69
+ * Two regimes, chosen once from the type and never surfaced again:
70
+ *
71
+ * - a type with no {@link RootType.key} keys every document on its own node
72
+ * IRI, so a document belongs to exactly one dataset. It is stamped with that
73
+ * dataset and the run id, and a drop is a delete.
74
+ * - a **canonically keyed** type keys documents on an identifier several
75
+ * datasets can share – two datasets referencing the same GeoNames place
76
+ * produce one document. Membership is therefore a set: a write **adds** the
77
+ * dataset to `referenced_by`, a drop **removes** it, and the document is
78
+ * deleted when the last referring dataset goes. That is what stops one
79
+ * dataset’s sweep from deleting what another still points at, without any
80
+ * special case for a dataset skipped as unchanged – its membership simply
81
+ * stays, untouched by anyone else’s sweep.
82
+ *
83
+ * The run is recorded on a keyed collection as `Date.parse(startedAt)` rather
84
+ * than as the run id, because *this dataset did not write it this run* has to
85
+ * be asked of one element of a nested array, and Typesense correlates a
86
+ * comparison within an element but **not** a negation – `run:<thisRun`
87
+ * correlates, `run:!=thisRun` silently answers at document level and sweeps
88
+ * nothing. See `test/membership-filters.integration.test.ts`, where both are
89
+ * pinned against the engine.
90
+ */
91
+ export declare function openDocuments<TDocument extends {
92
+ id: string;
93
+ }>(client: Client, collection: string, options: RunDocumentsOptions): RunDocuments<TDocument>;
94
+ /**
95
+ * The collection definition plus the bookkeeping a writer keeps on it: the
96
+ * private membership columns, and the nesting a keyed collection’s
97
+ * `referenced_by` needs enabled.
98
+ *
99
+ * A single-dataset collection carries `source` only when the type declares no
100
+ * dataset field of its own; when it does, that field already holds the same
101
+ * IRI ({@link provenanceFieldOf}) and a second column would be the same value
102
+ * twice, free to drift.
103
+ */
104
+ export declare function withBookkeeping(definition: CollectionCreateSchema, searchType: SearchType): CollectionCreateSchema;
105
+ /**
106
+ * Reject a {@link SearchType} whose declaration cannot carry this module’s
107
+ * bookkeeping, at writer construction – so it fails before a run touches the
108
+ * index rather than at the sweep, after the writes.
109
+ *
110
+ * A **canonically keyed** type may not declare a field over the dataset. Such a
111
+ * field asks *which dataset is this document’s*, and a document several
112
+ * datasets share has no answer: it would hold whichever wrote last, and a user
113
+ * filtering by one of the others would silently miss it.
114
+ *
115
+ * A single-dataset type declaring one makes it the collection’s provenance
116
+ * column, so it has to answer that question the way the private `source` did:
117
+ * not `array` (Typesense reads `field:=[a,b]` over a `string[]` as *contains
118
+ * any*, so a departed dataset would take every document merely mentioning it);
119
+ * no `transform` (the sweep matches the stored value against the run’s raw
120
+ * dataset IRIs); and `facetable` on its own name where the writer enumerates
121
+ * the indexed datasets by faceting it – a reference inheriting a facet policy
122
+ * facets a companion holding only the admitted keys, so enumerating through it
123
+ * would miss every dataset the policy excludes.
124
+ *
125
+ * @param requireFacetable Whether this writer enumerates the indexed datasets
126
+ * ({@link RunDocuments.dropUnselected}); a writer that only ever filters by a
127
+ * known IRI does not need the column facetable.
128
+ */
129
+ export declare function assertWritableType(searchType: SearchType, options: {
130
+ readonly requireFacetable: boolean;
131
+ readonly schema?: SearchSchema;
132
+ }): void;
133
+ /**
134
+ * Fail an EXISTING collection that predates its type being canonically keyed,
135
+ * naming the two steps that fix it.
136
+ *
137
+ * The field cannot be added: nesting is a create-time collection setting, and
138
+ * even where it could be it would be **empty** on every document already
139
+ * indexed. Membership is only written by a dataset that reprocesses, and
140
+ * skip-unchanged means most do not – so those documents would match no
141
+ * dataset, never be retracted and never be collected, while looking perfectly
142
+ * healthy. Dropping the collection alone has the same hole: a rebuilt
143
+ * collection is repopulated only by datasets that changed, so the pipeline
144
+ * version has to rotate with it to force every dataset through.
145
+ */
146
+ export declare function assertBookkeepingPresent(searchType: SearchType, existing: readonly CollectionFieldSchema[], collection: string): void;
147
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../src/documents.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AACnF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAY,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAmCtE;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,QAAS,CAAC;AAEpD,wEAAwE;AACxE,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;qCACiC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;mEAC+D;IAC/D,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY,CAAC,SAAS,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IAC5D,4CAA4C;IAC5C,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1E,uCAAuC;IACvC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;;;OAIG;IACH,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,yEAAyE;IACzE,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC;;;;;;;OAOG;IACH,cAAc,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAAC,SAAS,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAC5D,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,mBAAmB,GAC3B,YAAY,CAAC,SAAS,CAAC,CAIzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,sBAAsB,EAClC,UAAU,EAAE,UAAU,GACrB,sBAAsB,CA4BxB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE;IACP,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;CAChC,GACA,IAAI,CAoDN;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,SAAS,qBAAqB,EAAE,EAC1C,UAAU,EAAE,MAAM,GACjB,IAAI,CAWN"}