woodsportal-client-sdk 1.1.4 → 2.0.705

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 (82) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/README.md +199 -77
  3. package/dist/adapters/angular/index.d.ts +77 -5
  4. package/dist/adapters/angular/index.js +12 -7
  5. package/dist/adapters/angular/index.js.map +1 -1
  6. package/dist/adapters/native/index.d.ts +3 -0
  7. package/dist/adapters/native/index.js +11 -0
  8. package/dist/adapters/native/index.js.map +1 -0
  9. package/dist/adapters/react/index.d.ts +77 -5
  10. package/dist/adapters/react/index.js +9 -16
  11. package/dist/adapters/react/index.js.map +1 -1
  12. package/dist/adapters/vue/index.d.ts +77 -5
  13. package/dist/adapters/vue/index.js +12 -7
  14. package/dist/adapters/vue/index.js.map +1 -1
  15. package/dist/auth-error-codes-D7CXVBEN.js +3 -0
  16. package/dist/auth-error-codes-D7CXVBEN.js.map +1 -0
  17. package/dist/auth-interceptor-policy-K46P4VM4.js +6 -0
  18. package/dist/auth-interceptor-policy-K46P4VM4.js.map +1 -0
  19. package/dist/auth-utils-JGNW73IJ.js +7 -0
  20. package/dist/{auth-utils-A4WPJMPK.js.map → auth-utils-JGNW73IJ.js.map} +1 -1
  21. package/dist/chunk-2OLIVT2F.js +343 -0
  22. package/dist/chunk-2OLIVT2F.js.map +1 -0
  23. package/dist/chunk-32ERSTFY.js +354 -0
  24. package/dist/chunk-32ERSTFY.js.map +1 -0
  25. package/dist/{chunk-Y5MRAAGK.js → chunk-AYTO6ND7.js} +3 -3
  26. package/dist/chunk-AYTO6ND7.js.map +1 -0
  27. package/dist/chunk-BXHEEGBF.js +2747 -0
  28. package/dist/chunk-BXHEEGBF.js.map +1 -0
  29. package/dist/chunk-COHBSTHF.js +82 -0
  30. package/dist/chunk-COHBSTHF.js.map +1 -0
  31. package/dist/chunk-CUVU6DCT.js +3 -0
  32. package/dist/chunk-CUVU6DCT.js.map +1 -0
  33. package/dist/chunk-D5GHE2CO.js +172 -0
  34. package/dist/chunk-D5GHE2CO.js.map +1 -0
  35. package/dist/chunk-DB6W3CJT.js +73 -0
  36. package/dist/chunk-DB6W3CJT.js.map +1 -0
  37. package/dist/chunk-ITBMRGCP.js +16 -0
  38. package/dist/chunk-ITBMRGCP.js.map +1 -0
  39. package/dist/chunk-JRT5XSVY.js +161 -0
  40. package/dist/chunk-JRT5XSVY.js.map +1 -0
  41. package/dist/chunk-JSXP2F2H.js +1849 -0
  42. package/dist/chunk-JSXP2F2H.js.map +1 -0
  43. package/dist/chunk-KGNMN7B3.js +173 -0
  44. package/dist/chunk-KGNMN7B3.js.map +1 -0
  45. package/dist/chunk-MLBZ5FQA.js +1305 -0
  46. package/dist/chunk-MLBZ5FQA.js.map +1 -0
  47. package/dist/chunk-OH7VT6MX.js +22 -0
  48. package/dist/chunk-OH7VT6MX.js.map +1 -0
  49. package/dist/chunk-VDMDWMMX.js +519 -0
  50. package/dist/chunk-VDMDWMMX.js.map +1 -0
  51. package/dist/chunk-XDYXEYDM.js +1081 -0
  52. package/dist/chunk-XDYXEYDM.js.map +1 -0
  53. package/dist/crmCacheRefresh-B9sAQzI7.d.ts +292 -0
  54. package/dist/cross-tab-session-FCAQTXUB.js +11 -0
  55. package/dist/cross-tab-session-FCAQTXUB.js.map +1 -0
  56. package/dist/entries/auth.d.ts +96 -0
  57. package/dist/entries/auth.js +19 -0
  58. package/dist/entries/auth.js.map +1 -0
  59. package/dist/entries/crm.d.ts +239 -0
  60. package/dist/entries/crm.js +33 -0
  61. package/dist/entries/crm.js.map +1 -0
  62. package/dist/index-CWYPrDBp.d.ts +56 -0
  63. package/dist/index.d.ts +641 -400
  64. package/dist/index.js +96 -1745
  65. package/dist/index.js.map +1 -1
  66. package/dist/route-guard-contract-CSJMmfDA.d.ts +856 -0
  67. package/dist/storage-migration-CWOS6CNV.js +4 -0
  68. package/dist/storage-migration-CWOS6CNV.js.map +1 -0
  69. package/dist/use-file-BcO5l-Zf.d.ts +29 -0
  70. package/dist/use-sync-BQfzHacf.d.ts +66 -0
  71. package/package.json +155 -110
  72. package/dist/auth-utils-A4WPJMPK.js +0 -4
  73. package/dist/chunk-6OS7H3VO.js +0 -1291
  74. package/dist/chunk-6OS7H3VO.js.map +0 -1
  75. package/dist/chunk-NB7AINV4.js +0 -35
  76. package/dist/chunk-NB7AINV4.js.map +0 -1
  77. package/dist/chunk-OSSWRJXO.js +0 -17
  78. package/dist/chunk-OSSWRJXO.js.map +0 -1
  79. package/dist/chunk-Y5MRAAGK.js.map +0 -1
  80. package/dist/chunk-YLZA5S7A.js +0 -102
  81. package/dist/chunk-YLZA5S7A.js.map +0 -1
  82. package/dist/use-uploader-dlc_3esL.d.ts +0 -51
