@awsmbiz/inbis-api-client 0.0.0-stage → 0.1.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.
- package/README.md +152 -2
- package/dist/index.d.ts +744 -0
- package/dist/index.js +568 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -3
package/README.md
CHANGED
|
@@ -1,3 +1,153 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @awsmbiz/inbis-api-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed browser calls for Inbis apps: an [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/)
|
|
4
|
+
client for the app's own routes, with types generated from the app's `/openapi.json`, and
|
|
5
|
+
Better Auth's client with the admin plugin for auth admin endpoints. Both reject failed calls
|
|
6
|
+
with the same `ApiError`, and the package includes TanStack Query helpers.
|
|
7
|
+
|
|
8
|
+
| Export | Contents |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `createApiClient<paths>(options?)` | `openapi-fetch` client (`GET`, `POST`, `PATCH`, …) typed by the generated `paths`. Sends cookies (`credentials: "include"`). A failed call **rejects** with an `ApiError`, so `data` is always set when a call resolves. Options: `baseUrl` (default: the page's origin), `headers`, `fetch`. |
|
|
11
|
+
| `createAuthAdminClient(options?)` | The auth admin client: Better Auth's admin plugin plus the platform's admin routes (see [The auth admin client](#the-auth-admin-client)). Failures reject with an `ApiError`. |
|
|
12
|
+
| `ApiError`, `isApiError(value)` | The one error type (see below). `isApiError` also recognises an `ApiError` from another copy of the package. |
|
|
13
|
+
| `API_ERROR_CODES` | Codes callers commonly branch on: `NETWORK_ERROR`, `STEP_UP_REQUIRED`, `TWO_FACTOR_REQUIRED`, `SESSION_NOT_FRESH`, `SESSION_EXPIRED`, `ADMIN_LOCKOUT`, `SETTINGS_VERSION_CONFLICT`, `VALIDATION_FAILED`, `RATE_LIMITED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `INTERNAL_ERROR`, `REQUEST_FAILED`. Any other server code (`LAST_ADMIN`, `INVITATION_NOT_PENDING`, …) is passed through unchanged. |
|
|
14
|
+
| `apiQueryOptions(api, path, init?)`, `apiQueryKey(path, params?)`, `invalidateApi(queryClient, ...paths)` | TanStack Query helpers for app routes. |
|
|
15
|
+
| `authAdminKeys`, `authAdminQueries(auth)` | Query keys and query options for auth admin data. |
|
|
16
|
+
| `invalidateQueries(queryClient, ...keys)` | Invalidates each key prefix. |
|
|
17
|
+
| Types | App routes: `ApiClient`, `ApiClientOptions`, `ApiQueryOptions`, `QueryInvalidator`. Errors: `ApiErrorOptions`, `AccessRequirement`. Auth admin client: `AuthAdminClient`, `AuthAdminClientOptions`, `BetterAuthAdminClient`, `AdminRoutesClient`, `ListUsersQuery`; users: `AdminUsersQuery`, `AdminUsersPage`, `AdminUserSummary`, `AdminUserDetail`, `AdminUserSignInMethod`, `UserActionsAdminClient`, `CreateUserInput`, `CreatedUser`; invitations: `InvitationsAdminClient`, `Invitation`, `InvitationStatus`, `ListInvitationsQuery`; dashboard and activity: `AuthAdminStats`, `AuditEvent`, `AuditEventsQuery`; roles: `RoleAdminClient`, `AdminPermission`, `AdminRole`, `AdminRoleInput`, `AdminUserRole`; settings: `AuthSettingsAdminClient`, `AuthProvidersAdminClient`, `AuthSettingsView`, `AuthSettingsSnapshot`, `RuntimeAuthSettings`, `AuthSettingsPatch`, `AuthProviderStatus`, `RegistrationMode`, `AuthMethodName`, `BreachedPasswordCheck`, `RateLimitRule`, `RateLimitKey` |
|
|
18
|
+
|
|
19
|
+
## The auth admin client
|
|
20
|
+
|
|
21
|
+
`createAuthAdminClient({ baseURL?, basePath?, adminPath?, fetch? })` returns one object for every
|
|
22
|
+
auth administration call. `baseURL` defaults to the page's origin; `basePath` (default
|
|
23
|
+
`/api/auth`) must match the server's auth routes; `adminPath` (default `/api/admin`) is where the
|
|
24
|
+
app mounts the admin routes below. Every member rejects with an `ApiError`, revives dates, and is
|
|
25
|
+
for administrators only (the server applies the admin policy; changes that matter also need a
|
|
26
|
+
recent sign-in, `SESSION_NOT_FRESH`).
|
|
27
|
+
|
|
28
|
+
| Member | Calls | Server side |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `admin` | Better Auth's admin plugin, typed by Better Auth: `listUsers`, `getUser`, `createUser`, `setRole`, `banUser`, `unbanUser`, `listUserSessions`, `revokeUserSessions`, `removeUser`, … Resolves to the response data | `mountAuth` (`@awsmbiz/inbis-auth/hono`) |
|
|
31
|
+
| `userActions` | `create({ name, email \| phoneNumber, password?, role? })` (by email or phone number) → `{ user }`; `revokeSession({ userId, sessionId })` (by session ID: listings never carry tokens) | `mountAuth` |
|
|
32
|
+
| `invitations` | `create({ email \| phoneNumber, role? })`, `list({ limit?, cursor?, status? })` (`status`: `pending`, `accepted`, `expired` or `revoked`), `resend({ invitationId })`, `revoke({ invitationId })` | `mountAuth` |
|
|
33
|
+
| `users` | `list({ search?, role?, banned?, twoFactor?, emailVerified?, sort?, direction?, limit?, offset? })`: the user directory with every filter at once; `get(userId)`: one user with sign-in methods and 2FA status; `resetTwoFactor(userId)` → `{ sessionsRevoked }`: removes a lost authenticator and backup codes and revokes their sessions (never the caller's own account) | `mountAuthUsers` (`@awsmbiz/inbis-auth/hono`) |
|
|
34
|
+
| `stats()` | The dashboard counts | `mountAuthStats` (`@awsmbiz/inbis-auth/hono`) |
|
|
35
|
+
| `auditEvents` | `list({ action?, actor?, subject?, from?, to?, limit?, cursor? })`: the audit timeline, newest first | `mountAuditTimeline` (`@awsmbiz/inbis-audit/hono`) |
|
|
36
|
+
| `permissions`, `roles`, `userRoles` | `permissions.list()`; `roles.list()`, `get(roleId)`, `create({ name, description?, permissions, inherits? })`, `update(roleId, changes)`, `delete(roleId)`; `userRoles.list(userId)`, `assign(userId, roleId)`, `unassign(userId, roleId)` (idempotent; both return the user's roles). Refusals (`ROLE_NAME_TAKEN`, `SYSTEM_ROLE_IMMUTABLE`, `ROLE_INHERITED`, …) carry the envelope's `details` | `mountRoleAdmin` (`@awsmbiz/inbis-access-control/hono`) |
|
|
37
|
+
| `settings`, `providers` | `settings.get()`, `settings.update({ version, settings })` (a JSON merge patch; `null` removes an override); `providers.status()`: each method and provider configured / enabled | `mountAuthSettings` (`@awsmbiz/inbis-auth/hono`) |
|
|
38
|
+
|
|
39
|
+
`@awsmbiz/inbis-admin-ui`'s screens take this client; its README shows which screen needs which member.
|
|
40
|
+
|
|
41
|
+
**The admin route types are hand-written mirrors, not generated.** The platform's admin app
|
|
42
|
+
routes (`/api/admin/*` from `mountAuthStats`, `mountAuthUsers`, `mountAuthSettings`,
|
|
43
|
+
`mountAuditTimeline`, `mountRoleAdmin`) are plain Hono routes mounted by the platform packages,
|
|
44
|
+
not `@awsmbiz/inbis-api` OpenAPI routes, so they are not in the app's `/openapi.json` and there is
|
|
45
|
+
nothing to generate from (a known gap; see `docs/auth-capability-matrix.md`). The types here
|
|
46
|
+
(`AuthAdminStats`, `AdminRole`, `AuditEvent`, …) mirror the server's documented responses by
|
|
47
|
+
hand. This package's tests check each call against a fake server written to those responses, and
|
|
48
|
+
the playground's browser suite runs the admin screens against the real routes; a server change
|
|
49
|
+
needs the matching change here. Better Auth's own admin endpoints (`admin`) are typed by
|
|
50
|
+
Better Auth.
|
|
51
|
+
|
|
52
|
+
## Generating the app's types
|
|
53
|
+
|
|
54
|
+
The types come from the app's OpenAPI document, which `@awsmbiz/inbis-api` serves at `/openapi.json`.
|
|
55
|
+
Generate them with [`openapi-typescript`](https://openapi-ts.dev/) as a dev dependency of the
|
|
56
|
+
app, and commit the output:
|
|
57
|
+
|
|
58
|
+
```jsonc
|
|
59
|
+
// app package.json
|
|
60
|
+
"scripts": {
|
|
61
|
+
"generate:api-types": "openapi-typescript http://localhost:8787/openapi.json -o web/api-types.ts"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": { "openapi-typescript": "7.13.0" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run it while the Worker is up (`pnpm dev`), or point it at a saved copy of the document. Run it
|
|
67
|
+
again whenever a route changes; `tsc` then reports every call that no longer matches. This
|
|
68
|
+
package's own tests generate their fixture types the same way (`pnpm generate:fixture-types`
|
|
69
|
+
from `test/fixture-app.ts`), and a test fails when the committed fixture types fall out of date.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { createApiClient } from "@awsmbiz/inbis-api-client";
|
|
73
|
+
import type { paths } from "./api-types";
|
|
74
|
+
|
|
75
|
+
export const api = createApiClient<paths>();
|
|
76
|
+
|
|
77
|
+
const { data: note } = await api.GET("/v1/notes/{id}", { params: { path: { id } } });
|
|
78
|
+
await api.PATCH("/v1/notes/{id}", { params: { path: { id } }, body: { title, version } });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Errors
|
|
82
|
+
|
|
83
|
+
Every failed call rejects with an `ApiError`, read from either error shape:
|
|
84
|
+
|
|
85
|
+
- app routes (`@awsmbiz/inbis-api`): `{ error: { code, message, requestId, details? } }`;
|
|
86
|
+
- auth routes (Better Auth, `@awsmbiz/inbis-auth`): `{ code, message, details? }`.
|
|
87
|
+
|
|
88
|
+
| Field | Meaning |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `status` | HTTP status, `0` when no response arrived |
|
|
91
|
+
| `code` | Stable server code. Without one, the code comes from the status (`RATE_LIMITED` for 429, `INTERNAL_ERROR` for 5xx, …, else `REQUEST_FAILED`). `NETWORK_ERROR` when the server could not be reached; the original error is kept as `cause`. |
|
|
92
|
+
| `message` | Safe to show |
|
|
93
|
+
| `requestId` | The server's request ID (app routes), else `null` |
|
|
94
|
+
| `details` | Caller-safe extras: `allowedMethods`, validation `issues`, `lockedOutAdmins` and `paths` (`ADMIN_LOCKOUT`), … |
|
|
95
|
+
| `requirement` | For `TWO_FACTOR_REQUIRED` / `STEP_UP_REQUIRED`: `{ kind: "two-factor" }` or `{ kind: "step-up", allowedMethods }`, the same shape as `accessRequirementOf` in `@awsmbiz/inbis-auth/client`. `null` otherwise. |
|
|
96
|
+
| `isVersionConflict` | A stale-version conflict: `409` with a `…VERSION_CONFLICT` code, such as `SETTINGS_VERSION_CONFLICT`. Reload, then let the user reapply their change. |
|
|
97
|
+
| `isNetworkError` | `code === "NETWORK_ERROR"` |
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
try {
|
|
101
|
+
await auth.admin.banUser({ userId, banReason });
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (isApiError(error) && error.requirement) return promptStepUp(error.requirement);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## TanStack Query
|
|
109
|
+
|
|
110
|
+
The helpers return plain `{ queryKey, queryFn }` objects. Pass them to `useQuery`, `query`
|
|
111
|
+
or `prefetchQuery`. `queryFn` passes TanStack's abort `signal` on to the request. Invalidation
|
|
112
|
+
accepts any `QueryClient`.
|
|
113
|
+
|
|
114
|
+
**Key conventions**
|
|
115
|
+
|
|
116
|
+
- App routes: `["api", path]`, or `["api", path, params]` when the call has `params`, e.g.
|
|
117
|
+
`["api", "/v1/notes/{id}", { path: { id: "n1" } }]`. `invalidateApi(queryClient, "/v1/notes")`
|
|
118
|
+
refreshes every query of that path, whatever its params. `invalidateApi(queryClient)` with no
|
|
119
|
+
path refreshes every app-route query.
|
|
120
|
+
- Auth admin (`authAdminKeys`): `["auth-admin", "users", "list", query]`,
|
|
121
|
+
`["auth-admin", "users", "detail", userId]`, `["auth-admin", "users", "detail", userId, "sessions"]`,
|
|
122
|
+
`["auth-admin", "users", "directory", query]` (`users.list`), `["auth-admin", "users", "detail", userId, "profile"]` (`users.get`),
|
|
123
|
+
`["auth-admin", "invitations", "list", query]`, `["auth-admin", "stats"]`,
|
|
124
|
+
`["auth-admin", "audit-events", "list", query]`, `["auth-admin", "settings"]`,
|
|
125
|
+
`["auth-admin", "providers", "status"]`, `["auth-admin", "permissions"]`, `["auth-admin", "roles", "list"]`,
|
|
126
|
+
`["auth-admin", "roles", "detail", roleId]`, `["auth-admin", "users", "detail", userId, "roles"]` (`userRoles.list`).
|
|
127
|
+
After a role change or an assignment (user counts), invalidate `authAdminKeys.roles()`. After any change to a user (role, ban, sessions,
|
|
128
|
+
delete), invalidate `authAdminKeys.users()`. After an invitation change, invalidate
|
|
129
|
+
`authAdminKeys.invitations()`. `authAdminKeys.all` covers everything.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const auth = createAuthAdminClient();
|
|
133
|
+
const queries = authAdminQueries(auth);
|
|
134
|
+
|
|
135
|
+
const users = useQuery(queries.userList({ limit: 20, offset: 0, searchValue }));
|
|
136
|
+
const note = useQuery(apiQueryOptions(api, "/v1/notes/{id}", { params: { path: { id } } }));
|
|
137
|
+
|
|
138
|
+
const ban = useMutation({
|
|
139
|
+
mutationFn: (userId: string) => auth.admin.banUser({ userId }),
|
|
140
|
+
onSuccess: () => invalidateQueries(queryClient, authAdminKeys.users()),
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Tests
|
|
145
|
+
|
|
146
|
+
`test/` runs in Node (a browser package; no Workers APIs). The tests check typed calls, error
|
|
147
|
+
parsing and network failures against a fake server. The app side is a real `@awsmbiz/inbis-api` app
|
|
148
|
+
(`test/fixture-app.ts`), so errors use the platform's own envelope. The auth side is a stand-in
|
|
149
|
+
for the auth routes (`test/fake-auth-server.ts`). Compile-time checks (`@ts-expect-error`,
|
|
150
|
+
`expectTypeOf`) run in `pnpm typecheck`.
|
|
151
|
+
|
|
152
|
+
Dependencies: `openapi-fetch` 0.17.0, `openapi-typescript-helpers` 0.1.0, `better-auth` 1.7.14
|
|
153
|
+
(browser client only).
|