@zerotal/client 1.6.3 → 1.7.2

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/CHANGELOG.md CHANGED
@@ -8,6 +8,35 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ### Fixed
12
+
13
+ - **The documented import could not be bundled for a browser.** `import { Socket } from
14
+ "@zerotal/client"` — the line the broadcasting guide taught — failed with `Browser build cannot
15
+ import() Bun builtin: "bun"`. The root entry also exports `ClientProvider`, which extends core's
16
+ `ServiceProvider`, which reaches `CommandRunner`, which reaches the built-in CLI commands, one of
17
+ which does `await import("bun")`. That is a *resolution*-time error, so tree-shaking never got the
18
+ chance to drop the half nobody in a browser wanted: one server-only export made the whole package
19
+ unusable in the environment it exists for.
20
+
21
+ `@zerotal/client` now resolves to a browser-safe entry under the `browser` export condition —
22
+ `Socket`, `ApiClient`, `CircuitBreaker`, `createApiClient` and their types, and none of the
23
+ server-side exports. Bun and Node consumers still get the full barrel, `ClientProvider` included,
24
+ so nothing about registering the package changes. `@zerotal/client/Socket` still works and is
25
+ still the leanest import when `Socket` is all you need; `ClientProvider` also gained an explicit
26
+ `./provider` subpath.
27
+
28
+ Two supporting changes: `createApiClient` moved out of `index.ts` into its own module, because a
29
+ browser entry re-exporting it from the barrel pulls the barrel back in — the first attempt at this
30
+ fix did exactly that and bundled straight back into the same error. And the modules a browser can
31
+ reach now import from narrow core subpaths (`@zerotal/core/errors`, `@zerotal/core/helpers`)
32
+ rather than the root, because importing *anything at all* from core's root is enough to poison a
33
+ browser bundle.
34
+
35
+ Guarded by a test that runs `Bun.build` with `target: "browser"` — no unit test can see this,
36
+ since every module imports fine under Bun — plus one that holds the browser entry in step with
37
+ the barrel, since two entry points means two places to remember to add an export to.
38
+
39
+
11
40
  ## [1.0.3] — 2026-08-07
12
41
 
13
42
  ### Changed
