@livestore/react 0.2.0-dev.2 → 0.3.0-dev.10

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 (38) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/LiveStoreProvider.d.ts +6 -4
  3. package/dist/LiveStoreProvider.d.ts.map +1 -1
  4. package/dist/LiveStoreProvider.js +45 -28
  5. package/dist/LiveStoreProvider.js.map +1 -1
  6. package/dist/LiveStoreProvider.test.js +8 -2
  7. package/dist/LiveStoreProvider.test.js.map +1 -1
  8. package/dist/__tests__/fixture.d.ts +1 -2
  9. package/dist/__tests__/fixture.d.ts.map +1 -1
  10. package/dist/__tests__/fixture.js +6 -10
  11. package/dist/__tests__/fixture.js.map +1 -1
  12. package/dist/useQuery.d.ts +1 -1
  13. package/dist/useQuery.d.ts.map +1 -1
  14. package/dist/useQuery.js.map +1 -1
  15. package/dist/useRow.js.map +1 -1
  16. package/dist/useRow.test.js +13 -18
  17. package/dist/useRow.test.js.map +1 -1
  18. package/dist/useScopedQuery.d.ts +12 -1
  19. package/dist/useScopedQuery.d.ts.map +1 -1
  20. package/dist/useScopedQuery.js +13 -1
  21. package/dist/useScopedQuery.js.map +1 -1
  22. package/dist/utils/useStateRefWithReactiveInput.d.ts +1 -1
  23. package/dist/utils/useStateRefWithReactiveInput.d.ts.map +1 -1
  24. package/dist/utils/useStateRefWithReactiveInput.js.map +1 -1
  25. package/package.json +18 -17
  26. package/src/LiveStoreProvider.test.tsx +13 -2
  27. package/src/LiveStoreProvider.tsx +68 -36
  28. package/src/__snapshots__/useRow.test.tsx.snap +54 -53
  29. package/src/__tests__/fixture.tsx +6 -11
  30. package/src/useQuery.ts +1 -1
  31. package/src/useRow.test.tsx +71 -71
  32. package/src/useRow.ts +1 -1
  33. package/src/useScopedQuery.ts +14 -2
  34. package/src/utils/useStateRefWithReactiveInput.ts +1 -1
  35. package/dist/useTemporaryQuery.d.ts +0 -22
  36. package/dist/useTemporaryQuery.d.ts.map +0 -1
  37. package/dist/useTemporaryQuery.js +0 -75
  38. package/dist/useTemporaryQuery.js.map +0 -1