package/CHANGELOG.md CHANGED
@@ -5,10 +5,138 @@ 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.1.2] - 2026-08-14
9
+
10
+ ### Fixed
11
+
12
+ - Block HTTP requests whose URL contains unresolved id segments (`undefined` / `null` / `NaN`).
13
+ - When the embedding page CSP blocks API calls, log the violated directive and the fix to apply.
14
+
15
+ ## [4.1.0] - 2026-08-06
16
+
17
+ ### Added
18
+
19
+ - CRM request payload helpers to resolve path vs query params for list/details/association flows.
20
+ - `selectedStage` LIST filter support and related board/list stage URL handling.
21
+
22
+ ### Fixed
23
+
24
+ - Association / primary-company list params (`isPrimaryCompany`) and ticket LIST stage leakage across tabs.
25
+ - Admin locked pipeline treated as selected for LIST filters; `setSelectedStage` no-op when unchanged.
26
+
27
+ ## [4.0.0] - 2026-06-06
28
+
29
+ ### Removed (breaking)
30
+
31
+ - **`state/legacy/`** — legacy table UI store (`use-table-ui`, `create-store`, `table-stores`, `resolve-table-list-params`).
32
+ - **Flat root `api.*` shims** — only `api.auth`, `api.crm`, `api.navigation` remain.
33
+ - **Top-level navigation exports** — `url`, `routeParam`, `breadcrumbsDetails` (use `api.navigation.*`).
34
+ - **`config` export** — use `hubContext` / `setHubContext`.
35
+ - **`store.useTable` / `store.legacyTableUi`** — use `store.tableUi` or `useTableUi()` from framework adapters.
36
+
37
+ ### Changed (breaking)
38
+
39
+ - **Table UI state:** canonical `state/crm/table-ui.ts` + `table-ui-actions.ts`; reactive hook **`useTableUi()`** (replaces `useLegacyTableUi`).
40
+ - **List mutations:** `api.crm.objects.list()` and `api.crm.pipelines.list()` **require** `payload.tableParams` (no global store fallback).
41
+ - **`setObjectsData`** accepts `{ stageId }` from resolved list params (board append no longer reads global legacy store).
42
+
43
+ ### Added
44
+
45
+ - `store.tableUi: { store, actions }` on root `store` export.
46
+ - `features/crm/helpers/normalize-table-list-params.ts` — validates required `tableParams`.
47
+ - Tests updated for nested-only API surface.
48
+
49
+ ### Migration (3.x → 4.0)
50
+
51
+ | 3.x | 4.0 |
52
+ |-----|-----|
53
+ | `useLegacyTableUi()` | `useTableUi()` |
54
+ | `store.legacyTableUi()` | `store.tableUi.actions` / `useTableUi()` |
55
+ | `api.login()` | `api.auth.login()` |
56
+ | `url.makeLink()` | `api.navigation.url.makeLink()` |
57
+ | `list({ hubspotObjectTypeId })` | `list({ hubspotObjectTypeId, tableParams: getTableParam() })` |
58
+
59
+ See [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md).
60
+
61
+ ## [3.0.0] - 2026-06-06
62
+
63
+ ### Changed (breaking)
64
+
65
+ - **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).
66
+ - **Mutation aliases:** primary alias matches nested leaf (`api.crm.objects.list()` → `{ list, mutate, isLoading }`).
67
+ - **Session helpers:** `api.auth.session.isAuthenticated()`, `refreshAccessToken()`, `isAccessTokenExpired()` (flat names deprecated).
68
+ - **Profile update:** nested key `api.auth.updateProfile()` (was `profileUpdate`).
69
+ - **Store:** `store.legacyTableUi` (was `store.useTable` — shim retained until 4.0).
70
+ - **Hub context:** prefer `hubContext` export; `config` deprecated.
71
+
72
+ ### Added
73
+
74
+ - [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md) — full 2.x→3.x migration matrix.
75
+ - `src/test/apis/nested-api.test.ts`, `flat-api-governance.test.ts` — nested paths + shim registry guards.
76
+ - Concurrent `me()` dedup (in-flight share).
77
+
78
+ ### Deprecated (remove in 4.0)
79
+
80
+ - Flat root `api.*` keys (`api.objects`, `api.login`, …) — delegate to nested paths.
81
+ - Top-level `url`, `routeParam`, `breadcrumbsDetails` — use `api.navigation.*`.
82
+ - `store.useTable`, `config` export.
83
+
84
+ ### Migration
85
+
86
+ See [docs/PUBLIC-API-NAMING.md](docs/PUBLIC-API-NAMING.md) for the full table. Examples:
87
+
88
+ | 2.x | 3.0 |
89
+ |-----|-----|
90
+ | `api.login()` | `api.auth.login()` |
91
+ | `api.objects()` / `{ getObjects }` | `api.crm.objects.list()` / `{ list }` |
92
+ | `api.getAccessToken()` | `api.auth.session.getAccessToken()` |
93
+ | `url.makeLink()` | `api.navigation.url.makeLink()` |
94
+
95
+ ## [2.0.0] - 2026-06-05
96
+
97
+ ### Changed (breaking)
98
+
99
+ - **Folder structure:** layered layout — `core/`, `features/auth|crm|navigation`, `state/crm/`. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
100
+ - **Subpath entries:** `woodsportal-client-sdk/auth` and `/crm` no longer re-import the monolithic `index.ts` graph.
101
+ - **Navigation URL helpers:** `url.useMakeLink` → `url.makeLink`, `url.useUpdateLink` → `url.updateLink` (factory functions; not React hooks).
102
+
103
+ ### Removed (breaking)
104
+
105
+ - `clint` namespace export
106
+ - `http-clint.ts`, `localStoraget.ts` typo shims
107
+ - `routing/route-param.ts` (use `routeParam` from main or `/crm` entry)
108
+
109
+ ### Migration
110
+
111
+ | Before (1.x) | After (2.0) |
112
+ | ----------------------------------------------------- | ---------------------------- |
113
+ | `url.useMakeLink()` | `url.makeLink()` |
114
+ | `url.useUpdateLink()` | `url.updateLink()` |
115
+ | `import … from 'woodsportal-client-sdk'` (deep paths) | Use documented subpaths only |
116
+ | `clint` | `Client` from main export |
117
+
118
+ ### Added
119
+
120
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [CONTRIBUTING.md](CONTRIBUTING.md), [SECURITY.md](SECURITY.md)
121
+ - CRM entry bundle budget in `npm run size:check`
122
+ - `examples/smoke-consumer/smoke-auth-only.mjs` — auth subpath isolation smoke
123
+
8
124
  ## [Unreleased]
