@arki/dot 0.1.2 → 0.1.4

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
@@ -3,26 +3,31 @@
3
3
  > TypeScript-first application composition framework for the ARKI package family.
4
4
 
5
5
  `@arki/dot` is the kernel that wires **pips**, lifecycle hooks, dependency
6
- graphs, and diagnostics into a deterministic application boot. It gives library
7
- authors a stable contract for declaring how their package participates in an
8
- app, and gives app developers a single place to wire those packages together.
6
+ injection, and diagnostics into a deterministic application boot. It gives
7
+ library authors a stable contract for declaring how their package
8
+ participates in an app, and gives app developers a single place to wire
9
+ those packages together — with the type checker verifying the wiring before
10
+ anything runs.
9
11
 
10
12
  ## What is a pip?
11
13
 
12
14
  A **pip** is the unit a DOT app is built from — one self-describing,
13
15
  lifecycle-aware piece of an application. Each pip:
14
16
 
15
- - declares a **name**, **version**, and the kinds of **services it provides**;
17
+ - declares a **name**, **version**, and the services it **needs** as a shape
18
+ of type witnesses;
19
+ - **provides** services by returning them from its `boot` hook — provides
20
+ are *inferred from the return type*, never declared separately;
16
21
  - runs a **5-hook lifecycle** — `configure` → `boot` → `start` → `stop` → `dispose`;
17
- - **publishes typed services** to a shared, type-safe registry that later pips
18
- can read from;
19
- - **composes deterministically** — pips boot in declaration order and dispose
20
- in reverse.
22
+ - **publishes typed services** to a shared, type-safe registry that later
23
+ pips can read from;
24
+ - **composes deterministically** — pips boot in declaration order and
25
+ dispose in reverse.
21
26
 
22
27
  The name comes from the small dots on dice, dominoes, and music notation:
23
- each pip is one small mark, and the *combination* of pips is what gives the app
24
- its value. Two pips on a die make a value of two; six pips make six. The pips
25
- **are** the app — not optional add-ons to a hidden core.
28
+ each pip is one small mark, and the *combination* of pips is what gives the
29
+ app its value. Two pips on a die make a value of two; six pips make six. The
30
+ pips **are** the app — not optional add-ons to a hidden core.
26
31
 
27
32
  ```ts
28
33
  import { defineApp } from '@arki/dot';
@@ -61,6 +66,19 @@ bun add @arki/dot
61
66
  ```ts
62
67
  import { defineApp, pip, service } from '@arki/dot';
63
68
 