package/src/useQuery.ts CHANGED
@@ -28,7 +28,7 @@ export const useQuery = <TQuery extends LiveQueryAny>(query: TQuery): GetResult<
28
28
  export const useQueryRef = <TQuery extends LiveQueryAny>(
29
29
  query$: TQuery,
30
30
  parentOtelContext?: otel.Context,
31
- ): React.MutableRefObject<GetResult<TQuery>> => {
31
+ ): React.RefObject<GetResult<TQuery>> => {
32
32
  const { store } = useStore()
33
33
 
34
34
  React.useDebugValue(`LiveStore:useQuery:${query$.id}:${query$.label}`)
@@ -10,6 +10,8 @@ import { describe, expect, it } from 'vitest'
10
10
  import { AppComponentSchema, AppRouterSchema, makeTodoMvcReact, tables, todos } from './__tests__/fixture.js'
11
11
  import * as LiveStoreReact from './mod.js'
12
12
 
13
+ // const strictMode = process.env.REACT_STRICT_MODE !== undefined
14
+
13
15
  // NOTE running tests concurrently doesn't work with the default global db graph
14
16
  describe('useRow', () => {
15
17
  it('should update the data based on component key', () =>
@@ -257,88 +259,86 @@ describe('useRow', () => {
257
259
  unmount()
258
260
  }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise))
259
261
 
260
- let cachedProvider: BasicTracerProvider | undefined
261
-
262
262
  describe('otel', () => {
263
- const exporter = new InMemorySpanExporter()
264
-
265
- const provider = cachedProvider ?? new BasicTracerProvider()
266
- cachedProvider = provider
267
- provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
263
+ const provider = new BasicTracerProvider({})
268
264
  provider.register()
269
265
 
270
- const otelTracer = otel.trace.getTracer('test')
266
+ it.each([{ strictMode: true }, { strictMode: false }])(
267
+ 'should update the data based on component key strictMode=%s',
268
+ async ({ strictMode }) => {
269
+ const exporter = new InMemorySpanExporter()
271
270
 
272
- const span = otelTracer.startSpan('test')
273
- const otelContext = otel.trace.setSpan(otel.context.active(), span)
271
+ // const provider = cachedProvider ?? new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
272
+ provider.addSpanProcessor(new SimpleSpanProcessor(exporter))
274
273
 
275
- it('should update the data based on component key', async () => {
276
- const { strictMode } = await Effect.gen(function* () {
277
- const { wrapper, store, reactivityGraph, makeRenderCount, strictMode } = yield* makeTodoMvcReact({
278
- useGlobalReactivityGraph: false,
279
- otelContext,
280
- otelTracer,
281
- })
274
+ const otelTracer = otel.trace.getTracer(`testing-${strictMode ? 'strict' : 'non-strict'}`)
282
275
 
283
- const renderCount = makeRenderCount()
276
+ const span = otelTracer.startSpan('test-root')
277
+ const otelContext = otel.trace.setSpan(otel.context.active(), span)
284
278
 
285
- const { result, rerender, unmount } = renderHook(
286
- (userId: string) => {
287
- renderCount.inc()
279
+ await Effect.gen(function* () {
280
+ const { wrapper, store, reactivityGraph, makeRenderCount } = yield* makeTodoMvcReact({
281
+ useGlobalReactivityGraph: false,
282
+ otelContext,
283
+ otelTracer,
284
+ strictMode,
285
+ })
288
286
 
289
- const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
290
- return { state, setState }
291
- },
292
- { wrapper, initialProps: 'u1' },
293
- )
287
+ const renderCount = makeRenderCount()
294
288
 
295
- expect(result.current.state.id).toBe('u1')
296
- expect(result.current.state.username).toBe('')
297
- expect(renderCount.val).toBe(1)
289
+ const { result, rerender, unmount } = renderHook(
290
+ (userId: string) => {
291
+ renderCount.inc()
298
292
 
299
- React.act(() =>
300
- store.mutate(
301
- LiveStore.rawSqlMutation({
302
- sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
303
- }),
304
- ),
305
- )
293
+ const [state, setState] = LiveStoreReact.useRow(AppComponentSchema, userId, { reactivityGraph })
294
+ return { state, setState }
295
+ },
296
+ { wrapper, initialProps: 'u1' },
297
+ )
306
298
 
307
- rerender('u2')
308
-
309
- expect(result.current.state.id).toBe('u2')
310
- expect(result.current.state.username).toBe('username_u2')
311
- expect(renderCount.val).toBe(2)
312
-
313
- unmount()
314
- span.end()
315
-
316
- return { strictMode }
317
- }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
318
-
319
- const mapAttributes = (attributes: otel.Attributes) => {
320
- return ReadonlyRecord.map(attributes, (val, key) => {
321
- if (key === 'stackInfo') {
322
- const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
323
- // stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
324
- stackInfo.frames.forEach((_) => {
325
- if (_.name.includes('renderHook.wrapper')) {
326
- _.name = 'renderHook.wrapper'
327
- }
328
- _.filePath = '__REPLACED_FOR_SNAPSHOT__'
329
- })
330
- return JSON.stringify(stackInfo)
331
- }
332
- return val
333
- })
334
- }
299
+ expect(result.current.state.id).toBe('u1')
300
+ expect(result.current.state.username).toBe('')
301
+ expect(renderCount.val).toBe(1)
335
302
 
336
- // TODO improve testing setup so "obsolete" warning is avoided
337
- if (strictMode) {
338
- expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=true')
339
- } else {
340
- expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot('strictMode=false')
341
- }
342
- })
303
+ React.act(() =>
304
+ store.mutate(
305
+ LiveStore.rawSqlMutation({
306
+ sql: LiveStore.sql`INSERT INTO UserInfo (id, username) VALUES ('u2', 'username_u2')`,
307
+ }),
308
+ ),
309
+ )
310
+
311
+ rerender('u2')
312
+
313
+ expect(result.current.state.id).toBe('u2')
314
+ expect(result.current.state.username).toBe('username_u2')
315
+ expect(renderCount.val).toBe(2)
316
+
317
+ unmount()
318
+ span.end()
319
+
320
+ return { strictMode }
321
+ }).pipe(Effect.scoped, Effect.tapCauseLogPretty, Effect.runPromise)
322
+
323
+ const mapAttributes = (attributes: otel.Attributes) => {
324
+ return ReadonlyRecord.map(attributes, (val, key) => {
325
+ if (key === 'stackInfo') {
326
+ const stackInfo = JSON.parse(val as string) as LiveStore.StackInfo
327
+ // stackInfo.frames.shift() // Removes `renderHook.wrapper` from the stack
328
+ stackInfo.frames.forEach((_) => {
329
+ if (_.name.includes('renderHook.wrapper')) {
330
+ _.name = 'renderHook.wrapper'
331
+ }
332
+ _.filePath = '__REPLACED_FOR_SNAPSHOT__'
333
+ })
334
+ return JSON.stringify(stackInfo)
335
+ }
336
+ return val
337
+ })
338
+ }
339
+
340
+ expect(getSimplifiedRootSpan(exporter, mapAttributes)).toMatchSnapshot()
341
+ },
342
+ )
343
343
  })
344
344
  })
package/src/useRow.ts CHANGED
@@ -120,7 +120,7 @@ export const useRow: {
120
120
  },
121
121
  )
122
122
 
123
- const query$Ref = useQueryRef(query$, otelContext) as React.MutableRefObject<RowQuery.Result<TTableDef>>
123
+ const query$Ref = useQueryRef(query$, otelContext) as React.RefObject<RowQuery.Result<TTableDef>>
124
124
 
125
125
  const setState = React.useMemo<StateSetters<TTableDef>>(() => {
126
126
  if (table.options.isSingleColumn) {
@@ -30,6 +30,17 @@ export type DepKey = string | number | ReadonlyArray<string | number>
30
30
  * Creates a query, subscribes and destroys it when the component unmounts.
31
31
  *
32
32
  * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
33
+ * This hook should be used instead of `useQuery` when the query should be dynamically created based on some props.
34
+ * Otherwise when using `useQuery` the query will be leaked (i.e. never destroyed) when the component re-renders/unmounts.
35
+ *
36
+ * Example:
37
+ * ```tsx
38
+ * const issue = useScopedQuery(() => queryDb(tables.issues.query.where('id', issueId).first()), ['issue-details', issueId])
39
+ * ```
40
+ *
41
+ * Important: On Expo/React Native please make sure the key contains a globally unique identifier, otherwise the query might get reused unintentionally.
42
+ * Example: `['issue-details', issueId]`
43
+ * See this issue to track progress: https://github.com/livestorejs/livestore/issues/231
33
44
  */
34
45
  export const useScopedQuery = <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey): TResult =>
35
46
  useScopedQueryRef(makeQuery, key).current
@@ -37,7 +48,7 @@ export const useScopedQuery = <TResult>(makeQuery: () => LiveQuery<TResult>, key
37
48
  export const useScopedQueryRef = <TResult>(
38
49
  makeQuery: () => LiveQuery<TResult>,
39
50
  key: DepKey,
40
- ): React.MutableRefObject<TResult> => {
51
+ ): React.RefObject<TResult> => {
41
52
  const { query$ } = useMakeScopedQuery(makeQuery, key)
42
53
 
43
54
  return useQueryRef(query$)
@@ -60,7 +71,7 @@ export const useMakeScopedQuery = <TResult, TQueryInfo extends QueryInfo>(
60
71
  () => (Array.isArray(key) ? key.join('-') : key) + '-' + store.reactivityGraph.id + '-' + makeQuery.toString(),
61
72
  [key, makeQuery, store.reactivityGraph.id],
62
73
  )
63
- const fullKeyRef = React.useRef<string>()
74
+ const fullKeyRef = React.useRef<string | undefined>(undefined)
64
75
 
65
76
  const { query$, otelContext } = React.useMemo(() => {
66
77
  if (fullKeyRef.current !== undefined && fullKeyRef.current !== fullKey) {
@@ -96,6 +107,7 @@ export const useMakeScopedQuery = <TResult, TQueryInfo extends QueryInfo>(
96
107
  )
97
108
 
98
109
  const otelContext = otel.trace.setSpan(otel.context.active(), span)
110
+ // console.debug('useScopedQuery:startSpan', fullKey, spanName)
99
111
 
100
112
  const query$ = makeQuery(otelContext)
101
113
 
@@ -12,7 +12,7 @@ import React from 'react'
12
12
  */
13
13
  export const useStateRefWithReactiveInput = <T>(
14
14
  inputState: T,
15
- ): [React.MutableRefObject<T>, (newState: T | ((prev: T) => T)) => void] => {
15
+ ): [React.RefObject<T>, (newState: T | ((prev: T) => T)) => void] => {
16
16
  const [_, rerender] = React.useState(0)
17
17
 
18
18
  const lastKnownInputStateRef = React.useRef<T>(inputState)
@@ -1,22 +0,0 @@
1
- import type { QueryInfo } from '@livestore/common';
2
- import type { LiveQuery } from '@livestore/livestore';
3
- import * as otel from '@opentelemetry/api';
4
- import React from 'react';
5
- export type DepKey = string | number | ReadonlyArray<string | number>;
6
- /**
7
- * Creates a query, subscribes and destroys it when the component unmounts.
8
- *
9
- * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
10
- */
11
- export declare const useScopedQuery: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => TResult;
12
- export declare const useScopedQueryRef: <TResult>(makeQuery: () => LiveQuery<TResult>, key: DepKey) => React.MutableRefObject<TResult>;
13
- export declare const useMakeScopedQuery: <TResult, TQueryInfo extends QueryInfo>(makeQuery: (otelContext: otel.Context) => LiveQuery<TResult, TQueryInfo>, key: DepKey, options?: {
14
- otel?: {
15
- spanName?: string;
16
- attributes?: otel.Attributes;
17
- };
18
- }) => {
19
- query$: LiveQuery<TResult, TQueryInfo>;
20
- otelContext: otel.Context;
21
- };
22
- //# sourceMappingURL=useTemporaryQuery.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useTemporaryQuery.d.ts","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AAuBzB,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,CAAA;AAErE;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,aAAa,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,MAAM,KAAG,OAChD,CAAA;AAE3C,eAAO,MAAM,iBAAiB,GAAI,OAAO,aAC5B,MAAM,SAAS,CAAC,OAAO,CAAC,OAC9B,MAAM,KACV,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAIhC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,EAAE,UAAU,SAAS,SAAS,aAC3D,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,OACnE,MAAM,YACD;IACR,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,UAAU,CAAC,EAAE,IAAI,CAAC,UAAU,CAAA;KAC7B,CAAA;CACF,KACA;IAAE,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAA;CA4ErE,CAAA"}
@@ -1,75 +0,0 @@
1
- import * as otel from '@opentelemetry/api';
2
- import React from 'react';
3
- import { useStore } from './LiveStoreContext.js';
4
- import { useQueryRef } from './useQuery.js';
5
- // NOTE Given `useMemo` will be called multiple times (e.g. when using React Strict mode or Fast Refresh),
6
- // we are using this cache to avoid starting multiple queries/spans for the same component.
7
- // This is somewhat against some recommended React best practices, but it should be fine in our case below.
8
- // Please definitely open an issue if you see or run into any problems with this approach!
9
- const cache = new Map();
10
- /**
11
- * Creates a query, subscribes and destroys it when the component unmounts.
12
- *
13
- * The `key` is used to determine whether the a new query should be created or if the existing one should be reused.
14
- */
15
- export const useScopedQuery = (makeQuery, key) => useScopedQueryRef(makeQuery, key).current;
16
- export const useScopedQueryRef = (makeQuery, key) => {
17
- const { query$ } = useMakeScopedQuery(makeQuery, key);
18
- return useQueryRef(query$);
19
- };
20
- export const useMakeScopedQuery = (makeQuery, key, options) => {
21
- const { store } = useStore();
22
- const fullKey = React.useMemo(
23
- // NOTE We're using the `makeQuery` function body string to make sure the key is unique across the app
24
- // TODO we should figure out whether this could cause some problems and/or if there's a better way to do this
25
- () => (Array.isArray(key) ? key.join('-') : key) + '-' + store.reactivityGraph.id + '-' + makeQuery.toString(), [key, makeQuery, store.reactivityGraph.id]);
26
- const fullKeyRef = React.useRef();
27
- const { query$, otelContext } = React.useMemo(() => {
28
- if (fullKeyRef.current !== undefined && fullKeyRef.current !== fullKey) {
29
- // console.debug('fullKey changed', 'prev', fullKeyRef.current.split('-')[0]!, '-> new', fullKey.split('-')[0]!)
30
- const cachedItem = cache.get(fullKeyRef.current);
31
- if (cachedItem !== undefined && cachedItem._tag === 'active') {
32
- cachedItem.rc--;
33
- if (cachedItem.rc === 0) {
34
- // console.debug('rc=0-changed', cachedItem.query$.id, cachedItem.query$.label)
35
- cachedItem.query$.destroy();
36
- cachedItem.span.end();
37
- cache.set(fullKeyRef.current, { _tag: 'destroyed' });
38
- }
39
- }
40
- }
41
- const cachedItem = cache.get(fullKey);
42
- if (cachedItem !== undefined && cachedItem._tag === 'active') {
43
- // console.debug('rc++', cachedItem.query$.id, cachedItem.query$.label)
44
- cachedItem.rc++;
45
- return cachedItem;
46
- }
47
- const spanName = options?.otel?.spanName ?? `LiveStore:useScopedQuery:${key}`;
48
- const span = store.otel.tracer.startSpan(spanName, { attributes: options?.otel?.attributes }, store.otel.queriesSpanContext);
49
- const otelContext = otel.trace.setSpan(otel.context.active(), span);
50
- const query$ = makeQuery(otelContext);
51
- cache.set(fullKey, { _tag: 'active', rc: 1, query$, span, otelContext });
52
- return { query$, otelContext };
53
- // eslint-disable-next-line react-hooks/exhaustive-deps
54
- }, [fullKey]);
55
- fullKeyRef.current = fullKey;
56
- React.useEffect(() => {
57
- return () => {
58
- const fullKey = fullKeyRef.current;
59
- const cachedItem = cache.get(fullKey);
60
- // NOTE in case the fullKey changed then the query was already destroyed in the useMemo above
61
- if (cachedItem === undefined || cachedItem._tag === 'destroyed')
62
- return;
63
- // console.debug('rc--', cachedItem.query$.id, cachedItem.query$.label)
64
- cachedItem.rc--;
65
- if (cachedItem.rc === 0) {
66
- // console.debug('rc=0', cachedItem.query$.id, cachedItem.query$.label)
67
- cachedItem.query$.destroy();
68
- cachedItem.span.end();
69
- cache.delete(fullKey);
70
- }
71
- };
72
- }, []);
73
- return { query$, otelContext };
74
- };
75
- //# sourceMappingURL=useTemporaryQuery.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useTemporaryQuery.js","sourceRoot":"","sources":["../src/useTemporaryQuery.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,IAAI,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,0GAA0G;AAC1G,2FAA2F;AAC3F,2GAA2G;AAC3G,0FAA0F;AAC1F,MAAM,KAAK,GAAG,IAAI,GAAG,EAYlB,CAAA;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAU,SAAmC,EAAE,GAAW,EAAW,EAAE,CACnG,iBAAiB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,OAAO,CAAA;AAE3C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,SAAmC,EACnC,GAAW,EACsB,EAAE;IACnC,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAErD,OAAO,WAAW,CAAC,MAAM,CAAC,CAAA;AAC5B,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,SAAwE,EACxE,GAAW,EACX,OAKC,EACsE,EAAE;IACzE,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO;IAC3B,sGAAsG;IACtG,6GAA6G;IAC7G,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,eAAe,CAAC,EAAE,GAAG,GAAG,GAAG,SAAS,CAAC,QAAQ,EAAE,EAC9G,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAC3C,CAAA;IACD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,EAAU,CAAA;IAEzC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QACjD,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,IAAI,UAAU,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YACvE,gHAAgH;YAEhH,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;YAChD,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7D,UAAU,CAAC,EAAE,EAAE,CAAA;gBAEf,IAAI,UAAU,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;oBACxB,+EAA+E;oBAC/E,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;oBAC3B,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAA;oBACrB,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;gBACtD,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACrC,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7D,uEAAuE;YACvE,UAAU,CAAC,EAAE,EAAE,CAAA;YAEf,OAAO,UAAU,CAAA;QACnB,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,4BAA4B,GAAG,EAAE,CAAA;QAE7E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CACtC,QAAQ,EACR,EAAE,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EACzC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAC9B,CAAA;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAA;QAEnE,MAAM,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,CAAA;QAErC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;QAExE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;QAC9B,uDAAuD;IACzD,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAA;IAEb,UAAU,CAAC,OAAO,GAAG,OAAO,CAAA;IAE5B,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,OAAO,GAAG,EAAE;YACV,MAAM,OAAO,GAAG,UAAU,CAAC,OAAQ,CAAA;YACnC,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACrC,6FAA6F;YAC7F,IAAI,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,KAAK,WAAW;gBAAE,OAAM;YAEvE,uEAAuE;YAEvE,UAAU,CAAC,EAAE,EAAE,CAAA;YAEf,IAAI,UAAU,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC;gBACxB,uEAAuE;gBACvE,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;gBAC3B,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAA;gBACrB,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACvB,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;AAChC,CAAC,CAAA"}