9
125
 
10
126
  ### Added
11
127
 
128
+ - **Client-lane MFA APIs:** `verifyOtp`, `sendMfaOtp`, pending passkey MFA step, enrollment (TOTP, phone verify, WebAuthn/passkeys), passwordless passkey login, and `getMfaStatus` / `setMfaPreferences`.
129
+ - **Client-lane security settings APIs:** `getSecurityOverview`, `getSecurityLoginActivity`, `getSecuritySessions`, `revokeSecuritySession`, `revokeOtherSecuritySessions`.
130
+ - **MFA-aware login:** `api.login()` stores only the temp access JWT when `twoFactorRequired` is true (no refresh token until MFA completes).
131
+ - Exported TypeScript types for MFA and security DTOs (`MfaMethod`, `SecurityOverview`, `ActiveSession`, etc.).
132
+ - **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.
133
+
134
+ ### Changed
135
+
136
+ - **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.
137
+
138
+ ### Added
139
+
12
140
  - **`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
141
 
14
142
  ### Added
package/README.md CHANGED
@@ -2,13 +2,32 @@
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)
29
+
30
+ Monorepo watch rebuild: `yarn local` (or `npm run local`) in this repo — same script name as client/admin, but **library watch only** (no Vite dev server). See the developer guide for the two-terminal workflow.
12
31
 
13
32
  ---
14
33
 
@@ -19,7 +38,7 @@ TypeScript/JavaScript **ESM** client for the **WoodsPortal** HTTP API: authentic
19
38
  3. **Use HTTPS** for `baseURL` in every deployed environment.
20
39
  4. **Do not log** passwords, refresh tokens, access tokens, or full API error bodies in production telemetry.
21
40
  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).
41
+ 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
42
 
24
43
  ---
25
44
 
@@ -42,70 +61,97 @@ The SDK uses one shared Axios instance. If **`initializeHttpClient` has never be
42
61
  Call **`initializeHttpClient` once** during application bootstrap (before any `api.*` calls):
43
62
 
44
63
  ```typescript
