@auth-craft/client 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 ADDED
@@ -0,0 +1,315 @@
1
+ # @auth-craft/client
2
+
3
+ Typed, contract-driven frontend client for Auth Craft.
4
+
5
+ Every method is generated from `@auth-craft/api-contract` — the **same** endpoint
6
+ declarations the backend routes validate against. So the client's paths, request
7
+ bodies, query params, path params **and response types cannot drift** from the
8
+ server: if a route changes and the contract is updated, your call sites re-type
9
+ at build time. Tokens are isolated in a Web Worker (via `@auth-craft/transport`,
10
+ absorbed from FetchGuard), protecting them from XSS.
11
+
12
+ > Migrating from the old `@auth-craft/frontend-sdk`? Jump to
13
+ > [Migration](#migration-from-auth-craftfrontend-sdk).
14
+
15
+ ---
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ npm install @auth-craft/client
21
+ ```
22
+
23
+ ### Bundler note (Vite)
24
+
25
+ The transport runs a Web Worker. With Vite, exclude it from pre-bundling:
26
+
27
+ ```ts
28
+ // vite.config.ts
29
+ export default defineConfig({
30
+ optimizeDeps: { exclude: ['@auth-craft/transport'] }
31
+ })
32
+ ```
33
+
34
+ If you bundle the SDK to plain JS (e.g. consuming from `dist/`), pass a
35
+ `workerFactory` so the worker URL resolves in your app (see [Config](#config)).
36
+
37
+ ---
38
+
39
+ ## Quick start
40
+
41
+ ```ts
42
+ import { createAuthCraftClient } from '@auth-craft/client'
43
+
44
+ const client = createAuthCraftClient({
45
+ baseUrl: 'https://auth.example.com',
46
+ strategy: 'cookie-auth', // or 'body-auth'
47
+ allowedDomains: ['auth.example.com']
48
+ })
49
+
50
+ // One-time init — boots the Worker and restores any existing session.
51
+ await client.init()
52
+
53
+ // Log in. Returns a discriminated result: tokens | challenge | mfa.
54
+ const r = await client.auth.authenticate({
55
+ body: { identityType: 'email', identityValue: 'user@example.com', credentialType: 'password', credentialValue: 'secret' }
56
+ })
57
+
58
+ if (r.ok) {
59
+ // session established → call authenticated endpoints
60
+ const me = await client.auth.me()
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## The call shape
67
+
68
+ Every generated method takes **one argument object** with up to three keys —
69
+ always the same shape, so you never have to remember a per-method signature:
70
+
71
+ ```ts
72
+ client.<group>.<method>({
73
+ params?: { ...pathParams }, // required only when the path has :params
74
+ body?: { ...requestBody }, // present only when the endpoint has a body
75
+ query?: { ...queryParams } // present only when the endpoint has a query
76
+ })
77
+ ```
78
+
79
+ - `params` is **required** when the path contains a segment like `:tenantId`.
80
+ - `body` is typed from the contract's request schema (missing/extra fields are a
81
+ compile error).
82
+ - `query` is always optional.
83
+
84
+ Every method returns `Promise<Result<T>>` from
85
+ [`ts-micro-result`](https://www.npmjs.com/package/ts-micro-result):
86
+
87
+ ```ts
88
+ const r = await client.auth.me()
89
+ if (r.ok) {
90
+ console.log(r.data.displayName) // typed from the contract response schema
91
+ } else {
92
+ console.error(r.errors[0].code) // backend error codes, forwarded as-is
93
+ }
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Config
99
+
100
+ ```ts
101
+ interface AuthCraftClientConfig {
102
+ /** Base URL of the Auth Craft backend (no trailing slash needed). */
103
+ baseUrl: string
104
+ /** Refresh-token delivery: 'cookie-auth' (httpOnly cookie) or 'body-auth'. */
105
+ strategy: 'cookie-auth' | 'body-auth'
106
+ /** Hosts the Worker may attach tokens to (anti-exfiltration allow-list). */
107
+ allowedDomains: string[]
108
+ /** Refresh this many ms before access-token expiry (proactive). */
109
+ refreshEarlyMs?: number
110
+ /** Headers added to every request (e.g. an edge-gate secret). */
111
+ defaultHeaders?: Record<string, string>
112
+ /** Custom Worker factory — required when consuming the SDK as compiled JS. */
113
+ workerFactory?: () => Worker
114
+ /**
115
+ * Auth scope → sent as the `X-Auth-Context` header on every request.
116
+ * 'system' = admin, 'tenant' = tenant-scoped, omit/'customer' = default.
117
+ */
118
+ context?: 'customer' | 'tenant' | 'system'
119
+ }
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Method groups
125
+
126
+ `client.<group>.<method>(...)`. Authenticated unless marked **public**.
127
+
128
+ ### `auth`
129
+ | Method | HTTP | Notes |
130
+ |---|---|---|
131
+ | `authenticate({ body })` | POST /auth/authenticate | **public**, login — returns tokens / challenge / mfa |
132
+ | `register({ body })` | POST /auth/register | **public** — returns `{ userId, verification? }` (does NOT log in) |
133
+ | `completeChallenge({ body })` | POST /auth/complete-challenge | **public**, login — finishes a 2-step credential challenge |
134
+ | `completeMfaChallenge({ body })` | POST /auth/complete-mfa-challenge | **public**, login — finishes MFA |
135
+ | `issueMfaChallenge({ body })` | POST /auth/issue-mfa-challenge | **public** — send an MFA code (SMS/email) |
136
+ | `me()` | GET /auth/me | current user profile |
137
+ | `selectTenant({ body })` | POST /auth/select-tenant | switch tenant context (re-issues tokens) |
138
+ | `logout()` | POST /auth/logout | clears the session |
139
+
140
+ > `refresh` is handled **automatically** by the transport (proactive, before
141
+ > expiry). You don't call it.
142
+
143
+ ### `password`
144
+ `changePassword({ body })` · `forgotPassword({ body })` *(public)* · `resetPassword({ body })` *(public)*
145
+
146
+ ### `verification`
147
+ `verifyCode({ body })` *(public)* · `resendVerification({ body })` *(public)*
148
+
149
+ ### `session`
150
+ `listSessions({ query? })` · `deleteSession({ body })` · `revokeToken({ body })`
151
+
152
+ ### `mfa`
153
+ `setupAuthenticator({ body })` · `verifyAuthenticator({ body })` · `listAuthenticators({ query? })` · `removeAuthenticator({ body })` · `updateMfaSettings({ body })`
154
+
155
+ > MFA verify: the code goes **inside `data`**, e.g.
156
+ > `verifyAuthenticator({ body: { method: 'totp', data: { code: '123456' } } })`.
157
+
158
+ ### `oauth`
159
+ `oauthState()` *(public)* · `oauthCallback({ body })` *(public, login)* · `linkProvider({ body })` · `unlinkProvider({ body })` · `getProviders()`
160
+
161
+ ### `users`
162
+ `updateProfile({ body })`
163
+
164
+ ### `tenant`
165
+ All scoped under `/tenants/:tenantId/...` — **`params.tenantId` is required**:
166
+
167
+ `getTenantProfile({ params })` · `updateTenantProfile({ params, body })` · `createInvite({ params, body })` · `listInvites({ params, query? })` · `acceptInvite({ params, body })` · `declineInvite({ params, body })` · `revokeInvite({ params, body })` · `listMembers({ params, query? })` · `updateMember({ params, body })` · `removeMember({ params, body })`
168
+
169
+ ```ts
170
+ await client.tenant.listMembers({ params: { tenantId: 'org-123' } })
171
+ await client.tenant.createInvite({
172
+ params: { tenantId: 'org-123' },
173
+ body: { inviteeEmail: 'new@example.com', roles: ['member'], authPerms: ['tenant.member.view'] }
174
+ })
175
+ ```
176
+
177
+ ---
178
+
179
+ ## Login: handling challenge & MFA
180
+
181
+ A login call (`authenticate`, `completeChallenge`, `oauthCallback`) resolves to a
182
+ **discriminated union** — tokens, a credential challenge, or an MFA requirement.
183
+ Use the exported type guards to branch:
184
+
185
+ ```ts
186
+ import { isAuthTokens, isChallengeRequired, isMfaRequired } from '@auth-craft/api-contract'
187
+
188
+ const r = await client.auth.authenticate({
189
+ body: { identityType: 'email', identityValue: 'user@example.com', credentialType: 'otp' }
190
+ })
191
+ if (!r.ok) { /* real error */ return }
192
+
193
+ if (isChallengeRequired(r.data)) {
194
+ // e.g. emailed OTP — collect the code, then:
195
+ const done = await client.auth.completeChallenge({
196
+ body: { challengeId: r.data.challenge.data.challengeId as string, code: userCode }
197
+ })
198
+ } else if (isMfaRequired(r.data)) {
199
+ // collect MFA code for one of r.data.mfa.methods, then:
200
+ const done = await client.auth.completeMfaChallenge({
201
+ body: { challengeId: r.data.mfa.challengeId, method: r.data.mfa.methods[0], code: userCode }
202
+ })
203
+ } else if (isAuthTokens(r.data)) {
204
+ // session established
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Scopes (customer / tenant / system)
211
+
212
+ Set `context` so the backend reads the right refresh-token cookie / scope:
213
+
214
+ ```ts
215
+ const adminClient = createAuthCraftClient({ /* ... */, context: 'system' })
216
+ ```
217
+
218
+ This stamps `X-Auth-Context: system` on every request. (In a deployment with the
219
+ Cloudflare BFF gateway, the gateway is the source of truth for scope; the header
220
+ is the local-dev convention.)
221
+
222
+ ---
223
+
224
+ ## Lifecycle
225
+
226
+ ```ts
227
+ await client.init() // boot Worker; idempotent enough to call once at startup
228
+ client.destroy() // terminate the Worker and clear in-memory state
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Why it can't drift
234
+
235
+ | Concern | Guaranteed by |
236
+ |---|---|
237
+ | Path / path params | shared `contract.path`; a CI test diffs every contract path against the live route registry |
238
+ | HTTP method | same registry diff (method + path) |
239
+ | Request body / query | `InferInput` of the contract schema — wrong/missing fields are a compile error |
240
+ | Response shape | `InferOutput` of the contract response schema; a CI test fails if a data endpoint lacks one |
241
+ | Method coverage | methods are **generated** from the contract group (`buildClient` iterates its keys) — a new endpoint appears automatically; there is no hand-maintained list to fall behind |
242
+ | Server reads the same shape | the backend route's `ctx.body`/`query`/`params` are typed from the **same** contract schema; renaming a field breaks the client call site *and* the route handler in one `tsc` run |
243
+ | Per-method transform bugs | there are none — the envelope→`Result<T>` step lives once in the transport |
244
+
245
+ ---
246
+
247
+ ## Migration from `@auth-craft/frontend-sdk`
248
+
249
+ The old `@auth-craft/frontend-sdk` was hand-written (per-method paths/types), which
250
+ let request/response shapes drift from the server. It is **removed**. This client
251
+ replaces it. The package name and factory change, and every call uses the uniform
252
+ `{ body / params / query }` shape.
253
+
254
+ ### Setup
255
+
256
+ ```ts
257
+ // OLD
258
+ import { createAuthCraft } from '@auth-craft/frontend-sdk'
259
+ const client = createAuthCraft({ baseUrl, strategy, allowedDomains })
260
+ await client.init()
261
+
262
+ // NEW
263
+ import { createAuthCraftClient } from '@auth-craft/client'
264
+ const client = createAuthCraftClient({ baseUrl, strategy, allowedDomains })
265
+ await client.init()
266
+ ```
267
+
268
+ ### Call-site changes (the important part)
269
+
270
+ | Old | New |
271
+ |---|---|
272
+ | `client.auth.authenticate(req)` | `client.auth.authenticate({ body: req })` |
273
+ | `client.auth.me()` | `client.auth.me()` *(unchanged)* |
274
+ | `client.password.change(req)` | `client.password.changePassword({ body: req })` |
275
+ | `client.password.forgot(req)` | `client.password.forgotPassword({ body: req })` |
276
+ | `client.password.reset(req)` | `client.password.resetPassword({ body: req })` |
277
+ | `client.verification.verify(req)` | `client.verification.verifyCode({ body: req })` |
278
+ | `client.verification.resend(req)` | `client.verification.resendVerification({ body: req })` |
279
+ | `client.session.list(includeRevoked)` | `client.session.listSessions({ query: { includeRevoked: 'true' } })` |
280
+ | `client.session.delete(req)` | `client.session.deleteSession({ body: req })` |
281
+ | `client.session.revoke(req)` | `client.session.revokeToken({ body: req })` |
282
+ | `client.mfa.setup(req)` | `client.mfa.setupAuthenticator({ body: req })` |
283
+ | `client.mfa.verify({ method, code })` | `client.mfa.verifyAuthenticator({ body: { method, data: { code } } })` ⚠️ code in `data` |
284
+ | `client.mfa.list(opts)` | `client.mfa.listAuthenticators({ query: opts })` |
285
+ | `client.mfa.delete({ method, id })` | `client.mfa.removeAuthenticator({ body: { method, authenticatorId } })` ⚠️ `authenticatorId` |
286
+ | `client.mfa.updateSettings(req)` | `client.mfa.updateMfaSettings({ body: req })` |
287
+ | `client.oauth.callback(req)` | `client.oauth.oauthCallback({ body: req })` |
288
+ | `client.oauth.link(req)` | `client.oauth.linkProvider({ body: req })` ⚠️ no `accessToken` field |
289
+ | `client.oauth.unlink(req)` | `client.oauth.unlinkProvider({ body: req })` |
290
+ | `client.oauth.listProviders()` | `client.oauth.getProviders()` |
291
+ | *(none)* | `client.oauth.oauthState()` — **new**, required for the CSRF redirect flow |
292
+ | `client.user.updateProfile(req)` | `client.users.updateProfile({ body: req })` |
293
+ | `client.tenant.createInvite(req)` | `client.tenant.createInvite({ params: { tenantId }, body })` ⚠️ tenantId in path |
294
+ | `client.tenant.listMembers()` | `client.tenant.listMembers({ params: { tenantId } })` ⚠️ tenantId in path |
295
+ | `client.tenant.getProfile()` | `client.tenant.getTenantProfile({ params: { tenantId } })` |
296
+ | `client.tenant.checkPendingInvites(req)` | **removed** — no public route (internal only) |
297
+
298
+ ### Behaviour changes you must be aware of
299
+
300
+ - **Tenant routes are path-scoped.** Every tenant method now requires
301
+ `params.tenantId`. The old client called `/tenant/...` (no id) and 404'd.
302
+ - **MFA codes go in `data`**, and the field is `authenticatorId` (not `id`).
303
+ - **OAuth never accepts `accessToken`** — the backend rejects it (token
304
+ substitution). Use `code` or `credential`. For the redirect/code flow, call
305
+ `oauth.oauthState()` first and forward the returned `state` to the provider.
306
+ - **Permissions are string arrays** (`roles`, `authPerms`), not numeric masks.
307
+ - **Login returns a union** — branch with `isAuthTokens` / `isChallengeRequired`
308
+ / `isMfaRequired` (see [above](#login-handling-challenge--mfa)). The old client
309
+ returned ad-hoc shapes per path.
310
+ - **`refresh` is automatic** — remove any manual refresh calls.
311
+
312
+ ### Errors
313
+
314
+ Both old and new return `Result<T>` from `ts-micro-result`, so `if (r.ok) { r.data } else { r.errors }`
315
+ is unchanged. The new client forwards the backend's error codes verbatim.
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @auth-craft/client — contract-driven typed client.
3
+ *
4
+ * Wires the absorbed transport (FetchGuard) to the shared api-contract, so the
5
+ * client's methods are generated from — and type-checked against — the same
6
+ * endpoint declarations the backend routes use. No hand-written per-method
7
+ * paths/types: a route change in the contract re-types the call sites here.
8
+ *
9
+ * Module groups mirror the route groups: auth, password, verification, session,
10
+ * mfa, oauth, users, tenant. Tenant methods take `{ params: { tenantId }, ... }`
11
+ * because the routes are scoped under /tenants/:tenantId/...
12
+ */
13
+ import { authContract, mfaContract, oauthContract, passwordContract, sessionContract, tenantsContract, usersContract, verificationContract } from '@auth-craft/api-contract';
14
+ import { type ContractClient } from './proxy';
15
+ export interface AuthCraftClientConfig {
16
+ baseUrl: string;
17
+ strategy: 'cookie-auth' | 'body-auth';
18
+ allowedDomains: string[];
19
+ refreshEarlyMs?: number;
20
+ defaultHeaders?: Record<string, string>;
21
+ workerFactory?: () => Worker;
22
+ /** Auth context scope → X-Auth-Context header. */
23
+ context?: 'customer' | 'tenant' | 'system';
24
+ }
25
+ export declare class AuthCraftClient {
26
+ private fg;
27
+ private readonly config;
28
+ auth: ContractClient<typeof authContract>;
29
+ password: ContractClient<typeof passwordContract>;
30
+ verification: ContractClient<typeof verificationContract>;
31
+ session: ContractClient<typeof sessionContract>;
32
+ mfa: ContractClient<typeof mfaContract>;
33
+ oauth: ContractClient<typeof oauthContract>;
34
+ users: ContractClient<typeof usersContract>;
35
+ tenant: ContractClient<typeof tenantsContract>;
36
+ constructor(config: AuthCraftClientConfig);
37
+ init(): Promise<void>;
38
+ private wireGroups;
39
+ buildUrl(path: string): string;
40
+ destroy(): void;
41
+ }
42
+ export declare function createAuthCraftClient(config: AuthCraftClientConfig): AuthCraftClient;
43
+ export { buildClient } from './proxy';
44
+ export type { ContractClient, EndpointMethod, AuthResult } from './proxy';
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACrB,MAAM,0BAA0B,CAAA;AAEjC,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,SAAS,CAAA;AAE1D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,aAAa,GAAG,WAAW,CAAA;IACrC,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAA;IAC5B,kDAAkD;IAClD,OAAO,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAA;CAC3C;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,EAAE,CAAgC;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAE9C,IAAI,EAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAA;IAC1C,QAAQ,EAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAA;IAClD,YAAY,EAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAA;IAC1D,OAAO,EAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAA;IAChD,GAAG,EAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAA;IACxC,KAAK,EAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAA;IAC5C,KAAK,EAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAA;IAC5C,MAAM,EAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAA;gBAEnC,MAAM,EAAE,qBAAqB;IAInC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB3B,OAAO,CAAC,UAAU;IAYlB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAK9B,OAAO,IAAI,IAAI;CAIhB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAEpF;AAED,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @auth-craft/client — contract-driven typed client.
3
+ *
4
+ * Wires the absorbed transport (FetchGuard) to the shared api-contract, so the
5
+ * client's methods are generated from — and type-checked against — the same
6
+ * endpoint declarations the backend routes use. No hand-written per-method
7
+ * paths/types: a route change in the contract re-types the call sites here.
8
+ *
9
+ * Module groups mirror the route groups: auth, password, verification, session,
10
+ * mfa, oauth, users, tenant. Tenant methods take `{ params: { tenantId }, ... }`
11
+ * because the routes are scoped under /tenants/:tenantId/...
12
+ */
13
+ import { authContract, mfaContract, oauthContract, passwordContract, sessionContract, tenantsContract, usersContract, verificationContract } from '@auth-craft/api-contract';
14
+ import { createClient } from '@auth-craft/transport';
15
+ import { buildClient } from './proxy';
16
+ export class AuthCraftClient {
17
+ fg = null;
18
+ config;
19
+ auth;
20
+ password;
21
+ verification;
22
+ session;
23
+ mfa;
24
+ oauth;
25
+ users;
26
+ tenant;
27
+ constructor(config) {
28
+ this.config = config;
29
+ }
30
+ async init() {
31
+ const headers = this.config.context && this.config.context !== 'customer'
32
+ ? { ...this.config.defaultHeaders, 'X-Auth-Context': this.config.context }
33
+ : this.config.defaultHeaders;
34
+ this.fg = createClient({
35
+ provider: {
36
+ type: this.config.strategy,
37
+ refreshUrl: this.buildUrl('/auth/refresh'),
38
+ loginUrl: this.buildUrl('/auth/authenticate'),
39
+ logoutUrl: this.buildUrl('/auth/logout'),
40
+ ...(headers ? { headers } : {})
41
+ },
42
+ allowedDomains: this.config.allowedDomains,
43
+ ...(this.config.refreshEarlyMs !== undefined ? { refreshEarlyMs: this.config.refreshEarlyMs } : {}),
44
+ ...(headers ? { defaultHeaders: headers } : {}),
45
+ ...(this.config.workerFactory ? { workerFactory: this.config.workerFactory } : {})
46
+ });
47
+ await this.fg.whenReady();
48
+ this.wireGroups(this.fg);
49
+ }
50
+ wireGroups(fg) {
51
+ const url = (p) => this.buildUrl(p);
52
+ this.auth = buildClient(fg, authContract, url);
53
+ this.password = buildClient(fg, passwordContract, url);
54
+ this.verification = buildClient(fg, verificationContract, url);
55
+ this.session = buildClient(fg, sessionContract, url);
56
+ this.mfa = buildClient(fg, mfaContract, url);
57
+ this.oauth = buildClient(fg, oauthContract, url);
58
+ this.users = buildClient(fg, usersContract, url);
59
+ this.tenant = buildClient(fg, tenantsContract, url);
60
+ }
61
+ buildUrl(path) {
62
+ const p = path.startsWith('/') ? path : `/${path}`;
63
+ return `${this.config.baseUrl}${p}`;
64
+ }
65
+ destroy() {
66
+ this.fg?.destroy();
67
+ this.fg = null;
68
+ }
69
+ }
70
+ export function createAuthCraftClient(config) {
71
+ return new AuthCraftClient(config);
72
+ }
73
+ export { buildClient } from './proxy';
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Contract-driven typed proxy.
3
+ *
4
+ * Given a transport (the absorbed FetchGuard client) and a map of contract
5
+ * endpoints, this builds a typed method per endpoint. The `kind` discriminator
6
+ * decides how each one is dispatched:
7
+ *
8
+ * data → transport.callJson(method, url, body) (auto-bound, typed Result<T>)
9
+ * login → transport.login(body, url) (Worker session bootstrap)
10
+ * logout → transport.logout(body)
11
+ * exchange → transport.exchangeToken(url, { payload })
12
+ *
13
+ * Types flow straight from the contract: the input is inferred from the body
14
+ * schema, the output from the response schema (or AuthResult for login/logout).
15
+ * A route change to the contract therefore re-types every call site at build.
16
+ */
17
+ import type { Endpoint, PathParams } from '@auth-craft/api-contract';
18
+ import { type FetchGuardClient } from '@auth-craft/transport';
19
+ import { type Result } from 'ts-micro-result';
20
+ import type { InferInput, InferOutput } from 'valibot';
21
+ export interface AuthResult {
22
+ authenticated: boolean;
23
+ user?: unknown;
24
+ expiresAt?: number | null;
25
+ }
26
+ /** Whether an endpoint has any `:param` segments. */
27
+ type HasParams<P extends string> = PathParams<P> extends never ? false : true;
28
+ /** The `params` argument, required only when the path has params. */
29
+ type ParamsArg<P extends string> = HasParams<P> extends true ? {
30
+ params: Record<PathParams<P>, string | number>;
31
+ } : {
32
+ params?: undefined;
33
+ };
34
+ /** Body argument, present only when the endpoint declares a body schema. */
35
+ type BodyArg<E> = E extends {
36
+ body: infer S;
37
+ } ? {
38
+ body: InferInput<S extends import('valibot').GenericSchema ? S : never>;
39
+ } : {
40
+ body?: undefined;
41
+ };
42
+ /** Query argument, present only when the endpoint declares a query schema. */
43
+ type QueryArg<E> = E extends {
44
+ query: infer S;
45
+ } ? {
46
+ query?: InferInput<S extends import('valibot').GenericSchema ? S : never>;
47
+ } : {
48
+ query?: undefined;
49
+ };
50
+ /** The single call argument object for a generated method. */
51
+ type CallArg<E extends Endpoint> = ParamsArg<E['path']> & BodyArg<E> & QueryArg<E>;
52
+ /** The resolved return type of a generated method. */
53
+ type CallReturn<E extends Endpoint> = E extends {
54
+ kind: 'data';
55
+ } ? E extends {
56
+ response: infer R;
57
+ } ? Result<InferOutput<R extends import('valibot').GenericSchema ? R : never>> : Result<unknown> : E extends {
58
+ kind: 'login';
59
+ } ? E extends {
60
+ response: infer R;
61
+ } ? Result<InferOutput<R extends import('valibot').GenericSchema ? R : never>> : Result<AuthResult> : Result<AuthResult>;
62
+ /** Does this arg shape need any argument at all? */
63
+ type NeedsArg<E extends Endpoint> = HasParams<E['path']> extends true ? true : E extends {
64
+ body: unknown;
65
+ } ? true : false;
66
+ export type EndpointMethod<E extends Endpoint> = NeedsArg<E> extends true ? (arg: CallArg<E>) => Promise<CallReturn<E>> : (arg?: CallArg<E>) => Promise<CallReturn<E>>;
67
+ export type ContractClient<C extends Record<string, Endpoint>> = {
68
+ [K in keyof C]: EndpointMethod<C[K]>;
69
+ };
70
+ /**
71
+ * Build a typed client object from a contract group.
72
+ */
73
+ export declare function buildClient<C extends Record<string, Endpoint>>(transport: FetchGuardClient, contract: C, buildUrl: (path: string) => string): ContractClient<C>;
74
+ export {};
75
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
package/dist/proxy.js ADDED
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Contract-driven typed proxy.
3
+ *
4
+ * Given a transport (the absorbed FetchGuard client) and a map of contract
5
+ * endpoints, this builds a typed method per endpoint. The `kind` discriminator
6
+ * decides how each one is dispatched:
7
+ *
8
+ * data → transport.callJson(method, url, body) (auto-bound, typed Result<T>)
9
+ * login → transport.login(body, url) (Worker session bootstrap)
10
+ * logout → transport.logout(body)
11
+ * exchange → transport.exchangeToken(url, { payload })
12
+ *
13
+ * Types flow straight from the contract: the input is inferred from the body
14
+ * schema, the output from the response schema (or AuthResult for login/logout).
15
+ * A route change to the contract therefore re-types every call site at build.
16
+ */
17
+ import { fillPath } from '@auth-craft/api-contract';
18
+ import { deserializeResult } from '@auth-craft/transport';
19
+ import { err, ok } from 'ts-micro-result';
20
+ /**
21
+ * Build a typed client object from a contract group.
22
+ */
23
+ export function buildClient(transport, contract, buildUrl) {
24
+ const out = {};
25
+ for (const key of Object.keys(contract)) {
26
+ const ep = contract[key];
27
+ out[key] = async (arg) => {
28
+ const path = fillPath(ep.path, arg?.params);
29
+ const url = buildUrl(path) + buildQueryString(arg?.query);
30
+ const body = arg?.body;
31
+ const requiresAuth = ep.public !== true;
32
+ switch (ep.kind) {
33
+ case 'data':
34
+ return transport.callJson(ep.method, url, body, { requiresAuth });
35
+ case 'login':
36
+ return dispatchLogin(transport, body, url);
37
+ case 'logout':
38
+ return transport.logout(body);
39
+ case 'exchange':
40
+ return transport.exchangeToken(url, {
41
+ ...(body !== undefined ? { payload: body } : {})
42
+ });
43
+ default:
44
+ return err({ code: 'UNKNOWN_KIND', message: `Unknown endpoint kind for '${key}'.` });
45
+ }
46
+ };
47
+ }
48
+ return out;
49
+ }
50
+ /**
51
+ * Dispatch a login-kind endpoint and normalize the outcome.
52
+ *
53
+ * FetchGuard's login() resolves the AuthResult when a session is established. A
54
+ * 2-step login (credential challenge / MFA required) comes back as a non-error
55
+ * HTTP body that FetchGuard surfaces via `meta.params.body`. We deserialize that
56
+ * envelope so the caller gets the discriminated login result (tokens | challenge
57
+ * | mfa) instead of an opaque "not authenticated" — matching the old SDK's
58
+ * behaviour, but in ONE place instead of per method.
59
+ */
60
+ async function dispatchLogin(transport, body, url) {
61
+ const result = await transport.login(body, url);
62
+ // Session established → return the tokens/auth-state shape.
63
+ if (result.ok && result.data.authenticated) {
64
+ return ok({
65
+ accessToken: '',
66
+ expiresAt: result.data.expiresAt ?? 0
67
+ });
68
+ }
69
+ // Not authenticated: the backend body may carry a challenge / MFA requirement
70
+ // (a legitimate 2-step step), or a real error. Deserialize and forward it.
71
+ if (!result.ok) {
72
+ const params = result.meta?.params;
73
+ if (params?.body) {
74
+ const parsed = deserializeResult(params.body);
75
+ // Forward whatever the backend said — challenge/mfa (ok) or error (err).
76
+ return parsed;
77
+ }
78
+ return err(result.errors);
79
+ }
80
+ // ok but not authenticated and no body to interpret — surface a generic error.
81
+ return err({ code: 'LOGIN_FAILED', message: 'Authentication did not establish a session' });
82
+ }
83
+ /** Serialize a query object to a `?a=1&b=2` string (skips undefined). */
84
+ function buildQueryString(query) {
85
+ if (!query)
86
+ return '';
87
+ const params = new URLSearchParams();
88
+ for (const [k, v] of Object.entries(query)) {
89
+ if (v !== undefined && v !== null)
90
+ params.set(k, String(v));
91
+ }
92
+ const qs = params.toString();
93
+ return qs ? `?${qs}` : '';
94
+ }
95
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.test-d.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.test-d.d.ts","sourceRoot":"","sources":["../src/types.test-d.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Type-level POC: the generated client methods are typed FROM the contract.
3
+ * Correct calls compile; wrong calls must fail (verified by @ts-expect-error).
4
+ * This is the mechanical guarantee — a contract change re-types these call sites.
5
+ *
6
+ * This file is type-checked (tsc --noEmit), not executed.
7
+ */
8
+ import { authContract } from '@auth-craft/api-contract';
9
+ // ✓ me: no body, no params → callable with no argument
10
+ void client.me();
11
+ // ✓ authenticate: body inferred from the contract schema
12
+ void client.authenticate({
13
+ body: { identityType: 'email', identityValue: 'a@b.com', credentialType: 'password', credentialValue: 'x' }
14
+ });
15
+ // ✓ selectTenant: body { tenantId }
16
+ void client.selectTenant({ body: { tenantId: 't1' } });
17
+ // ✗ authenticate missing required fields → MUST error
18
+ // @ts-expect-error identityValue + credentialType are required
19
+ void client.authenticate({ body: { identityType: 'email' } });
20
+ // ✗ selectTenant wrong field name → MUST error
21
+ // @ts-expect-error 'tenant' is not a valid field
22
+ void client.selectTenant({ body: { tenant: 't1' } });
23
+ // ✗ me takes no body → MUST error
24
+ // @ts-expect-error me accepts no argument with body
25
+ void client.me({ body: { x: 1 } });
26
+ //# sourceMappingURL=types.test-d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.test-d.js","sourceRoot":"","sources":["../src/types.test-d.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAKvD,uDAAuD;AACvD,KAAK,MAAM,CAAC,EAAE,EAAE,CAAA;AAEhB,yDAAyD;AACzD,KAAK,MAAM,CAAC,YAAY,CAAC;IACvB,IAAI,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,EAAE;CAC5G,CAAC,CAAA;AAEF,oCAAoC;AACpC,KAAK,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;AAEtD,sDAAsD;AACtD,+DAA+D;AAC/D,KAAK,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;AAE7D,+CAA+C;AAC/C,iDAAiD;AACjD,KAAK,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;AAEpD,kCAAkC;AAClC,oDAAoD;AACpD,KAAK,MAAM,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA"}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@auth-craft/client",
3
+ "version": "0.1.0",
4
+ "description": "Contract-driven typed frontend client for Auth Craft. Methods are generated from @auth-craft/api-contract over the @auth-craft/transport (Web-Worker token isolation), so routes and client cannot drift.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist", "README.md"],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "clean": "rm -rf dist",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run"
21
+ },
22
+ "dependencies": {
23
+ "@auth-craft/api-contract": "workspace:*",
24
+ "@auth-craft/transport": "workspace:*",
25
+ "ts-micro-result": "^3.3.0",
26
+ "valibot": "^1.4.1"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^6.0.3",
30
+ "vitest": "^4.1.8"
31
+ },
32
+ "license": "MIT"
33
+ }