hazo_umetrics 0.1.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.
Files changed (43) hide show
  1. package/CHANGE_LOG.md +28 -0
  2. package/README.md +129 -0
  3. package/SETUP_CHECKLIST.md +118 -0
  4. package/config/hazo_umetrics_config.ini.sample +35 -0
  5. package/dist/api/index.d.ts +78 -0
  6. package/dist/api/index.d.ts.map +1 -0
  7. package/dist/api/index.js +375 -0
  8. package/dist/index.client.d.ts +23 -0
  9. package/dist/index.client.d.ts.map +1 -0
  10. package/dist/index.client.js +44 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +5 -0
  14. package/dist/lib/index.d.ts +7 -0
  15. package/dist/lib/index.d.ts.map +1 -0
  16. package/dist/lib/index.js +7 -0
  17. package/dist/server/cookie.d.ts +9 -0
  18. package/dist/server/cookie.d.ts.map +1 -0
  19. package/dist/server/cookie.js +32 -0
  20. package/dist/server/experiments.d.ts +30 -0
  21. package/dist/server/experiments.d.ts.map +1 -0
  22. package/dist/server/experiments.js +17 -0
  23. package/dist/server/flags.d.ts +44 -0
  24. package/dist/server/flags.d.ts.map +1 -0
  25. package/dist/server/flags.js +207 -0
  26. package/dist/server/ga4.d.ts +30 -0
  27. package/dist/server/ga4.d.ts.map +1 -0
  28. package/dist/server/ga4.js +19 -0
  29. package/dist/server/stats.d.ts +51 -0
  30. package/dist/server/stats.d.ts.map +1 -0
  31. package/dist/server/stats.js +129 -0
  32. package/dist/server/types.d.ts +65 -0
  33. package/dist/server/types.d.ts.map +1 -0
  34. package/dist/server/types.js +2 -0
  35. package/dist/ui/index.d.ts +2 -0
  36. package/dist/ui/index.d.ts.map +1 -0
  37. package/dist/ui/index.js +3 -0
  38. package/dist/ui/metrics_panel.d.ts +33 -0
  39. package/dist/ui/metrics_panel.d.ts.map +1 -0
  40. package/dist/ui/metrics_panel.js +195 -0
  41. package/migrations/db_setup_postgres.sql +116 -0
  42. package/migrations/db_setup_sqlite.sql +116 -0
  43. package/package.json +116 -0