45
- import { initializeHttpClient } from "woodsportal-client-sdk";
64
+ import { initializeHttpClient } from 'woodsportal-client-sdk'
46
65
 
47
66
  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
- });
67
+ baseURL: process.env.VITE_API_BASE_URL!, // example: Vite — use your env mechanism
68
+ timeout: 50_000,
69
+ hubId: process.env.VITE_HUB_ID,
70
+ devPortalId: process.env.VITE_DEV_PORTAL_ID,
71
+ skipCurrentPublicPath: () => false,
72
+ routes: {
73
+ unauthorized: '/unauthorized',
74
+ login: '/login'
75
+ },
76
+ onLogout: async () => {
77
+ // Clear cookies / storage and route to login — implementation is app-specific
78
+ }
79
+ })
61
80
  ```
62
81
 
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.
82
+ 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
83
 
65
84
  ---
66
85
 
67
- ## Public API
86
+ ## Public API (3.0)
68
87
 
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`, … |
88
+ | Export | Purpose |
89
+ | ----------------------------------------- | -------------------------------------------------------------------------- |
90
+ | **`api.auth`** | Login, MFA, security, SSO, users, session helpers (`api.auth.session.*`). |
91
+ | **`api.crm`** | Pipelines, objects, notes, emails, files, uploads, cache purge. |
92
+ | **`api.navigation`** | URL factories (`makeLink`, `updateLink`), route params, breadcrumbs. |
93
+ | **`store`** | `storage`, CRM nanostores (`table`, `user`, …), `tableUi` (pagination UI). |
94
+ | **`hubContext`** | Hub / portal identifiers from browser storage. |
95
+ | **`initializeHttpClient`** | Axios base URL, timeouts, hub headers, auth callbacks. |
96
+ | **`getFormErrors`**, **`getFieldErrors`** | Map Axios errors to form-level / field-level messages. |
97
+ | **`resolveApiErrorDisplay`** | Map API / transport errors to user-facing title, description, variant, retry/support (see below). |
79
98
 
80
- There is **no** `api.all()` or generic “invoke any route” helper — call the specific member on **`api`**.
99
+ **4.0:** nested `api.*` only; `useTableUi()` + required `tableParams` on list mutations. See [CHANGELOG.md](./CHANGELOG.md).
100
+
101
+ Naming rules and migration: [docs/PUBLIC-API-NAMING.md](./docs/PUBLIC-API-NAMING.md).
81
102
 
82
103
  ---
83
104
 
84
- ## Mutation-style methods (`api.login`, …)
105
+ ## API error resolver (`resolveApiErrorDisplay`)
106
+
107
+ WoodsPortal HTTP errors return `errorCode`, `category`, and `errorMessage` (see woodsportal-api `docs/API-ERROR-CODES.md`). **`resolveApiErrorDisplay(error, options?)`** turns any thrown value into stable UI copy:
85
108
 
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.
109
+ ```typescript
110
+ import { resolveApiErrorDisplay } from 'woodsportal-client-sdk'
111
+
112
+ const display = resolveApiErrorDisplay(err)
113
+ // display.title, display.description, display.variant, display.showRetry, display.showSupport
114
+ // display.errorCode, display.correlationId (when present)
115
+ ```
116
+
117
+ **Behavior:**
118
+
119
+ 1. Non-HTTP failures (timeout, offline, 503) → transport variants via `classifyHttpError`.
120
+ 2. API responses → `parseApiErrorPayload` + lookup in `API_ERROR_DISPLAY_CONFIG` (all active codes + reserved fallbacks).
121
+ 3. Unknown `errorCode` → fallback by `category` (`AUTH`, `ACCESS`, `VALIDATION`, `FILE`, `RATE_LIMIT`, …).
122
+ 4. **Description:** prefers API `errorMessage` (server i18n); uses config `fallbackDescription` only when the API message is empty. Does **not** surface `detailedMessage` to end users.
123
+
124
+ Auth-specific full-page copy (`HUBSPOT_REAUTH_REQUIRED`, `PORTAL_INACTIVE`, …) remains in **`getUnauthorizedPageCopy`**; the resolver delegates where appropriate.
125
+
126
+ **When API adds a code:** update `ErrorCode.java`, `API-ERROR-CODES.md`, and `src/main/core/errors/api-error-display-config.ts` together. The sync guard test in `src/test/core/errors/` fails if an active code lacks config.
127
+
128
+ ---
129
+
130
+ ## Mutation-style methods (`api.auth.login`, …)
131
+
132
+ Most nested factories wrap **`createMutation`**: invoke once with optional **`MutationOptions`**, then call the returned **`mutate`** or the **leaf alias** (e.g. **`login`**, **`list`**).
87
133
 
