woodsportal-client-sdk 1.1.5-dev.0 → 4.0.0-dev.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +171 -77
  3. package/dist/adapters/angular/index.d.ts +58 -5
  4. package/dist/adapters/angular/index.js +8 -7
  5. package/dist/adapters/angular/index.js.map +1 -1
  6. package/dist/adapters/react/index.d.ts +58 -5
  7. package/dist/adapters/react/index.js +7 -6
  8. package/dist/adapters/react/index.js.map +1 -1
  9. package/dist/adapters/vue/index.d.ts +58 -5
  10. package/dist/adapters/vue/index.js +8 -7
  11. package/dist/adapters/vue/index.js.map +1 -1
  12. package/dist/auth-utils-VT7HSLMA.js +3 -0
  13. package/dist/{auth-utils-A4WPJMPK.js.map → auth-utils-VT7HSLMA.js.map} +1 -1
  14. package/dist/authentication-BfYhAeMs.d.ts +463 -0
  15. package/dist/cache-purge-G5WkHckd.d.ts +236 -0
  16. package/dist/chunk-3FUHGFAQ.js +167 -0
  17. package/dist/chunk-3FUHGFAQ.js.map +1 -0
  18. package/dist/chunk-4ZV3MQIB.js +2000 -0
  19. package/dist/chunk-4ZV3MQIB.js.map +1 -0
  20. package/dist/{chunk-Y5MRAAGK.js → chunk-AYTO6ND7.js} +3 -3
  21. package/dist/chunk-AYTO6ND7.js.map +1 -0
  22. package/dist/chunk-G2KECOVQ.js +504 -0
  23. package/dist/chunk-G2KECOVQ.js.map +1 -0
  24. package/dist/chunk-H57IQHVF.js +20 -0
  25. package/dist/chunk-H57IQHVF.js.map +1 -0
  26. package/dist/chunk-KBXI2JBA.js +1200 -0
  27. package/dist/chunk-KBXI2JBA.js.map +1 -0
  28. package/dist/chunk-KCZFT6OM.js +416 -0
  29. package/dist/chunk-KCZFT6OM.js.map +1 -0
  30. package/dist/chunk-SSS4DNXP.js +304 -0
  31. package/dist/chunk-SSS4DNXP.js.map +1 -0
  32. package/dist/entries/auth.d.ts +68 -0
  33. package/dist/entries/auth.js +13 -0
  34. package/dist/entries/auth.js.map +1 -0
  35. package/dist/entries/crm.d.ts +208 -0
  36. package/dist/entries/crm.js +24 -0
  37. package/dist/entries/crm.js.map +1 -0
  38. package/dist/index-CCwMopD8.d.ts +38 -0
  39. package/dist/index.d.ts +405 -406
  40. package/dist/index.js +23 -1720
  41. package/dist/index.js.map +1 -1
  42. package/dist/use-sync-DpazhM4d.d.ts +60 -0
  43. package/dist/use-uploader-2F1zc7Cl.d.ts +23 -0
  44. package/package.json +53 -8
  45. package/dist/auth-utils-A4WPJMPK.js +0 -4
  46. package/dist/chunk-J7MDPY5P.js +0 -54
  47. package/dist/chunk-J7MDPY5P.js.map +0 -1
  48. package/dist/chunk-NB7AINV4.js +0 -35
  49. package/dist/chunk-NB7AINV4.js.map +0 -1
  50. package/dist/chunk-RDCT25UV.js +0 -1066
  51. package/dist/chunk-RDCT25UV.js.map +0 -1
  52. package/dist/chunk-Y5MRAAGK.js.map +0 -1
  53. package/dist/chunk-YLZA5S7A.js +0 -102
  54. package/dist/chunk-YLZA5S7A.js.map +0 -1
  55. package/dist/use-sync-LbURBOs_.d.ts +0 -29
