@auth-craft/client 0.1.0 → 0.1.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 (109) hide show
  1. package/README.md +144 -8
  2. package/dist/core/core.d.ts +72 -0
  3. package/dist/core/core.d.ts.map +1 -0
  4. package/dist/core/core.js +105 -0
  5. package/dist/core/core.js.map +1 -0
  6. package/dist/core/proxy.d.ts +79 -0
  7. package/dist/core/proxy.d.ts.map +1 -0
  8. package/dist/core/proxy.js +99 -0
  9. package/dist/core/proxy.js.map +1 -0
  10. package/dist/index.d.ts +15 -28
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +23 -55
  13. package/dist/index.js.map +1 -1
  14. package/dist/transport/client.d.ts +250 -0
  15. package/dist/transport/client.d.ts.map +1 -0
  16. package/dist/transport/client.js +940 -0
  17. package/dist/transport/client.js.map +1 -0
  18. package/dist/transport/constants.d.ts +9 -0
  19. package/dist/transport/constants.d.ts.map +1 -0
  20. package/dist/transport/constants.js +9 -0
  21. package/dist/transport/constants.js.map +1 -0
  22. package/dist/transport/error-codes.d.ts +41 -0
  23. package/dist/transport/error-codes.d.ts.map +1 -0
  24. package/dist/transport/error-codes.js +38 -0
  25. package/dist/transport/error-codes.js.map +1 -0
  26. package/dist/transport/errors.d.ts +55 -0
  27. package/dist/transport/errors.d.ts.map +1 -0
  28. package/dist/transport/errors.js +55 -0
  29. package/dist/transport/errors.js.map +1 -0
  30. package/dist/transport/helpers.d.ts +135 -0
  31. package/dist/transport/helpers.d.ts.map +1 -0
  32. package/dist/transport/helpers.js +185 -0
  33. package/dist/transport/helpers.js.map +1 -0
  34. package/dist/transport/index.d.ts +22 -0
  35. package/dist/transport/index.d.ts.map +1 -0
  36. package/dist/transport/index.js +32 -0
  37. package/dist/transport/index.js.map +1 -0
  38. package/dist/transport/messages.d.ts +104 -0
  39. package/dist/transport/messages.d.ts.map +1 -0
  40. package/dist/transport/messages.js +24 -0
  41. package/dist/transport/messages.js.map +1 -0
  42. package/dist/transport/provider/create-provider.d.ts +38 -0
  43. package/dist/transport/provider/create-provider.d.ts.map +1 -0
  44. package/dist/transport/provider/create-provider.js +119 -0
  45. package/dist/transport/provider/create-provider.js.map +1 -0
  46. package/dist/transport/provider/parser/body.d.ts +7 -0
  47. package/dist/transport/provider/parser/body.d.ts.map +1 -0
  48. package/dist/transport/provider/parser/body.js +17 -0
  49. package/dist/transport/provider/parser/body.js.map +1 -0
  50. package/dist/transport/provider/parser/cookie.d.ts +8 -0
  51. package/dist/transport/provider/parser/cookie.d.ts.map +1 -0
  52. package/dist/transport/provider/parser/cookie.js +17 -0
  53. package/dist/transport/provider/parser/cookie.js.map +1 -0
  54. package/dist/transport/provider/parser/normalize.d.ts +6 -0
  55. package/dist/transport/provider/parser/normalize.d.ts.map +1 -0
  56. package/dist/transport/provider/parser/normalize.js +21 -0
  57. package/dist/transport/provider/parser/normalize.js.map +1 -0
  58. package/dist/transport/provider/presets.d.ts +31 -0
  59. package/dist/transport/provider/presets.d.ts.map +1 -0
  60. package/dist/transport/provider/presets.js +35 -0
  61. package/dist/transport/provider/presets.js.map +1 -0
  62. package/dist/transport/provider/register-presets.d.ts +10 -0
  63. package/dist/transport/provider/register-presets.d.ts.map +1 -0
  64. package/dist/transport/provider/register-presets.js +32 -0
  65. package/dist/transport/provider/register-presets.js.map +1 -0
  66. package/dist/transport/provider/storage/indexeddb.d.ts +26 -0
  67. package/dist/transport/provider/storage/indexeddb.d.ts.map +1 -0
  68. package/dist/transport/provider/storage/indexeddb.js +72 -0
  69. package/dist/transport/provider/storage/indexeddb.js.map +1 -0
  70. package/dist/transport/provider/strategy/body.d.ts +28 -0
  71. package/dist/transport/provider/strategy/body.d.ts.map +1 -0
  72. package/dist/transport/provider/strategy/body.js +73 -0
  73. package/dist/transport/provider/strategy/body.js.map +1 -0
  74. package/dist/transport/provider/strategy/cookie.d.ts +29 -0
  75. package/dist/transport/provider/strategy/cookie.d.ts.map +1 -0
  76. package/dist/transport/provider/strategy/cookie.js +70 -0
  77. package/dist/transport/provider/strategy/cookie.js.map +1 -0
  78. package/dist/transport/result.d.ts +28 -0
  79. package/dist/transport/result.d.ts.map +1 -0
  80. package/dist/transport/result.js +87 -0
  81. package/dist/transport/result.js.map +1 -0
  82. package/dist/transport/types.d.ts +488 -0
  83. package/dist/transport/types.d.ts.map +1 -0
  84. package/dist/transport/types.js +3 -0
  85. package/dist/transport/types.js.map +1 -0
  86. package/dist/transport/utils/binary.d.ts +20 -0
  87. package/dist/transport/utils/binary.d.ts.map +1 -0
  88. package/dist/transport/utils/binary.js +51 -0
  89. package/dist/transport/utils/binary.js.map +1 -0
  90. package/dist/transport/utils/formdata.d.ts +26 -0
  91. package/dist/transport/utils/formdata.d.ts.map +1 -0
  92. package/dist/transport/utils/formdata.js +77 -0
  93. package/dist/transport/utils/formdata.js.map +1 -0
  94. package/dist/transport/utils/registry.d.ts +26 -0
  95. package/dist/transport/utils/registry.d.ts.map +1 -0
  96. package/dist/transport/utils/registry.js +51 -0
  97. package/dist/transport/utils/registry.js.map +1 -0
  98. package/dist/transport/worker-post.d.ts +44 -0
  99. package/dist/transport/worker-post.d.ts.map +1 -0
  100. package/dist/transport/worker-post.js +104 -0
  101. package/dist/transport/worker-post.js.map +1 -0
  102. package/dist/transport/worker.d.ts +10 -0
  103. package/dist/transport/worker.d.ts.map +1 -0
  104. package/dist/transport/worker.js +421 -0
  105. package/dist/transport/worker.js.map +1 -0
  106. package/dist/types.test-d.js +3 -9
  107. package/dist/types.test-d.js.map +1 -1
  108. package/eslint-isolation.mjs +82 -0
  109. package/package.json +30 -16