88
134
  **Loading:** **`isLoading()`** returns whether **any** in-flight call exists for that factory (overlapping calls are supported).
89
135
 
90
136
  **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
137
 
92
138
  ```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: "…" });
139
+ import type { LoginPayload } from 'woodsportal-client-sdk'
140
+ import { api } from 'woodsportal-client-sdk'
141
+
142
+ const { login, mutate, isLoading } = api.auth.login({
143
+ onSuccess: async (data, payload) => {
144
+ // Persist session in app state if needed; tokens are handled inside the SDK login path
145
+ },
146
+ onError: (error, payload) => {
147
+ // Log a redacted message; map to UI state — avoid logging credentials
148
+ },
149
+ onLoadingChange: (loading) => {
150
+ // Drive a global or local spinner
151
+ }
152
+ })
153
+
154
+ await login({ username: 'user@example.com', password: '…' })
109
155
  // `mutate` is identical to `login` here
110
156
  ```
111
157
 
@@ -114,23 +160,23 @@ await login({ username: "user@example.com", password: "…" });
114
160
  ## React example (login form)
115
161
 
116
162
  ```tsx
117
- import type { LoginPayload } from "woodsportal-client-sdk";
118
- import { api } from "woodsportal-client-sdk";
163
+ import type { LoginPayload } from 'woodsportal-client-sdk'
164
+ import { api } from 'woodsportal-client-sdk'
119
165
 
120
166
  const { login } = api.login({
121
- onSuccess: () => undefined,
122
- onError: () => undefined,
123
- onLoadingChange: () => undefined,
124
- });
167
+ onSuccess: () => undefined,
168
+ onError: () => undefined,
169
+ onLoadingChange: () => undefined
170
+ })
125
171
 
126
172
  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);
173
+ e.preventDefault()
174
+ const formData = new FormData(e.currentTarget)
175
+ const payload: LoginPayload = {
176
+ username: String(formData.get('username') ?? ''),
177
+ password: String(formData.get('password') ?? '')
178
+ }
179
+ await login(payload)
134
180
  }
135
181
  ```
136
182
 
@@ -178,8 +224,8 @@ npm link woodsportal-client-sdk
178
224
  Consume with the same import paths as npm:
179
225
 
180
226
  ```ts
181
- import { api } from "woodsportal-client-sdk";
182
- import { useTable, useSync } from "woodsportal-client-sdk/react";
227
+ import { api } from 'woodsportal-client-sdk'
228
+ import { useTable, useSync } from 'woodsportal-client-sdk/react'
183
229
  // import { useTable, useSync } from "woodsportal-client-sdk/vue";
184
230
  // import { useTable, useSync } from "woodsportal-client-sdk/angular";
185
231
  ```
