@eaera-com/cms-sdk 0.1.65 → 0.1.67

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 (39) hide show
  1. package/README.md +0 -479
  2. package/dist/chunks/{_shared-DdBWuPm_.js → _shared-DZlTUzoa.js} +2 -2
  3. package/dist/chunks/{_shared-DdBWuPm_.js.map → _shared-DZlTUzoa.js.map} +1 -1
  4. package/dist/chunks/{storage.constant--64BDSPW.js → storage.constant-CVWam5ls.js} +2 -1
  5. package/dist/chunks/storage.constant-CVWam5ls.js.map +1 -0
  6. package/dist/common/constants/storage.constant.d.ts +1 -0
  7. package/dist/common/constants/storage.constant.d.ts.map +1 -1
  8. package/dist/index.js +171 -118
  9. package/dist/index.js.map +1 -1
  10. package/dist/server/auth/cbs-callback.post.js +2 -2
  11. package/dist/server/auth/cbs-start.post.js +1 -1
  12. package/dist/server/auth/idp-callback.get.js +2 -2
  13. package/dist/server/auth/idp-link.post.js +2 -2
  14. package/dist/server/auth/idp-login.get.js +1 -1
  15. package/dist/server/auth/idp-unlink.delete.js +2 -2
  16. package/dist/server/auth/login.post.js +2 -2
  17. package/dist/server/auth/logout.post.js +2 -2
  18. package/dist/server/auth/refresh.post.js +2 -2
  19. package/dist/server/authz/portal-access.get.js +2 -2
  20. package/dist/server/clear-token.delete.js +1 -1
  21. package/dist/server/persist.post.js +1 -1
  22. package/dist/server/set-token.post.js +1 -1
  23. package/dist/services/executions/executions.type.d.ts +8 -0
  24. package/dist/services/executions/executions.type.d.ts.map +1 -1
  25. package/dist/services/notifications/index.d.ts.map +1 -1
  26. package/dist/services/notifications/notifications.type.d.ts +7 -0
  27. package/dist/services/notifications/notifications.type.d.ts.map +1 -1
  28. package/dist/services/people/packops/index.d.ts +13 -6
  29. package/dist/services/people/packops/index.d.ts.map +1 -1
  30. package/dist/services/people/process/actions.d.ts +8 -1
  31. package/dist/services/people/process/actions.d.ts.map +1 -1
  32. package/dist/services/people/process/ct.types.d.ts +7 -0
  33. package/dist/services/people/process/ct.types.d.ts.map +1 -1
  34. package/dist/stores/authz.d.ts +1 -1
  35. package/dist/stores/authz.d.ts.map +1 -1
  36. package/dist/stores/executions.d.ts +12 -0
  37. package/dist/stores/executions.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunks/storage.constant--64BDSPW.js.map +0 -1