package/CHANGELOG.md CHANGED
@@ -5,10 +5,119 @@ All notable changes to **woodsportal-client-sdk** are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.0.0] - 2026-06-06
9
+
10
+ ### Removed (breaking)
11
+
12
+ - **`state/legacy/`** — legacy table UI store (`use-table-ui`, `create-store`, `table-stores`, `resolve-table-list-params`).
13
+ - **Flat root `api.*` shims** — only `api.auth`, `api.crm`, `api.navigation` remain.
14
+ - **Top-level navigation exports** — `url`, `routeParam`, `breadcrumbsDetails` (use `api.navigation.*`).
15
+ - **`config` export** — use `hubContext` / `setHubContext`.
16
+ - **`store.useTable` / `store.legacyTableUi`** — use `store.tableUi` or `useTableUi()` from framework adapters.
17
+
18
+ ### Changed (breaking)
19
+
20
+ - **Table UI state:** canonical `state/crm/table-ui.ts` + `table-ui-actions.ts`; reactive hook **`useTableUi()`** (replaces `useLegacyTableUi`).
21
+ - **List mutations:** `api.crm.objects.list()` and `api.crm.pipelines.list()` **require** `payload.tableParams` (no global store fallback).
22
+ - **`setObjectsData`** accepts `{ stageId }` from resolved list params (board append no longer reads global legacy store).
23
+
24
+ ### Added
25
+
26
+ - `store.tableUi: { store, actions }` on root `store` export.
27
+ - `features/crm/helpers/normalize-table-list-params.ts` — validates required `tableParams`.
28
+ - Tests updated for nested-only API surface.
29
+
30
+ ### Migration (3.x → 4.0)
31
+
32
+ | 3.x | 4.0 |
33
+ |-----|-----|
34
+ | `useLegacyTableUi()` | `useTableUi()` |
35
+ | `store.legacyTableUi()` | `store.tableUi.actions` / `useTableUi()` |
36
+ | `api.login()` | `api.auth.login()` |
37
+ | `url.makeLink()` | `api.navigation.url.makeLink()` |
38
+ | `list({ hubspotObjectTypeId })` | `list({ hubspotObjectTypeId, tableParams: getTableParam() })` |
39
+
40
+ See [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md).
41
+
42
+ ## [3.0.0] - 2026-06-06
43
+
44
+ ### Changed (breaking)
45
+
46
+ - **Nested public API:** `api.auth`, `api.crm`, `api.navigation` replace the flat `api.*` spread as the canonical surface. See [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md).
47
+ - **Mutation aliases:** primary alias matches nested leaf (`api.crm.objects.list()` → `{ list, mutate, isLoading }`).
48
+ - **Session helpers:** `api.auth.session.isAuthenticated()`, `refreshAccessToken()`, `isAccessTokenExpired()` (flat names deprecated).
49
+ - **Profile update:** nested key `api.auth.updateProfile()` (was `profileUpdate`).
50
+ - **Store:** `store.legacyTableUi` (was `store.useTable` — shim retained until 4.0).
51
+ - **Hub context:** prefer `hubContext` export; `config` deprecated.
52
+
53
+ ### Added
54
+
55
+ - [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md) — full 2.x→3.x migration matrix.
56
+ - `src/test/apis/nested-api.test.ts`, `flat-api-governance.test.ts` — nested paths + shim registry guards.
57
+ - Concurrent `me()` dedup (in-flight share).
58
+
59
+ ### Deprecated (remove in 4.0)
60
+
61
+ - Flat root `api.*` keys (`api.objects`, `api.login`, …) — delegate to nested paths.
62
+ - Top-level `url`, `routeParam`, `breadcrumbsDetails` — use `api.navigation.*`.
63
+ - `store.useTable`, `config` export.
64
+
65
+ ### Migration
66
+
67
+ See [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md) for the full table. Examples:
68
+
69
+ | 2.x | 3.0 |
70
+ |-----|-----|
71
+ | `api.login()` | `api.auth.login()` |
72
+ | `api.objects()` / `{ getObjects }` | `api.crm.objects.list()` / `{ list }` |
73
+ | `api.getAccessToken()` | `api.auth.session.getAccessToken()` |
74
+ | `url.makeLink()` | `api.navigation.url.makeLink()` |
75
+
76
+ ## [2.0.0] - 2026-06-05
77
+
78
+ ### Changed (breaking)
79
+
80
+ - **Folder structure:** layered layout — `core/`, `features/auth|crm|navigation`, `state/crm/`. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
81
+ - **Subpath entries:** `woodsportal-client-sdk/auth` and `/crm` no longer re-import the monolithic `index.ts` graph.
82
+ - **Navigation URL helpers:** `url.useMakeLink` → `url.makeLink`, `url.useUpdateLink` → `url.updateLink` (factory functions; not React hooks).
83
+
84
+ ### Removed (breaking)
85
+
86
+ - `clint` namespace export
87
+ - `http-clint.ts`, `localStoraget.ts` typo shims
88
+ - `routing/route-param.ts` (use `routeParam` from main or `/crm` entry)
89
+
90
+ ### Migration
91
+
92
+ | Before (1.x) | After (2.0) |
93
+ | ----------------------------------------------------- | ---------------------------- |
94
+ | `url.useMakeLink()` | `url.makeLink()` |
95
+ | `url.useUpdateLink()` | `url.updateLink()` |
96
+ | `import … from 'woodsportal-client-sdk'` (deep paths) | Use documented subpaths only |
97
+ | `clint` | `Client` from main export |
98
+
99
+ ### Added
100
+
101
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md)
102
+ - CRM entry bundle budget in `npm run size:check`
103
+ - `examples/smoke-consumer/smoke-auth-only.mjs` — auth subpath isolation smoke
104
+
8
105
  ## [Unreleased]
9
106
 
10
107
  ### Added
11
108
 
109
+ - **Client-lane MFA APIs:** `verifyOtp`, `sendMfaOtp`, pending passkey MFA step, enrollment (TOTP, phone verify, WebAuthn/passkeys), passwordless passkey login, and `getMfaStatus` / `setMfaPreferences`.
110
+ - **Client-lane security settings APIs:** `getSecurityOverview`, `getSecurityLoginActivity`, `getSecuritySessions`, `revokeSecuritySession`, `revokeOtherSecuritySessions`.
111
+ - **MFA-aware login:** `api.login()` stores only the temp access JWT when `twoFactorRequired` is true (no refresh token until MFA completes).
112
+ - Exported TypeScript types for MFA and security DTOs (`MfaMethod`, `SecurityOverview`, `ActiveSession`, etc.).
113
+ - **Docs:** [`docs/MFA-SECURITY-SDK.md`](docs/MFA-SECURITY-SDK.md) — full SDK method → API reference with examples; JSDoc on all MFA/security facades for IDE hover.
114
+
115
+ ### Changed
116
+
117
+ - **Source layout:** production code under `src/main/`, unit tests under `src/test/` (Spring Boot / Maven mirror). Update local imports or docs that referenced `src/` directly.
118
+
119
+ ### Added
120
+
12
121
  - **`formatHubSpotActivityDateTime`**, **`formatHubSpotActivityDateTimeParts`**, **`formatGmtOffset`**, **`normalizeToTimestamp`**, and **`DEFAULT_HUBSPOT_TIMEZONE`** (`Asia/Kolkata`) for HubSpot-style activity timestamps (e.g. `May 27, 2026 at 11:31 PM GMT+5:30`).