@@ -189,11 +235,11 @@ After `npm run build`, the main entry and framework adapters (`/react`, `/vue`,
189
235
  ### React
190
236
 
191
237
  ```tsx
192
- import { useTable } from "woodsportal-client-sdk/react";
238
+ import { useTable } from 'woodsportal-client-sdk/react'
193
239
 
194
240
  function ObjectsTable() {
195
- const table = useTable();
196
- // table.tableData, table.setTableData(...)
241
+ const table = useTable()
242
+ // table.tableData, table.setTableData(...)
197
243
  }
198
244
  ```
199
245
 
@@ -203,9 +249,9 @@ Call composables inside `setup()` (or `<script setup>`):
203
249
 
204
250
  ```vue
205
251
  <script setup lang="ts">
206
- import { useTable } from "woodsportal-client-sdk/vue";
252
+ import { useTable } from 'woodsportal-client-sdk/vue'
207
253
 
208
- const table = useTable();
254
+ const table = useTable()
209
255
  </script>
210
256
  ```
211
257
 
@@ -214,17 +260,93 @@ const table = useTable();
214
260
  Call composables in an injection context (constructor, field initializer, or `runInInjectionContext`):
215
261
 
216
262
  ```typescript
217
- import { Component } from "@angular/core";
218
- import { useTable } from "woodsportal-client-sdk/angular";
263
+ import { Component } from '@angular/core'
264
+ import { useTable } from 'woodsportal-client-sdk/angular'
219
265
 
220
- @Component({ /* ... */ })
266
+ @Component({
267
+ /* ... */
268
+ })
221
269
  export class ObjectsTableComponent {
222
- readonly table = useTable();
270
+ readonly table = useTable()
223
271
  }
224
272
  ```
225
273
 
226
274
  ---
227
275
 
276
+ ## Cache purge (CRM Sync)
277
+
278
+ Prefer **`POST /api/{hubId}/{portalId}/cache-purge-jobs`** over habitual `cache=false` on list reads. Requires `FEATURE_CACHE_PURGE_API_ENABLED` on the API.
279
+
280
+ | Export | Use |
281
+ | ------------------------------------------------------- | --------------------------------------------------- |
282
+ | `createCachePurgeJob` | POST + optional warm job poll |
283
+ | `buildCrmListPurgeTarget` / `buildCrmSinglePurgeTarget` | List or detail scope |
284
+ | `buildEngagementPurgeTarget` | `notes` / `emails` / `files` (requires `recordIds`) |
285
+ | `purgeCrmListCache` / `purgeEngagementCaches` | Convenience wrappers returning `PurgeResult` |
286
+ | `purgeCrmObjectDataCache` | Legacy list-only boolean shorthand |
287
+
288
+ API guide: `woodsportal-api/docs/CACHE-PURGE-API.md` in the monorepo. Types: `src/types/cache-purge.ts`; helpers: `src/utils/cache/`.
289
+
290
+ ---
291
+
292
+ ## MFA & login (client lane)
293
+
294
+ 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.
295
+
296
+ **Full guide:** [`docs/MFA-SECURITY-SDK.md`](docs/MFA-SECURITY-SDK.md) (every method, payload, and example).
297
+
298
+ Backend reference: `woodsportal-api/docs/MFA-FRONTEND-DEVELOPER-GUIDE.md`.
299
+
300
+ ### Login & MFA step (unauthenticated)
301
+
302
+ | SDK method | HTTP | Purpose |
303
+ | --------------------------------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------- |
304
+ | `login({ username, password })` | `POST /api/auth/login?hubId=` | Password login; may return `twoFactorRequired` |
305
+ | `verifyOtp({ token, otp, method })` | `POST /api/auth/verify-otp?hubId=` | Complete OTP/TOTP/backup MFA step; full session on success |
306
+ | `sendMfaOtp({ token, method })` | `POST /api/auth/mfa/pending/otp/send` | Resend OTP or switch to email/SMS on MFA gate |
307
+ | `pendingPasskeyOptions({ token, portalId? })` | `POST /api/auth/mfa/pending/passkey/authenticate/options` | Start passkey MFA-step ceremony |
308
+ | `pendingPasskeyVerify({ token, challengeId, credential, portalId? })` | `POST /api/auth/mfa/pending/passkey/authenticate/verify?hubId=` | Finish passkey MFA step; full session on success |
309
+ | `passkeyLoginOptions({ email, hubId?, portalId? })` | `POST /api/auth/passkey/login/options?hubId=` | Passwordless passkey login start |
310
+ | `passkeyLoginVerify({ challengeId, credential, portalId? })` | `POST /api/auth/passkey/login/verify?hubId=` | Passwordless login finish; may still require MFA |
311
+
312
+ ### MFA enrollment (authenticated)
313
+
314
+ | SDK method | HTTP | Purpose |
315
+ | --------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------- |
316
+ | `getMfaStatus({ portalId? })` | `GET /api/auth/mfa/status?portalId=` | Enrollment + policy snapshot |
317
+ | `setMfaPreferences({ defaultMethod, portalId? })` | `PUT /api/auth/mfa/preferences?portalId=` | Set scoped default MFA method |
318
+ | `startPhoneVerify({ phone })` | `POST /api/auth/mfa/phone/verify/start` | Send phone verification OTP (E.164) |
319
+ | `confirmPhoneVerify({ phone, code })` | `POST /api/auth/mfa/phone/verify/confirm` | Confirm phone; enables SMS at login |
320
+ | `totpEnrollStart({ portalId? })` | `POST /api/auth/mfa/totp/enroll/start?portalId=` | Start TOTP; returns QR/`otpauthUri` |
321
+ | `totpEnrollVerify({ code, portalId? })` | `POST /api/auth/mfa/totp/enroll/verify?portalId=` | Confirm TOTP; backup codes returned once |
322
+ | `totpDisable({ password })` | `POST /api/auth/mfa/totp/disable` | Disable TOTP for current scope |
323
+ | `webauthnRegisterOptions({ portalId? })` | `POST /api/auth/mfa/webauthn/register/options?portalId=` | Passkey registration ceremony |
324
+ | `webauthnRegisterVerify({ challengeId, credential, nickname?, portalId? })` | `POST /api/auth/mfa/webauthn/register/verify?portalId=` | Complete passkey registration |
325
+ | `webauthnAuthOptions({ portalId? })` | `POST /api/auth/mfa/webauthn/authenticate/options?portalId=` | Logged-in passkey re-verify |
326
+ | `webauthnAuthVerify({ challengeId, credential, portalId? })` | `POST /api/auth/mfa/webauthn/authenticate/verify?portalId=` | Complete logged-in passkey verify |
327
+ | `listWebauthnCredentials({ portalId? })` | `GET /api/auth/mfa/webauthn/credentials?portalId=` | List passkeys |
328
+ | `deleteWebauthnCredential({ credentialRecordId, portalId? })` | `DELETE /api/auth/mfa/webauthn/credentials/{id}?portalId=` | Remove a passkey |
329
+
330
+ WebAuthn ceremonies use `@simplewebauthn/browser` in the host app; the SDK transports credential JSON only.
331
+
332
+ ---
333
+
334
+ ## Security settings (client lane)
335
+
336
+ 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).
337
+
338
+ | SDK method | HTTP | Purpose |
339
+ | ---------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------- |
340
+ | `getSecurityOverview({ portalId? })` | `GET /api/auth/security/overview?portalId=` | Password age, MFA methods, policy flags |
341
+ | `getSecurityLoginActivity({ page?, limit?, sort? })` | `GET /api/auth/security/login-activity` | Paginated login history |
342
+ | `getSecuritySessions({ currentFamilyId?, refreshToken? })` | `GET /api/auth/security/sessions` | Active sessions; pass refresh to mark current |
343
+ | `revokeSecuritySession({ familyId, refreshToken? })` | `POST /api/auth/security/sessions/{familyId}/revoke` | Sign out one device |
344
+ | `revokeOtherSecuritySessions({ refreshToken? })` | `POST /api/auth/security/sessions/revoke-others` | Sign out all other devices |
345
+
346
+ Pass `refreshToken` (or use `getRefreshToken()` from SDK cookies) so the API can mark the current session when listing or revoking others.
347
+
348
+ ---
349
+
228
350
  ## Security & privacy
