@sylwellsoftware/glue 0.1.0-alpha.1 → 0.3.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.
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Glue
2
2
 
3
- Glue is the platform-neutral reactive core used by Fray. The experimental
4
- `0.1.0-alpha.1` candidate is not yet published. Its implementation and tests
5
- are strict TypeScript; the ESM build includes declarations and declaration
6
- maps.
3
+ Glue is a small, platform-neutral reactive value and live-query library. Fray
4
+ uses it as its state/data-flow layer, but Glue does not depend on Fray, a DOM,
5
+ or any UI framework. Its implementation and tests are strict TypeScript; the
6
+ ESM build includes declarations and declaration maps.
7
7
 
8
- After publication, install it with pnpm:
8
+ Install with pnpm:
9
9
 
10
10
  ```bash
11
11
  pnpm add @sylwellsoftware/glue
@@ -15,6 +15,73 @@ Glue is ESM-only. Core emitters, derived state, and diagnostics support Node 22+
15
15
  and modern ESM runtimes without a DOM. `LiveQuery` needs `AbortController`, and
16
16
  `RestQueryHandler` needs Fetch and URL capabilities unless they are injected.
17
17
 
18
+ ## Design model
19
+
20
+ Glue models values that stay current rather than requests that callers must
21
+ manually rerun and redistribute. The same small read-side protocol applies to
22
+ local state, computed state, query inputs, and asynchronous results:
23
+
24
+ ```ts
25
+ interface ReadableEmitter<TValue, TError = unknown> {
26
+ get(): TValue
27
+ getFetchState(): FetchStateValue
28
+ getError(): TError | null
29
+ subscribe(listener: (notification: {
30
+ value: TValue
31
+ fetchState: FetchStateValue
32
+ error: TError | null
33
+ event: EventBubble<unknown> | null
34
+ }) => void): () => void
35
+ }
36
+ ```
37
+
38
+ That uniformity is the central design constraint. It lets a consumer bind to a
39
+ current value without knowing whether the value is mutable, derived, or backed
40
+ by asynchronous retrieval. Richer responsibilities remain separate:
41
+
42
+ | Concept | Responsibility |
43
+ | --- | --- |
44
+ | `BaseEmitter` | Synchronously readable value/snapshot, subscriptions, mapping, equality, diagnostics, and disposal |
45
+ | `Emitter` | An explicitly writable leaf value |
46
+ | `DerivedEmitter` | A cached value computed from one or more readable emitters |
47
+ | `QueryArg` | A named query-input view over another emitter when a semantic name is useful |
48
+ | `LiveQuery` | Reactive request timing, latest-request ownership, status/error state, and cached results |
49
+ | `QueryHandler` | Non-reactive retrieval strategy over a plain named argument object |
50
+ | `RestQueryHandler` | HTTP URL construction, wire serialization, Fetch execution, and JSON result retrieval |
51
+ | `EventBubble` / `EventBus` | Optional cause-and-effect diagnostics without owning application history |
52
+
53
+ The intended flow is explicit and one-directional:
54
+
55
+ ```text
56
+ Emitter(s) ──► DerivedEmitter(s) ──► named query arguments
57
+
58
+
59
+ LiveQuery
60
+ │ current plain values
61
+
62
+ QueryHandler
63
+
64
+
65
+ value + fetch state + error
66
+ ```
67
+
68
+ This separation prevents several kinds of accidental coupling:
69
+
70
+ - leaf values do not need to know that a distant consumer may use them in a
71
+ request;
72
+ - derived values express semantic computation rather than transport encoding;
73
+ - `LiveQuery` decides *when* the current inputs require retrieval, while its
74
+ handler decides *how* retrieval works;
75
+ - REST-specific formats, base URLs, authentication wrappers, and response
76
+ validation remain at application/adapter boundaries;
77
+ - one live query can be mapped into multiple local views without multiplying
78
+ network requests.
79
+
80
+ Glue deliberately favors explicit graphs over hidden tracking, proxy-created
81
+ state, hooks, or a global store. If a value can be computed, prefer a
82
+ `DerivedEmitter` to manually mirroring it. Use callbacks for commands and
83
+ emitters for state that other objects need to read, combine, or observe.
84
+
18
85
  ## Emitters
19
86
 
20
87
  ```ts
