@alexisapp/leave-core 0.0.1-beta.1

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 (114) hide show
  1. package/README.md +260 -0
  2. package/dist/chunk-P5WZALLT.mjs +1633 -0
  3. package/dist/chunk-R7NHFDIU.mjs +53 -0
  4. package/dist/chunk-TUQKZ7GW.mjs +207 -0
  5. package/dist/chunk-VS74AXZ6.mjs +70 -0
  6. package/dist/components/index.d.ts +11 -0
  7. package/dist/components/index.mjs +23 -0
  8. package/dist/domain/index.d.ts +2 -0
  9. package/dist/domain/index.mjs +0 -0
  10. package/dist/en-GB-TSTNTOGN.mjs +81 -0
  11. package/dist/forms/index.d.ts +2 -0
  12. package/dist/forms/index.mjs +0 -0
  13. package/dist/graphql-BI4OTV8N.d.ts +1814 -0
  14. package/dist/hooks/index.d.ts +50 -0
  15. package/dist/hooks/index.mjs +106 -0
  16. package/dist/i18n/index.d.ts +18 -0
  17. package/dist/i18n/index.mjs +16 -0
  18. package/dist/index.d.ts +133 -0
  19. package/dist/index.mjs +109 -0
  20. package/dist/leaveStatusUtils-C26heVdh.d.ts +11 -0
  21. package/dist/mutations/index.d.ts +2 -0
  22. package/dist/mutations/index.mjs +0 -0
  23. package/dist/queries/index.d.ts +489 -0
  24. package/dist/queries/index.mjs +15 -0
  25. package/dist/stores/index.d.ts +2 -0
  26. package/dist/stores/index.mjs +0 -0
  27. package/dist/utils/index.d.ts +40 -0
  28. package/dist/utils/index.mjs +53 -0
  29. package/package.json +94 -0
  30. package/src/client/createKyInstance.ts +34 -0
  31. package/src/client/execute.ts +153 -0
  32. package/src/client/index.ts +4 -0
  33. package/src/client/initializeClient.ts +48 -0
  34. package/src/client/resetClient.ts +10 -0
  35. package/src/client/types.ts +12 -0
  36. package/src/components/AsyncBoundary.tsx +29 -0
  37. package/src/components/index.ts +1 -0
  38. package/src/domain/index.ts +2 -0
  39. package/src/errors/AuthError.ts +12 -0
  40. package/src/errors/DomainError.ts +15 -0
  41. package/src/errors/GraphQLError.ts +16 -0
  42. package/src/errors/LeaveError.ts +13 -0
  43. package/src/errors/NetworkError.ts +12 -0
  44. package/src/errors/classifyError.ts +46 -0
  45. package/src/errors/errorMessages.ts +69 -0
  46. package/src/errors/index.ts +13 -0
  47. package/src/forms/index.ts +2 -0
  48. package/src/graphql/codegen-gateway.ts +26 -0
  49. package/src/graphql/codegen-hr-core.ts +31 -0
  50. package/src/graphql/generated-gateway/fragment-masking.ts +84 -0
  51. package/src/graphql/generated-gateway/gql.ts +140 -0
  52. package/src/graphql/generated-gateway/graphql.ts +10828 -0
  53. package/src/graphql/generated-gateway/index.ts +2 -0
  54. package/src/graphql/generated-hr-core/fragment-masking.ts +84 -0
  55. package/src/graphql/generated-hr-core/gql.ts +185 -0
  56. package/src/graphql/generated-hr-core/graphql.ts +19385 -0
  57. package/src/graphql/generated-hr-core/index.ts +2 -0
  58. package/src/graphql/index.ts +278 -0
  59. package/src/graphql/operations/gateway/leave-change/mutations.graphql +74 -0
  60. package/src/graphql/operations/gateway/leave-change/queries.graphql +51 -0
  61. package/src/graphql/operations/gateway/leave-policy-employee-reference/mutations.graphql +26 -0
  62. package/src/graphql/operations/gateway/leave-self-certified/mutations.graphql +45 -0
  63. package/src/graphql/operations/gateway/leave-self-certified/queries.graphql +80 -0
  64. package/src/graphql/operations/gateway/leave-type-code/mutations.graphql +25 -0
  65. package/src/graphql/operations/gateway/self-certified-policy/mutations.graphql +29 -0
  66. package/src/graphql/operations/gateway/self-certified-policy/queries.graphql +34 -0
  67. package/src/graphql/operations/gateway/time-bank/mutations.graphql +23 -0
  68. package/src/graphql/operations/gateway/time-bank/queries.graphql +5 -0
  69. package/src/graphql/operations/gateway/time-off-settings/mutations.graphql +19 -0
  70. package/src/graphql/operations/gateway/time-off-settings/queries.graphql +15 -0
  71. package/src/graphql/operations/gateway/user/queries.graphql +11 -0
  72. package/src/graphql/operations/hr-core/balance/mutations.graphql +34 -0
  73. package/src/graphql/operations/hr-core/balance/queries.graphql +21 -0
  74. package/src/graphql/operations/hr-core/employee/queries.graphql +27 -0
  75. package/src/graphql/operations/hr-core/employment/queries.graphql +40 -0
  76. package/src/graphql/operations/hr-core/file/mutations.graphql +15 -0
  77. package/src/graphql/operations/hr-core/group/queries.graphql +13 -0
  78. package/src/graphql/operations/hr-core/leave/mutations.graphql +68 -0
  79. package/src/graphql/operations/hr-core/leave/queries.graphql +150 -0
  80. package/src/graphql/operations/hr-core/leave-type/queries.graphql +33 -0
  81. package/src/graphql/operations/hr-core/member/queries.graphql +58 -0
  82. package/src/graphql/operations/hr-core/office/queries.graphql +26 -0
  83. package/src/graphql/operations/hr-core/policy/mutations.graphql +43 -0
  84. package/src/graphql/operations/hr-core/policy/queries.graphql +46 -0
  85. package/src/graphql/operations/hr-core/scope/mutations.graphql +19 -0
  86. package/src/graphql/operations/hr-core/team/queries.graphql +14 -0
  87. package/src/graphql/operations/hr-core/user/queries.graphql +37 -0
  88. package/src/graphql/operations/hr-core/work-calendar/queries.graphql +60 -0
  89. package/src/graphql/operations/hr-core/work-week/queries.graphql +139 -0
  90. package/src/hooks/index.ts +3 -0
  91. package/src/hooks/useBalance.ts +58 -0
  92. package/src/hooks/useCurrentEmployeeId.ts +15 -0
  93. package/src/hooks/useLeaveList.ts +91 -0
  94. package/src/i18n/index.ts +2 -0
  95. package/src/i18n/instance.ts +52 -0
  96. package/src/i18n/locale.ts +23 -0
  97. package/src/i18n/translations/en-GB.json +67 -0
  98. package/src/index.ts +19 -0
  99. package/src/mutations/index.ts +2 -0
  100. package/src/queries/employeeQueryFactory.ts +97 -0
  101. package/src/queries/index.ts +5 -0
  102. package/src/queries/leaveQueryFactory.ts +171 -0
  103. package/src/queries/policyQueryFactory.ts +87 -0
  104. package/src/queries/settingsQueryFactory.ts +31 -0
  105. package/src/queries/userQueryFactory.ts +13 -0
  106. package/src/stores/index.ts +2 -0
  107. package/src/utils/__tests__/formatDateRangeUtils.test.ts +61 -0
  108. package/src/utils/__tests__/leaveStatusUtils.test.ts +27 -0
  109. package/src/utils/__tests__/splitLeaveSectionsUtils.test.ts +71 -0
  110. package/src/utils/formatDateRangeUtils.ts +71 -0
  111. package/src/utils/index.ts +8 -0
  112. package/src/utils/leaveStatusUtils.ts +39 -0
  113. package/src/utils/splitLeaveSectionsUtils.ts +46 -0
  114. package/src/utils/typeSafeUtils.ts +4 -0