package/README.md CHANGED
@@ -6,9 +6,13 @@ Every method is generated from `@auth-craft/api-contract` — the **same** endpo
6
6
  declarations the backend routes validate against. So the client's paths, request
7
7
  bodies, query params, path params **and response types cannot drift** from the
8
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`,
9
+ at build time. Tokens are isolated in a Web Worker (a vendored transport,
10
10
  absorbed from FetchGuard), protecting them from XSS.
11
11
 
12
+ > **One scope per app?** Import [`@auth-craft/client/core`](#auth-craftclientcore--per-scope-isolation)
13
+ > + only the contract groups you need, so other scopes' code stays out of your
14
+ > bundle. The root export below wires every group.
15
+
12
16
  > Migrating from the old `@auth-craft/frontend-sdk`? Jump to
13
17
  > [Migration](#migration-from-auth-craftfrontend-sdk).
14
18
 
@@ -22,12 +26,12 @@ npm install @auth-craft/client
22
26
 
23
27
  ### Bundler note (Vite)
24
28
 
25
- The transport runs a Web Worker. With Vite, exclude it from pre-bundling:
29
+ The client runs a Web Worker. With Vite, exclude it from pre-bundling:
26
30
 
27
31
  ```ts
28
32
  // vite.config.ts
29
33
  export default defineConfig({
30
- optimizeDeps: { exclude: ['@auth-craft/transport'] }
34
+ optimizeDeps: { exclude: ['@auth-craft/client'] }
31
35
  })
