@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.
- package/README.md +260 -0
- package/dist/chunk-P5WZALLT.mjs +1633 -0
- package/dist/chunk-R7NHFDIU.mjs +53 -0
- package/dist/chunk-TUQKZ7GW.mjs +207 -0
- package/dist/chunk-VS74AXZ6.mjs +70 -0
- package/dist/components/index.d.ts +11 -0
- package/dist/components/index.mjs +23 -0
- package/dist/domain/index.d.ts +2 -0
- package/dist/domain/index.mjs +0 -0
- package/dist/en-GB-TSTNTOGN.mjs +81 -0
- package/dist/forms/index.d.ts +2 -0
- package/dist/forms/index.mjs +0 -0
- package/dist/graphql-BI4OTV8N.d.ts +1814 -0
- package/dist/hooks/index.d.ts +50 -0
- package/dist/hooks/index.mjs +106 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.mjs +16 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.mjs +109 -0
- package/dist/leaveStatusUtils-C26heVdh.d.ts +11 -0
- package/dist/mutations/index.d.ts +2 -0
- package/dist/mutations/index.mjs +0 -0
- package/dist/queries/index.d.ts +489 -0
- package/dist/queries/index.mjs +15 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.mjs +0 -0
- package/dist/utils/index.d.ts +40 -0
- package/dist/utils/index.mjs +53 -0
- package/package.json +94 -0
- package/src/client/createKyInstance.ts +34 -0
- package/src/client/execute.ts +153 -0
- package/src/client/index.ts +4 -0
- package/src/client/initializeClient.ts +48 -0
- package/src/client/resetClient.ts +10 -0
- package/src/client/types.ts +12 -0
- package/src/components/AsyncBoundary.tsx +29 -0
- package/src/components/index.ts +1 -0
- package/src/domain/index.ts +2 -0
- package/src/errors/AuthError.ts +12 -0
- package/src/errors/DomainError.ts +15 -0
- package/src/errors/GraphQLError.ts +16 -0
- package/src/errors/LeaveError.ts +13 -0
- package/src/errors/NetworkError.ts +12 -0
- package/src/errors/classifyError.ts +46 -0
- package/src/errors/errorMessages.ts +69 -0
- package/src/errors/index.ts +13 -0
- package/src/forms/index.ts +2 -0
- package/src/graphql/codegen-gateway.ts +26 -0
- package/src/graphql/codegen-hr-core.ts +31 -0
- package/src/graphql/generated-gateway/fragment-masking.ts +84 -0
- package/src/graphql/generated-gateway/gql.ts +140 -0
- package/src/graphql/generated-gateway/graphql.ts +10828 -0
- package/src/graphql/generated-gateway/index.ts +2 -0
- package/src/graphql/generated-hr-core/fragment-masking.ts +84 -0
- package/src/graphql/generated-hr-core/gql.ts +185 -0
- package/src/graphql/generated-hr-core/graphql.ts +19385 -0
- package/src/graphql/generated-hr-core/index.ts +2 -0
- package/src/graphql/index.ts +278 -0
- package/src/graphql/operations/gateway/leave-change/mutations.graphql +74 -0
- package/src/graphql/operations/gateway/leave-change/queries.graphql +51 -0
- package/src/graphql/operations/gateway/leave-policy-employee-reference/mutations.graphql +26 -0
- package/src/graphql/operations/gateway/leave-self-certified/mutations.graphql +45 -0
- package/src/graphql/operations/gateway/leave-self-certified/queries.graphql +80 -0
- package/src/graphql/operations/gateway/leave-type-code/mutations.graphql +25 -0
- package/src/graphql/operations/gateway/self-certified-policy/mutations.graphql +29 -0
- package/src/graphql/operations/gateway/self-certified-policy/queries.graphql +34 -0
- package/src/graphql/operations/gateway/time-bank/mutations.graphql +23 -0
- package/src/graphql/operations/gateway/time-bank/queries.graphql +5 -0
- package/src/graphql/operations/gateway/time-off-settings/mutations.graphql +19 -0
- package/src/graphql/operations/gateway/time-off-settings/queries.graphql +15 -0
- package/src/graphql/operations/gateway/user/queries.graphql +11 -0
- package/src/graphql/operations/hr-core/balance/mutations.graphql +34 -0
- package/src/graphql/operations/hr-core/balance/queries.graphql +21 -0
- package/src/graphql/operations/hr-core/employee/queries.graphql +27 -0
- package/src/graphql/operations/hr-core/employment/queries.graphql +40 -0
- package/src/graphql/operations/hr-core/file/mutations.graphql +15 -0
- package/src/graphql/operations/hr-core/group/queries.graphql +13 -0
- package/src/graphql/operations/hr-core/leave/mutations.graphql +68 -0
- package/src/graphql/operations/hr-core/leave/queries.graphql +150 -0
- package/src/graphql/operations/hr-core/leave-type/queries.graphql +33 -0
- package/src/graphql/operations/hr-core/member/queries.graphql +58 -0
- package/src/graphql/operations/hr-core/office/queries.graphql +26 -0
- package/src/graphql/operations/hr-core/policy/mutations.graphql +43 -0
- package/src/graphql/operations/hr-core/policy/queries.graphql +46 -0
- package/src/graphql/operations/hr-core/scope/mutations.graphql +19 -0
- package/src/graphql/operations/hr-core/team/queries.graphql +14 -0
- package/src/graphql/operations/hr-core/user/queries.graphql +37 -0
- package/src/graphql/operations/hr-core/work-calendar/queries.graphql +60 -0
- package/src/graphql/operations/hr-core/work-week/queries.graphql +139 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useBalance.ts +58 -0
- package/src/hooks/useCurrentEmployeeId.ts +15 -0
- package/src/hooks/useLeaveList.ts +91 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/instance.ts +52 -0
- package/src/i18n/locale.ts +23 -0
- package/src/i18n/translations/en-GB.json +67 -0
- package/src/index.ts +19 -0
- package/src/mutations/index.ts +2 -0
- package/src/queries/employeeQueryFactory.ts +97 -0
- package/src/queries/index.ts +5 -0
- package/src/queries/leaveQueryFactory.ts +171 -0
- package/src/queries/policyQueryFactory.ts +87 -0
- package/src/queries/settingsQueryFactory.ts +31 -0
- package/src/queries/userQueryFactory.ts +13 -0
- package/src/stores/index.ts +2 -0
- package/src/utils/__tests__/formatDateRangeUtils.test.ts +61 -0
- package/src/utils/__tests__/leaveStatusUtils.test.ts +27 -0
- package/src/utils/__tests__/splitLeaveSectionsUtils.test.ts +71 -0
- package/src/utils/formatDateRangeUtils.ts +71 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/leaveStatusUtils.ts +39 -0
- package/src/utils/splitLeaveSectionsUtils.ts +46 -0
- 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 |
|