package/README.md CHANGED
@@ -1,479 +0,0 @@
1
- # @eaera-com/cms-sdk
2
-
3
- Data-access layer for EAERA CMS, built for **Nuxt 3** consumers.
4
-
5
- - **HTTP**: [`ofetch`](https://github.com/unjs/ofetch) (same engine as Nuxt `$fetch`) — retry, timeout, typed responses, casing opt-in.
6
- - **State**: [Pinia](https://pinia.vuejs.org/) + a **built-in cookie-based persistence plugin** (per-state, optional `httpOnly`) — no `pinia-plugin-persistedstate` dependency.
7
- - **Auth**: single-source-of-truth `TokenManager` with pluggable storage adapters (memory / localStorage / cookie / httpOnly-bridge).
8
- - **Multi-backend**: one `createCmsSDK()` facade can target multiple endpoints (CMS, report, …) sharing one token + hooks.
9
- - **Domains**: `auth` · `content` · `rules` · `executions` · `settings` — five typed services + five Pinia stores.
10
- - **Explicit imports**: nothing is auto-imported. You import exactly what you use.
11
-
12
- > The auth service mirrors the **confirmed** CMS backend surface (login / logout / OAuth refresh only). The content/rules/executions/settings domains ship with a **mock mode** so the SDK is usable before the backend is fully wired; flip `mock: false` once the endpoints exist.
13
-
14
- ## Requirements
15
-
16
- | Dep | Version |
17
- |---|---|
18
- | Node | `>= 20` (dev repo pins `22` via `.nvmrc`) |
19
- | Nuxt | `^3.0.0 \|\| ^4.0.0` (if using the Nuxt module) |
20
- | Vue | `^3.5.0` (peer) |
21
- | Pinia | `^3.0.4` (peer) |
22
-
23
- ## Install
24
-
25
- ```bash
26
- pnpm add @eaera-com/cms-sdk
27
- # Peer deps (usually already present in a Nuxt app):
28
- # pnpm add pinia vue
29
- ```
30
-
31
- > Persistence is **built into the SDK module** (cookie-based, per-state, optional `httpOnly`). No extra package or module is required.
32
-
33
- ## Quick start (Nuxt 3)
34
-
35
- Register the module and point it at your runtime config:
36
-
37
- ```ts
38
- // nuxt.config.ts
39
- export default defineNuxtConfig({
40
- modules: [
41
- '@pinia/nuxt',
42
- '@eaera-com/cms-sdk/nuxt',
43
- ],
44
- runtimeConfig: {
45
- public: {
46
- cmsUrl: process.env.CMS_URL ?? 'https://cms.example.com',
47
- reportUrl: process.env.REPORT_URL,
48
- clientId: process.env.CMS_CLIENT_ID ?? 'web',
49
- },
50
- },
51
- cmsSdk: {
52
- // Each value is a KEY NAME in runtimeConfig.public (not the URL itself).
53
- endpoints: { cms: 'cmsUrl', report: 'reportUrl' },
54
- clientId: 'clientId', // runtimeConfig.public key holding the client id
55
- mock: process.env.NODE_ENV !== 'production',
56
- },
57
- })
58
- ```
59
-
60
- > The module forces `'memory'` storage for `TokenManager` internally — the httpOnly cookie `cms_access_token` (managed by the module's `/api/cms/*` routes) is the real token persistence. The `storage` option only matters for standalone `createCmsSDK()`.
61
-
62
- The module exposes `useNuxtApp().$cms`. Stores are **imported explicitly**:
63
-
64
- ```vue
65
- <script setup lang="ts">
66
- import { useContentStore, CONTENT_TYPE } from '@eaera-com/cms-sdk'
67
-
68
- const { $cms } = useNuxtApp()
69
- const content = useContentStore() // Pinia store; SDK attached by the module
70
-
71
- await content.fetchByType(CONTENT_TYPE.MT5_ACCOUNT, { perPage: 50 })
72
- </script>
73
-
74
- <template>
75
- <div v-for="acc in content.mt5Accounts" :key="acc.uuid">
76
- {{ acc.data.login }} — {{ acc.data.group }} (leverage 1:{{ acc.data.leverage }})
77
- </div>
78
- </template>
79
- ```
80
-
81
- ## Imperative usage (no Nuxt module)
82
-
83
- ```ts
84
- import { createPinia } from 'pinia'
85
- import { createCmsSDK } from '@eaera-com/cms-sdk'
86
-
87
- const sdk = createCmsSDK({
88
- endpoints: { cms: 'https://cms.example.com' },
89
- clientId: 'web',
90
- credentials: { storage: 'localStorage' },
91
- mock: true,
92
- })
93
-
94
- const accounts = await sdk.content.list<Mt5Account>({ type: 'Mt5Account', perPage: 50 })
95
- const rule = await sdk.rules.detail('abc-uuid')
96
- ```
97
-
98
- To use the Pinia stores outside Nuxt, install Pinia + the built-in persistence plugin and attach the SDK. (Pinia 3.x defers `pinia.use` plugins until the pinia is installed on an app, so `app.use(pinia)` is required. httpOnly state is **not** available standalone — use a non-httpOnly storage such as `localStorage`.)
99
-
100
- ```ts
101
- import { createApp } from 'vue'
102
- import { createPinia } from 'pinia'
103
- import { createPersistPlugin, useContentStore } from '@eaera-com/cms-sdk'
104
-
105
- const pinia = createPinia()
106
- createApp({}).use(pinia)
107
- pinia.use(createPersistPlugin({ storage: localStorage, ssr: false }))
108
-
109
- const content = useContentStore(pinia)
110
- content.setSdk(sdk)
111
- await content.fetchByType('Mt5Account')
112
- ```
113
-
114
- ---
115
-
116
- ## API reference
117
-
118
- ### `createCmsSDK(options)` → `ICmsSDK`
119
-
120
- | Option | Type | Default | Description |
121
- |---|---|---|---|
122
- | `endpoints` | `{ cms: string; report?: string; poolTrading?: string; [k]: string }` | — | **Required.** Backend URLs; each becomes a `$Fetch` in `sdk.apis`. |
123
- | `clientId` | `string` | — | Sent as `X-Client-Id` header. |
124
- | `credentials` | `ICredentialsConfig` | `{ storage: 'cookie' }` | Token storage + header config (see below). |
125
- | `device` | `{ deviceId?, deviceIp? }` | — | Static or function values sent as `DeviceID` / `DeviceIP`. |
126
- | `timeout` | `number` | `30000` | Request timeout (ms). |
127
- | `retry` | `number` | `2` | Retries on `408/425/429/5xx`. |
128
- | `mock` | `boolean` | `false` | Return fake data instead of hitting the backend. |
129
- | `responseCasing` | `'camel' \| 'snake'` | `'camel'` | Asks the backend for a casing via `x-response-casing`. |
130
- | `requestCasing` | `'camel' \| 'snake'` | `'camel'` | Declares request-body casing via `x-request-casing`. |
131
- | `onError` | `(e: CmsError) => void` | — | Global error callback (after normalisation). |
132
- | `onUnauthorized` | `(e: CmsError) => void` | — | Called on `401` (e.g. force logout). |
133
- | `onToken` | `(token: string \| null) => void \| Promise<void>` | — | Hook fired whenever the access token changes (refresh, 401). |
134
- | `refresh` | `() => Promise<string \| null>` | — | Silent refresh on 401 (Nuxt module wires this to `/api/cms/refresh`). |
135
- | `authRoute` | `$Fetch` | — | `$Fetch` used for token-lifecycle flows so the httpOnly cookie is set atomically. |
136
-
137
- **Returns** `ICmsSDK`:
138
-
139
- | Member | Type | Description |
140
- |---|---|---|
141
- | `apis` | `Record<string, $Fetch>` | One typed fetcher per endpoint (e.g. `sdk.apis.cms`, `sdk.apis.report`). |
142
- | `tokenManager` | `TokenManager` | Single source of truth for the access token. |
143
- | `auth` | `IAuthService` | Auth domain service (login / logout / token). |
144
- | `content` | `IContentService` | Content CRUD + actions. |
145
- | `rules` | `IRulesService` | Content-rule CRUD. |
146
- | `executions` | `IExecutionsService` | Read-only execution feed. |
147
- | `settings` | `ISettingsService` | Dashboard settings (monitored groups). |
148
- | `addRequestMiddleware(target, mw)` | `() => void` | Register a runtime request hook (`'all'` or an endpoint name); returns an unsubscribe. |
149
- | `setMock(enabled)` | `(b) => void` | Toggle mock mode at runtime. |
150
- | `syncToken(token)` | `(t: string \| null) => Promise<void>` | Set/clear token (login/restore/logout). |
151
-
152
- ### `sdk.auth` — Auth service
153
-
154
- | Method | Params | Returns | Notes |
155
- |---|---|---|---|
156
- | `login` | `payload: ILoginPayload, userAgent?` | `IAuthRouteResponse` | `POST /api/auth/login` (or `/api/cms/login` via proxy). |
157
- | `logout` | — | `unknown` | `POST /api/auth/logout`. |
158
- | `token` | — | `ITokenResponse` | OAuth refresh (`POST /api/oauth/token`); returns the new access token in the body. |
159
-
160
- > When a `routeApi` (Nuxt module) is configured, all three flows route through the `/api/cms/*` proxies so httpOnly cookies are set / cleared / rotated atomically.
161
-
162
- ### `sdk.content` — Content service
163
-
164
- Generic CMS content CRUD over the **singular** `/api/v1/content` namespace; type-specific reads use `query: { type }`.
165
-
166
- | Method | Params | Returns | Notes |
167
- |---|---|---|---|
168
- | `list` | `query?: ContentQuery` | `ContentListResponse<ContentItem<T>>` | Paginated (1-based `page`/`perPage`), filterable by `type`/`status`/`packId`. |
169
- | `detail` | `uuid: string` | `ContentItem<T>` | Throws on miss. |
170
- | `create` | `payload: ContentCreatePayload<T>` | `ContentItem<T>` | POST. |
171
- | `update` | `uuid, payload: ContentUpdatePayload<T>` | `ContentItem<T>` | PUT (data/slug). |
172
- | `remove` | `uuid: string` | `void` | Soft-delete. |
173
- | `listActions` | `uuid: string` | `ContentAction[]` | CT actions available on the item. |
174
- | `invokeAction` | `uuid, actionName, payload?` | `ActionResponse` | Run a CT action (e.g. `change_group`, `change_leverage`). |
175
-
176
- ```ts
177
- const { items } = await sdk.content.list<Mt5Account>({ type: 'Mt5Account', perPage: 50 })
178
-
179
- await sdk.content.invokeAction(acc.uuid, 'change_leverage', {
180
- params: { newLeverage: 100 },
181
- intentionId: crypto.randomUUID(),
182
- })
183
- ```
184
-
185
- ### `sdk.rules` — Rules service
186
-
187
- Content-rule CRUD over `/api/v1/rules`. A rule binds a **trigger** → **condition** → **execution**.
188
-
189
- | Method | Params | Returns | Notes |
190
- |---|---|---|---|
191
- | `list` | `query?: RuleListQuery` | `ContentRuleListResponse` | No pagination (`total === items.length`). |
192
- | `detail` | `uuid: string` | `ContentRule` | Full rule. |
193
- | `create` | `payload: RuleCreatePayload` | `ContentRule` | POST (flat payload). |
194
- | `update` | `uuid, payload: RuleUpdatePayload` | `ContentRule` | PUT. |
195
- | `remove` | `uuid: string` | `void` | Deactivate. |
196
-
197
- The SDK ships **rule-builder helpers** so the dashboard authors valid triggers + actions (see `services/rules/triggers.ts` + `conditions.ts`):
198
-
199
- ```ts
200
- import {
201
- buildSignalTrigger, buildCronTrigger, CRON_PRESETS,
202
- CONDITION_FIELDS, CONDITION_OPERATORS,
203
- ACTION_KINDS, LEVERAGE_OPTIONS,
204
- } from '@eaera-com/cms-sdk'
205
-
206
- await sdk.rules.create({
207
- name: 'High drawdown → move group',
208
- contentType: 'Mt5Account',
209
- trigger: buildSignalTrigger(), // signal over the metric stream
210
- conditionType: 'expression',
211
- conditionExpr: 'account_metric_by_deal.relative_balance_drawdown > 0.30',
212
- executionType: 'action',
213
- executionParams: { actionName: 'change_group', params: { newGroup: 'live\\restricted' } },
214
- })
215
- ```
216
-
217
- ### `sdk.executions` — Executions service (read-only)
218
-
219
- Backs the detection feed + the action-history table. No remediation write endpoint — operator remediation goes through `content.invokeAction`.
220
-
221
- | Method | Params | Returns | Notes |
222
- |---|---|---|---|
223
- | `list` | `params?: ExecutionListParams` | `ExecutionListResponse` | `GET /api/v1/executions`, or `GET /api/v1/rules/content/{contentId}/executions` when `contentId` is set. |
224
- | `detail` | `uuid: string` | `ContentExecution` | Single execution row. |
225
-
226
- ### `sdk.settings` — Settings service
227
-
228
- | Method | Params | Returns | Notes |
229
- |---|---|---|---|
230
- | `getMonitoredGroups` | — | `Promise<string[]>` | Reads `setting.dashboard.monitored_groups`. |
231
- | `setMonitoredGroups` | `groups: string[]` | `Promise<string[]>` | Writes the setting. |
232
-
233
- ### Types
234
-
235
- Each domain exports its DTOs (`ContentItem<T>`, `Mt5Account`, `ContentRule`, `ContentExecution`, …) plus the helper types (`ConditionField`, `ActionKindDef`, `CronPreset`, `TriggerMode`, `LeverageOption`, …). The mt5 pack registers **9 content types** via `CONTENT_TYPE` (`Mt5Server`, `Mt5Account`, `Mt5SyncState`, `ReportDefinition`, `LlmPrompt`, `LlmConversation`, `KalshiServer`, `KalshiEvent`, `KalshiFuture`). See `types-and-constants.md` in the docs hub for the full catalog.
236
-
237
- ### Helpers
238
-
239
- ```ts
240
- import { formatPrice, computeSpread, categoryLabel } from '@eaera-com/cms-sdk'
241
-
242
- formatPrice(1.0876, 5) // '1.08760'
243
- computeSpread(1.0875, 1.0876) // 0.0001
244
- categoryLabel('fx') // 'Forex'
245
- ```
246
-
247
- ---
248
-
249
- ## Pinia stores
250
-
251
- All stores must be **imported explicitly**. The Nuxt module attaches the SDK automatically; elsewhere call `setSdk(sdk)` once.
252
-
253
- ### `useAuthStore` — `cms-auth`
254
-
255
- | | |
256
- |---|---|
257
- | **State** | `authUser`, `employee`, `allowRoutes`, `accessToken` (reactive mirror of the token) |
258
- | **Getters** | `isLoggedIn` → `!!accessToken`; `routeList` → parsed route permissions |
259
- | **Actions** | `setSdk(sdk)`, `login`, `refreshToken`, `logout` |
260
- | **Persist** | `fields: { authUser: { httpOnly: true } }` → cookie `cms-persist-auth.authUser` (httpOnly, server-written via `/api/cms-sdk/persist`) |
261
-
262
- > `accessToken` is a **reactive mirror** of `TokenManager.currentToken`; it is intentionally **NOT** persisted by Pinia. The durable copy lives in the httpOnly cookie (managed by the Nuxt module), and the mirror is restored from that cookie on SSR.
263
-
264
- > `allowRoutes` is **derived** from `authUser.allowRoutes` via a watcher, so the menu stays in sync with the persisted user profile on hydrate.
265
-
266
- ### `useContentStore` — `cms-content`
267
-
268
- Holds **9 typed slices** (one per mt5-pack CT) and exposes **7 generic actions** that delegate 1:1 to the `content` service.
269
-
270
- | | |
271
- |---|---|
272
- | **State** | `mt5Servers`, `mt5Accounts`, `mt5SyncStates`, `reportDefinitions`, `llmPrompts`, `llmConversations`, `kalshiServers`, `kalshiEvents`, `kalshiFutures`, `isLoading`, `loadError`, `lastQueryByType` |
273
- | **Getters** | `isEmpty`, `groups` (distinct `Mt5Server.data.name`), `getByType(typeName)`, `getActionsForType(typeName)`, `knownTypes` |
274
- | **Actions** | `setSdk`, `fetchByType(typeName, query?)`, `fetchOne(uuid)`, `create`, `update`, `remove`, `fetchActions(uuid)`, `invokeAction(uuid, actionName, payload?)` |
275
- | **Persist** | `pick: ['lastQueryByType']` → cookie `cms-content-v1.lastQueryByType` (non-httpOnly) |
276
-
277
- > Mutations (`create`/`update`/`remove`/`invokeAction`) do **not** auto-refetch the affected slice — call `fetchByType(typeName)` again if you need it refreshed.
278
-
279
- ### `useRulesStore` — `cms-rules`
280
-
281
- | | |
282
- |---|---|
283
- | **State** | `items`, `total`, `isLoading`, `loadError`, `lastQuery`, `selectedRule` |
284
- | **Getters** | `isEmpty`, `activeRules` |
285
- | **Actions** | `setSdk`, `fetch(query?)`, `select(uuid)`, `create`, `update`, `toggle(uuid, active)`, `remove` |
286
- | **Persist** | `pick: ['lastQuery']` → cookie `cms-rules-v1.lastQuery` (non-httpOnly) |
287
-
288
- ### `useExecutionsStore` — `cms-executions`
289
-
290
- | | |
291
- |---|---|
292
- | **State** | `items`, `total`, `isLoading`, `loadError`, `lastParams` |
293
- | **Getters** | `isEmpty` |
294
- | **Actions** | `setSdk`, `list(params?)` (read-only) |
295
- | **Persist** | `pick: ['lastParams']` → cookie `cms-executions-v1.lastParams` (non-httpOnly) |
296
-
297
- ### `useSettingsStore` — `cms-settings`
298
-
299
- | | |
300
- |---|---|
301
- | **State** | `monitoredGroups`, `isLoading`, `loadError` |
302
- | **Getters** | `isEmpty` |
303
- | **Actions** | `setSdk`, `fetchGroups()`, `addGroup(group)`, `removeGroup(group)` (optimistic + rollback) |
304
- | **Persist** | `pick: ['monitoredGroups']` → cookie `cms-settings-v1.monitoredGroups` (non-httpOnly) |
305
-
306
- ---
307
-
308
- ## Authentication & token flow
309
-
310
- > **Primary call path: Pinia store actions.** Call `useAuthStore()` actions from your app — they own the SDK and the reactive state. `$cms.*` (services / raw `$Fetch`) is an escape hatch.
311
-
312
- The durable token copy lives in an **httpOnly cookie** (`cms_access_token`) managed by the Nuxt module; a parallel `cms_refresh_token` cookie backs silent refresh. `TokenManager` is the in-memory source for request headers; `useAuthStore.accessToken` is a reactive mirror (restored from the cookie on SSR, **never** persisted to localStorage).
313
-
314
- ### How it flows (Nuxt module)
315
-
316
- ```
317
- login → useAuthStore.login() → /api/cms/login (server route)
318
- → backend POST /api/auth/login → token in response BODY (accessToken)
319
- → route sets httpOnly cookies cms_access_token + cms_refresh_token (atomic)
320
- → returns { ..., token } → client mirrors token in memory
321
- logout → useAuthStore.logout() → /api/cms/logout
322
- → backend POST /api/auth/logout (Bearer from cookie) + both cookies cleared
323
- refresh → on 401, createApi calls /api/cms/refresh once
324
- → backend /api/oauth/token (grant_type=refresh_token, reads cms_refresh_token cookie)
325
- → rotates both cookies, replays the request; on failure clears session + onUnauthorized
326
- sliding → rotated token in response header → onToken → /api/cms/set-token (cookie refreshed, no drift)
327
- data → useContentStore.fetchByType() / useRulesStore.fetch() → direct client→backend
328
- → onRequest injects Authorization from TokenManager
329
- F5 → SSR reads httpOnly token cookie → restores TokenManager + accessToken mirror;
330
- persist plugin hydrates authUser (cms-persist-auth.authUser) from its cookie
331
- 401 → onResponseError → onToken(null) → /api/cms/clear-token + onUnauthorized fired
332
- ```
333
-
334
- ### Server routes registered by the module
335
-
336
- | Route | Purpose |
337
- |---|---|
338
- | `POST /api/cms/login` | login → backend `/api/auth/login`, sets httpOnly `cms_access_token` + `cms_refresh_token` from body, returns `{ ..., token }` |
339
- | `POST /api/cms/logout` | logout (backend `POST /api/auth/logout` + clears both cookies) |
340
- | `POST /api/cms/refresh` | silent refresh — reads httpOnly `cms_refresh_token` → backend `/api/oauth/token` (`grant_type=refresh_token`), rotates both cookies, returns new access token |
341
- | `POST /api/cms/set-token` | sync cookie on sliding-refresh / external token restore |
342
- | `DELETE /api/cms/clear-token` | clear cookie on 401 |
343
- | `POST /api/cms-sdk/persist` | set/clear an **allowlisted** httpOnly persist cookie (internal persistence plugin) |
344
-
345
- These are thin proxies (raw `ofetch`, no SDK instance) — they read `device_id`/`device_ip`/`user-agent` from the request, call the backend, and set/clear the httpOnly cookie.
346
-
347
- ### `TokenManager`
348
-
349
- ```ts
350
- sdk.tokenManager.get() // string | null
351
- sdk.tokenManager.has() // boolean
352
- sdk.tokenManager.set(t) // store + onRefreshed hook
353
- sdk.tokenManager.clear()
354
- sdk.tokenManager.getHeader() // `${prefix}${token}` ready for a header
355
- sdk.tokenManager.swapStorage(storage) // upgrade storage at runtime
356
- ```
357
-
358
- ### `sdk.syncToken(token)`
359
-
360
- Used by login (via the route body `token`) / external SSO / 401. When an `onToken` hook is configured (the Nuxt module wires one), `syncToken` awaits it (sets memory + syncs the httpOnly cookie); otherwise it falls back to `TokenManager.set/clear`.
361
-
362
- ```ts
363
- await sdk.syncToken('jwt-from-external-sso') // set
364
- await sdk.syncToken(null) // clear
365
- ```
366
-
367
- ### Credentials storage (TokenManager backend)
368
-
369
- The Nuxt module forces **`'memory'`** for TokenManager (the httpOnly cookie is the real persistence). For standalone `createCmsSDK()` you can choose:
370
-
371
- | `storage` | Backend | Use when |
372
- |---|---|---|
373
- | `'memory'` | in-process variable | SSR-safe; token lost on reload |
374
- | `'localStorage'` | browser localStorage | SPA, acceptable XSS exposure |
375
- | `'cookie'` | browser cookie (`document.cookie`) | SPA only — **client-side**; absent during SSR |
376
-
377
- ### Requirements (Nuxt)
378
-
379
- - The SDK registers its **built-in cookie-based persistence plugin** automatically via the Nuxt module — **no `pinia-plugin-persistedstate` (or any extra module) is needed**. Persistence is per-state: sensitive state (`authUser`) uses an **httpOnly cookie** written through `/api/cms-sdk/persist`, while UI prefs (`lastQueryByType`, `lastQuery`, `lastParams`, `monitoredGroups`) use **non-httpOnly cookies** written directly on the client. Both are SSR-readable (instant on F5).
380
- - The httpOnly cookie is **not readable by JS**; client-side calls rely on the store mirror restored from the SSR payload.
381
-
382
- > Cookie defaults for the httpOnly cookie: `Secure`, `SameSite=Lax`, `Path=/`, `Max-Age=30 days`, `httpOnly: true` (see `COOKIE_DEFAULTS`).
383
-
384
- > **Cookie size limit:** cookies are ~4 KB. If your JWT exceeds this, the set-token/login route will fail; consider smaller tokens.
385
-
386
- ---
387
-
388
- ## Error handling
389
-
390
- Every failed request is normalised to a `CmsError` and passed to `onError` (and `onUnauthorized` on 401). You may also `try/catch` locally.
391
-
392
- ```ts
393
- import { CmsError } from '@eaera-com/cms-sdk'
394
-
395
- try {
396
- await sdk.content.detail('missing-uuid')
397
- } catch (e) {
398
- const err = e as CmsError
399
- err.message // string
400
- err.statusCode // number (0 = network)
401
- err.code // string
402
- err.fieldErrors // Record<string,string> | undefined (422)
403
- err.requestId // from x-request-id header
404
- err.isUnauthorized // 401
405
- err.isValidation // 422
406
- err.isNetwork // statusCode === 0
407
- }
408
- ```
409
-
410
- ---
411
-
412
- ## Runtime request middlewares
413
-
414
- Register a custom request hook at runtime (the "dynamic interceptor" escape hatch). `target` is `'all'` or an endpoint name (`'cms'`, `'report'`, …). Returns an unsubscribe.
415
-
416
- ```ts
417
- const off = sdk.addRequestMiddleware('all', (options, ctx) => {
418
- options.headers.set('X-Experiment', 'variant-b')
419
- })
420
- // later
421
- off()
422
- ```
423
-
424
- ---
425
-
426
- ## Mock mode
427
-
428
- While the backend is under development, `mock: true` makes every domain service resolve from a local fake dataset and never touch the network.
429
-
430
- ```ts
431
- const sdk = createCmsSDK({ endpoints: { cms: 'http://x' }, mock: true })
432
-
433
- await sdk.content.list({ type: 'Mt5Account' }) // fake data
434
- sdk.setMock(false) // switch to real backend at runtime
435
- ```
436
-
437
- ---
438
-
439
- ## Project structure
440
-
441
- ```
442
- src/
443
- ├── core/ # Stable foundation
444
- │ ├── createCmsSDK.ts # Facade entry point (builds everything)
445
- │ ├── createApi.ts # ofetch factory + auth/refresh/error hooks
446
- │ ├── errors.ts # CmsError (normalised error shape)
447
- │ ├── sdk.type.ts # Core public types (ICmsSDK, ICmsSDKOptions, …)
448
- │ └── credentials/ # TokenManager + storage adapters
449
- ├── services/ # 5 domain services (stateless, call $Fetch)
450
- │ ├── auth/ # login / logout / OAuth refresh (confirmed BE)
451
- │ ├── content/ # CRUD + actions over /api/v1/content
452
- │ ├── rules/ # rule CRUD + trigger/condition builders
453
- │ ├── executions/ # read-only execution feed
454
- │ └── settings/ # monitored groups
455
- ├── stores/ # 5 Pinia setup stores + persistence plugin
456
- ├── server/ # h3 handlers (auth proxies + token/persist)
457
- ├── common/
458
- │ ├── constants/ # UPPER_SNAKE const objects (no hardcoding)
459
- │ ├── helpers/ # formatPrice / computeSpread / categoryLabel
460
- │ └── utils/ # SDK-internal utilities (clone, casing)
461
- ├── nuxt.ts # Nuxt module (registers $cms + stores + persistence)
462
- └── index.ts # Public barrel exports
463
- ```
464
-
465
- Layering rule: `stores` → `services` → `core` (createApi/TokenManager) → `common` (constants/helpers/utils). Nothing reaches upward.
466
-
467
- ## Development
468
-
469
- ```bash
470
- pnpm install
471
- pnpm test # vitest
472
- pnpm typecheck # tsc --noEmit
473
- pnpm build # vite build → dist/
474
- ```
475
-
476
- ## License
477
-
478
- See [LICENSE](./LICENSE). Published to the public npm registry — see
479
- [docs/npm-publishing.md](./docs/npm-publishing.md) for the release pipeline.
@@ -1,4 +1,4 @@
1
- import { a as COOKIE_DEFAULTS } from './storage.constant--64BDSPW.js';
1
+ import { a as COOKIE_DEFAULTS } from './storage.constant-CVWam5ls.js';
2
2
  import { useRuntimeConfig } from '#imports';
3
3
 
4
4
  const TOKEN_COOKIE_OPTIONS = {
@@ -24,4 +24,4 @@ function readSdkConfig(event) {
24
24
  }
25
25
 
26
26
  export { TOKEN_COOKIE_OPTIONS as T, readSdkConfig as r };
27
- //# sourceMappingURL=_shared-DdBWuPm_.js.map
27
+ //# sourceMappingURL=_shared-DZlTUzoa.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_shared-DdBWuPm_.js","sources":["../../src/server/auth/_shared.ts"],"sourcesContent":["import {\n COOKIE_DEFAULTS,\n REFRESH_TOKEN_STORAGE_KEY,\n TOKEN_STORAGE_KEY,\n} from '../../common/constants/storage.constant';\nimport type { RequestCasing, ResponseCasing } from '../../common/constants/http.constant';\nimport { useRuntimeConfig } from '#imports';\n\n/** Cookie options for the httpOnly access-token cookie (server-side set). */\nexport const TOKEN_COOKIE_OPTIONS = {\n httpOnly: COOKIE_DEFAULTS.HTTP_ONLY,\n secure: COOKIE_DEFAULTS.SECURE,\n sameSite: COOKIE_DEFAULTS.SAME_SITE.toLowerCase() as 'lax',\n path: COOKIE_DEFAULTS.PATH,\n maxAge: COOKIE_DEFAULTS.MAX_AGE_SECONDS,\n};\n\nexport { TOKEN_STORAGE_KEY, REFRESH_TOKEN_STORAGE_KEY };\n\n/**\n * Resolve the CMS backend URL + client id from the module options the Nuxt\n * module stored on `runtimeConfig.public.cmsSdkOptions` (mirrors the plugin\n * resolution). Runs inside the consumer's Nitro server where `useRuntimeConfig`\n * is auto-imported.\n */\nexport function readSdkConfig(event: unknown): {\n cmsUrl: string;\n clientId: string;\n /** Default portal id for portal-scoped flows (module option `cmsSdk.portal`). */\n portal?: string;\n requestCasing?: RequestCasing;\n responseCasing?: ResponseCasing;\n} {\n const config = useRuntimeConfig(event) as { public?: Record<string, any> } | undefined;\n const pub = (config?.public ?? {}) as Record<string, any>;\n const opts = (pub.cmsSdkOptions ?? {}) as {\n endpoints?: Record<string, string>;\n clientId?: string;\n portal?: string;\n requestCasing?: RequestCasing;\n responseCasing?: ResponseCasing;\n };\n const cmsUrl = pub[opts.endpoints?.cms ?? 'cmsUrl'];\n const clientId = pub[opts.clientId ?? 'clientId'];\n return {\n cmsUrl,\n clientId,\n portal: opts.portal,\n requestCasing: opts.requestCasing,\n responseCasing: opts.responseCasing,\n };\n}\n"],"names":[],"mappings":";;;AASO,MAAM,oBAAA,GAAuB;AAAA,EAClC,UAAU,eAAA,CAAgB,SAAA;AAAA,EAC1B,QAAQ,eAAA,CAAgB,MAAA;AAAA,EACxB,QAAA,EAAU,eAAA,CAAgB,SAAA,CAAU,WAAA,EAAY;AAAA,EAChD,MAAM,eAAA,CAAgB,IAAA;AAAA,EACtB,QAAQ,eAAA,CAAgB;AAC1B;AAUO,SAAS,cAAc,KAAA,EAO5B;AACA,EAAA,MAAM,MAAA,GAAS,iBAAiB,KAAK,CAAA;AACrC,EAAA,MAAM,GAAA,GAAO,MAAA,EAAQ,MAAA,IAAU,EAAC;AAChC,EAAA,MAAM,IAAA,GAAQ,GAAA,CAAI,aAAA,IAAiB,EAAC;AAOpC,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,SAAA,EAAW,OAAO,QAAQ,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,IAAA,CAAK,QAAA,IAAY,UAAU,CAAA;AAChD,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,eAAe,IAAA,CAAK,aAAA;AAAA,IACpB,gBAAgB,IAAA,CAAK;AAAA,GACvB;AACF;;;;"}
1
+ {"version":3,"file":"_shared-DZlTUzoa.js","sources":["../../src/server/auth/_shared.ts"],"sourcesContent":["import {\n COOKIE_DEFAULTS,\n REFRESH_TOKEN_STORAGE_KEY,\n TOKEN_STORAGE_KEY,\n} from '../../common/constants/storage.constant';\nimport type { RequestCasing, ResponseCasing } from '../../common/constants/http.constant';\nimport { useRuntimeConfig } from '#imports';\n\n/** Cookie options for the httpOnly access-token cookie (server-side set). */\nexport const TOKEN_COOKIE_OPTIONS = {\n httpOnly: COOKIE_DEFAULTS.HTTP_ONLY,\n secure: COOKIE_DEFAULTS.SECURE,\n sameSite: COOKIE_DEFAULTS.SAME_SITE.toLowerCase() as 'lax',\n path: COOKIE_DEFAULTS.PATH,\n maxAge: COOKIE_DEFAULTS.MAX_AGE_SECONDS,\n};\n\nexport { TOKEN_STORAGE_KEY, REFRESH_TOKEN_STORAGE_KEY };\n\n/**\n * Resolve the CMS backend URL + client id from the module options the Nuxt\n * module stored on `runtimeConfig.public.cmsSdkOptions` (mirrors the plugin\n * resolution). Runs inside the consumer's Nitro server where `useRuntimeConfig`\n * is auto-imported.\n */\nexport function readSdkConfig(event: unknown): {\n cmsUrl: string;\n clientId: string;\n /** Default portal id for portal-scoped flows (module option `cmsSdk.portal`). */\n portal?: string;\n requestCasing?: RequestCasing;\n responseCasing?: ResponseCasing;\n} {\n const config = useRuntimeConfig(event) as { public?: Record<string, any> } | undefined;\n const pub = (config?.public ?? {}) as Record<string, any>;\n const opts = (pub.cmsSdkOptions ?? {}) as {\n endpoints?: Record<string, string>;\n clientId?: string;\n portal?: string;\n requestCasing?: RequestCasing;\n responseCasing?: ResponseCasing;\n };\n const cmsUrl = pub[opts.endpoints?.cms ?? 'cmsUrl'];\n const clientId = pub[opts.clientId ?? 'clientId'];\n return {\n cmsUrl,\n clientId,\n portal: opts.portal,\n requestCasing: opts.requestCasing,\n responseCasing: opts.responseCasing,\n };\n}\n"],"names":[],"mappings":";;;AASO,MAAM,oBAAA,GAAuB;AAAA,EAClC,UAAU,eAAA,CAAgB,SAAA;AAAA,EAC1B,QAAQ,eAAA,CAAgB,MAAA;AAAA,EACxB,QAAA,EAAU,eAAA,CAAgB,SAAA,CAAU,WAAA,EAAY;AAAA,EAChD,MAAM,eAAA,CAAgB,IAAA;AAAA,EACtB,QAAQ,eAAA,CAAgB;AAC1B;AAUO,SAAS,cAAc,KAAA,EAO5B;AACA,EAAA,MAAM,MAAA,GAAS,iBAAiB,KAAK,CAAA;AACrC,EAAA,MAAM,GAAA,GAAO,MAAA,EAAQ,MAAA,IAAU,EAAC;AAChC,EAAA,MAAM,IAAA,GAAQ,GAAA,CAAI,aAAA,IAAiB,EAAC;AAOpC,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,SAAA,EAAW,OAAO,QAAQ,CAAA;AAClD,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,IAAA,CAAK,QAAA,IAAY,UAAU,CAAA;AAChD,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAQ,IAAA,CAAK,MAAA;AAAA,IACb,eAAe,IAAA,CAAK,aAAA;AAAA,IACpB,gBAAgB,IAAA,CAAK;AAAA,GACvB;AACF;;;;"}
@@ -10,6 +10,7 @@ const COOKIE_DEFAULTS = {
10
10
  };
11
11
  const STORE_PERSIST_KEY = {
12
12
  AUTH: "cms-auth-v1",
13
+ AUTHZ: "cms-authz-v1",
13
14
  CONTENT: "cms-content-v1",
14
15
  RULES: "cms-rules-v1",
15
16
  EXECUTIONS: "cms-executions-v1",
@@ -41,4 +42,4 @@ const COOKIE_SAME_SITE = {
41
42
  };
42
43
 
43
44
  export { COOKIE_SAME_SITE as C, PERSIST_KEY as P, REFRESH_TOKEN_STORAGE_KEY as R, STORE_PERSIST_KEY as S, TOKEN_STORAGE_KEY as T, COOKIE_DEFAULTS as a, PERSIST_ALLOWED_KEYS as b, PERSIST_MAX_BYTES as c, PERSIST_PREFIX as d, STORAGE_NAME as e };
44
- //# sourceMappingURL=storage.constant--64BDSPW.js.map
45
+ //# sourceMappingURL=storage.constant-CVWam5ls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.constant-CVWam5ls.js","sources":["../../src/common/constants/storage.constant.ts"],"sourcesContent":["/**\n * Token storage constants — the single source for the storage key so the\n * memory/localStorage/cookie adapters never drift apart.\n */\nexport const TOKEN_STORAGE_KEY = 'cms_access_token' as const;\n\n/**\n * Refresh-token cookie key. Stored as an httpOnly cookie (parallel to the access\n * token) so the SDK can silently rotate the access token when it expires.\n */\nexport const REFRESH_TOKEN_STORAGE_KEY = 'cms_refresh_token' as const;\n\n/** Cookie adapter defaults (overridable via `credentials.cookie`). */\nexport const COOKIE_DEFAULTS = {\n MAX_AGE_SECONDS: 60 * 60 * 24 * 30, // 30 days\n PATH: '/',\n SECURE: true,\n SAME_SITE: 'Lax',\n HTTP_ONLY: true,\n} as const;\n\n/** Pinia persistence keys (versioned for schema migration). */\nexport const STORE_PERSIST_KEY = {\n AUTH: 'cms-auth-v1',\n AUTHZ: 'cms-authz-v1',\n CONTENT: 'cms-content-v1',\n RULES: 'cms-rules-v1',\n EXECUTIONS: 'cms-executions-v1',\n SETTINGS: 'cms-settings-v1',\n LLM: 'cms-llm-v1',\n LLM_PROVIDERS: 'cms-llm-providers-v1',\n BARS: 'cms-bars-v1',\n HISTORY: 'cms-history-v1',\n KALSHI: 'cms-kalshi-v1',\n GOV: 'cms-gov-v1',\n PEOPLE: 'cms-people-v1',\n} as const;\n\n/**\n * Internal cookie persistence (replaces pinia-plugin-persistedstate).\n *\n * Each persisted state → its own cookie `${PERSIST_KEY.<store>}.${field}`.\n * The common `/api/cms-sdk/persist` endpoint may only set keys in\n * `PERSIST_ALLOWED_KEYS` (single source of truth, shared by the plugin + the\n * endpoint). The token key is always excluded as a belt-and-suspenders guard.\n */\nexport const PERSIST_PREFIX = 'cms-persist-' as const;\n\nexport const PERSIST_KEY = {\n AUTH: 'cms-persist-auth',\n} as const;\n\n/** Max bytes per persisted cookie value (~4KB cookie cap, with headroom). */\nexport const PERSIST_MAX_BYTES = 3500;\n\n/** Allowlist of cookie keys the persist endpoint may set. */\nexport const PERSIST_ALLOWED_KEYS = [`${PERSIST_KEY.AUTH}.authUser`] as const;\n\n/**\n * Storage backend names accepted by `TokenManager`. Kept in sync with\n * `IStorageName` in `core/credentials/credential.type.ts`. The\n * `httpOnlyBridge` value is the recommended production backend (delegates\n * to a Nuxt server route).\n */\nexport const STORAGE_NAME = {\n MEMORY: 'memory',\n LOCAL_STORAGE: 'localStorage',\n COOKIE: 'cookie',\n HTTP_ONLY_BRIDGE: 'httpOnlyBridge',\n} as const;\nexport type StorageName = (typeof STORAGE_NAME)[keyof typeof STORAGE_NAME];\n\n/**\n * `Cookie SameSite` values (RFC 6265). Used by `cookieStorage` and the\n * SDK Nuxt module when setting the httpOnly access-token cookie. Mirrors\n * the `CookieSameSite` type in `core/credentials/storages/cookieStorage.ts`.\n */\nexport const COOKIE_SAME_SITE = {\n STRICT: 'Strict',\n LAX: 'Lax',\n NONE: 'None',\n} as const;\nexport type CookieSameSite = (typeof COOKIE_SAME_SITE)[keyof typeof COOKIE_SAME_SITE];\n"],"names":[],"mappings":"AAIO,MAAM,iBAAA,GAAoB;AAM1B,MAAM,yBAAA,GAA4B;AAGlC,MAAM,eAAA,GAAkB;AAAA,EAC7B,eAAA,EAAiB,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA;AAAA;AAAA,EAChC,IAAA,EAAM,GAAA;AAAA,EACN,MAAA,EAAQ,IAAA;AAAA,EACR,SAAA,EAAW,KAAA;AAAA,EACX,SAAA,EAAW;AACb;AAGO,MAAM,iBAAA,GAAoB;AAAA,EAC/B,IAAA,EAAM,aAAA;AAAA,EACN,KAAA,EAAO,cAAA;AAAA,EACP,OAAA,EAAS,gBAAA;AAAA,EACT,KAAA,EAAO,cAAA;AAAA,EACP,UAAA,EAAY,mBAAA;AAAA,EACZ,QAAA,EAAU,iBAAA;AAAA,EACV,GAAA,EAAK,YAAA;AAAA,EACL,aAAA,EAAe,sBAAA;AAAA,EACf,IAAA,EAAM,aAAA;AAAA,EACN,OAAA,EAAS,gBAAA;AAAA,EACT,MAAA,EAAQ,eAAA;AAAA,EACR,GAAA,EAAK,YAAA;AAAA,EACL,MAAA,EAAQ;AACV;AAUO,MAAM,cAAA,GAAiB;AAEvB,MAAM,WAAA,GAAc;AAAA,EACzB,IAAA,EAAM;AACR;AAGO,MAAM,iBAAA,GAAoB;AAG1B,MAAM,oBAAA,GAAuB,CAAC,CAAA,EAAG,WAAA,CAAY,IAAI,CAAA,SAAA,CAAW;AAQ5D,MAAM,YAAA,GAAe;AAAA,EAC1B,MAAA,EAAQ,QAAA;AAAA,EACR,aAAA,EAAe,cAAA;AAAA,EACf,MAAA,EAAQ,QAAA;AAAA,EACR,gBAAA,EAAkB;AACpB;AAQO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,MAAA,EAAQ,QAAA;AAAA,EACR,GAAA,EAAK,KAAA;AAAA,EACL,IAAA,EAAM;AACR;;;;"}
@@ -19,6 +19,7 @@ export declare const COOKIE_DEFAULTS: {
19
19
  /** Pinia persistence keys (versioned for schema migration). */
20
20
  export declare const STORE_PERSIST_KEY: {
21
21
  readonly AUTH: "cms-auth-v1";
22
+ readonly AUTHZ: "cms-authz-v1";
22
23
  readonly CONTENT: "cms-content-v1";
23
24
  readonly RULES: "cms-rules-v1";
24
25
  readonly EXECUTIONS: "cms-executions-v1";
@@ -1 +1 @@
1
- {"version":3,"file":"storage.constant.d.ts","sourceRoot":"","sources":["../../../src/common/constants/storage.constant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAG,kBAA2B,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAG,mBAA4B,CAAC;AAEtE,sEAAsE;AACtE,eAAO,MAAM,eAAe;;;;;;CAMlB,CAAC;AAEX,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;CAapB,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAG,cAAuB,CAAC;AAEtD,eAAO,MAAM,WAAW;;CAEd,CAAC;AAEX,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,wCAA4C,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;;;CAKf,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"storage.constant.d.ts","sourceRoot":"","sources":["../../../src/common/constants/storage.constant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAG,kBAA2B,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAG,mBAA4B,CAAC;AAEtE,sEAAsE;AACtE,eAAO,MAAM,eAAe;;;;;;CAMlB,CAAC;AAEX,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;CAcpB,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,EAAG,cAAuB,CAAC;AAEtD,eAAO,MAAM,WAAW;;CAEd,CAAC;AAEX,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,wCAA4C,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;;;CAKf,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;CAInB,CAAC;AACX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC"}