@traceten/sdk-node 1.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [1.0.0] - Unreleased
9
+
10
+ First stable release.
11
+
12
+ ### Added
13
+
14
+ - `Client` with `page()`, `track()` and `payment()`, buffered delivery with batching and retry.
15
+ - `flush()` / `close()` for explicit and shutdown-time delivery.
16
+ - Webhook signature-verification helpers.
17
+ - Required `apiKey`, sent as `Authorization: Bearer` on every request. It buys an
18
+ ingestion quota isolated from the shared per-site one.
19
+ - `host` must use `https` off-loopback: the key rides every request, so plaintext
20
+ http would put a live credential on the wire.
21
+ - `payment()` accepts optional `settlementAmount`/`settlementCurrency` — the
22
+ provider's own conversion, used by Traceten only as a fallback when `currency`
23
+ isn't one it can price on its own. Send both or neither.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Traceten
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,312 @@
1
+ <img src="https://traceten.com/logos/traceten-wordmark-black.png" alt="Traceten" width="320" />
2
+
3
+ # @traceten/sdk-node
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@traceten/sdk-node.svg)](https://www.npmjs.com/package/@traceten/sdk-node)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
7
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
8
+
9
+ The Traceten server-side SDK for Node.js. Send AI-traffic pageviews and revenue
10
+ events from your backend over authenticated, ad-blocker-resistant HTTP.
11
+
12
+ Use this when the browser snippet cannot run: server-rendered flows, webhooks,
13
+ mobile/API backends, or when you want delivery that ad-blockers and privacy
14
+ browsers cannot strip.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install @traceten/sdk-node
20
+ # or: pnpm add @traceten/sdk-node
21
+ # or: yarn add @traceten/sdk-node
22
+ ```
23
+
24
+ Node 18 or newer. The only runtime dependency is `undici`.
25
+
26
+ ## Quickstart
27
+
28
+ ```ts
29
+ import { Client } from "@traceten/sdk-node";
30
+
31
+ const traceten = new Client({
32
+ siteId: "ttid_7Rb4TrC1dTbnD8w3s1TS12", // your site key, from the dashboard's install page
33
+ host: "https://ingest.traceten.com",
34
+ // Required. A secret: load it from your environment, never hardcode it.
35
+ apiKey: process.env.TRACETEN_API_KEY!,
36
+ });
37
+
38
+ // A pageview. visitorId comes from your own request context (a cookie you set,
39
+ // a user id, etc.) — the server has no Traceten cookie to read.
40
+ traceten.page({
41
+ url: "https://acme.com/pricing",
42
+ referrer: "https://chat.openai.com/",
43
+ visitorId: "123e4567-e89b-42d3-a456-426614174000",
44
+ });
45
+
46
+ // A conversion. visitorId is required so the revenue can be attributed.
47
+ traceten.track("subscription_started", {
48
+ visitorId: "123e4567-e89b-42d3-a456-426614174000",
49
+ valueCents: 4900,
50
+ currency: "usd",
51
+ });
52
+
53
+ // Flush and stop the background timer on shutdown.
54
+ await traceten.close();
55
+ ```
56
+
57
+ `page()` and `track()` return immediately. They buffer the event and send it in
58
+ the background. They never throw on network problems, they throw only on
59
+ programmer errors (a bad URL, a missing `visitorId`, a malformed event name).
60
+
61
+ ## The API key
62
+
63
+ `apiKey` is required. Create a key in the dashboard under **Settings -> API
64
+ keys**, or use the key shown once when you created the site.
65
+
66
+ Keep it on your server. It is a secret: never put it in client-side code, a
67
+ mobile app, or a public repository. It is not the same value as `siteId`, which
68
+ is public and already embedded in your pages.
69
+
70
+ The key does two things:
71
+
72
+ - **Gets you in.** The SDK posts to the authenticated ingestion endpoints, which
73
+ return `401` without a valid key.
74
+ - **Gets you your own quota.** Authenticated traffic is rate-limited on a bucket
75
+ tied to the key, separate from the shared per-site bucket. `siteId` is public,
76
+ so anyone who can read your page source can send events under it. With a key,
77
+ that traffic cannot exhaust your allowance and 429 your conversion calls.
78
+
79
+ The client validates the key's shape when you construct it and throws if it is
80
+ missing or malformed. An integration that silently drops every event is worse
81
+ than one that fails on its first line.
82
+
83
+ ### Permissions
84
+
85
+ A key carries a set of permissions that decide which endpoints it can reach.
86
+ This SDK sends to `/v1/server/*`, which requires **`ingest:write`**. Tick that
87
+ permission when you create the key.
88
+
89
+ The client cannot check this for you. Permissions live on the server and the key
90
+ looks identical either way, so a key without `ingest:write` is rejected with the
91
+ same `401` as an invalid one.
92
+
93
+ Grant only what you need. A key used solely for server-side ingestion does not
94
+ need permission to read your analytics or erase visitor data, and if it leaks it
95
+ cannot do either.
96
+
97
+ To rotate a key: create the new one, deploy it, then revoke the old one.
98
+ Revocation normally takes effect within about a minute. If our database is
99
+ unreachable at that moment, an edge location that was already using the key may
100
+ keep honouring it for up to about fifteen minutes more, so that a database blip
101
+ cannot silently drop your events.
102
+
103
+ ## Identifiers
104
+
105
+ Because it runs on your server, the SDK has no cookie and no DOM. You supply
106
+ `visitorId` (and optionally `sessionId`) from your own request context. A
107
+ `visitorId` is either a UUID or an `h:<64-hex>` identify hash. The SDK never
108
+ fabricates one.
109
+
110
+ The robust way to get this value is `window.traceten.getVisitorId()`, called
111
+ client-side and forwarded to your backend (a form field, a fetch body, a
112
+ header) — it always resolves the current cookie, so it keeps working if a
113
+ customer turns cross-subdomain cookies on or off later. If you read the
114
+ cookie by name instead, its name depends on the site's cookie scope:
115
+ cross-subdomain cookies are off by default, giving plain `_traceten_vid`;
116
+ once a customer enables it, the cookie becomes `_traceten_vid_` followed by
117
+ eight characters of the site key. The install page shows the exact current
118
+ name. Read that name exactly, never by prefix: two Traceten sites under one
119
+ registered domain each set their own cookie, and a prefix match picks
120
+ whichever the browser happens to list first, which merges two visitors the
121
+ suffix exists to keep apart. If a request carries no such value, send the
122
+ event without a `visitorId` rather than inventing one.
123
+
124
+ ## API
125
+
126
+ ### `new Client(options)`
127
+
128
+ | Option | Type | Default | Notes |
129
+ | --------------- | ----------------------- | ---------- | ------------------------------------------------------------ |
130
+ | `siteId` | `string` | (required) | Your site key (`ttid_…`, case-sensitive) from the dashboard's install page. 1-64 chars, `A-Z a-z 0-9 _ -`. |
131
+ | `host` | `string` | (required) | Ingest base URL. Absolute `https` (http on loopback only). |
132
+ | `apiKey` | `string` | (required) | Secret key, sent as `Authorization: Bearer`. See below. |
133
+ | `flushAt` | `number` | `50` | Flush the events queue at this size. Clamped to `[1, 50]`. |
134
+ | `flushInterval` | `number` | `5000` | Background flush cadence in ms. |
135
+ | `maxRetries` | `number` | `3` | Retry attempts on 5xx / 429 / network error. |
136
+ | `timeoutMs` | `number` | `10000` | Per-request timeout in ms. |
137
+ | `onError` | `(err: Error) => void` | - | Called when a batch is permanently dropped. |
138
+ | `flushOnExit` | `boolean` | `true` | Register best-effort flush on `beforeExit`/`SIGTERM`/`SIGINT`.|
139
+
140
+ ### `page(props)`
141
+
142
+ | Prop | Type | Default | Notes |
143
+ | ----------- | ---------------- | ------------ | ---------------------------------------- |
144
+ | `url` | `string` | (required) | Valid URL, up to 2048 chars. |
145
+ | `referrer` | `string` | `""` | |
146
+ | `visitorId` | `string` | - | UUID or `h:<hash>`. |
147
+ | `sessionId` | `string` | - | |
148
+ | `userAgent` | `string` | - | The end user's User-Agent, if you know it.|
149
+ | `eventName` | `string` | `"pageview"` | Must match `^[a-z][a-z0-9_-]*$`. |
150
+ | `timestamp` | `Date \| string` | now | |
151
+
152
+ The `userAgent` you pass is what gets recorded as the visitor's user agent.
153
+ Omit it and the event falls back to the `User-Agent` this SDK's HTTP client
154
+ sent, which describes your server, not the visitor. User agent is an input to
155
+ Traceten's traffic classification, so passing the real one materially improves
156
+ your results.
157
+
158
+ ### `track(name, opts)`
159
+
160
+ `name` must match `^[a-z][a-z0-9_]*$` (no hyphen).
161
+
162
+ | Option | Type | Default | Notes |
163
+ | ------------ | ---------------- | ---------- | ------------------------------------------------- |
164
+ | `visitorId` | `string` | (required) | UUID or `h:<hash>`. Required to attribute revenue.|
165
+ | `properties` | `object` | `{}` | Arbitrary JSON. |
166
+ | `valueCents` | `number` | - | Non-negative integer, minor units. -> `value_cents`.|
167
+ | `currency` | `string` | - | ISO-4217, lowercased on send. |
168
+ | `sessionId` | `string` | - | |
169
+ | `timestamp` | `Date \| string` | now | |
170
+
171
+ ### `goal(name, opts)`
172
+
173
+ Records a goal completion. Same arguments and same endpoint as `track()`, with
174
+ one difference: the reserved names below are rejected.
175
+
176
+ ```ts
177
+ traceten.goal("demo_booked", {
178
+ visitorId: "123e4567-e89b-42d3-a456-426614174000",
179
+ properties: { plan: "pro" },
180
+ });
181
+ ```
182
+
183
+ A goal is a custom event. `goal("demo_booked", ...)` and
184
+ `track("demo_booked", ...)` send exactly the same payload, and the goal is
185
+ listed by `GET /v1/goals` either way. Use `goal()` when the event is something
186
+ you want to count and put in a funnel, and `track()` when you are recording
187
+ revenue.
188
+
189
+ **Reserved names.** These belong to the Stripe and Shopify integrations, which
190
+ emit them for real subscription and payment events. `goal()` throws on them, so
191
+ a name collision cannot quietly corrupt your revenue funnel:
192
+
193
+ `payment`, `free_trial`, `trial_started`, `trial_converted`,
194
+ `subscription_started`, `subscription_upgraded`, `subscription_downgraded`,
195
+ `subscription_renewed`, `subscription_cancel_scheduled`,
196
+ `subscription_reactivated`, `subscription_ended`.
197
+
198
+ `track()` still accepts them, because that is how those events are legitimately
199
+ sent.
200
+
201
+ **Whitespace is not trimmed.** `goal(" signup ")` throws. The browser snippet
202
+ trims a name read from an HTML attribute, because attribute values pick up
203
+ whitespace from how the page is formatted; a name written in server code does
204
+ not, so a stray space is a bug worth surfacing rather than quietly fixing.
205
+
206
+ **Property keys and values are both stored, and both are readable back.**
207
+ `GET /v1/goals/{name}/properties` returns every property key sent with a goal
208
+ and that key's most common values. Do not put an email address, a person's name,
209
+ or a postal address in either half of a property.
210
+
211
+ Ingestion drops a property whose key is exactly `email`, `phone`, `name`,
212
+ `password`, `token`, `ssn`, `credit_card` or `card_number`, and redacts email,
213
+ phone, card and national-ID patterns inside string values. It has no pattern
214
+ for a personal name or a street address, and it does not scan keys at all, so
215
+ `{"full_name": "Alice Chen"}` is stored and returned exactly as sent.
216
+
217
+ ### `payment(props): Promise<PaymentResult>`
218
+
219
+ Records a payment from ANY payment processor (`POST /v1/server/payments`). The
220
+ only method here that sends immediately and reports a delivery failure to the
221
+ caller: a dropped pageview is a dropped pageview, a dropped payment is missing
222
+ revenue.
223
+
224
+ ```ts
225
+ const res = await traceten.payment({
226
+ transactionId: "pay_9fK2mQ", // required — the processor's id. Idempotency key.
227
+ amount: 49.99, // required — MAJOR unit, not cents
228
+ currency: "USD", // required — ISO-4217, sent uppercase
229
+ provider: "dodo", // optional — your label. Defaults to "api".
230
+ email: "ada@example.com", // optional — hashed server-side, never stored
231
+ visitorId, // optional — a stronger match than the email
232
+ customerId: "cus_123", // optional
233
+ renewal: false, // optional
234
+ refunded: false, // optional — never send a negative amount
235
+ isFreeTrial: false, // optional — implied by amount: 0
236
+ settlementAmount: 45.50, // optional — your processor's own converted figure
237
+ settlementCurrency: "USD", // optional — must be set together with settlementAmount
238
+ });
239
+
240
+ res.status; // "recorded" | "trial" | "refunded" | "duplicate"
241
+ ```
242
+
243
+ `amount` is the MAJOR unit, the opposite of `track()`'s `valueCents`: `49.99`
244
+ for $49.99, `5000` for ¥5000.
245
+
246
+ `settlementAmount`/`settlementCurrency` are a fallback for when `currency`
247
+ isn't one Traceten can price natively (the ~30 ECB-published codes): your
248
+ processor's own conversion of the payment into a currency it can price, so
249
+ the payment isn't dropped. Set both or neither — sending one without the
250
+ other causes the SDK to omit both.
251
+
252
+ Re-posting the same `transactionId` returns `"duplicate"` and creates nothing,
253
+ which is why a 5xx is retried here. It rejects on an invalid field and after
254
+ every retry is exhausted.
255
+
256
+ ⚠️ Do NOT send payments here for a processor you have also connected natively.
257
+ Traceten would record the payment twice and overstate your revenue.
258
+
259
+ ### `flush(): Promise<void>`
260
+
261
+ Sends everything queued now. Resolves when every in-flight request settles.
262
+
263
+ ### `close(): Promise<void>` / `shutdown(): Promise<void>`
264
+
265
+ Flushes, stops the background timer, and removes the exit hooks. Idempotent.
266
+ After `close()`, `page()`, `track()` and `goal()` throw.
267
+
268
+ ## Delivery and retries
269
+
270
+ - Pageviews go to `POST {host}/v1/server/events` in batch envelopes of up to 50.
271
+ - Conversions go to `POST {host}/v1/server/conversions`, one body per event.
272
+ - Every request carries `Authorization: Bearer <apiKey>`. Without a valid key
273
+ these endpoints return `401`.
274
+ - On 5xx, 429, or a network/timeout error, the batch is retried with
275
+ exponential backoff and full jitter, up to `maxRetries`.
276
+ - On any other 4xx the payload is malformed, so it is dropped and `onError` is
277
+ called. It is not retried.
278
+
279
+ ## Full docs
280
+
281
+ See [API.md](./API.md) for the complete guide, or [docs.traceten.com/sdks/node](https://docs.traceten.com/sdks/node) for the hosted version.
282
+
283
+ ## Versioning
284
+
285
+ This package follows [Semantic Versioning](https://semver.org/). Before `1.0.0`,
286
+ minor versions may include breaking changes — pin an exact version in production
287
+ until then. See [CHANGELOG.md](./CHANGELOG.md) for release history.
288
+
289
+ ## Contributing
290
+
291
+ Issues and pull requests are welcome. For anything beyond a small fix, please
292
+ open an issue first to discuss the change. Run the checks below before
293
+ submitting a PR — CI enforces the same steps on every pull request:
294
+
295
+ ```bash
296
+ npm install
297
+ npm run typecheck
298
+ npm run build
299
+ npm test
300
+ ```
301
+
302
+ ## License
303
+
304
+ MIT © Traceten — see [LICENSE](./LICENSE).
305
+
306
+ ## Links
307
+
308
+ - [Documentation](https://docs.traceten.com/sdks/node)
309
+ - [Traceten](https://traceten.com) — AI traffic attribution for the AI search era
310
+ - [Issues](https://github.com/traceten/sdk-node/issues)
311
+ - [Changelog](./CHANGELOG.md)
312
+ - [Python SDK](https://github.com/traceten/sdk-python) · [Go SDK](https://github.com/traceten/traceten-go) · [AI crawler tracking](https://github.com/traceten/ai-crawl)
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The buffered Traceten client.
3
+ *
4
+ * `page()` and `track()` split at the API surface (two endpoints, two payload
5
+ * shapes) but unify in one buffered client with a single flush that drains both
6
+ * internal queues. See DESIGN.md §3–§4.
7
+ */
8
+ import type { ClientOptions, PageProps, PaymentProps, PaymentResult, TrackOptions } from "./types.js";
9
+ export declare class Client {
10
+ private readonly siteId;
11
+ private readonly host;
12
+ private readonly flushAt;
13
+ private readonly transport;
14
+ private readonly onError;
15
+ private events;
16
+ private conversions;
17
+ private timer;
18
+ private readonly inflight;
19
+ private exitHooks;
20
+ private closed;
21
+ constructor(options: ClientOptions);
22
+ /**
23
+ * Enqueue a pageview (`/v1/server/events`). Returns immediately; never throws on
24
+ * network problems. Throws synchronously on programmer errors (bad url,
25
+ * bad eventName, bad visitorId).
26
+ */
27
+ page(props: PageProps): void;
28
+ /**
29
+ * Enqueue a conversion (`/v1/server/conversions`). `opts.visitorId` is REQUIRED —
30
+ * omitting it is a programmer error and throws synchronously (a conversion
31
+ * cannot be attributed without it). Returns immediately otherwise.
32
+ */
33
+ track(name: string, opts: TrackOptions): void;
34
+ /**
35
+ * Enqueue a goal completion. Identical to {@link Client.track} in every
36
+ * respect except the name check: the reserved Stripe/Shopify names are
37
+ * refused, because a goal that collides with one silently corrupts the site's
38
+ * revenue funnel.
39
+ *
40
+ * A goal IS a custom event — this is the same `/v1/server/conversions` call
41
+ * under a name that matches what the dashboard calls it.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * traceten.goal("demo_booked", { visitorId, properties: { plan: "pro" } });
46
+ * ```
47
+ */
48
+ goal(name: string, opts: TrackOptions): void;
49
+ /**
50
+ * Record a payment from ANY payment processor (`POST /v1/server/payments`).
51
+ *
52
+ * ⚠️ The only method here that is AWAITED and that can reject. `page()` and
53
+ * `track()` are buffered and fire-and-forget because a lost pageview is a lost
54
+ * pageview; a lost payment is missing revenue, and the endpoint's whole point
55
+ * is the idempotency answer it returns — which a queue could not give back.
56
+ *
57
+ * Safe to call again with the same `transactionId`: the server deduplicates
58
+ * durably on `(site, provider, transactionId)` and answers `"duplicate"`
59
+ * rather than recording a second payment.
60
+ *
61
+ * `settlementAmount`/`settlementCurrency` are an optional fallback: your
62
+ * processor's own conversion of the payment into a currency Traceten can
63
+ * price, used only when `currency` itself isn't one Traceten prices
64
+ * natively. Set both or neither — one without the other is dropped.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const res = await traceten.payment({
69
+ * transactionId: charge.id,
70
+ * amount: 49.99,
71
+ * currency: "USD",
72
+ * provider: "dodo",
73
+ * email: customer.email,
74
+ * });
75
+ * // res.status === "recorded"
76
+ * ```
77
+ */
78
+ payment(props: PaymentProps): Promise<PaymentResult>;
79
+ /**
80
+ * Drain both queues now. Resolves once every in-flight request settles.
81
+ * Never rejects — delivery failures surface via `onError`.
82
+ */
83
+ flush(): Promise<void>;
84
+ /**
85
+ * Flush, stop the background timer, and remove exit hooks. Idempotent — safe
86
+ * to call more than once. After close, `page()`/`track()` throw.
87
+ */
88
+ close(): Promise<void>;
89
+ /** Alias for {@link Client.close} — matches the cross-language `shutdown()`. */
90
+ shutdown(): Promise<void>;
91
+ private assertOpen;
92
+ /** Trigger a flush when either queue has reached the flush threshold. */
93
+ private maybeFlush;
94
+ /** Track an in-flight send so `close()` can await it, then self-remove. */
95
+ private launch;
96
+ /**
97
+ * Register best-effort flush-on-exit hooks.
98
+ *
99
+ * `beforeExit` fires when the loop would otherwise empty — we flush and let
100
+ * the pending I/O keep the process alive until it settles. For `SIGTERM` /
101
+ * `SIGINT` we flush, then re-exit ONLY if we were the sole handler (otherwise
102
+ * the host app owns the shutdown decision and forcing `process.exit` would
103
+ * stomp its own handlers / exit code).
104
+ */
105
+ private registerExitHooks;
106
+ }
107
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwBH,OAAO,KAAK,EACV,aAAa,EAIb,SAAS,EACT,YAAY,EACZ,aAAa,EAEb,YAAY,EACb,MAAM,YAAY,CAAC;AA8BpB,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAwB;IAE3C,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,aAAa;IAqDlC;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA0B5B;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI;IAoC7C;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI;IAI5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IA8B1D;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,gFAAgF;IAChF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAMzB,OAAO,CAAC,UAAU;IAMlB,yEAAyE;IACzE,OAAO,CAAC,UAAU;IAMlB,2EAA2E;IAC3E,OAAO,CAAC,MAAM;IAQd;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;CAoB1B"}