package/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # @leave/core
2
+
3
+ Platform-agnostic core package for the leave management feature. Contains the GraphQL client, error handling, query/mutation factories, form schemas, domain logic, i18n, and stores.
4
+
5
+ This is a leaf package — it has zero platform dependencies (`react-native`, `react-dom` are forbidden).
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ pnpm install
11
+ pnpm build # tsup — produces dist/ with ESM (.mjs) + declarations (.d.ts)
12
+ pnpm clean # rm -rf dist
13
+ pnpm typecheck # tsc --noEmit
14
+ pnpm lint # eslint src/
15
+ ```
16
+
17
+ ## Build
18
+
19
+ `pnpm build` runs tsup which produces ESM output (`.mjs`) and TypeScript declarations (`.d.ts`) for all subpath exports into `dist/`.
20
+
21
+ Dual-resolution strategy:
22
+
23
+ - **Metro (React Native):** resolves the `react-native` condition → raw `.ts` in `src/` — Metro transpiles at build time
24
+ - **Vite (Web):** resolves the `import` condition → pre-built `.mjs` in `dist/`
25
+
26
+ ## Public API
27
+
28
+ ### Client Lifecycle
29
+
30
+ ```typescript
31
+ import { initializeClient, resetClient, execute } from '@leave/core';
32
+ import type { ClientConfig, GraphQLEndpoints } from '@leave/core';
33
+ ```
34
+
35
+ #### `initializeClient(config: ClientConfig): void`
36
+
37
+ Creates a module-scoped ky instance configured for GraphQL requests. Must be called before any `execute` call. Throws if called twice without `resetClient()` first (singleton guard — see ADR-011).
38
+
39
+ #### `resetClient(): void`
40
+
41
+ Resets the module-scoped singleton to `null`. Call in `useEffect` cleanup on unmount (prevents stale closure on MF remount) and in `afterEach()` in tests.
42
+
43
+ #### `execute.gateway<TResult, TVariables>({ query, variables, signal? }): Promise<TResult>`
44
+
45
+ Sends a GraphQL request to the gateway service (new leave operations). `query` is a `TypedDocumentString` from `generated-gateway/` — `TResult` and `TVariables` are inferred from it. Classifies all errors at this boundary. Auth errors (401/403) trigger `onAuthError()` callback, then throw — zero retry.
46
+
47
+ ```typescript
48
+ // Types are inferred from the document — no manual generics needed
49
+ const data = await execute.gateway({
50
+ query: ListLeaveSelfCertifiedDocument,
51
+ variables: { filters: { ... } },
52
+ signal,
53
+ });
54
+ ```
55
+
56
+ #### `execute.hrCore<TResult, TVariables>({ query, variables, signal? }): Promise<TResult>`
57
+
58
+ Same as `execute.gateway` but targets HR Core service (legacy employee/policy lookups). `query` is a `TypedDocumentString` from `generated-hr-core/`.
59
+
60
+ ### Types
61
+
62
+ #### `ClientConfig`
63
+
64
+ | Property | Type | Required | Description |
65
+ | ------------- | ------------------------------- | -------- | --------------------------------------------------------- |
66
+ | `endpoints` | `GraphQLEndpoints` | Yes | Fully resolved GraphQL endpoint URLs |
67
+ | `getToken` | `() => Promise<string \| null>` | No | Mobile only — returns Auth0 JWT |
68
+ | `onAuthError` | `() => void` | No | Mobile only — called on 401/403 before throwing |
69
+ | `devToken` | `string` | No | Hardcoded token for dev. Takes precedence over `getToken` |
70
+
71
+ #### `GraphQLEndpoints`
72
+
73
+ | Property | Type | Description |
74
+ | --------- | -------- | ------------------------------------ |
75
+ | `gateway` | `string` | Full URL for gateway GraphQL service |
76
+ | `hrCore` | `string` | Full URL for HR Core GraphQL service |
77
+
78
+ ### Error Classes
79
+
80
+ | Class | Code | Description |
81
+ | -------------- | --------------- | --------------------------------------------------- |
82
+ | `LeaveError` | varies | Base class. All domain errors extend this. |
83
+ | `AuthError` | `AUTH_ERROR` | 401/403. Never retried. Triggers `onAuthError()`. |
84
+ | `GraphQLError` | `GRAPHQL_ERROR` | Server returned `errors[]` in the GraphQL response. |
85
+ | `NetworkError` | `NETWORK_ERROR` | Fetch failed, timeout, no connectivity. |
86
+ | `DomainError` | `DOMAIN_ERROR` | Business rule violation from server. |
87
+
88
+ All errors are classified via `classifyError()` at the `execute` boundary — consumers catch typed `LeaveError` subclasses. Classification is idempotent (passing a `LeaveError` through returns it unchanged). `AbortError` (host unmounts mid-request) is classified as `ABORT_ERROR` — a no-op in the UI (no toast, no error boundary).
89
+
90
+ ### Error Code Mapping
91
+
92
+ `ErrorCode` enum and `getErrorCode(error)` provide consumer-facing error classification for UI messages:
93
+
94
+ ```typescript
95
+ import { ErrorCode, getErrorCode } from '@leave/core';
96
+
97
+ const code = getErrorCode(error); // e.g. ErrorCode.CONNECTION_FAILURE
98
+ ```
99
+
100
+ Platforms use `getErrorCode()` to map errors to i18n-keyed display messages. Core provides the enum and classification; platforms provide the translations.
101
+
102
+ ### Error Helpers
103
+
104
+ ```typescript
105
+ import { isRetryable, isHttpError, getErrorMessage } from '@leave/core';
106
+ ```
107
+
108
+ - `isRetryable(error: unknown): boolean` — `true` for `NetworkError`, `GraphQLError`, 5xx HTTP; `false` for `AuthError`, `DomainError`
109
+ - `isHttpError(error: unknown): error is HTTPError` — type guard for ky's `HTTPError`
110
+ - `getErrorMessage(error: unknown): { title: string; description: string }` — returns i18n-localized error title and description using `ErrorCode` mapping
111
+
112
+ ### AsyncBoundary
113
+
114
+ ```typescript
115
+ import { AsyncBoundary } from '@leave/core/components';
116
+ ```
117
+
118
+ Composable boundary combining `QueryErrorResetBoundary` + `ErrorBoundary` (`react-error-boundary`) + `Suspense`. Platform packages provide their own `fallbackRender`.
119
+
120
+ ```typescript
121
+ <AsyncBoundary
122
+ suspenseFallback={<LoadingSpinner />}
123
+ errorBoundaryProps={{
124
+ fallbackRender: ({ error, resetErrorBoundary }) => (
125
+ <MyErrorFallback error={error} onRetry={resetErrorBoundary} />
126
+ ),
127
+ }}
128
+ >
129
+ <MyDataComponent />
130
+ </AsyncBoundary>
131
+ ```
132
+
133
+ The `onReset` handler automatically calls `QueryErrorResetBoundary.reset()` so TanStack Query retries failed queries on retry.
134
+
135
+ ## GraphQL Codegen
136
+
137
+ Two separate GraphQL services require two codegen configs:
138
+
139
+ | Service | Endpoint | Codegen Config | Generated Output | Entities |
140
+ | ----------- | ------------- | -------------------- | -------------------- | ---------------------------------------------------- |
141
+ | **Gateway** | `/v2/graphql` | `codegen-gateway.ts` | `generated-gateway/` | LeaveSelfCertified, LeaveChange, SelfCertifiedPolicy |
142
+ | **HR Core** | `/graphql` | `codegen-hr-core.ts` | `generated-hr-core/` | Leave, Employee, Policy, LeaveBalance |
143
+
144
+ ### Commands
145
+
146
+ ```bash
147
+ pnpm codegen # Run both configs sequentially
148
+ pnpm codegen:gateway # Generate gateway types only
149
+ pnpm codegen:hr-core # Generate HR Core types only
150
+ ```
151
+
152
+ ### Schema Source
153
+
154
+ Schemas are introspected from remote API endpoints at codegen time — no local schema files are stored in the repo. This matches the salary-review-ui pattern.
155
+
156
+ - Gateway: `https://api-2.dev-alexishr.io/v2/graphql`
157
+ - HR Core: `https://api-2.dev-alexishr.io/graphql`
158
+
159
+ To force a fresh introspection (e.g. after API schema changes): `turbo codegen --force`
160
+
161
+ ### Operation Structure
162
+
163
+ ```
164
+ src/graphql/operations/
165
+ ├── gateway/ # Operations for execute.gateway()
166
+ │ ├── leave-self-certified/ # queries.graphql, mutations.graphql
167
+ │ ├── leave-change/ # queries.graphql, mutations.graphql
168
+ │ └── self-certified-policy/ # queries.graphql
169
+ └── hr-core/ # Operations for execute.hrCore()
170
+ ├── leave/ # queries.graphql, mutations.graphql
171
+ ├── policy/ # queries.graphql
172
+ └── employee/ # queries.graphql
173
+ ```
174
+
175
+ ### Generated Output
176
+
177
+ `generated-gateway/` and `generated-hr-core/` — **never edit these files manually**. They are overwritten by codegen.
178
+
179
+ Uses `documentMode: 'string'` because `execute()` takes `document: string` (not AST). All document constants are `TypedDocumentString` instances that serialize to string.
180
+
181
+ ### Endpoint-to-Domain Mapping
182
+
183
+ | Operation | Executor | Why |
184
+ | ----------------------------------------------- | ----------------- | ------------------------------------- |
185
+ | getLeave, listLeave, createLeave, updateLeave | `execute.hrCore` | Standard leave CRUD in HR Core |
186
+ | getEmployeeVacationBalance, listVacationBalance | `execute.hrCore` | Balance data in HR Core |
187
+ | getEmployee | `execute.hrCore` | Employee data in HR Core |
188
+ | getLeavePolicy, listLeavePolicies | `execute.hrCore` | Leave policies in HR Core |
189
+ | getLeaveSelfCertified, create/update | `execute.gateway` | New self-certified service on gateway |
190
+ | getLeaveChange, leaveChangeCreate | `execute.gateway` | New change-request service on gateway |
191
+ | getLeaveSelfCertifiedPolicy | `execute.gateway` | Self-certified policy on gateway |
192
+
193
+ ## Query Factories
194
+
195
+ Import from `@leave/core/queries`. Pattern matches salary-review-ui (`officesQueries`, `employeesQueries`).
196
+
197
+ Four factories with cascading keys and embedded `queryOptions` (ADR-014):
198
+
199
+ | Factory | Root key (`all`) | Domains covered |
200
+ | ----------------- | ---------------- | --------------------------------------------------------- |
201
+ | `leaveQueries` | `['leave']` | Leave CRUD, balance, approvals, self-certified, time bank |
202
+ | `policyQueries` | `['policy']` | Leave policies, self-certified policies, leave types |
203
+ | `employeeQueries` | `['employee']` | Employee, employment, work week, holidays |
204
+ | `settingsQueries` | `['settings']` | Time-off settings |
205
+
206
+ Each factory uses a cascading key hierarchy — every level spreads the parent:
207
+
208
+ ```typescript
209
+ import { leaveQueries } from '@leave/core/queries';
210
+
211
+ // Key-only levels (for invalidation targeting)
212
+ leaveQueries.all // ['leave']
213
+ leaveQueries.lists() // [...all, 'list']
214
+ leaveQueries.details() // [...all, 'detail']
215
+
216
+ // Leaf levels return queryOptions() with embedded queryFn
217
+ leaveQueries.list(variables?) // queryOptions({ queryKey: [...lists(), variables], queryFn: ... })
218
+ leaveQueries.detail(id) // queryOptions({ queryKey: [...details(), id], queryFn: ... })
219
+
220
+ // Usage with TanStack Query
221
+ const { data } = useQuery(leaveQueries.list({ filters: { status: 'PENDING' } }));
222
+ const { data } = useQuery(leaveQueries.detail(leaveId));
223
+
224
+ // Nuclear invalidation — wipes all leave queries
225
+ queryClient.invalidateQueries({ queryKey: leaveQueries.all });
226
+ ```
227
+
228
+ All `queryFn` implementations pass `{ signal }` to `execute` for request cancellation on component unmount.
229
+
230
+ ## Subpath Exports
231
+
232
+ | Subpath | Purpose |
233
+ | -------------- | --------------------------------------------------------------------------- |
234
+ | `@leave/core` | Client lifecycle (`initializeClient`, `resetClient`, `execute`) |
235
+ | `./queries` | TanStack Query `queryOptions` factories |
236
+ | `./mutations` | Mutation wrappers (callback-based) |
237
+ | `./forms` | TanStack Form + Zod form schemas |
238
+ | `./domain` | Business rules, permissions, types |
239
+ | `./i18n` | i18next instance + locale resources |
240
+ | `./components` | `AsyncBoundary` (react-error-boundary + Suspense + QueryErrorResetBoundary) |
241
+ | `./stores` | Zustand stores |
242
+
243
+ Metro (React Native) resolves raw `.ts` via the `react-native` condition. Bundler/Node resolves compiled `dist/`.
244
+
245
+ ## Peer Dependencies
246
+
247
+ | Package | Range |
248
+ | ----------------------- | ---------------------- |
249
+ | `react` | `^18.0.0 \|\| ^19.0.0` |
250
+ | `@tanstack/react-query` | `^5.0.0` |
251
+ | `react-error-boundary` | `^5.0.0` |
252
+ | `zod` | `^4.0.0` |
253
+
254
+ ## Dependencies
255
+
256
+ | Package | Range | Purpose |
257
+ | --------- | --------- | ---------------------------- |
258
+ | `ky` | `^1.0.0` | HTTP client (timeout, retry) |
259
+ | `i18next` | `^23.0.0` | Internationalization |
260
+ | `graphql` | `^16.0.0` | GraphQL utilities |