229
351
 
230
352
  - Send credentials and tokens **only over HTTPS** in production.
@@ -1,28 +1,93 @@
1
- import { E as EmailState, M as MultiObjectTableState, N as NoteState, S as SyncState, T as TableState, U as UploaderState } from '../../use-uploader-dlc_3esL.js';
1
+ import { F as FileState, M as MultiObjectTableState, U as UploaderState } from '../../use-file-BcO5l-Zf.js';
2
+ import { E as EmailState, N as NoteState, O as ObjectState, S as SyncState, T as TableState, a as TableUiState, U as UserState } from '../../use-sync-BQfzHacf.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;
13
+ clearLatestTablePrependData(): void;
8
14
  setTablePrependData(response: any, props?: any): Promise<void>;
15
+ updateTablePrependData(response: any, payload?: any): any;
16
+ };
17
+ declare const useTableUi: () => TableUiState & {
18
+ setTableUniqueId(v: string | null): void;
19
+ setSort(v: string): void;
20
+ setLimit(v: number): void;
21
+ setAfter(v: string): void;
22
+ setPage(v: number | string): void;
23
+ setNextPage(v: number | string): void;
24
+ setStageId(v: number | string): void;
25
+ setSelectedStage(v: string): void;
26
+ setTotalItems(v: number): void;
27
+ setNumOfPages(v: number): void;
28
+ setCurrentPage(v: number): void;
29
+ setSearch(v: string): void;
30
+ setFilterPropertyName(v: string): void;
31
+ setFilterOperator(v: string): void;
32
+ setFilterValue(v: string): void;
33
+ setIsPrimaryCompany(v: boolean | null): void;
34
+ setTableFilterData(v: Record<string, unknown>): void;
35
+ setTableDefPermissions(v: Record<string, unknown>): void;
36
+ setView(mView: string | null): void;
37
+ changePipeline(mView: string | null): void;
38
+ setSelectedPipeline(pipelines: any[], pipeLineId?: string): void;
39
+ resetTableParam(): void;
40
+ getTableParam(companyAsMediator?: boolean, currentPageOverride?: number): {
41
+ after: string | number;
42
+ } | {
43
+ after?: string | undefined;
44
+ limit: number;
45
+ page: string | number;
46
+ };
47
+ buildListTableParams(options?: {
48
+ companyAsMediator?: boolean;
49
+ currentPageOverride?: number;
50
+ }): {
51
+ after: string | number;
52
+ } | {
53
+ after?: string | undefined;
54
+ limit: number;
55
+ page: string | number;
56
+ };
57
+ setGridData(type: string, deals: any[]): Promise<any[]>;
58
+ setDefaultPipeline(data: any, hubspotObjectTypeId: string, viewName?: string): any;
9
59
  };