@@ -56,6 +123,42 @@ current ones.
56
123
  `emitter.map(fn)` transforms the complete value. `emitter.mapEach(fn)` requires
57
124
  an array and transforms its non-nullish members.
58
125
 
126
+ ### Ownership, equality, and disposal
127
+
128
+ Emitters eagerly cache their current snapshot so reads are synchronous.
129
+ `Object.is` is the default value equality rule; pass `equals` when the domain
130
+ has a better equivalence relation. A derived emitter subscribes eagerly to its
131
+ sources and releases those subscriptions when sources are replaced or the
132
+ derived value is disposed.
133
+
134
+ The object that creates a long-lived emitter/query normally owns its disposal.
135
+ Disposal is idempotent, prevents new subscriptions, and releases owned source
136
+ subscriptions. A UI or service lifecycle should therefore dispose the graph it
137
+ constructs rather than relying on garbage collection to sever active edges.
138
+
139
+ ## Query arguments
140
+
141
+ `LiveQuery` accepts a named record of any readable emitters, so wrapping every
142
+ input in `QueryArg` is neither required nor desirable. Use `QueryArg` when a
143
+ stable query-facing name and separately owned bridge clarify the boundary:
144
+
145
+ ```ts
146
+ import {DerivedEmitter, Emitter, LiveQuery, QueryArg} from '@sylwellsoftware/glue'
147
+
148
+ const firstName = new Emitter('Ada')
149
+ const lastName = new Emitter('Lovelace')
150
+ const searchText = new DerivedEmitter(
151
+ [firstName, lastName] as const,
152
+ ([first, last]) => `${first} ${last}`,
153
+ )
154
+ const search = new QueryArg('search', searchText)
155
+ const users = new LiveQuery({handler, args: {search}})
156
+ ```
157
+
158
+ Here the leaf emitters know nothing about querying, and the computation knows
159
+ nothing about REST. Dispose `users`, `search`, and `searchText` at the lifetime
160
+ boundary that created them.
161
+
59
162
  ## Live queries
60
163
 