package/api-surface.md ADDED
@@ -0,0 +1,367 @@
1
+ # @zerotal/client — public API surface
2
+
3
+ <!-- AUTO-GENERATED by scripts/api-surface.ts. Do not edit by hand.
4
+ Run `bun run api:surface` to regenerate after an intentional API change. -->
5
+
6
+ ## . `(./src/index.ts)`
7
+
8
+ class ApiClient = {
9
+ new <Routes extends ApiRouteMap>(config?: ApiClientConfig): ApiClient<Routes>
10
+ delete: <Path extends PathsFor<Routes, 'DELETE'>>(path: Path, params?: ParamRecord<Path & string>, options?: RequestOptions) => Promise<ResponseOf<Routes, 'DELETE', Path & string>>
11
+ get: <Path extends PathsFor<Routes, 'GET'>>(path: Path, params?: ParamRecord<Path & string>, options?: GetOptions<QueryOf<Routes, 'GET', Path & string>>) => Promise<ResponseOf<Routes, 'GET', Path & string>>
12
+ patch: <Path extends PathsFor<Routes, 'PATCH'>>(path: Path, body?: BodyOf<Routes, 'PATCH', Path & string>, options?: MutationOptions<QueryOf<Routes, 'PATCH', Path & string>>) => Promise<ResponseOf<Routes, 'PATCH', Path & string>>
13
+ post: <Path extends PathsFor<Routes, 'POST'>>(path: Path, body?: BodyOf<Routes, 'POST', Path & string>, options?: MutationOptions<QueryOf<Routes, 'POST', Path & string>>) => Promise<ResponseOf<Routes, 'POST', Path & string>>
14
+ put: <Path extends PathsFor<Routes, 'PUT'>>(path: Path, body?: BodyOf<Routes, 'PUT', Path & string>, options?: MutationOptions<QueryOf<Routes, 'PUT', Path & string>>) => Promise<ResponseOf<Routes, 'PUT', Path & string>>
15
+ setToken: (token: TokenSource | null) => void
16
+ }
17
+
18
+ class ApiClientError = {
19
+ new (status: number, statusText: string, body: string, headers?: Headers): ApiClientError
20
+ readonly body: string
21
+ readonly code: string
22
+ readonly context?: Record<string, unknown> | undefined
23
+ readonly headers: Headers | undefined
24
+ readonly status: number
25
+ readonly statusText: string
26
+ retryAfterMs: number | null
27
+ }
28
+
29
+ class Channel = {
30
+ new (name: string, _socket: Socket): Channel
31
+ _dispatch: (frame: ServerFrame) => void
32
+ error: (cb: Listener) => Channel
33
+ listen: (event: string, cb: Listener) => Channel
34
+ readonly name: string
35
+ stopListening: (event: string, cb?: Listener) => Channel
36
+ subscribed: (cb: Listener) => Channel
37
+ unsubscribe: () => void
38
+ }
39
+
40
+ class CircuitBreaker = {
41
+ new (options?: CircuitBreakerOptions): CircuitBreaker
42
+ call: <T>(fn: () => Promise<T>) => Promise<T>
43
+ failures: number
44
+ reset: () => void
45
+ state: CircuitState
46
+ }
47
+
48
+ class CircuitBreakerOpenError = {
49
+ new (): CircuitBreakerOpenError
50
+ readonly code: string
51
+ readonly context?: Record<string, unknown> | undefined
52
+ readonly status: number
53
+ }
54
+
55
+ class ClientProvider = {
56
+ new (app: Application): ClientProvider
57
+ static dependsOn?: (new (app: Application) => ServiceProvider)[]
58
+ static environments: AppEnvironment[]
59
+ static priority?: number
60
+ static provides: readonly ['client']
61
+ devProcesses: () => DevProcessDefinition[]
62
+ doctorChecks: () => DoctorCheck[]
63
+ onBooted: () => Promise<void>
64
+ onBooting: () => Promise<void>
65
+ onRegister: () => void
66
+ onRequestProcessed: (_ctx: HttpContext) => Promise<void>
67
+ onRequestReceived: (_ctx: HttpContext) => Promise<void>
68
+ onResponseSent: (_ctx: HttpContext) => Promise<void>
69
+ onStarted: () => Promise<void>
70
+ onStarting: () => Promise<void>
71
+ onStopped: () => Promise<void>
72
+ onStopping: () => Promise<void>
73
+ replContext: () => Record<string, unknown>
74
+ }
75
+
76
+ class PresenceChannel = {
77
+ new (name: string, _socket: Socket): PresenceChannel
78
+ _dispatch: (frame: ServerFrame) => void
79
+ error: (cb: Listener) => PresenceChannel
80
+ here: (cb: (members: PresenceMember[]) => void) => PresenceChannel
81
+ joining: (cb: (member: PresenceMember) => void) => PresenceChannel
82
+ leaving: (cb: (member: PresenceMember) => void) => PresenceChannel
83
+ listen: (event: string, cb: Listener) => PresenceChannel
84
+ readonly name: string
85
+ stopListening: (event: string, cb?: Listener) => PresenceChannel
86
+ subscribed: (cb: Listener) => PresenceChannel
87
+ unsubscribe: () => void
88
+ }
89
+
90
+ class Socket = {
91
+ new (options?: SocketOptions): Socket
92
+ channel: (name: string) => Channel
93
+ channels: () => Channel[]
94
+ connect: () => void
95
+ disconnect: () => void
96
+ join: (name: string) => PresenceChannel
97
+ leave: (name: string) => void
98
+ leaveChannel: (wireName: string) => void
99
+ on: (state: SocketState, cb: (detail?: unknown) => void) => () => void
100
+ presence: (name: string) => PresenceChannel
101
+ private: (name: string) => Channel
102
+ socketId: () => string | null
103
+ state: SocketState
104
+ }
105
+
106
+ class ValidationError = {
107
+ new (statusText: string, body: string, errors: Record<string, string[]>, validationMessage: string, headers?: Headers): ValidationError
108
+ all: () => Record<string, string[]>
109
+ fields: () => string[]
110
+ first: (field: string) => string | undefined
111
+ has: (field: string) => boolean
112
+ readonly body: string
113
+ readonly code: string
114
+ readonly context?: Record<string, unknown> | undefined
115
+ readonly errors: Record<string, string[]>
116
+ readonly headers: Headers | undefined
117
+ readonly status: number
118
+ readonly statusText: string
119
+ readonly validationMessage: string
120
+ retryAfterMs: number | null
121
+ }
122
+
123
+ const Client = ApiClient<Partial<Record<`GET ${string}` | `POST ${string}` | `PUT ${string}` | `PATCH ${string}` | `DELETE ${string}`, RouteShape>>>
124
+
125
+ function ClientConfig = (options?: Partial<ClientConfigShape>) => Partial<ClientConfigShape>
126
+
127
+ function createApiClient = <Routes extends ApiRouteMap>(config?: ApiClientConfig) => ApiClient<Routes>
128
+
129
+ interface ApiClientConfig = {
130
+ baseUrl?: string
131
+ circuitBreaker?: CircuitBreaker | CircuitBreakerOptions
132
+ csrf?: boolean | { cookie?: string; header?: string;}
133
+ headers?: Record<string, string>
134
+ onError?: (error: ApiClientError) => void | Promise<void>
135
+ onForbidden?: (error: ApiClientError) => void | Promise<void>
136
+ onRequest?: RequestInterceptor | RequestInterceptor[]
137
+ onResponse?: ResponseInterceptor | ResponseInterceptor[]
138
+ onUnauthorized?: (error: ApiClientError, retry: (headerOverrides?: Record<string, string>) => Promise<unknown>) => Promise<unknown> | void
139
+ retry?: number | RetryOptions
140
+ timeout?: number
141
+ token?: TokenSource
142
+ withCredentials?: boolean
143
+ }
144
+
145
+ interface CircuitBreakerOptions = {
146
+ isFailure?: (error: unknown) => boolean
147
+ resetTimeout?: number
148
+ threshold?: number
149
+ }
150
+
151
+ interface ClientConfigShape = {
152
+ baseUrl?: string
153
+ circuitBreaker?: CircuitBreaker | CircuitBreakerOptions
154
+ csrf?: boolean | { cookie?: string; header?: string;}
155
+ headers?: Record<string, string>
156
+ onError?: (error: ApiClientError) => void | Promise<void>
157
+ onForbidden?: (error: ApiClientError) => void | Promise<void>
158
+ onRequest?: RequestInterceptor | RequestInterceptor[]
159
+ onResponse?: ResponseInterceptor | ResponseInterceptor[]
160
+ onUnauthorized?: (error: ApiClientError, retry: (headerOverrides?: Record<string, string>) => Promise<unknown>) => Promise<unknown> | void
161
+ retry?: number | RetryOptions
162
+ timeout?: number
163
+ token?: TokenSource
164
+ withCredentials?: boolean
165
+ }
166
+
167
+ interface GetOptions = {
168
+ headers?: Record<string, string>
169
+ init?: Omit<RequestInit, 'method' | 'headers' | 'body'>
170
+ meta?: (meta: ResponseMeta) => void
171
+ query?: Q
172
+ responseType?: 'text' | 'json' | 'auto' | 'blob' | 'arrayBuffer'
173
+ retry?: number | false | RetryOptions
174
+ signal?: AbortSignal
175
+ timeout?: number
176
+ }
177
+
178
+ interface MutationOptions = {
179
+ headers?: Record<string, string>
180
+ init?: Omit<RequestInit, 'method' | 'headers' | 'body'>
181
+ meta?: (meta: ResponseMeta) => void
182
+ params?: Record<string, string | number>
183
+ query?: Q
184
+ responseType?: 'text' | 'json' | 'auto' | 'blob' | 'arrayBuffer'
185
+ retry?: number | false | RetryOptions
186
+ signal?: AbortSignal
187
+ timeout?: number
188
+ }
189
+
190
+ interface PresenceMember = {
191
+ id: string | number
192
+ info: Record<string, unknown>
193
+ }
194
+
195
+ interface RequestConfig = {
196
+ body: BodyInit | undefined
197
+ headers: Record<string, string>
198
+ method: string
199
+ url: string
200
+ }
201
+
202
+ interface RequestOptions = {
203
+ headers?: Record<string, string>
204
+ init?: Omit<RequestInit, 'method' | 'headers' | 'body'>
205
+ meta?: (meta: ResponseMeta) => void
206
+ responseType?: 'text' | 'json' | 'auto' | 'blob' | 'arrayBuffer'
207
+ retry?: number | false | RetryOptions
208
+ signal?: AbortSignal
209
+ timeout?: number
210
+ }
211
+
212
+ interface ResponseContext = {
213
+ data: unknown
214
+ headers: Headers
215
+ request: RequestConfig
216
+ status: number
217
+ }
218
+
219
+ interface ResponseMeta = {
220
+ headers: Headers
221
+ status: number
222
+ }
223
+
224
+ interface RetryOptions = {
225
+ attempts?: number
226
+ backoff?: (attempt: number, retryAfterMs: number | null) => number
227
+ methods?: string[]
228
+ respectRetryAfter?: boolean
229
+ statuses?: number[]
230
+ }
231
+
232
+ interface RouteShape = {
233
+ body?: Record<string, unknown>
234
+ params?: Record<string, string | number>
235
+ query?: Record<string, string | number | boolean | undefined>
236
+ response?: unknown
237
+ }
238
+
239
+ interface SocketLike = {
240
+ close: (code?: number, reason?: string) => void
241
+ onclose: ((ev: { code?: number; reason?: string;}) => void) | null
242
+ onerror: ((ev: unknown) => void) | null
243
+ onmessage: ((ev: { data: unknown;}) => void) | null
244
+ onopen: ((ev: unknown) => void) | null
245
+ readyState: number
246
+ send: (data: string) => void
247
+ }
248
+
249
+ interface SocketOptions = {
250
+ WebSocket?: new (url: string) => SocketLike
251
+ auth?: { params?: Record<string, string>; headers?: Record<string, string>;}
252
+ authEndpoint?: string | false
253
+ autoConnect?: boolean
254
+ fetch?: typeof fetch
255
+ host?: string
256
+ maxReconnectDelay?: number
257
+ path?: string
258
+ pingInterval?: number
259
+ port?: string | number
260
+ reconnect?: boolean
261
+ reconnectDelay?: number
262
+ scheme?: 'ws' | 'wss'
263
+ url?: string
264
+ }
265
+
266
+ type ApiRouteMap = { [x: `GET ${string}`]: RouteShape | undefined; [x: `POST ${string}`]: RouteShape | undefined; [x: `PUT ${string}`]: RouteShape | undefined; [x: `PATCH ${string}`]: RouteShape | undefined; [x: `DELETE ${string}`]: RouteShape | undefined;}
267
+
268
+ type BodyOf = `${Method} ${Path}` extends keyof Routes ? Routes[`${Method} ${Path}`] extends { body: infer B;} ? B : Record<string, unknown> : Record<string, unknown>
269
+
270
+ type CircuitState = 'closed' | 'open' | 'half-open'
271
+
272
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
273
+
274
+ type ParamRecord = [PathParams<Path>] extends [never] ? undefined : { [K in PathParams<Path>]: string | number; }
275
+
276
+ type PathParams = Path extends `${string}{${infer Param}}${infer Tail}` ? Param | PathParams<Tail> : never
277
+
278
+ type PathsFor = { [K in keyof Routes & string]: K extends `${Method} ${infer Path}` ? Path : never; }[keyof Routes & string]
279
+
280
+ type QueryOf = `${Method} ${Path}` extends keyof Routes ? Routes[`${Method} ${Path}`] extends { query: infer Q;} ? Q : Record<string, string | number | boolean | undefined> : Record<string, string | number | boolean | undefined>
281
+
282
+ type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>
283
+
284
+ type ResponseInterceptor = (ctx: ResponseContext) => ResponseContext | Promise<ResponseContext>
285
+
286
+ type ResponseOf = `${Method} ${Path}` extends keyof Routes ? Routes[`${Method} ${Path}`] extends { response: infer R;} ? R : unknown : unknown
287
+
288
+ type SocketState = 'error' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
289
+
290
+ type TokenSource = string | (() => string | null | undefined | Promise<string | null | undefined>)
291
+
292
+ ## ./Socket `(./src/Socket.ts)`
293
+
294
+ class Channel = {
295
+ new (name: string, _socket: Socket): Channel
296
+ _dispatch: (frame: ServerFrame) => void
297
+ error: (cb: Listener) => Channel
298
+ listen: (event: string, cb: Listener) => Channel
299
+ readonly name: string
300
+ stopListening: (event: string, cb?: Listener) => Channel
301
+ subscribed: (cb: Listener) => Channel
302
+ unsubscribe: () => void
303
+ }
304
+
305
+ class PresenceChannel = {
306
+ new (name: string, _socket: Socket): PresenceChannel
307
+ _dispatch: (frame: ServerFrame) => void
308
+ error: (cb: Listener) => PresenceChannel
309
+ here: (cb: (members: PresenceMember[]) => void) => PresenceChannel
310
+ joining: (cb: (member: PresenceMember) => void) => PresenceChannel
311
+ leaving: (cb: (member: PresenceMember) => void) => PresenceChannel
312
+ listen: (event: string, cb: Listener) => PresenceChannel
313
+ readonly name: string
314
+ stopListening: (event: string, cb?: Listener) => PresenceChannel
315
+ subscribed: (cb: Listener) => PresenceChannel
316
+ unsubscribe: () => void
317
+ }
318
+
319
+ class Socket = {
320
+ new (options?: SocketOptions): Socket
321
+ channel: (name: string) => Channel
322
+ channels: () => Channel[]
323
+ connect: () => void
324
+ disconnect: () => void
325
+ join: (name: string) => PresenceChannel
326
+ leave: (name: string) => void
327
+ leaveChannel: (wireName: string) => void
328
+ on: (state: SocketState, cb: (detail?: unknown) => void) => () => void
329
+ presence: (name: string) => PresenceChannel
330
+ private: (name: string) => Channel
331
+ socketId: () => string | null
332
+ state: SocketState
333
+ }
334
+
335
+ interface PresenceMember = {
336
+ id: string | number
337
+ info: Record<string, unknown>
338
+ }
339
+
340
+ interface SocketLike = {
341
+ close: (code?: number, reason?: string) => void
342
+ onclose: ((ev: { code?: number; reason?: string;}) => void) | null
343
+ onerror: ((ev: unknown) => void) | null
344
+ onmessage: ((ev: { data: unknown;}) => void) | null
345
+ onopen: ((ev: unknown) => void) | null
346
+ readyState: number
347
+ send: (data: string) => void
348
+ }
349
+
350
+ interface SocketOptions = {
351
+ WebSocket?: new (url: string) => SocketLike
352
+ auth?: { params?: Record<string, string>; headers?: Record<string, string>;}
353
+ authEndpoint?: string | false
354
+ autoConnect?: boolean
355
+ fetch?: typeof fetch
356
+ host?: string
357
+ maxReconnectDelay?: number
358
+ path?: string
359
+ pingInterval?: number
360
+ port?: string | number
361
+ reconnect?: boolean
362
+ reconnectDelay?: number
363
+ scheme?: 'ws' | 'wss'
364
+ url?: string
365
+ }
366
+
367
+ type SocketState = 'error' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/client",
3
- "version": "1.6.3",
3
+ "version": "1.7.2",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -8,11 +8,16 @@
8
8
  "main": "./src/index.ts",