10
60
  declare const useMultiObjectActions: () => MultiObjectTableState & {
11
61
  setMultiObjectData(response: any, payload: any): void;
12
62
  clearMultiObjectPrependData(hubspotObjectTypeId?: string): void;
63
+ clearLatestMultiObjectPrependData(hubspotObjectTypeId?: string): void;
13
64
  setMultiObjectPrependData(response: any, props?: any): Promise<void>;
14
65
  };
66
+ declare const useObject: () => ObjectState & {
67
+ setObjectData(response: any, payload?: any): void;
68
+ updateObjectData(response: any, payload?: any): any;
69
+ setObjectAssociationPrependData(response: any, props?: any): Promise<void>;
70
+ clearLatestObjectAssociationPrependData(props?: any): void;
71
+ clearObjectData(): void;
72
+ };
15
73
  declare const useNote: () => NoteState & {
74
+ setListQueryParams(params: any): void;
16
75
  setNotes(response: any, payload: any): void;
17
76
  setPrependNote(response: any): Promise<void>;
77
+ clearLatestPrependNote(): void;
18
78
  clearPrependNotes(): void;
19
- updatePrependNote(response: any): Promise<void>;
79
+ updatePrependNote(response: any): Promise<any>;
20
80
  };
21
81
  declare const useEmail: () => EmailState & {
82
+ setListQueryParams(params: any): void;
22
83
  setEmails(response: any, payload: any): void;
23
84
  setPrependEmail(response: any): Promise<void>;
85
+ clearLatestPrependEmail(): void;
24
86
  clearPrependEmails(): void;
25
- updatePrependEmail(response: any): Promise<void>;
87
+ updatePrependEmail(response: any): Promise<any>;
88
+ };
89
+ declare const useUser: () => UserState & {
90
+ setProfile(response: any): void;
26
91
  };
27
92
  declare const useSync: () => SyncState & {
28
93
  setIsSyncLoading(status: boolean): void;
@@ -34,5 +99,12 @@ declare const useUploader: () => UploaderState & {
34
99
  setAttachment(response: any): void;
35
100
  clearAttachments(): void;
36
101
  };
102
+ declare const useFile: () => FileState & {
103
+ setListQueryParams(params: any): void;
104
+ setFiles(response: any, payload: any): void;
105
+ updateTreeWithCreate(parentFolderId: string, response: "loading" | any, _createKind?: "file" | "folder"): Promise<void>;
106
+ clearLatestTreeLoadingNode(parentFolderId: string): void;
107
+ clearFiles(): void;
108
+ };
37
109
 
38
- export { useEmail, useMultiObjectActions, useNote, useSync, useTable, useUploader };
110
+ export { useEmail, useFile, useMultiObjectActions, useNote, useObject, useSync, useTable, useTableUi, useUploader, useUser };
@@ -1,7 +1,12 @@
1
- import { bindStoreWithActions } from '../../chunk-Y5MRAAGK.js';
2
- import { createAdapterHooks } from '../../chunk-OSSWRJXO.js';
3
- import '../../chunk-6OS7H3VO.js';
4
- import '../../chunk-NB7AINV4.js';
1
+ import { bindStoreWithActions } from '../../chunk-AYTO6ND7.js';
2
+ import { createAdapterHooks } from '../../chunk-OH7VT6MX.js';
3
+ import '../../chunk-2OLIVT2F.js';
4
+ import '../../chunk-JSXP2F2H.js';
5
+ import '../../chunk-XDYXEYDM.js';
6
+ import '../../chunk-32ERSTFY.js';
7
+ import '../../chunk-DB6W3CJT.js';
8
+ import '../../chunk-KGNMN7B3.js';
9
+ import '../../chunk-COHBSTHF.js';
5
10
  import { inject, DestroyRef, signal } from '@angular/core';
6
11
 
7
12
  function createAngularStoreComposable(store, actions) {
@@ -26,9 +31,9 @@ function createAngularStoreComposable(store, actions) {
26
31
  };
27
32
  }
28
33
 
29
- // src/adapters/angular/index.ts
30
- var { useTable, useMultiObjectActions, useNote, useEmail, useSync, useUploader } = createAdapterHooks(createAngularStoreComposable);
34
+ // src/main/adapters/angular/index.ts
35
+ var { useTable, useTableUi, useMultiObjectActions, useObject, useNote, useEmail, useUser, useSync, useUploader, useFile } = createAdapterHooks(createAngularStoreComposable);
31
36
 
32
- export { useEmail, useMultiObjectActions, useNote, useSync, useTable, useUploader };
37
+ export { useEmail, useFile, useMultiObjectActions, useNote, useObject, useSync, useTable, useTableUi, useUploader, useUser };
33
38
  //# sourceMappingURL=index.js.map
34
39
  //# sourceMappingURL=index.js.map