61
164
  ```js
@@ -98,14 +201,47 @@ process-local IDs, timestamps, weak owner references where supported, and
98
201
  explicit parent/child causality. The bus retains neither event history nor
99
202
  owners; its unsubscribe function is idempotent.
100
203
 
101
- ## Experimental commands
204
+ Tracing follows the same design as data flow: mutation, derivation, query
205
+ start, and query completion can retain explicit parent/child causality without
206
+ turning diagnostics into a second execution system. Applications decide
207
+ whether to retain, render, or export observed events.
208
+
209
+ ## Async commands
102
210
 
103
- `@sylwellsoftware/glue/experimental` exports `AsyncCommand`, an abortable
211
+ `AsyncCommand` is exported from Glue's package root. It is an abortable
104
212
  mutation lifecycle with explicit `ignore`, `replace`, and `reject` concurrency
105
213
  policies. It exposes the last result/error through the standard emitter
106
214
  snapshot and a read-only `isRunning` view. It deliberately does not own batch
107
215
  progress, retries, notifications, or UI behavior. See
108
- [EXPERIMENTAL.md](EXPERIMENTAL.md) for the provisional contract.
216
+ the API reference below for the command contract.
217
+
218
+ ## Integration with Fray and other consumers
219
+
220
+ Glue's UI seam is intentionally just the readable/writable emitter protocol.
221
+ A typical Fray path is:
222
+
223
+ ```text
224
+ browser event
225
+ └──► Fray control writes an Emitter
226
+ └──► DerivedEmitter computes shared/domain state
227
+ ├──► Fray renders a local view
228
+ └──► LiveQuery refreshes through a handler
229
+ └──► Fray renders query snapshot state
230
+ ```
231
+
232
+ Leaf controls should normally receive ordinary writable emitters, not
233
+ `QueryArg` objects. The component or service that understands an aggregate
234
+ interaction owns its derived value. The data-aware consumer owns the query
235
+ bridge and watches the result it actually renders. This keeps UI components
236
+ reusable for local state, static data, remote data, and tests.
237
+
238
+ Fray's theme and color selection is not a Glue feature. An application may
239
+ store selected theme/color identifiers in ordinary Glue emitters when it wants
240
+ observable or persistent selection state, but Fray and the browser remain
241
+ responsible for CSS assets, stylesheet links, and rendering.
242
+
243
+ Nothing in this contract is Fray-specific: another UI framework, a CLI, a Node
244
+ service, or a test can consume the same emitters and live queries.
109
245
 
110
246
  ## Local checks
111
247
 
@@ -120,7 +256,7 @@ Glue intentionally does not own a DOM renderer, component lifecycle,
120
256
  application-specific query encoding, persistent event history, CommonJS build,
121
257
  or framework adapter. Fray consumes Glue as a peer; browser UI belongs there.
122
258
 
123
- See the [workspace overview](../../README.md), [alpha API
259
+ See the [workspace overview](../../README.md), [API
124
260
  surface](../../docs/API_SURFACE.md), [changelog](../../CHANGELOG.md),
125
261
  [contribution guide](../../CONTRIBUTING.md), and [security
126
262
  policy](../../SECURITY.md).
@@ -19,7 +19,7 @@ export interface AsyncCommandOptions<TArguments, TResult, TError = unknown> {
19
19
  export declare class AsyncCommandConcurrencyError extends Error {
20
20
  constructor();
21
21
  }
22
- /** Experimental abortable mutation state with an explicit concurrency policy. */
22
+ /** Abortable mutation state with an explicit concurrency policy. */
23
23
  export declare class AsyncCommand<TArguments, TResult, TError = unknown> extends BaseEmitter<TResult | undefined, TError> {
24
24
  readonly execute: AsyncCommandExecutor<TArguments, TResult>;
25
25
  readonly concurrency: AsyncCommandConcurrency;
@@ -1 +1 @@
1
- {"version":3,"file":"asyncCommand.d.ts","sourceRoot":"","sources":["../../src/commands/asyncCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,6BAA6B,CAAA;AACvD,OAAO,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAA;AACtD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,4BAA4B,CAAA;AAG/D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kCAAkC,CAAA;AAErE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAErE,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAC9C;AAED,MAAM,MAAM,oBAAoB,CAAC,UAAU,EAAE,OAAO,IAAI,CACpD,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,mBAAmB,KAC3B,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;AAEnC,MAAM,WAAW,mBAAmB,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IACtE,OAAO,EAAE,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IAClD,WAAW,CAAC,EAAE,uBAAuB,CAAA;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IACrC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB;AAWD,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD;AAED,iFAAiF;AACjF,qBAAa,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAC3D,SAAQ,WAAW,CAAC,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IAC3D,QAAQ,CAAC,WAAW,EAAE,uBAAuB,CAAA;IAC7C,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,2EAA2E;IAC3E,cAAc,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,IAAI,CAAO;gBAE9C,OAAO,EAAE,mBAAmB,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC;IAgCrE,GAAG,CACC,UAAU,EAAE,UAAU,EACtB,YAAY,GAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAuB,GAC7D,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAiE/B,KAAK,CAAC,YAAY,GAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAA2B,GAAG,OAAO;IAmBhF,KAAK,CAAC,YAAY,GAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAyB,GAAG,IAAI;IAelE,OAAO,IAAI,IAAI;IAWxB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,eAAe;CAO1B"}
1
+ {"version":3,"file":"asyncCommand.d.ts","sourceRoot":"","sources":["../../src/commands/asyncCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,6BAA6B,CAAA;AACvD,OAAO,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAA;AACtD,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,4BAA4B,CAAA;AAG/D,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,kCAAkC,CAAA;AAErE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAA;AAErE,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;CAC9C;AAED,MAAM,MAAM,oBAAoB,CAAC,UAAU,EAAE,OAAO,IAAI,CACpD,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE,mBAAmB,KAC3B,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;AAEnC,MAAM,WAAW,mBAAmB,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IACtE,OAAO,EAAE,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IAClD,WAAW,CAAC,EAAE,uBAAuB,CAAA;IACrC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IACrC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB;AAWD,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD;AAED,oEAAoE;AACpE,qBAAa,YAAY,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAC3D,SAAQ,WAAW,CAAC,OAAO,GAAG,SAAS,EAAE,MAAM,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IAC3D,QAAQ,CAAC,WAAW,EAAE,uBAAuB,CAAA;IAC7C,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IACxD,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,kBAAkB,CAAQ;IAClC,2EAA2E;IAC3E,cAAc,EAAE,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,IAAI,CAAO;gBAE9C,OAAO,EAAE,mBAAmB,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,CAAC;IAgCrE,GAAG,CACC,UAAU,EAAE,UAAU,EACtB,YAAY,GAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAuB,GAC7D,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAiE/B,KAAK,CAAC,YAAY,GAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAA2B,GAAG,OAAO;IAmBhF,KAAK,CAAC,YAAY,GAAE,WAAW,CAAC,OAAO,CAAC,GAAG,OAAyB,GAAG,IAAI;IAelE,OAAO,IAAI,IAAI;IAWxB,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,eAAe;CAO1B"}
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export type { BubbleGraph, EventListener } from './debugging/eventBus.js';
5
5
  export { BaseEmitter, DerivedEmitter } from './emitters/baseEmitter.js';
6
6
  export type { DerivedEmitterOptions, DerivedErrorEntry, DerivedErrors, EmitterNotification, EmitterOptions, EmitterValue, EmitterValues, MapOptions, ReadableEmitter, SnapshotUpdate, SubscribeOptions, } from './emitters/baseEmitter.js';
7
7
  export { Emitter } from './emitters/emitter.js';
8
+ export { AsyncCommand, AsyncCommandConcurrencyError } from './commands/asyncCommand.js';
9
+ export type { AsyncCommandConcurrency, AsyncCommandContext, AsyncCommandExecutor, AsyncCommandOptions, } from './commands/asyncCommand.js';
8
10
  export { LiveQuery } from './emitters/liveQuery.js';
9
11
  export type { LiveQueryOptions, QueryArgumentEmitters, QueryArgumentValues, } from './emitters/liveQuery.js';
10
12
  export { combineFetchStates, FetchState, FetchStateValues } from './enums/fetchState.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAA;AACtD,YAAY,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAA;AAChD,YAAY,EAAC,WAAW,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAEvE,OAAO,EAAC,WAAW,EAAE,cAAc,EAAC,MAAM,2BAA2B,CAAA;AACrE,YAAY,EACR,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,GACnB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAC,SAAS,EAAC,MAAM,yBAAyB,CAAA;AACjD,YAAY,EACR,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,GACtB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAC,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAC,QAAQ,EAAC,MAAM,6BAA6B,CAAA;AACpD,OAAO,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAA;AAC5D,YAAY,EACR,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,GACd,MAAM,iCAAiC,CAAA;AACxC,OAAO,EAAC,gBAAgB,EAAC,MAAM,qCAAqC,CAAA;AACpE,YAAY,EACR,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,OAAO,GACV,MAAM,qCAAqC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAC,MAAM,4BAA4B,CAAA;AACtD,YAAY,EAAC,YAAY,EAAC,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EAAC,QAAQ,EAAC,MAAM,yBAAyB,CAAA;AAChD,YAAY,EAAC,WAAW,EAAE,aAAa,EAAC,MAAM,yBAAyB,CAAA;AAEvE,OAAO,EAAC,WAAW,EAAE,cAAc,EAAC,MAAM,2BAA2B,CAAA;AACrE,YAAY,EACR,qBAAqB,EACrB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,GACnB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAC,OAAO,EAAC,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAC,YAAY,EAAE,4BAA4B,EAAC,MAAM,4BAA4B,CAAA;AACrF,YAAY,EACR,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACtB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAC,SAAS,EAAC,MAAM,yBAAyB,CAAA;AACjD,YAAY,EACR,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,GACtB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAC,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAC,MAAM,uBAAuB,CAAA;AACtF,YAAY,EAAC,eAAe,EAAC,MAAM,uBAAuB,CAAA;AAE1D,OAAO,EAAC,QAAQ,EAAC,MAAM,6BAA6B,CAAA;AACpD,OAAO,EAAC,YAAY,EAAC,MAAM,iCAAiC,CAAA;AAC5D,YAAY,EACR,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,GACd,MAAM,iCAAiC,CAAA;AACxC,OAAO,EAAC,gBAAgB,EAAC,MAAM,qCAAqC,CAAA;AACpE,YAAY,EACR,SAAS,EACT,gBAAgB,EAChB,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,OAAO,GACV,MAAM,qCAAqC,CAAA"}