69
+ type Db = { query(sql: string): Promise<unknown[]> };
70
+
71
+ const dbPip = pip({
72
+ name: 'database',
73
+ async boot() {
74
+ const db = await openDb(process.env.DATABASE_URL);
75
+ return { db }; // ← this IS the provides declaration
76
+ },
77
+ async dispose({ db }) {
78
+ await db.close();
79
+ },
80
+ });
81
+
64
82
  const billingPip = pip({
65
83
  name: 'billing',
66
84
  version: '1.0.0',
@@ -94,6 +112,319 @@ await app.stop();
94
112
  await app.dispose();
95
113
  ```
96
114
 
115
+ There is no registry to configure, no decorators, no reflection. The `needs`
116
+ shape is the injection contract; the `boot` return type is the provision
117
+ contract; the builder's type-level guard connects the two.
118
+
119
+ ## Dependency injection
120
+
121
+ ### Needs are witnesses, provides are inferred
122
+
123
+ `service<T>()` creates a phantom **type witness** — a runtime no-op that
124
+ carries `T` at the type level. The property name you give it is your local
125
+ alias *and* (for anonymous witnesses) the wire key:
126
+
127
+ ```ts
128
+ const search = pip({
129
+ name: 'search',
130
+ needs: {
131
+ db: service<Db>(), // wire key 'db', local alias 'db'
132
+ log: service<Logger>(), // wire key 'log'
133
+ },
134
+ async boot({ db, log, $app }) { // $app/$pip/$config: kernel context
135
+ log.info(`indexing for ${$app}`);
136
+ return { search: await buildIndex(db) };
137
+ },
138
+ });
139
+ ```
140
+
141
+ `start`, `stop`, and `dispose` additionally receive the pip's **own
142
+ provides** — and because teardown runs in reverse declaration order, a pip's
143
+ needs are still alive in its `dispose`:
144
+
145
+ ```ts
146
+ async dispose({ search, db }) { // own provide + still-live need
147
+ await search.flush(db);
148
+ },
149
+ ```
150
+
151
+ ### Wrong wiring does not compile
152
+
153
+ Declaration order IS boot order. The `.use()` guard makes unsatisfied needs,
154
+ type mismatches, and key collisions **compile errors at the call site**:
155
+
156
+ ```ts
157
+ defineApp('shop')
158
+ .use(billing) // ❌ "Expected 2 arguments, but got 1."
159
+ .use(database); // billing's `db` need has no earlier provider
160
+
161
+ defineApp('shop')
162
+ .use(database)
163
+ .use(database2); // ❌ collision: two providers of wire key 'db'
164
+ ```
165
+
166
+ Reordering the first example — or `rename`-ing one provider in the second —
167
+ makes both compile. The kernel re-validates at runtime with coded errors
168
+ (`E012` unsatisfied need, `E013` collision, `E014` reserved key) for
169
+ erased/dynamic composition, with full rollback of already-booted pips.
170
+
171
+ There is no dependency graph to debug and **cycles are unrepresentable**:
172
+ services flow strictly forward through the `.use()` chain. If two pips need
173
+ each other's services, that's a design smell the type system surfaces
174
+ immediately — merge them, or extract the shared piece into a third pip both
175
+ consume.
176
+
177
+ ### Tokens — cross-package service contracts
178
+
179
+ An anonymous `service<T>()` couples consumer and provider by property name.
180
+ When a contract spans packages, a **token** owns the wire key and the local
181
+ alias becomes free choice:
182
+
183
+ ```ts
184
+ // The package that owns the contract exports the token:
185
+ export const Db = token<NodePgDatabase<Schema>>()('arki.db');
186
+
187
+ // A provider publishes under the token's key:
188
+ const database = pip({
189
+ name: 'database',
190
+ async boot() {
191
+ return provide(Db, await connect()); // → { 'arki.db': NodePgDatabase }
192
+ },
193
+ });
194
+
195
+ // Any consumer, any package, any local alias:
196
+ const reports = pip({
197
+ name: 'reports',
198
+ needs: { warehouse: Db }, // wire key 'arki.db', alias yours
199
+ async boot({ warehouse }) { /* ... */ },
200
+ });
201
+ ```
202
+
203
+ ### Multi-instance with `rename`
204
+
205
+ Mounting an adapter twice would collide on its wire keys — loudly, at
206
+ compile time. `rename` is the multi-instance primitive, and
207
+ `Token.instance()` derives the matching contract:
208
+
209
+ ```ts
210
+ const ReportsDb = Db.instance('reports'); // token for key 'arki.db#reports'
211
+
212
+ const app = await defineApp('shop')
213
+ .use(database({ url: PRIMARY_URL }))
214
+ .use(rename(
215
+ database({ url: REPLICA_URL }),
216
+ { 'arki.db': ReportsDb.key }, // republish under the derived key
217
+ 'reports-db', // distinct pip name
218
+ ))
219
+ .use(pip({
220
+ name: 'analytics',
221
+ needs: { primary: Db, replica: ReportsDb },
222
+ async boot({ primary, replica }) { /* two live instances, both typed */ },
223
+ }))
224
+ .boot();
225
+ ```
226
+
227
+ Renames compose left-to-right and are applied at publish time — collision
228
+ checks see the final keys.
229
+
230
+ ### Lazy services
231
+
232
+ `lazy(init, { dispose })` publishes a **handle** instead of an open
233
+ resource. Initialization runs on first `get()` (memoized, single-flight;
234
+ failed attempts retry). Never-touched handles never initialize — and the
235
+ kernel auto-disposes initialized ones during teardown, *after* the
236
+ publishing pip's own `dispose` hook, even if that pip has none:
237
+
238
+ ```ts
239
+ const search = pip({
240
+ name: 'search',
241
+ async boot() {
242
+ return {
243
+ search: lazy(() => connectToElastic(), {
244
+ dispose: client => client.close(),
245
+ }),
246
+ };
247
+ },
248
+ });
249
+ ```
250
+
251
+ Consumers that shouldn't care whether a provider is eager or lazy declare a
252
+ **lifting witness** — `service.lazy<T>()` always delivers a `Lazy<T>`
253
+ handle, wrapping eager provides and passing lazy ones through by identity:
254
+
255
+ ```ts
256
+ const suggestions = pip({
257
+ name: 'suggestions',
258
+ needs: { search: service.lazy<SearchClient>() },
259
+ async start({ search }) {
260
+ const client = await search.get(); // first access initializes
261
+ /* ... */
262
+ },
263
+ });
264
+ ```
265
+
266
+ Swapping the search provider between eager and lazy is invisible to every
267
+ consumer — the wiring guard accepts both shapes against the same witness.
268
+
269
+ ### Kernel context — the reserved `$` namespace
270
+
271
+ Every service-carrying hook context includes `$app` (app name), `$pip` (pip
272
+ name), and `$config` (the `defineApp(name, { config })` bag). The `$` prefix
273
+ is enforced as reserved: `pip()` rejects `$`-prefixed needs aliases and
274
+ publish keys at compile time, and the kernel re-validates at runtime
275
+ (`DOT_LIFECYCLE_E014`) — kernel keys can never be shadowed.
276
+
277
+ ## A complex setup
278
+
279
+ A distilled commerce platform showing everything above working together.
280
+ (The package's stress-test suite boots a 28-pip version of this and asserts
281
+ exact boot/teardown ordering.)
282
+
283
+ ```ts
284
+ import { defineApp, lazy, pip, provide, rename, service, token } from '@arki/dot';
285
+
286
+ // ── contracts.ts — tokens owned by their respective packages ─────────
287
+ export const Env = token<AppEnv>()('shop.env');
288
+ export const Db = token<DbHandle>()('shop.db');
289
+ export const ReportsDb = Db.instance('reports');
290
+ export const Cache = token<CacheHandle>()('shop.cache');
291
+ export const Bus = token<MessageBus>()('shop.bus');
292
+
293
+ // ── infrastructure pips ───────────────────────────────────────────────
294
+ const env = pip({
295
+ name: 'env',
296
+ boot: ({ $config }) => provide(Env, parseEnv($config)),
297
+ });
298
+
299
+ const telemetry = pip({
300
+ name: 'telemetry',
301
+ needs: { env: Env },
302
+ boot: ({ env }) => ({ metrics: makeMetrics(env.OTEL_ENDPOINT) }),
303
+ dispose: ({ metrics }) => metrics.flush(),
304
+ });
305
+
306
+ const database = (url: string) =>
307
+ pip({
308
+ name: 'database',
309
+ needs: { metrics: service<Metrics>() },
310
+ async boot({ metrics }) {
311
+ return provide(Db, await connectPg(url, { metrics }));
312
+ },
313
+ async dispose(ctx) {
314
+ await ctx[Db.key].end(); // own provide, published under the token key
315
+ },
316
+ });
317
+
318
+ const cache = pip({
319
+ name: 'cache',
320
+ needs: { env: Env },
321
+ boot: ({ env }) => provide(Cache, connectRedis(env.REDIS_URL)),
322
+ dispose: async ctx => ctx[Cache.key].quit(),
323
+ });
324
+
325
+ const searchCluster = pip({
326
+ name: 'search-cluster',
327
+ needs: { env: Env },
328
+ boot: ({ env }) => ({
329
+ // Expensive external cluster — only opens if something get()s it.
330
+ search: lazy(() => connectElastic(env.ELASTIC_URL), {
331
+ dispose: client => client.close(),
332
+ }),
333
+ }),
334
+ });
335
+
336
+ const bus = pip({
337
+ name: 'bus',
338
+ boot: () => provide(Bus, makeInMemoryBus()),
339
+ });
340
+
341
+ // ── domain pips ───────────────────────────────────────────────────────
342
+ const catalog = pip({
343
+ name: 'catalog',
344
+ needs: {
345
+ db: Db,
346
+ cache: Cache,
347
+ search: service.lazy<SearchClient>(), // eager or lazy provider — same code
348
+ },
349
+ boot: ({ db, cache, search }) => ({
350
+ catalog: makeCatalog({ db, cache, search }),
351
+ }),
352
+ });
353
+
354
+ const checkout = pip({
355
+ name: 'checkout',
356
+ needs: { db: Db, bus: Bus, catalog: service<Catalog>() },
357
+ boot: ({ db, bus, catalog }) => ({
358
+ checkout: makeCheckout({ db, bus, catalog }),
359
+ }),
360
+ });
361
+
362
+ const reporting = pip({
363
+ name: 'reporting',
364
+ needs: { replica: ReportsDb, catalog: service<Catalog>() },
365
+ boot: ({ replica, catalog }) => ({ reporting: makeReporting(replica, catalog) }),
366
+ });
367
+
368
+ // ── process pips — real work happens in start/stop ───────────────────
369
+ const outboxWorker = pip({
370
+ name: 'outbox-worker',
371
+ needs: { db: Db, bus: Bus },
372
+ boot: ({ db, bus }) => ({ outbox: makeOutbox(db, bus) }),
373
+ start: ({ outbox }) => outbox.startPolling(),
374
+ stop: ({ outbox }) => outbox.drain(), // stop processing, keep resources
375
+ });
376
+
377
+ const http = pip({
378
+ name: 'http',
379
+ needs: {
380
+ env: Env,
381
+ catalog: service<Catalog>(),
382
+ checkout: service<Checkout>(),
383
+ reporting: service<Reporting>(),
384
+ },
385
+ async boot({ env, catalog, checkout, reporting }) {
386
+ return { server: buildServer({ port: env.PORT, catalog, checkout, reporting }) };
387
+ },
388
+ start: ({ server }) => server.listen(),
389
+ stop: ({ server }) => server.close(),
390
+ });
391
+
392
+ // ── composition — the order IS the architecture ──────────────────────
393
+ const app = await defineApp('shop', { config: process.env })
394
+ .use(env)
395
+ .use(telemetry)
396
+ .use(database(PRIMARY_URL))
397
+ .use(rename(database(REPLICA_URL), { 'shop.db': ReportsDb.key }, 'reports-db'))
398
+ .use(cache)
399
+ .use(searchCluster)
400
+ .use(bus)
401
+ .use(catalog)
402
+ .use(checkout)
403
+ .use(reporting)
404
+ .use(outboxWorker)
405
+ .use(http)
406
+ .start();
407
+
408
+ // SIGTERM → dispose() cascades stop() first, tears down in exact reverse:
409
+ // http, outbox (drained), reporting, checkout, catalog, bus,
410
+ // search cluster (only if it ever initialized), cache, reports-db,
411
+ // primary db, telemetry, env.
412
+ process.on('SIGTERM', () => void app.dispose());
413
+ ```
414
+
415
+ What the type checker is holding for you in that chain:
416
+
417
+ - move `.use(catalog)` above `.use(cache)` → compile error at that line;
418
+ - delete `.use(bus)` → compile errors at `checkout` and `outboxWorker`;
419
+ - mount the second `database(...)` without `rename` → collision error;
420
+ - change `makeCatalog` to return something that isn't a `Catalog` →
421
+ every consumer's `.use()` flags the mismatch.
422
+
423
+ Concurrent lifecycle calls are safe: transitions are serialized on one
424
+ queue, same-phase calls coalesce onto one in-flight promise, and a
425
+ `dispose()` racing a slow `start()` always runs after it — the app ends
426
+ `disposed`, never resurrected.
427
+
97
428
  ## Pip authoring
98
429
 
99
430
  `pip(config)` accepts a `needs` shape plus five lifecycle hooks. Hook
@@ -106,7 +437,7 @@ contexts carry the needed services (typed, under your local aliases) and
106
437
  | `boot` | Open connections; the returned record is what the pip provides. |
107
438
  | `start` | Begin processing (workers, subscribers, schedulers). |
108
439
  | `stop` | Stop processing in reverse declaration order. |
109
- | `dispose` | Free resources after `stop`. |
440
+ | `dispose` | Free resources after `stop`; lazy handles auto-clean afterwards. |
110
441
 
111
442
  Wiring is compile-time checked: `.use(pip)` fails to typecheck when the
112
443
  pip's needs aren't satisfied by earlier `.use()` calls, or when its
@@ -122,7 +453,7 @@ order is boot order — same input, same order, every time.
122
453
  `defineApp(name)` returns a builder. Calling `.use(pip)` accumulates
123
454
  pips. The lifecycle then flows:
124
455
 
125
- ```
456
+ ```text
126
457
  defined ──configure()──▶ configured ──boot()──▶ booted ──start()──▶ started
127
458
 
128
459
  disposed ◀──dispose()── stopped ◀──stop()───────┘
@@ -131,7 +462,19 @@ defined ──configure()──▶ configured ──boot()──▶ booted ─
131
462
  `boot()` runs `configure()` implicitly if you skipped it. `start()` runs
132
463
  `boot()` implicitly. `stop()` and `dispose()` always run in reverse
133
464
  declaration order, even when an earlier hook failed — failure isolation is
134
- part of the contract.
465
+ part of the contract. A boot failure rolls back every already-booted pip
466
+ before throwing.
467
+
468
+ Runtime failures carry stable codes (`DotLifecycleError.code`):
469
+
470
+ | Code | Meaning |
471
+ | -------------------- | ---------------------------------------------------- |
472
+ | `DOT_LIFECYCLE_E011` | Pip registered twice. |
473
+ | `DOT_LIFECYCLE_E012` | A need has no provider among earlier pips. |
474
+ | `DOT_LIFECYCLE_E013` | A published wire key collides with an earlier one. |
475
+ | `DOT_LIFECYCLE_E014` | A service key uses the reserved `$` (kernel) prefix. |
476
+
477
+ (Full table including hook-failure codes: [docs/lifecycle.md](./docs/lifecycle.md).)
135
478
 
136
479
  ## CLI
137
480
 
@@ -156,7 +499,9 @@ dot explain --app ./my-app.ts --json | jq '.data.pips'
156
499
  ```
157
500
 
158
501
  The CLI emits the same envelope shape as the in-process diagnostics snapshot
159
- (`app.diagnostics`), so the same downstream tools can consume either.
502
+ (`app.diagnostics`), so the same downstream tools can consume either. The
503
+ manifest's dependency edges are **observed, not declared** — the kernel
504
+ records which pip's published service satisfied which need during boot.
160
505
 
161
506
  ### `dot new <app-name>`
162
507
 
@@ -177,7 +522,14 @@ ship with the published tarball.
177
522
  `@arki/dot` is intentionally small: it defines the contracts (pip shape,
178
523
  lifecycle hooks, manifest schema, diagnostics envelope) and runs them. Adapters
179
524
  that bridge databases, queues, auth providers, and HTTP routers live in their
180
- own packages and consume `@arki/dot` as a peer dependency.
525
+ own packages and consume `@arki/dot` as a peer dependency:
526
+
527
+ ```ts
528
+ import { env } from '@arki/env/dot';
529
+ import { db } from '@arki/db/dot';
530
+ import { kv } from '@arki/kv/dot';
531
+ import { eventSourcing } from '@arki/event-sourcing/dot';
532
+ ```
181
533
 
182
534
  This keeps the kernel free of optional dependencies and lets each adapter
183
535
  ship at its own cadence.
@@ -189,8 +541,8 @@ The full docs live in [`docs/`](./docs):
189
541
  - [Principles](./docs/principles.md) — **read first.** The five rules every
190
542
  API, error, and PR is measured against. Slightly playful, very precise.
191
543
  - [Quickstart](./docs/quickstart.md) — boot your first app in five minutes.
192
- - [Pip authoring](./docs/pip-authoring.md) — write your own `DotPip`.
193
- - [Lifecycle](./docs/lifecycle.md) — the 5-hook contract.
544
+ - [Pip authoring](./docs/pip-authoring.md) — write your own pip.
545
+ - [Lifecycle](./docs/lifecycle.md) — the 5-hook contract, idempotency, error codes.
194
546
  - [Diagnostics](./docs/diagnostics.md) — `app.manifest`, `app.diagnostics`,
195
547
  `dot explain`, `dot doctor`.
196
548
  - [Adapter authoring](./docs/adapter-authoring.md) — expose your package as
@@ -49,7 +49,7 @@ export declare class DotAppImpl {
49
49
  runConfigure(): void;
50
50
  /** Public boot() — idempotent + concurrent-safe. */
51
51
  boot(): Promise<void>;
52
- /** Public start(). Boots first if needed. Idempotent. */
52
+ /** Public start(). Boots first if needed. Idempotent + concurrent-safe. */
53
53
  start(): Promise<void>;
54
54
  /** Public stop(). Idempotent + concurrent-safe. */
55
55
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"app-instance.d.ts","sourceRoot":"","sources":["../../src/kernel/app-instance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAEV,sBAAsB,EAKvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAqB,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACxF,OAAO,KAAK,EAA4C,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACnG,OAAO,KAAK,EAEV,cAAc,EAKf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAA4C,MAAM,oBAAoB,CAAC;AAkE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3C;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC7C,CAAC;AAEF;;;GAGG;AACH,qBAAa,UAAU;;gBAwDT,MAAM,EAAE,oBAAoB;IAyCxC;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAuIrD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,KAAK,IAAI,iBAAiB,CAE7B;IAED,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtC;IAED,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,IAAI,WAAW,IAAI,sBAAsB,CAExC;IAED;;;;OAIG;IACH,YAAY,IAAI,IAAI;IAgKpB,oDAAoD;IAC9C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA6P3B,yDAAyD;IACnD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmH5B,mDAAmD;IAC7C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkH3B,sDAAsD;IAChD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAkS/B;AA0BD,kFAAkF;AAElF,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"app-instance.d.ts","sourceRoot":"","sources":["../../src/kernel/app-instance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAEV,sBAAsB,EAKvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAqB,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACxF,OAAO,KAAK,EAA4C,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACnG,OAAO,KAAK,EAEV,cAAc,EAKf,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAA4C,MAAM,oBAAoB,CAAC;AAkE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,sDAAsD;IACtD,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3C;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CAC7C,CAAC;AAEF;;;GAGG;AACH,qBAAa,UAAU;;gBAmET,MAAM,EAAE,oBAAoB;IAoExC;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAuIrD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,KAAK,IAAI,iBAAiB,CAE7B;IAED,IAAI,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtC;IAED,IAAI,QAAQ,IAAI,cAAc,CAE7B;IAED,IAAI,WAAW,IAAI,sBAAsB,CAExC;IAED;;;;OAIG;IACH,YAAY,IAAI,IAAI;IA8KpB,oDAAoD;IAC9C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiR3B,2EAA2E;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA+H5B,mDAAmD;IAC7C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoH3B,sDAAsD;IAChD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA2T/B;AA0BD,kFAAkF;AAElF,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,oBAAoB,CAAC"}