13
122
 
14
123
  ### Added
package/README.md CHANGED
@@ -2,13 +2,30 @@
2
2
 
3
3
  TypeScript/JavaScript **ESM** client for the **WoodsPortal** HTTP API: authentication, SSO, users, pipelines, HubSpot-aligned objects, notes, emails, uploads, and files.
4
4
 
5
- | | |
6
- |---|---|
7
- | **npm** | [`woodsportal-client-sdk`](https://www.npmjs.com/package/woodsportal-client-sdk) |
8
- | **Source** | [`Digital-Woods/digitalwoods.io-woodsportal-client-sdk`](https://github.com/Digital-Woods/digitalwoods.io-woodsportal-client-sdk) |
9
- | **Issues** | [GitHub Issues](https://github.com/Digital-Woods/digitalwoods.io-woodsportal-client-sdk/issues) |
10
- | **Runtime** | Node **≥ 18**; **ESM only** (`"type": "module"` in consuming apps is recommended) |
11
- | **License** | **ISC** — see [`LICENSE`](./LICENSE) |
5
+ | | |
6
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
7
+ | **npm** | [`woodsportal-client-sdk`](https://www.npmjs.com/package/woodsportal-client-sdk) |
8
+ | **Source** | [`Digital-Woods/digitalwoods.io-woodsportal-client-sdk`](https://github.com/Digital-Woods/digitalwoods.io-woodsportal-client-sdk) |
9
+ | **Issues** | [GitHub Issues](https://github.com/Digital-Woods/digitalwoods.io-woodsportal-client-sdk/issues) |
10
+ | **Runtime** | Node **≥ 18**; **ESM only** (`"type": "module"` in consuming apps is recommended) |
11
+ | **License** | **ISC** — see [`LICENSE`](./LICENSE) |
12
+
13
+ ---
14
+
15
+ ## Project layout (Spring Boot–style)
16
+
17
+ Production and test sources are split like WoodsPortal Java services:
18
+
19
+ | Path | Role |
20
+ | ----------- | ----------------------------------------------------------------------------------------------------------------- |
21
+ | `src/main/` | Library source — `core/`, `features/`, `state/`, `adapters/` (see [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)) |
22
+ | `src/test/` | Unit tests mirroring `src/main/` package paths |
23
+
24
+ Example: `src/main/client/auth-headers.ts` ↔ `src/test/client/login-session.test.ts`.
25
+
26
+ See [`src/test/README.md`](src/test/README.md). Run tests with `npm test`.
27
+
28
+ **Contributors:** [docs/DEVELOPER-GUIDE.md](./docs/DEVELOPER-GUIDE.md) · [CONTRIBUTING.md](./CONTRIBUTING.md)
12
29
 
13
30
  ---
14
31
 
@@ -19,7 +36,7 @@ TypeScript/JavaScript **ESM** client for the **WoodsPortal** HTTP API: authentic
19
36
  3. **Use HTTPS** for `baseURL` in every deployed environment.
20
37
  4. **Do not log** passwords, refresh tokens, access tokens, or full API error bodies in production telemetry.
21
38
  5. **Handle errors** with `try/await catch` around `mutate()`, and map user-visible text with **`getFormErrors`** / **`getFieldErrors`** when the API returns validation payloads.
22
- 6. **Align hub context** with how your shell stores HubSpot data (`utils/config.ts` reads hub / dev-portal identifiers from app storage).
39
+ 6. **Align hub context** with how your shell stores HubSpot data (`core/utils/hub-context.ts` reads hub / dev-portal identifiers from app storage).
23
40
 
24
41
  ---
25
42
 
@@ -42,70 +59,71 @@ The SDK uses one shared Axios instance. If **`initializeHttpClient` has never be
42
59
  Call **`initializeHttpClient` once** during application bootstrap (before any `api.*` calls):
43
60
 
44
61
  ```typescript
45
- import { initializeHttpClient } from "woodsportal-client-sdk";
62
+ import { initializeHttpClient } from 'woodsportal-client-sdk'
46
63
 
47
64
  initializeHttpClient({
48
- baseURL: process.env.VITE_API_BASE_URL!, // example: Vite — use your env mechanism
49
- timeout: 50_000,
50
- hubId: process.env.VITE_HUB_ID,
51
- devPortalId: process.env.VITE_DEV_PORTAL_ID,
52
- skipCurrentPublicPath: () => false,
53
- routes: {
54
- unauthorized: "/unauthorized",
55
- login: "/login",
56
- },
57
- onLogout: async () => {
58
- // Clear cookies / storage and route to login — implementation is app-specific
59
- },
60
- });
65
+ baseURL: process.env.VITE_API_BASE_URL!, // example: Vite — use your env mechanism
66
+ timeout: 50_000,
67
+ hubId: process.env.VITE_HUB_ID,
68
+ devPortalId: process.env.VITE_DEV_PORTAL_ID,
69
+ skipCurrentPublicPath: () => false,
70
+ routes: {
71
+ unauthorized: '/unauthorized',
72
+ login: '/login'
73
+ },
74
+ onLogout: async () => {
75
+ // Clear cookies / storage and route to login — implementation is app-specific
76
+ }
77
+ })
61
78
  ```
62
79
 
63
- Hub and dev-portal identifiers are **also** read from browser storage in `src/utils/config.ts`. Keep that storage in sync with your HubSpot / portal shell so authenticated routes resolve the correct tenant context.
80
+ Hub and dev-portal identifiers are **also** read from browser storage in `core/utils/hub-context.ts`. Keep that storage in sync with your HubSpot / portal shell so authenticated routes resolve the correct tenant context.
64
81
 
65
82
  ---
66
83
 
67
- ## Public API
84
+ ## Public API (3.0)
68
85
 
69
- | Export | Purpose |
70
- |--------|---------|
71
- | **`api`** | Auth, SSO, users, pipelines, objects, notes, emails, uploads, files, token helpers. |
72
- | **`store`** | `storage` helpers and **`useTable`** (table state; name is historical — not a React hook). |
73
- | **`url`** | **`useMakeLink`**, **`useUpdateLink`** breadcrumb / URL helpers (not React hooks). |
74
- | **`routeParam`** | **`getRouteDetails`**, **`getParamDetails`**. |
75
- | **`breadcrumbsDetails`** | **`getBreadcrumbs`**, **`getTableTitle`**, **`getFormTitle`**. |
76
- | **`initializeHttpClient`** | Axios base URL, timeouts, hub headers, auth callbacks. |
77
- | **`getFormErrors`**, **`getFieldErrors`** | Map Axios errors to form-level / field-level messages. |
78
- | **Types** | `LoginPayload`, `MutationOptions`, `ChangePasswordPayload`, … |
86
+ | Export | Purpose |
87
+ | ----------------------------------------- | -------------------------------------------------------------------------- |
88
+ | **`api.auth`** | Login, MFA, security, SSO, users, session helpers (`api.auth.session.*`). |
89
+ | **`api.crm`** | Pipelines, objects, notes, emails, files, uploads, cache purge. |
90
+ | **`api.navigation`** | URL factories (`makeLink`, `updateLink`), route params, breadcrumbs. |
91
+ | **`store`** | `storage`, CRM nanostores (`table`, `user`, …), `tableUi` (pagination UI). |
92
+ | **`hubContext`** | Hub / portal identifiers from browser storage. |
93
+ | **`initializeHttpClient`** | Axios base URL, timeouts, hub headers, auth callbacks. |
94
+ | **`getFormErrors`**, **`getFieldErrors`** | Map Axios errors to form-level / field-level messages. |
79
95
 
80
- There is **no** `api.all()` or generic “invoke any route” helper call the specific member on **`api`**.
96
+ **4.0:** nested `api.*` only; `useTableUi()` + required `tableParams` on list mutations. See [CHANGELOG.md](./CHANGELOG.md).
97
+
98
+ Naming rules and migration: [docs/PUBLIC-API-NAMING.md](./docs/PUBLIC-API-NAMING.md).
81
99
 
82
100
  ---
83
101
 
84
- ## Mutation-style methods (`api.login`, …)
102
+ ## Mutation-style methods (`api.auth.login`, …)
85
103
 
86
- Most `api.*` factories wrap **`createMutation`**: invoke the factory **once** with optional **`MutationOptions`**, then call the returned **`mutate`** (often aliased, e.g. **`login`**) with a payload.
104
+ Most nested factories wrap **`createMutation`**: invoke once with optional **`MutationOptions`**, then call the returned **`mutate`** or the **leaf alias** (e.g. **`login`**, **`list`**).
87
105
 
88
106
  **Loading:** **`isLoading()`** returns whether **any** in-flight call exists for that factory (overlapping calls are supported).
89
107
 
90
108
  **Errors:** **`onError`** runs when the request fails; the returned promise **still rejects** — use `try/catch` or `.catch()` in addition to `onError` when you need local control flow.
91
109
 
92
110
  ```typescript
93
- import type { LoginPayload } from "woodsportal-client-sdk";
94
- import { api } from "woodsportal-client-sdk";
95
-
96
- const { login, mutate, isLoading } = api.login({
97
- onSuccess: async (data, payload) => {
98
- // Persist session in app state if needed; tokens are handled inside the SDK login path
99
- },
100
- onError: (error, payload) => {
101
- // Log a redacted message; map to UI state — avoid logging credentials
102
- },
103
- onLoadingChange: (loading) => {
104
- // Drive a global or local spinner
105
- },
106
- });
107
-
108
- await login({ username: "user@example.com", password: "" });
111
+ import type { LoginPayload } from 'woodsportal-client-sdk'
112
+ import { api } from 'woodsportal-client-sdk'
113
+
114
+ const { login, mutate, isLoading } = api.auth.login({
115
+ onSuccess: async (data, payload) => {
116
+ // Persist session in app state if needed; tokens are handled inside the SDK login path
117
+ },
118
+ onError: (error, payload) => {
119
+ // Log a redacted message; map to UI state — avoid logging credentials
120
+ },
121
+ onLoadingChange: (loading) => {
122
+ // Drive a global or local spinner
123
+ }
124
+ })
125
+
126
+ await login({ username: 'user@example.com', password: '' })
109
127
  // `mutate` is identical to `login` here
110
128
  ```
111
129
 
@@ -114,23 +132,23 @@ await login({ username: "user@example.com", password: "…" });
114
132
  ## React example (login form)
115
133
 
116
134
  ```tsx
117
- import type { LoginPayload } from "woodsportal-client-sdk";
118
- import { api } from "woodsportal-client-sdk";
135
+ import type { LoginPayload } from 'woodsportal-client-sdk'
136
+ import { api } from 'woodsportal-client-sdk'
119
137
 
120
138
  const { login } = api.login({
121
- onSuccess: () => undefined,
122
- onError: () => undefined,
123
- onLoadingChange: () => undefined,
124
- });
139
+ onSuccess: () => undefined,
140
+ onError: () => undefined,
141
+ onLoadingChange: () => undefined
142
+ })
125
143
 
126
144
  export async function submitLogin(e: React.FormEvent<HTMLFormElement>) {
127
- e.preventDefault();
128
- const formData = new FormData(e.currentTarget);
129
- const payload: LoginPayload = {
130
- username: String(formData.get("username") ?? ""),
131
- password: String(formData.get("password") ?? ""),
132
- };
133
- await login(payload);
145
+ e.preventDefault()
146
+ const formData = new FormData(e.currentTarget)
147
+ const payload: LoginPayload = {
148
+ username: String(formData.get('username') ?? ''),
149
+ password: String(formData.get('password') ?? '')
150
+ }
151
+ await login(payload)
134
152
  }
135
153
  ```
136
154
 
@@ -178,8 +196,8 @@ npm link woodsportal-client-sdk
178
196
  Consume with the same import paths as npm:
179
197
 
180
198
  ```ts
181
- import { api } from "woodsportal-client-sdk";
182
- import { useTable, useSync } from "woodsportal-client-sdk/react";
199
+ import { api } from 'woodsportal-client-sdk'
200
+ import { useTable, useSync } from 'woodsportal-client-sdk/react'
183
201
  // import { useTable, useSync } from "woodsportal-client-sdk/vue";
184
202
  // import { useTable, useSync } from "woodsportal-client-sdk/angular";
185
203
  ```
@@ -189,11 +207,11 @@ After `npm run build`, the main entry and framework adapters (`/react`, `/vue`,
189
207
  ### React
190
208
 
191
209
  ```tsx
192
- import { useTable } from "woodsportal-client-sdk/react";
210
+ import { useTable } from 'woodsportal-client-sdk/react'
193
211
 
194
212
  function ObjectsTable() {
195
- const table = useTable();
196
- // table.tableData, table.setTableData(...)
213
+ const table = useTable()
214
+ // table.tableData, table.setTableData(...)
197
215
  }
198
216
  ```
199
217
 
@@ -203,9 +221,9 @@ Call composables inside `setup()` (or `<script setup>`):
203
221
 
204
222
  ```vue
205
223
  <script setup lang="ts">
206
- import { useTable } from "woodsportal-client-sdk/vue";
224
+ import { useTable } from 'woodsportal-client-sdk/vue'
207
225
 
208
- const table = useTable();
226
+ const table = useTable()
209
227
  </script>
210
228
  ```
211
229
 
@@ -214,17 +232,93 @@ const table = useTable();
214
232
  Call composables in an injection context (constructor, field initializer, or `runInInjectionContext`):
215
233
 
216
234
  ```typescript
217
- import { Component } from "@angular/core";
218
- import { useTable } from "woodsportal-client-sdk/angular";
235
+ import { Component } from '@angular/core'
236
+ import { useTable } from 'woodsportal-client-sdk/angular'
219
237
 
220
- @Component({ /* ... */ })
238
+ @Component({
239
+ /* ... */
240
+ })
221
241
  export class ObjectsTableComponent {
222
- readonly table = useTable();
242
+ readonly table = useTable()
223
243
  }
224
244
  ```
225
245
 
226
246
  ---
227
247
 
248
+ ## Cache purge (CRM Sync)
249
+
250
+ Prefer **`POST /api/{hubId}/{portalId}/cache-purge-jobs`** over habitual `cache=false` on list reads. Requires `FEATURE_CACHE_PURGE_API_ENABLED` on the API.
251
+
252
+ | Export | Use |
253
+ | ------------------------------------------------------- | --------------------------------------------------- |
254
+ | `createCachePurgeJob` | POST + optional warm job poll |
255
+ | `buildCrmListPurgeTarget` / `buildCrmSinglePurgeTarget` | List or detail scope |
256
+ | `buildEngagementPurgeTarget` | `notes` / `emails` / `files` (requires `recordIds`) |
257
+ | `purgeCrmListCache` / `purgeEngagementCaches` | Convenience wrappers returning `PurgeResult` |
258
+ | `purgeCrmObjectDataCache` | Legacy list-only boolean shorthand |
259
+
260
+ API guide: `woodsportal-api/docs/CACHE-PURGE-API.md` in the monorepo. Types: `src/types/cache-purge.ts`; helpers: `src/utils/cache/`.
261
+
262
+ ---
263
+
264
+ ## MFA & login (client lane)
265
+
266
+ When `POST /api/auth/login` returns `twoFactorRequired: true`, the SDK stores **only** the temporary access JWT — **not** the refresh token. Complete MFA with `api.verifyOtp()` (or pending passkey verify); on success the SDK persists the full session.
267
+
268
+ **Full guide:** [`docs/MFA-SECURITY-SDK.md`](docs/MFA-SECURITY-SDK.md) (every method, payload, and example).
269
+
270
+ Backend reference: `woodsportal-api/docs/MFA-FRONTEND-DEVELOPER-GUIDE.md`.
271
+
272
+ ### Login & MFA step (unauthenticated)
273
+
274
+ | SDK method | HTTP | Purpose |
275
+ | --------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------- |
276
+ | `login({ username, password })` | `POST /api/auth/login?hubId=` | Password login; may return `twoFactorRequired` |
277
+ | `verifyOtp({ token, otp, method })` | `POST /api/auth/verify-otp?hubId=` | Complete OTP/TOTP/backup MFA step; full session on success |
278
+ | `sendMfaOtp({ token, method })` | `POST /api/auth/mfa/pending/otp/send` | Resend OTP or switch to email/SMS on MFA gate |
279
+ | `pendingPasskeyOptions({ token, portalId? })` | `POST /api/auth/mfa/pending/passkey/authenticate/options` | Start passkey MFA-step ceremony |
280
+ | `pendingPasskeyVerify({ token, challengeId, credential, portalId? })` | `POST /api/auth/mfa/pending/passkey/authenticate/verify?hubId=` | Finish passkey MFA step; full session on success |
281
+ | `passkeyLoginOptions({ email, hubId?, portalId? })` | `POST /api/auth/passkey/login/options?hubId=` | Passwordless passkey login start |
282
+ | `passkeyLoginVerify({ challengeId, credential, portalId? })` | `POST /api/auth/passkey/login/verify?hubId=` | Passwordless login finish; may still require MFA |
283
+
284
+ ### MFA enrollment (authenticated)
285
+
286
+ | SDK method | HTTP | Purpose |
287
+ | --------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------- |
288
+ | `getMfaStatus({ portalId? })` | `GET /api/auth/mfa/status?portalId=` | Enrollment + policy snapshot |
289
+ | `setMfaPreferences({ defaultMethod, portalId? })` | `PUT /api/auth/mfa/preferences?portalId=` | Set scoped default MFA method |
290
+ | `startPhoneVerify({ phone })` | `POST /api/auth/mfa/phone/verify/start` | Send phone verification OTP (E.164) |
291
+ | `confirmPhoneVerify({ phone, code })` | `POST /api/auth/mfa/phone/verify/confirm` | Confirm phone; enables SMS at login |
292
+ | `totpEnrollStart({ portalId? })` | `POST /api/auth/mfa/totp/enroll/start?portalId=` | Start TOTP; returns QR/`otpauthUri` |
293
+ | `totpEnrollVerify({ code, portalId? })` | `POST /api/auth/mfa/totp/enroll/verify?portalId=` | Confirm TOTP; backup codes returned once |
294
+ | `totpDisable({ password })` | `POST /api/auth/mfa/totp/disable` | Disable TOTP for current scope |
295
+ | `webauthnRegisterOptions({ portalId? })` | `POST /api/auth/mfa/webauthn/register/options?portalId=` | Passkey registration ceremony |
296
+ | `webauthnRegisterVerify({ challengeId, credential, nickname?, portalId? })` | `POST /api/auth/mfa/webauthn/register/verify?portalId=` | Complete passkey registration |
297
+ | `webauthnAuthOptions({ portalId? })` | `POST /api/auth/mfa/webauthn/authenticate/options?portalId=` | Logged-in passkey re-verify |
298
+ | `webauthnAuthVerify({ challengeId, credential, portalId? })` | `POST /api/auth/mfa/webauthn/authenticate/verify?portalId=` | Complete logged-in passkey verify |
299
+ | `listWebauthnCredentials({ portalId? })` | `GET /api/auth/mfa/webauthn/credentials?portalId=` | List passkeys |
300
+ | `deleteWebauthnCredential({ credentialRecordId, portalId? })` | `DELETE /api/auth/mfa/webauthn/credentials/{id}?portalId=` | Remove a passkey |
301
+
302
+ WebAuthn ceremonies use `@simplewebauthn/browser` in the host app; the SDK transports credential JSON only.
303
+
304
+ ---
305
+
306
+ ## Security settings (client lane)
307
+
308
+ Use dedicated security endpoints for the account Security page — **not** `GET /me` + `GET /mfa/status`. Full contract: `woodsportal-api/docs/SECURITY-FRONTEND-DEVELOPER-GUIDE.md`. **Examples:** [`docs/MFA-SECURITY-SDK.md`](docs/MFA-SECURITY-SDK.md).
309
+
310
+ | SDK method | HTTP | Purpose |
311
+ | ---------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------- |
312
+ | `getSecurityOverview({ portalId? })` | `GET /api/auth/security/overview?portalId=` | Password age, MFA methods, policy flags |
313
+ | `getSecurityLoginActivity({ page?, limit?, sort? })` | `GET /api/auth/security/login-activity` | Paginated login history |
314
+ | `getSecuritySessions({ currentFamilyId?, refreshToken? })` | `GET /api/auth/security/sessions` | Active sessions; pass refresh to mark current |
315
+ | `revokeSecuritySession({ familyId, refreshToken? })` | `POST /api/auth/security/sessions/{familyId}/revoke` | Sign out one device |
316
+ | `revokeOtherSecuritySessions({ refreshToken? })` | `POST /api/auth/security/sessions/revoke-others` | Sign out all other devices |
317
+
318
+ Pass `refreshToken` (or use `getRefreshToken()` from SDK cookies) so the API can mark the current session when listing or revoking others.
319
+
320
+ ---
321
+
228
322
  ## Security & privacy
229
323
 
230
324
  - Send credentials and tokens **only over HTTPS** in production.
@@ -1,21 +1,70 @@
1
- import { E as EmailState, N as NoteState, S as SyncState, T as TableState } from '../../use-sync-LbURBOs_.js';
1
+ import { M as MultiObjectTableState, U as UploaderState } from '../../use-uploader-2F1zc7Cl.js';
2
+ import { E as EmailState, N as NoteState, S as SyncState, a as TableState, T as TableUiState, U as UserState } from '../../use-sync-DpazhM4d.js';
2
3
 
3
4
  declare const useTable: () => TableState & {
4
- setObjectsData(response: any): Promise<void>;
5
+ setObjectsQueryParams(params: any): void;
6
+ setMultiObjectsQueryParams(hubspotObjectTypeId: string, params: any): void;
7
+ setObjectsData(response: any, context?: {
8
+ stageId?: string | number;
9
+ }): Promise<void>;
5
10
  setTableData(response: any, payload: any): void;
6
11
  modifiedObjectsData(results: any): void;
7
12
  clearTablePrependData(): void;
8
13
  setTablePrependData(response: any, props?: any): Promise<void>;
9
14
  };
15
+ declare const useTableUi: () => TableUiState & {
16
+ setTableUniqueId(v: string | null): void;
17
+ setSort(v: string): void;
18
+ setLimit(v: number): void;
19
+ setAfter(v: string): void;
20
+ setPage(v: number | string): void;
21
+ setNextPage(v: number | string): void;
22
+ setStageId(v: number | string): void;
23
+ setTotalItems(v: number): void;
24
+ setNumOfPages(v: number): void;
25
+ setCurrentPage(v: number): void;
26
+ setSearch(v: string): void;
27
+ setFilterPropertyName(v: string): void;
28
+ setFilterOperator(v: string): void;
29
+ setFilterValue(v: string): void;
30
+ setIsPrimaryCompany(v: boolean | null): void;
31
+ setTableFilterData(v: Record<string, unknown>): void;
32
+ setTableDefPermissions(v: Record<string, unknown>): void;
33
+ setView(mView: string | null): void;
34
+ changePipeline(mView: string | null): void;
35
+ setSelectedPipeline(pipelines: any[], pipeLineId?: string): void;
36
+ resetTableParam(): void;
37
+ getTableParam(companyAsMediator?: boolean, currentPageOverride?: number): {
38
+ after: string | number;
39
+ } | {
40
+ after?: string | undefined;
41
+ limit: number;
42
+ page: string | number;
43
+ };
44
+ setGridData(type: string, deals: any[]): Promise<any[]>;
45
+ setDefaultPipeline(data: any, hubspotObjectTypeId: string): any;
46
+ };
47
+ declare const useMultiObjectActions: () => MultiObjectTableState & {
48
+ setMultiObjectData(response: any, payload: any): void;
49
+ clearMultiObjectPrependData(hubspotObjectTypeId?: string): void;
50
+ setMultiObjectPrependData(response: any, props?: any): Promise<void>;
51
+ };
10
52
  declare const useNote: () => NoteState & {
53
+ setListQueryParams(params: any): void;
11
54
  setNotes(response: any, payload: any): void;
12
55
  setPrependNote(response: any): Promise<void>;
13
- updatePrependNote(response: any): Promise<void>;
56
+ clearPrependNotes(): void;
57
+ updatePrependNote(response: any): Promise<any>;
14
58
  };
15
59
  declare const useEmail: () => EmailState & {
60
+ setListQueryParams(params: any): void;
16
61
  setEmails(response: any, payload: any): void;
17
62
  setPrependEmail(response: any): Promise<void>;
18
- updatePrependEmail(response: any): Promise<void>;
63
+ clearPrependEmails(): void;
64
+ updatePrependEmail(response: any): Promise<any>;
65
+ };
66
+ declare const useUser: () => UserState & {
67
+ setProfile(response: any): void;
19
68
  };
20
69
  declare const useSync: () => SyncState & {
21
70
  setIsSyncLoading(status: boolean): void;
@@ -23,5 +72,9 @@ declare const useSync: () => SyncState & {
23
72
  setApiSync(status: boolean): void;
24
73
  setSyncDisable(status: boolean): void;
25
74
  };
75
+ declare const useUploader: () => UploaderState & {
76
+ setAttachment(response: any): void;
77
+ clearAttachments(): void;
78
+ };
26
79
 
27
- export { useEmail, useNote, useSync, useTable };
80
+ export { useEmail, useMultiObjectActions, useNote, useSync, useTable, useTableUi, useUploader, useUser };
@@ -1,7 +1,8 @@
1
- import { bindStoreWithActions } from '../../chunk-Y5MRAAGK.js';
2
- import { createAdapterHooks } from '../../chunk-J7MDPY5P.js';
3
- import '../../chunk-RDCT25UV.js';
4
- import '../../chunk-NB7AINV4.js';
1
+ import { bindStoreWithActions } from '../../chunk-AYTO6ND7.js';
2
+ import { createAdapterHooks } from '../../chunk-H57IQHVF.js';
3
+ import '../../chunk-SSS4DNXP.js';
4
+ import '../../chunk-KBXI2JBA.js';
5
+ import '../../chunk-3FUHGFAQ.js';
5
6
  import { inject, DestroyRef, signal } from '@angular/core';
6
7
 
7
8
  function createAngularStoreComposable(store, actions) {
@@ -26,9 +27,9 @@ function createAngularStoreComposable(store, actions) {
26
27
  };
27
28
  }
28
29
 
29
- // src/adapters/angular/index.ts
30
- var { useTable, useNote, useEmail, useSync } = createAdapterHooks(createAngularStoreComposable);
30
+ // src/main/adapters/angular/index.ts
31
+ var { useTable, useTableUi, useMultiObjectActions, useNote, useEmail, useUser, useSync, useUploader } = createAdapterHooks(createAngularStoreComposable);
31
32
 
32
- export { useEmail, useNote, useSync, useTable };
33
+ export { useEmail, useMultiObjectActions, useNote, useSync, useTable, useTableUi, useUploader, useUser };
33
34
  //# sourceMappingURL=index.js.map
34
35
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/adapters/shared/createAngularComposable.ts","../../../src/adapters/angular/index.ts"],"names":[],"mappings":";;;;;;AAGO,SAAS,4BAAA,CAGd,OAAkC,OAAA,EAAmB;AACnD,EAAA,OAAO,SAAS,QAAA,GAA8B;AAC1C,IAAA,MAAM,UAAA,GAAa,OAAO,UAAU,CAAA;AACpC,IAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,KAAA,EAAO,OAAO,CAAA;AACnD,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAa,CAAA;AAE7C,IAAA,UAAA,CAAW,SAAA;AAAA,MACP,OAAA,CAAQ,UAAU,MAAM;AACpB,QAAA,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,WAAA,EAAa,CAAA;AAAA,MACtC,CAAC;AAAA,KACL;AAEA,IAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAwB;AAAA,MACtC,GAAA,CAAI,SAAS,IAAA,EAAM;AACf,QAAA,MAAM,GAAA,GAAM,OAAO,IAAI,CAAA;AACvB,QAAA,IAAI,OAAO,OAAA,EAAS;AAChB,UAAA,OAAQ,QAAoC,GAAG,CAAA;AAAA,QACnD;AACA,QAAA,OAAO,QAAA,GAAW,GAAmB,CAAA;AAAA,MACzC;AAAA,KACH,CAAA;AAAA,EACL,CAAA;AACJ;;;ACzBO,IAAM,EAAE,QAAA,EAAU,OAAA,EAAS,UAAU,OAAA,EAAQ,GAAI,mBAAmB,4BAA4B","file":"index.js","sourcesContent":["import { DestroyRef, inject, signal } from \"@angular/core\";\nimport { bindStoreWithActions, type SubscribableStore } from \"./bindStoreWithActions\";\n\nexport function createAngularStoreComposable<\n TState extends object,\n TActions extends object,\n>(store: SubscribableStore<TState>, actions: TActions) {\n return function useStore(): TState & TActions {\n const destroyRef = inject(DestroyRef);\n const binding = bindStoreWithActions(store, actions);\n const snapshot = signal(binding.getSnapshot());\n\n destroyRef.onDestroy(\n binding.subscribe(() => {\n snapshot.set(binding.getSnapshot());\n }),\n );\n\n return new Proxy({} as TState & TActions, {\n get(_target, prop) {\n const key = String(prop);\n if (key in actions) {\n return (actions as Record<string, unknown>)[key];\n }\n return snapshot()[key as keyof TState];\n },\n });\n };\n}\n","import { createAdapterHooks } from \"../shared/createAdapterHooks\";\nimport { createAngularStoreComposable } from \"../shared/createAngularComposable\";\n\nexport const { useTable, useNote, useEmail, useSync } = createAdapterHooks(createAngularStoreComposable);\n"]}
1
+ {"version":3,"sources":["../../../src/main/adapters/shared/createAngularComposable.ts","../../../src/main/adapters/angular/index.ts"],"names":[],"mappings":";;;;;;;AAGO,SAAS,4BAAA,CAA6E,OAAkC,OAAA,EAAmB;AAC9I,EAAA,OAAO,SAAS,QAAA,GAA8B;AAC1C,IAAA,MAAM,UAAA,GAAa,OAAO,UAAU,CAAA;AACpC,IAAA,MAAM,OAAA,GAAU,oBAAA,CAAqB,KAAA,EAAO,OAAO,CAAA;AACnD,IAAA,MAAM,QAAA,GAAW,MAAA,CAAO,OAAA,CAAQ,WAAA,EAAa,CAAA;AAE7C,IAAA,UAAA,CAAW,SAAA;AAAA,MACP,OAAA,CAAQ,UAAU,MAAM;AACpB,QAAA,QAAA,CAAS,GAAA,CAAI,OAAA,CAAQ,WAAA,EAAa,CAAA;AAAA,MACtC,CAAC;AAAA,KACL;AAEA,IAAA,OAAO,IAAI,KAAA,CAAM,EAAC,EAAwB;AAAA,MACtC,GAAA,CAAI,SAAS,IAAA,EAAM;AACf,QAAA,MAAM,GAAA,GAAM,OAAO,IAAI,CAAA;AACvB,QAAA,IAAI,OAAO,OAAA,EAAS;AAChB,UAAA,OAAQ,QAAoC,GAAG,CAAA;AAAA,QACnD;AACA,QAAA,OAAO,QAAA,GAAW,GAAmB,CAAA;AAAA,MACzC;AAAA,KACH,CAAA;AAAA,EACL,CAAA;AACJ;;;ACtBO,IAAM,EAAE,QAAA,EAAU,UAAA,EAAY,qBAAA,EAAuB,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,OAAA,EAAS,WAAA,EAAY,GACzG,kBAAA,CAAmB,4BAA4B","file":"index.js","sourcesContent":["import { DestroyRef, inject, signal } from '@angular/core'\nimport { bindStoreWithActions, type SubscribableStore } from './bindStoreWithActions'\n\nexport function createAngularStoreComposable<TState extends object, TActions extends object>(store: SubscribableStore<TState>, actions: TActions) {\n return function useStore(): TState & TActions {\n const destroyRef = inject(DestroyRef)\n const binding = bindStoreWithActions(store, actions)\n const snapshot = signal(binding.getSnapshot())\n\n destroyRef.onDestroy(\n binding.subscribe(() => {\n snapshot.set(binding.getSnapshot())\n })\n )\n\n return new Proxy({} as TState & TActions, {\n get(_target, prop) {\n const key = String(prop)\n if (key in actions) {\n return (actions as Record<string, unknown>)[key]\n }\n return snapshot()[key as keyof TState]\n }\n })\n }\n}\n","import { createAdapterHooks } from '../shared/createAdapterHooks'\nimport { createAngularStoreComposable } from '../shared/createAngularComposable'\n\nexport const { useTable, useTableUi, useMultiObjectActions, useNote, useEmail, useUser, useSync, useUploader } =\n createAdapterHooks(createAngularStoreComposable)\n"]}