32
36
  ```
33
37
 
@@ -116,6 +120,12 @@ interface AuthCraftClientConfig {
116
120
  * 'system' = admin, 'tenant' = tenant-scoped, omit/'customer' = default.
117
121
  */
118
122
  context?: 'customer' | 'tenant' | 'system'
123
+ /**
124
+ * Subscribe to session state changes (login, logout, refresh, expiry).
125
+ * Fires after `init()`. Bridge it into app state or trigger auto-logout
126
+ * on `authenticated: false`.
127
+ */
128
+ onAuthStateChanged?: (state: { authenticated: boolean; user?: unknown; expiresAt?: number | null }) => void
119
129
  }
120
130
  ```
121
131
 
@@ -176,6 +186,116 @@ await client.tenant.createInvite({
176
186
 
177
187
  ---
178
188
 
189
+ ## `client.api` — authenticated requests to your own APIs
190
+
191
+ The typed groups above only cover **Auth Craft's** routes. Your app still has its
192
+ own backend (commerce, console, billing, …) that the *same* logged-in user must
193
+ call. `client.api` is that escape hatch: a raw HTTP transport that rides the
194
+ **same Worker-isolated session** — tokens stay out of JS, refresh is shared, and
195
+ you get a deserialized `Result<T>` back.
196
+
197
+ ```ts
198
+ // any URL you build — points at YOUR backend, not the auth server
199
+ const r = await client.api.post<{ items: Product[] }>(
200
+ 'https://api.example.com/core/products',
201
+ { name: 'Tee', price: 1900 }
202
+ )
203
+ if (r.ok) r.data.items // typed by the <T> you supply
204
+
205
+ await client.api.get<Plan>('https://api.example.com/core/plans/current')
206
+ await client.api.delete('https://api.example.com/core/team/members/remove', { userId })
207
+ ```
208
+
209
+ ```ts
210
+ interface ApiTransport {
211
+ get<T = unknown>(url: string, options?): Promise<Result<T>>
212
+ post<T = unknown>(url: string, body?: unknown, options?): Promise<Result<T>>
213
+ put<T = unknown>(url: string, body?: unknown, options?): Promise<Result<T>>
214
+ patch<T = unknown>(url: string, body?: unknown, options?): Promise<Result<T>>
215
+ delete<T = unknown>(url: string, body?: unknown, options?): Promise<Result<T>>
216
+ }
217
+ ```
218
+
219
+ - **URLs pass through as-is** — build absolute URLs to whichever backend you
220
+ target. (Unlike the typed groups, `api` does **not** prepend `baseUrl`.)
221
+ - **You supply the type** `<T>` — `api` is unaware of your app's contract. For
222
+ Auth Craft's own routes, prefer the typed groups, which can't drift.
223
+ - Throws if accessed before `init()`.
224
+
225
+ > The escape hatch is deliberately *outside* the contract: your app's routes are
226
+ > not Auth Craft's concern, so they don't belong in `@auth-craft/api-contract`.
227
+ > `api` gives you the authenticated session; you own the routes.
228
+
229
+ ---
230
+
231
+ ## `@auth-craft/client/core` — per-scope isolation
232
+
233
+ The root export (`createAuthCraftClient`) wires **every** group, so it can't be
234
+ tree-shaken per scope. A web app is almost always **one** scope (storefront =
235
+ customer, console = tenant, admin = system). For those, import the scope-agnostic
236
+ core and wire only the groups you need — other scopes' code then stays out of
237
+ your bundle and your dev call surface:
238
+
239
+ ```ts
240
+ import { createClientCore } from '@auth-craft/client/core'
241
+ import { authContract } from '@auth-craft/api-contract/auth'
242
+
243
+ const core = await createClientCore({
244
+ baseUrl, strategy: 'cookie-auth', allowedDomains, context: 'customer'
245
+ })
246
+ const auth = core.group(authContract) // typed, drift-proof
247
+ await core.api.get('https://api.example.com/core/products') // your own backend
248
+ ```
249
+
250
+ `core.group(contract)` attaches a typed client for one group; `core.api`,
251
+ `onAuthStateChanged`, `context`, `init()`/`destroy()` work exactly as on the full
252
+ client. A `tenant` console just wires more groups (`@auth-craft/api-contract/tenant`).
253
+
254
+ Verified: a customer app built this way contains **zero** tenant/mfa/oauth route
255
+ code. "Not in the bundle" ≠ "not on disk" — the unused group files still live in
256
+ `node_modules/@auth-craft/api-contract`, just unreferenced. That's isolation at
257
+ the call surface, which is the point.
258
+
259
+ ### Keeping it enforced (two guards)
260
+
261
+ 1. **Library guard (always on):** a test in this package fails if the core
262
+ (`src/core`) ever imports a contract group or the api-contract root — so the
263
+ isolation can't silently rot for downstream apps.
264
+
265
+ 2. **App guard (optional, your linter):** stop an app importing a scope it
266
+ doesn't own.
267
+
268
+ **ESLint** — a helper ships here:
269
+ ```ts
270
+ // app/eslint.config.mjs
271
+ import tseslint from 'typescript-eslint'
272
+ import { authCraftIsolation } from '@auth-craft/client/eslint'
273
+ export default tseslint.config(
274
+ authCraftIsolation({ allow: ['auth', 'password', 'verification', 'mfa', 'oauth', 'session', 'users'] })
275
+ )
276
+ ```
277
+ Omit a group (e.g. `tenant`) and importing it — or the all-groups root
278
+ `@auth-craft/client`, or the api-contract root — becomes an error.
279
+
280
+ **Biome** — no shipped preset (Biome config is JSON, not composable), but the
281
+ same intent is `noRestrictedImports` in `biome.json`:
282
+ ```jsonc
283
+ {
284
+ "linter": { "rules": { "nursery": { "noRestrictedImports": { "level": "error", "options": { "paths": {
285
+ "@auth-craft/client": "Use @auth-craft/client/core + per-group subpaths; the all-groups root can't be tree-shaken.",
286
+ "@auth-craft/api-contract": "Import a focused group (e.g. /auth), not the root.",
287
+ "@auth-craft/api-contract/tenant": "This app's scope does not include 'tenant'."
288
+ } } } } } }
289
+ }
290
+ ```
291
+ (Rule placement — `nursery` vs stable — follows your Biome version.)
292
+
293
+ **Neither linter?** No guard is strictly required: an app that never imports
294
+ `@auth-craft/api-contract/tenant` simply doesn't have it; the guards only catch
295
+ *accidental* imports.
296
+
297
+ ---
298
+
179
299
  ## Login: handling challenge & MFA
180
300
 
181
301
  A login call (`authenticate`, `completeChallenge`, `oauthCallback`) resolves to a
@@ -224,10 +344,15 @@ is the local-dev convention.)
224
344
  ## Lifecycle
225
345
 
226
346
  ```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
347
+ await client.init() // boot Worker; idempotent enough to call once at startup
348
+ client.destroy() // terminate the Worker and clear in-memory state
349
+ client.getTransport() // advanced: the raw FetchGuard transport handle
229
350
  ```
230
351
 
352
+ > `createAuthCraftClient(config)` returns the client **without** initializing —
353
+ > call `await client.init()` yourself. (`createClientCore(config)` from
354
+ > `@auth-craft/client/core` initializes for you and resolves a ready core.)
355
+
231
356
  ---
232
357
 
233
358
  ## Why it can't drift
@@ -247,8 +372,10 @@ client.destroy() // terminate the Worker and clear in-memory state
247
372
  ## Migration from `@auth-craft/frontend-sdk`
248
373
 
249
374
  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
375
+ let request/response shapes drift from the server. This client supersedes it: the
376
+ auth methods are now generated from the contract (drift-proof), and raw
377
+ authenticated HTTP moves to [`client.api`](#clientapi--authenticated-requests-to-your-own-apis).
378
+ The package name and factory change, and every auth call uses the uniform
252
379
  `{ body / params / query }` shape.
253
380
 
254
381
  ### Setup
@@ -293,7 +420,8 @@ await client.init()
293
420
  | `client.tenant.createInvite(req)` | `client.tenant.createInvite({ params: { tenantId }, body })` ⚠️ tenantId in path |
294
421
  | `client.tenant.listMembers()` | `client.tenant.listMembers({ params: { tenantId } })` ⚠️ tenantId in path |
295
422
  | `client.tenant.getProfile()` | `client.tenant.getTenantProfile({ params: { tenantId } })` |
296
- | `client.tenant.checkPendingInvites(req)` | **removed** — no public route (internal only) |
423
+ | `client.api.get/post(...)` *(raw)* | `client.api.get/post(...)` — **unchanged surface**, but on `client.api` (the old SDK exposed these on the root client) |
424
+ | `client.tenant.checkPendingInvites(req)` | not on the typed `tenant` group (no public auth-route). If your app exposes it on its **own** API, call it via `client.api.post('<your-url>', req)`. |
297
425
 
298
426
  ### Behaviour changes you must be aware of
299
427
 
@@ -308,6 +436,14 @@ await client.init()
308
436
  / `isMfaRequired` (see [above](#login-handling-challenge--mfa)). The old client
309
437
  returned ad-hoc shapes per path.
310
438
  - **`refresh` is automatic** — remove any manual refresh calls.
439
+ - **Raw authenticated requests moved to `client.api`.** The old SDK exposed
440
+ `client.get/post/put/patch/delete` (and `getFetchGuard()`) on the root client
441
+ for hitting your own backend. These now live on [`client.api`](#clientapi--authenticated-requests-to-your-own-apis)
442
+ with the **same signatures** (`Result<T>` in, `Result<T>` out) — just change
443
+ `client.post(...)` → `client.api.post(...)`. Auth-route calls should move to
444
+ the typed groups instead.
445
+ - **`onAuthStateChanged` is unchanged** — still a config callback, same
446
+ `{ authenticated, user?, expiresAt? }` payload; wire your auto-logout there.
311
447
 
312
448
  ### Errors
313
449
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @auth-craft/client/core — scope-agnostic frontend client core.
3
+ *
4
+ * Owns the Web-Worker transport (token isolation + auto-refresh), the session
5
+ * lifecycle, the authenticated raw-HTTP escape hatch (`api`), and a `group()`
6
+ * factory that attaches a typed contract group.
7
+ *
8
+ * It deliberately imports **no** contract group. A per-scope app builds the
9
+ * client and wires only the groups it needs:
10
+ *
11
+ * import { createClientCore } from '@auth-craft/client/core'
12
+ * import { authContract } from '@auth-craft/api-contract/auth'
13
+ *
14
+ * const core = await createClientCore(config)
15
+ * const auth = core.group(authContract) // typed, drift-proof
16
+ * core.api.post('https://api.example.com/...', body) // your own backend
17
+ *
18
+ * Because no tenant/system contract is imported, that app's bundle — and its
19
+ * node_modules call surface — never contains code for other scopes.
20
+ */
21
+ import type { Endpoint } from '@auth-craft/api-contract/core';
22
+ import type { Result } from 'ts-micro-result';
23
+ import { type FetchGuardClient, type FetchGuardRequestInit, type AuthResult as TransportAuthResult } from '../transport';
24
+ import { type ContractClient } from './proxy';
25
+ /**
26
+ * Authenticated raw-HTTP escape hatch for the host app's own (non-auth-craft)
27
+ * APIs. Rides the same Worker session as the typed groups; returns a
28
+ * deserialized `Result<T>`. URLs pass through as-is.
29
+ */
30
+ export interface ApiTransport {
31
+ get<T = unknown>(url: string, options?: FetchGuardRequestInit): Promise<Result<T>>;
32
+ post<T = unknown>(url: string, body?: unknown, options?: FetchGuardRequestInit): Promise<Result<T>>;
33
+ put<T = unknown>(url: string, body?: unknown, options?: FetchGuardRequestInit): Promise<Result<T>>;
34
+ patch<T = unknown>(url: string, body?: unknown, options?: FetchGuardRequestInit): Promise<Result<T>>;
35
+ delete<T = unknown>(url: string, body?: unknown, options?: FetchGuardRequestInit): Promise<Result<T>>;
36
+ }
37
+ export interface ClientCoreConfig {
38
+ baseUrl: string;
39
+ strategy: 'cookie-auth' | 'body-auth';
40
+ allowedDomains: string[];
41
+ refreshEarlyMs?: number;
42
+ defaultHeaders?: Record<string, string>;
43
+ workerFactory?: () => Worker;
44
+ /** Auth scope → `X-Auth-Context` header. */
45
+ context?: 'customer' | 'tenant' | 'system';
46
+ /** Session state changes (login/logout/refresh/expiry). Fires after init(). */
47
+ onAuthStateChanged?: (state: TransportAuthResult) => void;
48
+ }
49
+ export declare class ClientCore {
50
+ private fg;
51
+ private readonly config;
52
+ constructor(config: ClientCoreConfig);
53
+ init(): Promise<void>;
54
+ /**
55
+ * Attach a typed client for one contract group. Call once per group the app
56
+ * uses. The group's URLs are resolved against `baseUrl`.
57
+ */
58
+ group<C extends Record<string, Endpoint>>(contract: C): ContractClient<C>;
59
+ /**
60
+ * Authenticated raw-HTTP transport for the host app's own APIs. Throws if
61
+ * accessed before `init()`.
62
+ */
63
+ get api(): ApiTransport;
64
+ buildUrl(path: string): string;
65
+ /** Raw transport handle — for advanced use (e.g. manual subscriptions). */
66
+ getTransport(): FetchGuardClient;
67
+ destroy(): void;
68
+ }
69
+ export declare function createClientCore(config: ClientCoreConfig): Promise<ClientCore>;
70
+ export type { AuthResult, ContractClient, EndpointMethod } from './proxy';
71
+ export { buildClient } from './proxy';
72
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,UAAU,IAAI,mBAAmB,EACvC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnF,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACpG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACnG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;CACvG;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,aAAa,GAAG,WAAW,CAAC;IACtC,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,OAAO,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3C,+EAA+E;IAC/E,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAC3D;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,EAAE,CAAiC;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;gBAE9B,MAAM,EAAE,gBAAgB;IAI9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B3B;;;OAGG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC;IAMzE;;;OAGG;IACH,IAAI,GAAG,IAAI,YAAY,CAqBtB;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAK9B,2EAA2E;IAC3E,YAAY,IAAI,gBAAgB;IAKhC,OAAO,IAAI,IAAI;CAIhB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAIpF;AAED,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @auth-craft/client/core — scope-agnostic frontend client core.
3
+ *
4
+ * Owns the Web-Worker transport (token isolation + auto-refresh), the session
5
+ * lifecycle, the authenticated raw-HTTP escape hatch (`api`), and a `group()`
6
+ * factory that attaches a typed contract group.
7
+ *
8
+ * It deliberately imports **no** contract group. A per-scope app builds the
9
+ * client and wires only the groups it needs:
10
+ *
11
+ * import { createClientCore } from '@auth-craft/client/core'
12
+ * import { authContract } from '@auth-craft/api-contract/auth'
13
+ *
14
+ * const core = await createClientCore(config)
15
+ * const auth = core.group(authContract) // typed, drift-proof
16
+ * core.api.post('https://api.example.com/...', body) // your own backend
17
+ *
18
+ * Because no tenant/system contract is imported, that app's bundle — and its
19
+ * node_modules call surface — never contains code for other scopes.
20
+ */
21
+ import { createClient, } from '../transport';
22
+ import { buildClient } from './proxy';
23
+ export class ClientCore {
24
+ fg = null;
25
+ config;
26
+ constructor(config) {
27
+ this.config = config;
28
+ }
29
+ async init() {
30
+ const headers = this.config.context && this.config.context !== 'customer'
31
+ ? { ...this.config.defaultHeaders, 'X-Auth-Context': this.config.context }
32
+ : this.config.defaultHeaders;
33
+ this.fg = createClient({
34
+ provider: {
35
+ type: this.config.strategy,
36
+ refreshUrl: this.buildUrl('/auth/refresh'),
37
+ loginUrl: this.buildUrl('/auth/authenticate'),
38
+ logoutUrl: this.buildUrl('/auth/logout'),
39
+ ...(headers ? { headers } : {}),
40
+ },
41
+ allowedDomains: this.config.allowedDomains,
42
+ ...(this.config.refreshEarlyMs !== undefined ? { refreshEarlyMs: this.config.refreshEarlyMs } : {}),
43
+ ...(headers ? { defaultHeaders: headers } : {}),
44
+ ...(this.config.workerFactory ? { workerFactory: this.config.workerFactory } : {}),
45
+ });
46
+ await this.fg.whenReady();
47
+ if (this.config.onAuthStateChanged) {
48
+ this.fg.onAuthStateChanged(this.config.onAuthStateChanged);
49
+ }
50
+ }
51
+ /**
52
+ * Attach a typed client for one contract group. Call once per group the app
53
+ * uses. The group's URLs are resolved against `baseUrl`.
54
+ */
55
+ group(contract) {
56
+ const fg = this.fg;
57
+ if (!fg)
58
+ throw new Error('ClientCore not initialized. Call init() first.');
59
+ return buildClient(fg, contract, (p) => this.buildUrl(p));
60
+ }
61
+ /**
62
+ * Authenticated raw-HTTP transport for the host app's own APIs. Throws if
63
+ * accessed before `init()`.
64
+ */
65
+ get api() {
66
+ const fg = this.fg;
67
+ if (!fg)
68
+ throw new Error('ClientCore not initialized. Call init() first.');
69
+ // callJson forbids method/body in options — drop them before forwarding.
70
+ const opts = (o) => {
71
+ if (!o)
72
+ return {};
73
+ const { method: _m, body: _b, ...rest } = o;
74
+ return rest;
75
+ };
76
+ return {
77
+ get: (url, options) => fg.callJson('GET', url, undefined, opts(options)),
78
+ post: (url, body, options) => fg.callJson('POST', url, body, opts(options)),
79
+ put: (url, body, options) => fg.callJson('PUT', url, body, opts(options)),
80
+ patch: (url, body, options) => fg.callJson('PATCH', url, body, opts(options)),
81
+ delete: (url, body, options) => fg.callJson('DELETE', url, body, opts(options)),
82
+ };
83
+ }
84
+ buildUrl(path) {
85
+ const p = path.startsWith('/') ? path : `/${path}`;
86
+ return `${this.config.baseUrl}${p}`;
87
+ }
88
+ /** Raw transport handle — for advanced use (e.g. manual subscriptions). */
89
+ getTransport() {
90
+ if (!this.fg)
91
+ throw new Error('ClientCore not initialized. Call init() first.');
92
+ return this.fg;
93
+ }
94
+ destroy() {
95
+ this.fg?.destroy();
96
+ this.fg = null;
97
+ }
98
+ }
99
+ export async function createClientCore(config) {
100
+ const core = new ClientCore(config);
101
+ await core.init();
102
+ return core;
103
+ }
104
+ export { buildClient } from './proxy';
105
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/core/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
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
+ * Scope-agnostic: imports only `@auth-craft/api-contract/core` (the endpoint
18
+ * type + helpers), never a specific group — so a per-scope app that only wires
19
+ * `auth` never pulls in `tenant`/`system` code through this module.
20
+ */
21
+ import type { Endpoint, PathParams } from '@auth-craft/api-contract/core';
22
+ import { type Result } from 'ts-micro-result';
23
+ import type { InferInput, InferOutput } from 'valibot';
24
+ import { type FetchGuardClient } from '../transport';
25
+ export interface AuthResult {
26
+ authenticated: boolean;
27
+ user?: unknown;
28
+ expiresAt?: number | null;
29
+ }
30
+ /** Whether an endpoint has any `:param` segments. */
31
+ type HasParams<P extends string> = PathParams<P> extends never ? false : true;
32
+ /** The `params` argument, required only when the path has params. */
33
+ type ParamsArg<P extends string> = HasParams<P> extends true ? {
34
+ params: Record<PathParams<P>, string | number>;
35
+ } : {
36
+ params?: undefined;
37
+ };
38
+ /** Body argument, present only when the endpoint declares a body schema. */
39
+ type BodyArg<E> = E extends {
40
+ body: infer S;
41
+ } ? {
42
+ body: InferInput<S extends import('valibot').GenericSchema ? S : never>;
43
+ } : {
44
+ body?: undefined;
45
+ };
46
+ /** Query argument, present only when the endpoint declares a query schema. */
47
+ type QueryArg<E> = E extends {
48
+ query: infer S;
49
+ } ? {
50
+ query?: InferInput<S extends import('valibot').GenericSchema ? S : never>;
51
+ } : {
52
+ query?: undefined;
53
+ };
54
+ /** The single call argument object for a generated method. */
55
+ type CallArg<E extends Endpoint> = ParamsArg<E['path']> & BodyArg<E> & QueryArg<E>;
56
+ /** The resolved return type of a generated method. */
57
+ type CallReturn<E extends Endpoint> = E extends {
58
+ kind: 'data';
59
+ } ? E extends {
60
+ response: infer R;
61
+ } ? Result<InferOutput<R extends import('valibot').GenericSchema ? R : never>> : Result<unknown> : E extends {
62
+ kind: 'login';
63
+ } ? E extends {
64
+ response: infer R;
65
+ } ? Result<InferOutput<R extends import('valibot').GenericSchema ? R : never>> : Result<AuthResult> : Result<AuthResult>;
66
+ /** Does this arg shape need any argument at all? */
67
+ type NeedsArg<E extends Endpoint> = HasParams<E['path']> extends true ? true : E extends {
68
+ body: unknown;
69
+ } ? true : false;
70
+ export type EndpointMethod<E extends Endpoint> = NeedsArg<E> extends true ? (arg: CallArg<E>) => Promise<CallReturn<E>> : (arg?: CallArg<E>) => Promise<CallReturn<E>>;
71
+ export type ContractClient<C extends Record<string, Endpoint>> = {
72
+ [K in keyof C]: EndpointMethod<C[K]>;
73
+ };
74
+ /**
75
+ * Build a typed client object from a contract group.
76
+ */
77
+ export declare function buildClient<C extends Record<string, Endpoint>>(transport: FetchGuardClient, contract: C, buildUrl: (path: string) => string): ContractClient<C>;
78
+ export {};
79
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/core/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,99 @@
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
+ * Scope-agnostic: imports only `@auth-craft/api-contract/core` (the endpoint
18
+ * type + helpers), never a specific group — so a per-scope app that only wires
19
+ * `auth` never pulls in `tenant`/`system` code through this module.
20
+ */
21
+ import { fillPath } from '@auth-craft/api-contract/core';
22
+ import { err, ok } from 'ts-micro-result';
23
+ import { deserializeResult } from '../transport';
24
+ /**
25
+ * Build a typed client object from a contract group.
26
+ */
27
+ export function buildClient(transport, contract, buildUrl) {
28
+ const out = {};
29
+ for (const key of Object.keys(contract)) {
30
+ const ep = contract[key];
31
+ out[key] = async (arg) => {
32
+ const path = fillPath(ep.path, arg?.params);
33
+ const url = buildUrl(path) + buildQueryString(arg?.query);
34
+ const body = arg?.body;
35
+ const requiresAuth = ep.public !== true;
36
+ switch (ep.kind) {
37
+ case 'data':
38
+ return transport.callJson(ep.method, url, body, { requiresAuth });
39
+ case 'login':
40
+ return dispatchLogin(transport, body, url);
41
+ case 'logout':
42
+ return transport.logout(body);
43
+ case 'exchange':
44
+ return transport.exchangeToken(url, {
45
+ ...(body !== undefined ? { payload: body } : {}),
46
+ });
47
+ default:
48
+ return err({ code: 'UNKNOWN_KIND', message: `Unknown endpoint kind for '${key}'.` });
49
+ }
50
+ };
51
+ }
52
+ return out;
53
+ }
54
+ /**
55
+ * Dispatch a login-kind endpoint and normalize the outcome.
56
+ *
57
+ * FetchGuard's login() resolves the AuthResult when a session is established. A
58
+ * 2-step login (credential challenge / MFA required) comes back as a non-error
59
+ * HTTP body that FetchGuard surfaces via `meta.params.body`. We deserialize that
60
+ * envelope so the caller gets the discriminated login result (tokens | challenge
61
+ * | mfa) instead of an opaque "not authenticated" — matching the old SDK's
62
+ * behaviour, but in ONE place instead of per method.
63
+ */
64
+ async function dispatchLogin(transport, body, url) {
65
+ const result = await transport.login(body, url);
66
+ // Session established → return the tokens/auth-state shape.
67
+ if (result.ok && result.data.authenticated) {
68
+ return ok({
69
+ accessToken: '',
70
+ expiresAt: result.data.expiresAt ?? 0,
71
+ });
72
+ }
73
+ // Not authenticated: the backend body may carry a challenge / MFA requirement
74
+ // (a legitimate 2-step step), or a real error. Deserialize and forward it.
75
+ if (!result.ok) {
76
+ const params = result.meta?.params;
77
+ if (params?.body) {
78
+ const parsed = deserializeResult(params.body);
79
+ // Forward whatever the backend said — challenge/mfa (ok) or error (err).
80
+ return parsed;
81
+ }
82
+ return err(result.errors);
83
+ }
84
+ // ok but not authenticated and no body to interpret — surface a generic error.
85
+ return err({ code: 'LOGIN_FAILED', message: 'Authentication did not establish a session' });
86
+ }
87
+ /** Serialize a query object to a `?a=1&b=2` string (skips undefined). */
88
+ function buildQueryString(query) {
89
+ if (!query)
90
+ return '';
91
+ const params = new URLSearchParams();
92
+ for (const [k, v] of Object.entries(query)) {
93
+ if (v !== undefined && v !== null)
94
+ params.set(k, String(v));
95
+ }
96
+ const qs = params.toString();
97
+ return qs ? `?${qs}` : '';
98
+ }
99
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/core/proxy.ts"],"names":[],"mappings":"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"}