package/CHANGE_LOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # hazo_umetrics — Change Log
2
+
3
+ ## 0.1.1 — 2026-06-12
4
+
5
+ **Fixed:**
6
+ - `StatTrend` now uses `hazo_ui`'s `LineChart` instead of a minimal SVG sparkline — renders X-axis date labels, Y-axis gridlines with min/mid/max, and a hover cursor + value tooltip. Requires `hazo_ui` peer dep (already declared).
7
+ - Test-app Admin Mount: `/api/hazo_auth/me` response parsed correctly (`{ authenticated, user, permissions }` flat shape); was checking for non-existent `{ ok, data }` wrapper — Admin Mount now renders for `metrics.view` users.
8
+ - Test-app Event Log eval form: field names corrected to `experimentKey`/`subjectId` (camelCase) matching the `evalVariant` handler; was sending `key`/`subject_id` → `400 BAD_REQUEST`.
9
+ - Test-app `globals.css`: import `hazo_ui/dist/styles.css` + `@theme inline` color-utility mapping fixes transparent `HazoUiDialog` panel on pure Tailwind v4 apps.
10
+
11
+ ## 0.1.0 — 2026-06-12
12
+
13
+ M0 Core: headless spine + jest-verified logic. Interactive test-app is M0 Interactive (next session).
14
+
15
+ **Added:**
16
+ - Package scaffold: 4 entry points (server / client / api / ui), version 0.1.0, peer deps at canonical versions.
17
+ - 7-table DB schema (SQLite + PostgreSQL): `hazo_umetrics_stat`, `_stat_def`, `_experiment`, `_assignment`, `_exposure`, `_ga4_connection`, `_dashboard`. All tables carry `scope_id`.
18
+ - Stat store: `recordStat` (append-only), `getLatestStat`, `getStatSeries`, `registerStatCollector`, `runStatSnapshot`, `statSnapshotJob` (MaintenanceJob shape for M1 hazo_jobs wiring).
19
+ - Flag engine: `cyrb53` pure-JS hash, `weightedBucket` (D-5 general named-variant), `resolveVariant` with 3 consent paths (logged-in sticky / anon+consent cookie / anon no-consent ephemeral), `getVariant`, `isEnabled`.
20
+ - Cookie: `signSubjectCookie` / `readSubjectCookie` via `hazo_secure/crypto` AES-GCM (D-7).
21
+ - GA4 stubs: `createGa4Client`, `createGa4Provisioner` (STUB M1).
22
+ - Experiment significance stubs: `computeSignificance`, `estimateSampleSize` (STUB M2).
23
+ - API route factories: `createStatHandlers`, `createExperimentHandlers`, `createGa4ConnectHandlers`, `createQueryHandlers`; injectable `getAuth` + `onAudit`; manage routes gate on `metrics.manage`.
24
+ - Client entry: `HazoMetricsProvider`, `useMetrics`, `getVariant`, `isEnabled`, `identify`, `recordStat` (client shells, M1 wiring deferred).
25
+ - UI entry: `MetricsPanel`, `StatCards`, `StatTrend` (minimal shells, M1 wiring deferred).
26
+ - 49 jest tests: stat suite (8), flags suite (18), API suite (23).
27
+ - `metrics.manage: 'metrics.manage'` added to `hazo_admin` `HAZO_ADMIN_PERMISSIONS` (hazo_admin v0.3.1).
28
+ - Config sample: `[env]`, `[ga4]`, `[stats]`, `[ab_cookie]`, `[retention]` sections.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # hazo_umetrics
2
+
3
+ Product analytics for hazo apps — GA4 hybrid + first-party stat store + feature flags.
4
+
5
+ Gives every hazo Next.js app one consistent, auth-gated way to understand user behaviour:
6
+ - **GA4 hybrid** — traffic, SEO, and conversion funnels read via the GA4 Data API; auto-provisioned via GA4 Admin API (M1).
7
+ - **Stat store** — first-party numeric metrics over time, collector pattern, sparkline history.
8
+ - **Feature flags** — deterministic weighted-variant bucketing, signed consent cookie, sticky assignment.
9
+ - **A/B experiments** — same engine as flags + significance (deferred until traffic justifies it).
10
+
11
+ All surfaces mount inside `hazo_admin` via the existing `kind:'metrics'` section; every read/write is `scope_id`-gated.
12
+
13
+ ## Status
14
+
15
+ M0 Core shipped. M0 Interactive test-app is live (Admin Mount, Event Log, Metrics, Autotest). See `design/master_plan.md`.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install hazo_umetrics
21
+ ```
22
+
23
+ **Peer dependencies (install the ones you use):**
24
+
25
+ ```bash
26
+ npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui
27
+ ```
28
+
29
+ Required env vars for cookie signing (A/B assignment):
30
+ ```
31
+ HAZO_UMETRICS_COOKIE_KEY_CURRENT=v1
32
+ HAZO_UMETRICS_COOKIE_KEY_v1=<base64-encoded 32-byte AES-256 key>
33
+ ```
34
+
35
+ ## Entry points
36
+
37
+ | Import path | Contents |
38
+ |---|---|
39
+ | `hazo_umetrics` | Server: recordStat, getLatestStat, getStatSeries, resolveVariant, weightedBucket, GA4/experiment stubs |
40
+ | `hazo_umetrics/api` | Route factories: createStatHandlers, createExperimentHandlers, createGa4ConnectHandlers, createQueryHandlers |
41
+ | `hazo_umetrics/client` | Client: HazoMetricsProvider, useMetrics, getVariant, isEnabled, identify, recordStat |
42
+ | `hazo_umetrics/ui` | UI: MetricsPanel, StatCards, StatTrend (uses hazo_ui LineChart — requires hazo_ui peer dep) |
43
+
44
+ ## Stat store
45
+
46
+ ```ts
47
+ import { recordStat, getLatestStat, getStatSeries, registerStatCollector, runStatSnapshot } from 'hazo_umetrics';
48
+
49
+ // Append a reading (always inserts, never upserts)
50
+ await recordStat(adapter, { scope_id: 'my-app', metric_key: 'active_users', value: 42 });
51
+
52
+ // Latest value
53
+ const stat = await getLatestStat(adapter, { scope_id: 'my-app', metric_key: 'active_users' });
54
+
55
+ // Series (ascending by captured_at)
56
+ const series = await getStatSeries(adapter, { scope_id: 'my-app', metric_key: 'active_users', limit: 30 });
57
+
58
+ // Register a collector (runs in runStatSnapshot)
59
+ registerStatCollector({
60
+ key: 'active_users',
61
+ label: 'Active users',
62
+ format: 'count',
63
+ collect: async (scope_id) => countActiveUsers(scope_id),
64
+ });
65
+
66
+ // Run all collectors (call on a schedule via hazo_jobs in M1)
67
+ const results = await runStatSnapshot(adapter, { scope_id: 'my-app' });
68
+ ```
69
+
70
+ ## Feature flags
71
+
72
+ ```ts
73
+ import { resolveVariant, getVariant, isEnabled } from 'hazo_umetrics';
74
+
75
+ // Evaluate a flag with logged-in user (sticky assignment)
76
+ const { variant } = await resolveVariant(adapter, {
77
+ experimentKey: 'new_dashboard',
78
+ scope_id: 'my-app',
79
+ subjectId: user.id,
80
+ consent: true,
81
+ });
82
+
83
+ // Boolean flag shorthand
84
+ const enabled = await isEnabled(adapter, { experimentKey: 'new_feature', scope_id, subjectId });
85
+ ```
86
+
87
+ ## API route factories
88
+
89
+ ```ts
90
+ import { createStatHandlers, createExperimentHandlers } from 'hazo_umetrics/api';
91
+
92
+ const statHandlers = createStatHandlers({ getAdapter: () => myAdapter });
93
+ // → statHandlers.getStat, statHandlers.getStatSeries, statHandlers.recordStat
94
+
95
+ const expHandlers = createExperimentHandlers({ getAdapter: () => myAdapter });
96
+ // → expHandlers.listExperiments (metrics.view), expHandlers.startExperiment (metrics.manage)
97
+ ```
98
+
99
+ ## DB setup
100
+
101
+ Run `db_setup_sqlite.sql` (SQLite) or `db_setup_postgres.sql` (PostgreSQL) once. Creates 7 tables, all prefixed `hazo_umetrics_`.
102
+
103
+ ## Config
104
+
105
+ Copy `config/hazo_umetrics_config.ini.sample` to `hazo_umetrics_config.ini` and fill in values. Sections: `[env]`, `[ga4]`, `[stats]`, `[ab_cookie]`, `[retention]`.
106
+
107
+ ## Tailwind / shadcn setup for UI components
108
+
109
+ `MetricsPanel` renders inside `HazoUiDialog` and uses shadcn token classes (`bg-background`, `text-muted-foreground`, etc.). If those tokens aren't defined, the dialog panel will appear transparent. In your `globals.css`:
110
+
111
+ ```css
112
+ @import "tailwindcss";
113
+ /* shadcn tokens — required for bg-background, border, muted colors etc. */
114
+ @import "../node_modules/hazo_ui/dist/styles.css";
115
+
116
+ @source "../node_modules/hazo_umetrics/dist";
117
+ @source "../node_modules/hazo_ui/dist";
118
+
119
+ /* Tailwind v4 — map tokens to utilities */
120
+ @theme inline {
121
+ --color-background: hsl(var(--background));
122
+ --color-foreground: hsl(var(--foreground));
123
+ --color-muted-foreground: hsl(var(--muted-foreground));
124
+ --color-border: hsl(var(--border));
125
+ /* add remaining tokens as needed — see hazo_ui/dist/styles.css for the full list */
126
+ }
127
+ ```
128
+
129
+ (Adjust `@import` and `@source` depth to match your project's distance from `node_modules`.)
@@ -0,0 +1,118 @@
1
+ # hazo_umetrics — Setup Checklist
2
+
3
+ Follow these steps when adding hazo_umetrics to a consuming application.
4
+
5
+ ## 1. Install the package
6
+
7
+ ```bash
8
+ npm install hazo_umetrics
9
+ ```
10
+
11
+ Install peer dependencies you need:
12
+
13
+ ```bash
14
+ npm install hazo_core hazo_connect hazo_auth hazo_secure hazo_audit hazo_api hazo_ui
15
+ ```
16
+
17
+ ## 2. Run DB migrations
18
+
19
+ **SQLite:**
20
+ ```bash
21
+ # Run db_setup_sqlite.sql against your SQLite database
22
+ node -e "
23
+ const Database = require('better-sqlite3');
24
+ const fs = require('fs');
25
+ const db = new Database(process.env.DATABASE_PATH);
26
+ db.exec(fs.readFileSync(require.resolve('hazo_umetrics/db_setup_sqlite.sql'), 'utf-8'));
27
+ db.close();
28
+ "
29
+ ```
30
+
31
+ **PostgreSQL:** Run `node_modules/hazo_umetrics/db_setup_postgres.sql` against your database.
32
+
33
+ ## 3. Set env vars for cookie signing
34
+
35
+ ```bash
36
+ # Generate a 32-byte key: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
37
+ HAZO_UMETRICS_COOKIE_KEY_CURRENT=v1
38
+ HAZO_UMETRICS_COOKIE_KEY_v1=<base64-encoded 32-byte key>
39
+ ```
40
+
41
+ ## 4. Add config file (optional)
42
+
43
+ Copy `node_modules/hazo_umetrics/config/hazo_umetrics_config.ini.sample` to your app's config directory as `hazo_umetrics_config.ini` and fill in `[ga4]` when ready for M1.
44
+
45
+ ## 5. Mount API routes (Next.js)
46
+
47
+ ```ts
48
+ // app/api/hazo_umetrics/stats/route.ts
49
+ import { createStatHandlers } from 'hazo_umetrics/api';
50
+ import { getHazoConnect } from '@/lib/db';
51
+
52
+ const handlers = createStatHandlers({ getAdapter: getHazoConnect });
53
+
54
+ export async function GET(req: Request) { return handlers.getStat(req); }
55
+ export async function POST(req: Request) { return handlers.recordStat(req); }
56
+ ```
57
+
58
+ ## 6. Wrap your app in HazoMetricsProvider (client)
59
+
60
+ ```tsx
61
+ // app/layout.tsx
62
+ import { HazoMetricsProvider } from 'hazo_umetrics/client';
63
+
64
+ export default function RootLayout({ children }) {
65
+ return (
66
+ <HazoMetricsProvider scopeId={process.env.NEXT_PUBLIC_SCOPE_ID}>
67
+ {children}
68
+ </HazoMetricsProvider>
69
+ );
70
+ }
71
+ ```
72
+
73
+ ## 7. Configure Tailwind (if you use hazo_umetrics UI components)
74
+
75
+ `MetricsPanel` uses shadcn token utilities (`bg-background`, `text-muted-foreground`, `border`, etc.) from `hazo_ui`. Without the token definitions the dialog panel will render transparent. In your `globals.css`:
76
+
77
+ ```css
78
+ @import "tailwindcss";
79
+ /* shadcn tokens — required so bg-background, border, muted etc. resolve */
80
+ @import "../node_modules/hazo_ui/dist/styles.css";
81
+
82
+ @source "../node_modules/hazo_umetrics/dist";
83
+ @source "../node_modules/hazo_ui/dist";
84
+
85
+ /* Tailwind v4 — map HSL tokens to color utilities */
86
+ @theme inline {
87
+ --color-background: hsl(var(--background));
88
+ --color-foreground: hsl(var(--foreground));
89
+ --color-card: hsl(var(--card));
90
+ --color-card-foreground: hsl(var(--card-foreground));
91
+ --color-popover: hsl(var(--popover));
92
+ --color-popover-foreground: hsl(var(--popover-foreground));
93
+ --color-primary: hsl(var(--primary));
94
+ --color-primary-foreground: hsl(var(--primary-foreground));
95
+ --color-secondary: hsl(var(--secondary));
96
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
97
+ --color-muted: hsl(var(--muted));
98
+ --color-muted-foreground: hsl(var(--muted-foreground));
99
+ --color-accent: hsl(var(--accent));
100
+ --color-accent-foreground: hsl(var(--accent-foreground));
101
+ --color-destructive: hsl(var(--destructive));
102
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
103
+ --color-border: hsl(var(--border));
104
+ --color-input: hsl(var(--input));
105
+ --color-ring: hsl(var(--ring));
106
+ }
107
+ ```
108
+
109
+ (Adjust `@import` and `@source` depth to match your project's distance from `node_modules`.)
110
+
111
+ ## 8. Smoke test
112
+
113
+ ```ts
114
+ import { recordStat, getLatestStat } from 'hazo_umetrics';
115
+ await recordStat(adapter, { scope_id: 'my-app', metric_key: 'setup_test', value: 1 });
116
+ const stat = await getLatestStat(adapter, { scope_id: 'my-app', metric_key: 'setup_test' });
117
+ console.assert(stat?.value === 1, 'hazo_umetrics setup OK');
118
+ ```
@@ -0,0 +1,35 @@
1
+ ; hazo_umetrics configuration
2
+ ; Copy to hazo_umetrics_config.ini and fill in values for your environment.
3
+ ; Env-var overrides: HAZO_UMETRICS_<SECTION>_<KEY> (uppercase, underscores).
4
+
5
+ [env]
6
+ ; Uncomment to hard-code the environment name (overrides HAZO_ENV / NODE_ENV).
7
+ ; env = development
8
+
9
+ [ga4]
10
+ ; GA4 property ID (format: "properties/123456789"). Leave blank until M1 GA4 connect.
11
+ ; property_id =
12
+ ; GA4 Measurement ID (format: "G-XXXXXXXX"). Required for client-side event tracking.
13
+ ; measurement_id =
14
+ ; GA4 Data API base URL (default: https://analyticsdata.googleapis.com/v1beta/)
15
+ ; api_base_url =
16
+
17
+ [stats]
18
+ ; Default snapshot cadence for collectors that don't specify their own.
19
+ ; default_cadence = hourly
20
+ ; How many stat rows to return in getStatSeries when no limit is passed.
21
+ ; default_series_limit = 100
22
+
23
+ [ab_cookie]
24
+ ; Cookie name set for anonymous variant assignments.
25
+ ; name = hazo_umetrics_ab
26
+ ; TTL in seconds (default: 90 days).
27
+ ; ttl_seconds = 7776000
28
+ ; Env var prefix for the AES-256-GCM signing key.
29
+ ; Set HAZO_UMETRICS_COOKIE_KEY_CURRENT=v1 and HAZO_UMETRICS_COOKIE_KEY_v1=<base64 32 bytes>.
30
+ ; key_env_prefix = HAZO_UMETRICS_COOKIE_KEY
31
+
32
+ [retention]
33
+ ; How many days of own-capture data (stats, exposures, assignments) to keep.
34
+ ; Retention jobs are M3 (hazo_jobs wiring); this value documents the intent.
35
+ ; days = 90
@@ -0,0 +1,78 @@
1
+ import type { HazoConnectAdapter } from 'hazo_connect/server';
2
+ export type GetAuthFn = (req: Request, opts: {
3
+ required_permissions?: string[];
4
+ scope_id?: string;
5
+ strict?: boolean;
6
+ }) => Promise<{
7
+ authenticated: boolean;
8
+ user: {
9
+ id: string;
10
+ } | null;
11
+ permissions: string[];
12
+ permission_ok: boolean;
13
+ scope_ok?: boolean;
14
+ }>;
15
+ export interface ApiFactoryOptions {
16
+ getAdapter: () => Promise<HazoConnectAdapter> | HazoConnectAdapter;
17
+ getAuth?: GetAuthFn;
18
+ onAudit?: (entry: {
19
+ action: string;
20
+ actor_id: string;
21
+ scope_id: string;
22
+ details?: unknown;
23
+ }) => Promise<void>;
24
+ }
25
+ export declare function createStatHandlers(options: ApiFactoryOptions): {
26
+ /**
27
+ * GET /...?scope_id=<id>&metric_key=<key>
28
+ * Returns the latest reading for the metric, or null if none.
29
+ */
30
+ getStat(req: Request): Promise<Response>;
31
+ /**
32
+ * GET /...?scope_id=<id>&metric_key=<key>[&since=<iso>][&limit=<n>]
33
+ * Returns time-series readings.
34
+ */
35
+ getStatSeries(req: Request): Promise<Response>;
36
+ /**
37
+ * POST /...
38
+ * Body: { scope_id, metric_key, value, app_id?, unit?, dimensions? }
39
+ * Appends a new stat reading.
40
+ */
41
+ recordStat(req: Request): Promise<Response>;
42
+ };
43
+ export declare function createExperimentHandlers(options: ApiFactoryOptions): {
44
+ /**
45
+ * GET /...?scope_id=<id>
46
+ * Lists all experiments for a scope.
47
+ */
48
+ listExperiments(req: Request): Promise<Response>;
49
+ /**
50
+ * POST /...
51
+ * Body: { experimentKey, scope_id, subjectId?, consent? }
52
+ * Resolves the variant for a subject.
53
+ */
54
+ evalVariant(req: Request): Promise<Response>;
55
+ /**
56
+ * POST /...
57
+ * Body: { key, scope_id }
58
+ * Transitions an experiment to 'running'. Requires metrics.manage.
59
+ */
60
+ startExperiment(req: Request): Promise<Response>;
61
+ /**
62
+ * POST /...
63
+ * Body: { key, scope_id }
64
+ * Transitions an experiment to 'stopped'. Requires metrics.manage.
65
+ */
66
+ stopExperiment(req: Request): Promise<Response>;
67
+ };
68
+ export declare function createGa4ConnectHandlers(options: ApiFactoryOptions): {
69
+ /** STUB (M1): Initiate GA4 OAuth connection for a scope. */
70
+ connect(req: Request): Promise<Response>;
71
+ /** STUB (M1): Provision a GA4 property for a scope. */
72
+ provision(req: Request): Promise<Response>;
73
+ };
74
+ export declare function createQueryHandlers(options: ApiFactoryOptions): {
75
+ /** STUB (M1): Execute a GA4 or stat-store query. */
76
+ query(req: Request): Promise<Response>;
77
+ };
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAY9D,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,KAC3E,OAAO,CAAC;IACX,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC,CAAC;AAEH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,GAAG,kBAAkB,CAAC;IACnE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB;AAiGD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB;IAIzD;;;OAGG;iBACgB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAqB9C;;;OAGG;uBACsB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAwBpD;;;;OAIG;oBACmB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;EA4CpD;AAoBD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,iBAAiB;IAW/D;;;OAGG;yBACwB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAqBtD;;;;OAIG;qBACoB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAiDlD;;;;OAIG;yBACwB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8CtD;;;;OAIG;wBACuB,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;EA8CxD;AAMD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,iBAAiB;IAI/D,4DAA4D;iBACzC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAe9C,uDAAuD;mBAClC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;EAenD;AAMD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB;IAI1D,oDAAoD;eACnC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;EAe/C"}