9
9
  "types": "./src/index.ts",
10
10
  "exports": {
11
- ".": "./src/index.ts",
12
- "./Socket": "./src/Socket.ts"
11
+ ".": {
12
+ "browser": "./src/browser.ts",
13
+ "default": "./src/index.ts"
14
+ },
15
+ "./Socket": "./src/Socket.ts",
16
+ "./provider": "./src/provider/ClientProvider.ts"
13
17
  },
14
18
  "files": [
15
19
  "CHANGELOG.md",
20
+ "api-surface.md",
16
21
  "src",
17
22
  "!src/**/*.test.ts",
18
23
  "!src/**/*.test.tsx",
@@ -30,7 +35,7 @@
30
35
  "typecheck": "tsc --noEmit"
31
36
  },
32
37
  "dependencies": {
33
- "@zerotal/core": "1.6.3"
38
+ "@zerotal/core": "1.7.2"
34
39
  },
35
40
  "devDependencies": {
36
41
  "typescript": "^5.8.0"
@@ -1,4 +1,4 @@
1
- import { ZerotalError } from "@zerotal/core";
1
+ import { ZerotalError } from "@zerotal/core/errors";
2
2
  import { ApiClientError } from "./ApiClient.ts";
3
3
 
4
4
  // ── Types ─────────────────────────────────────────────────────────────────────
package/src/Socket.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // broadcasting protocol (the `ws` / `redis` drivers) and exposes a familiar
5
5
  // realtime-client API — the same `channel()/private()/join()/leave()` +
6
6
  // `listen()/here()/joining()/leaving()` surface — so it works with `@zerotal/flow`'s
7
- // `@on('echo:…')` listeners with zero external dependencies.
7
+ // `@on('socket:…')` listeners with zero external dependencies.
8
8
  //
9
9
  // import { Socket } from "@zerotal/client";
10
10
  //
@@ -335,14 +335,14 @@ export class Socket {
335
335
  return this._subscribe(wire, () => new PresenceChannel(wire, this)) as PresenceChannel;
336
336
  }
337
337
 
338
- /** Echo alias for {@link presence} (`window.Echo.join(...)`). */
338
+ /** Alias for {@link presence} (`window.Socket.join(...)`). */
339
339
  join(name: string): PresenceChannel {
340
340
  return this.presence(name);
341
341
  }
342
342
 
343
343
  /**
344
344
  * Leave a channel by its base name — unsubscribes the public, `private-`, and `presence-`
345
- * variants (Echo's `leave()` semantics).
345
+ * variants, which is what `leave()` means here.
346
346
  */
347
347
  leave(name: string): void {
348
348
  this.leaveChannel(name);
package/src/browser.ts ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The browser-safe half of this package.
3
+ *
4
+ * Resolved for `import … from "@zerotal/client"` under the `browser` export
5
+ * condition, so a bundler targeting a browser gets this file and a Bun/Node
6
+ * import gets `index.ts` unchanged. Nobody has to know: the documented import
7
+ * is the same either way.
8
+ *
9
+ * What it leaves out is `ClientProvider` and `ClientConfig` — the two things
10
+ * here that are genuinely server-side. `ClientProvider` extends core's
11
+ * `ServiceProvider`, which reaches `CommandRunner`, which reaches the built-in
12
+ * CLI commands, one of which does `await import("bun")`. That is a hard error in
13
+ * a browser build and it happens during *resolution*, so tree-shaking never gets
14
+ * a chance to drop the unused half. The whole package became unbundleable
15
+ * because of one export nobody in a browser wanted.
16
+ *
17
+ * The rule this encodes: `@zerotal/core`'s root entry is server-only. A module
18
+ * that can run in a browser imports from a narrow subpath — `@zerotal/core/errors`,
19
+ * `@zerotal/core/helpers` — and never from the root.
20
+ */
21
+ export type {
22
+ ApiRouteMap,
23
+ RouteShape,
24
+ HttpMethod,
25
+ PathParams,
26
+ ParamRecord,
27
+ PathsFor,
28
+ ResponseOf,
29
+ BodyOf,
30
+ QueryOf,
31
+ } from "./types.ts";
32
+
33
+ export { ApiClient, ApiClientError, ValidationError } from "./ApiClient.ts";
34
+ export type {
35
+ ApiClientConfig,
36
+ RequestConfig,
37
+ RequestInterceptor,
38
+ ResponseContext,
39
+ ResponseInterceptor,
40
+ ResponseMeta,
41
+ RequestOptions,
42
+ GetOptions,
43
+ MutationOptions,
44
+ TokenSource,
45
+ RetryOptions,
46
+ } from "./ApiClient.ts";
47
+
48
+ export { CircuitBreaker, CircuitBreakerOpenError } from "./CircuitBreaker.ts";
49
+ export type { CircuitBreakerOptions, CircuitState } from "./CircuitBreaker.ts";
50
+
51
+ export { Socket, Channel, PresenceChannel } from "./Socket.ts";
52
+ export type { SocketOptions, SocketState, SocketLike, PresenceMember } from "./Socket.ts";
53
+
54
+ export { createApiClient } from "./createApiClient.ts";
package/src/config.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { deepMerge } from "@zerotal/core";
1
+ import { deepMerge } from "@zerotal/core/helpers";
2
2
  import type { ApiClientConfig } from "./ApiClient.ts";
3
3
 
4
4
  export interface ClientConfigShape extends ApiClientConfig {
@@ -0,0 +1,36 @@
1
+ // ── Factory function ──────────────────────────────────────────────────────────
2
+ //
3
+ // Its own module rather than a member of `index.ts`, so the browser entry can
4
+ // re-export it without pulling the server half of the barrel back in. That is
5
+ // not hypothetical tidiness: `browser.ts` originally took it from `index.ts`,
6
+ // which re-imported `ClientProvider` and put the bundle straight back into the
7
+ // error this split exists to remove.
8
+
9
+ import { ApiClient } from "./ApiClient.ts";
10
+ import type { ApiRouteMap } from "./types.ts";
11
+ import type { ApiClientConfig } from "./ApiClient.ts";
12
+
13
+ /**
14
+ * Create a type-safe API client bound to your route map.
15
+ *
16
+ * @example
17
+ * // frontend/api.ts
18
+ * import { createApiClient } from '@zerotal/client';
19
+ * import type { Routes } from './routes';
20
+ *
21
+ * export const api = createApiClient<Routes>({
22
+ * baseUrl: '/api',
23
+ * headers: { 'X-Requested-With': 'XMLHttpRequest' },
24
+ * });
25
+ *
26
+ * // Anywhere in your UI:
27
+ * const user = await api.get('/users/{id}', { id: 1 });
28
+ * // ^? UserResource (inferred from Routes)
29
+ *
30
+ * const newUser = await api.post('/users', { name: 'Alice', email: 'alice@ex.com' });
31
+ */
32
+ export function createApiClient<Routes extends ApiRouteMap>(
33
+ config: ApiClientConfig = {},
34
+ ): ApiClient<Routes> {
35
+ return new ApiClient<Routes>(config);
36
+ }
package/src/errors.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ZerotalError } from "@zerotal/core";
1
+ import { ZerotalError } from "@zerotal/core/errors";
2
2
 
3
3
  /**
4
4
  * Thrown for any non-2xx HTTP response.
package/src/index.ts CHANGED
@@ -36,33 +36,4 @@ export { ClientProvider, Client } from "./provider/ClientProvider.ts";
36
36
  export { ClientConfig } from "./config.ts";
37
37
  export type { ClientConfigShape } from "./config.ts";
38
38
 
39
- // ── Factory function ──────────────────────────────────────────────────────────
40
-
41
- import { ApiClient } from "./ApiClient.ts";
42
- import type { ApiRouteMap } from "./types.ts";
43
- import type { ApiClientConfig } from "./ApiClient.ts";
44
-
45
- /**
46
- * Create a type-safe API client bound to your route map.
47
- *
48
- * @example
49
- * // frontend/api.ts
50
- * import { createApiClient } from '@zerotal/client';
51
- * import type { Routes } from './routes';
52
- *
53
- * export const api = createApiClient<Routes>({
54
- * baseUrl: '/api',
55
- * headers: { 'X-Requested-With': 'XMLHttpRequest' },
56
- * });
57
- *
58
- * // Anywhere in your UI:
59
- * const user = await api.get('/users/{id}', { id: 1 });
60
- * // ^? UserResource (inferred from Routes)
61
- *
62
- * const newUser = await api.post('/users', { name: 'Alice', email: 'alice@ex.com' });
63
- */
64
- export function createApiClient<Routes extends ApiRouteMap>(
65
- config: ApiClientConfig = {},
66
- ): ApiClient<Routes> {
67
- return new ApiClient<Routes>(config);
68
- }
39
+ export { createApiClient } from "./createApiClient.ts";