@nilejs/client 1.0.0 → 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nile-js
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -4,7 +4,7 @@ A type-safe frontend client for [Nile Js](https://nile-js.github.io/nile/) backe
4
4
 
5
5
  ## Features
6
6
 
7
- - **Type-safe:** Generates and consumes types from your Nile backend for full compile-time payload validation.
7
+ - **Type-safe:** Consumes shared or CLI-generated backend types for service, action and payload checking.
8
8
  - **Graceful Error Handling:** Returns the same Result pattern as the Nile Js backend (`isOk`, `isErr`, `value`, `error`) instead of throwing exceptions.
9
9
  - **Small:** HTTP calls use the platform `fetch`. The only runtime dependency is `socket.io-client`, for the socket client.
10
10
  - **Flexible:** Supports custom headers, credentials, and timeouts per request.
@@ -116,7 +116,97 @@ Check `isErr` first, then read `error` or `value`. The older `data` field is sti
116
116
  - `explore(params)`: Discover services/actions using wildcards.
117
117
  - `schema(params)`: Fetch action schemas as JSON Schema.
118
118
 
119
- Each method accepts an optional `timeout` and any standard fetch `headers` in its parameters.
119
+ Pass per-call settings in `fetchOptions`. Headers merge with the client’s configured headers; a per-call value overrides the same header regardless of case. Both `Headers` instances and header tuples are supported. The timeout covers reading the response body as well as connecting; an optional `signal` can cancel the call.
120
+
121
+ ```ts
122
+ const controller = new AbortController();
123
+ const result = await nile.invoke({
124
+ service: "tasks",
125
+ action: "create",
126
+ payload: { title: "Ship the next feature" },
127
+ fetchOptions: {
128
+ headers: { Authorization: "Bearer your-session-token" },
129
+ timeout: 5000,
130
+ signal: controller.signal,
131
+ },
132
+ });
133
+ ```
134
+
135
+ ## Nile 1.0.3 responses
136
+
137
+ HTTP responses contain exactly `status`, `message` and `data`. The client maps those into its existing Result API; it preserves custom messages, Zod’s prettified validation message, and generated diagnostic references. The default success message is `Action completed.`
138
+
139
+ For a validation failure, `result.error` is the readable message and `result.details` contains `{ errorCode, issues }`. `ValidationFailureData` is exported for applications that narrow this unknown failure data. Other failures can carry different details, so check the shape before using it. Diagnostic failures may include `Error Code: <unique-code> - <message>` in the message. Treat the code as a reference to share with your backend logs; do not use it as a fixed error category.
140
+
141
+ ## Topics over Socket.IO
142
+
143
+ The same client sends and receives typed topic messages through `nile.socket`. Actions stay on HTTP. The socket connects on its first `send` or `receive`, so an app that never uses topics never opens one. Topic definitions and their `canReceive` and `canSend` checks live on the backend.
144
+
145
+ ```ts
146
+ import { createNileClient } from "@nilejs/client";
147
+ import type { ServicePayloads } from "./generated/types";
148
+
149
+ type Topics = {
150
+ "tasks.changed": {
151
+ data: { taskId: string; status: string };
152
+ scope: { projectId: string };
153
+ };
154
+ };
155
+
156
+ const nile = createNileClient<ServicePayloads, Topics>({
157
+ baseUrl: "http://localhost:8000/api",
158
+ socket: {
159
+ // url: "https://realtime.example.com", when sockets run on another server
160
+ auth: () => {
161
+ return { token: "your-session-token" };
162
+ },
163
+ onReconnect: () => reloadTasks(),
164
+ },
165
+ });
166
+
167
+ const received = await nile.socket.receive({
168
+ topic: "tasks.changed",
169
+ scope: { projectId: "project-1" },
170
+ onMessage: (message) => {
171
+ console.log(message.data.taskId, message.data.status, message.from?.userId);
172
+ },
173
+ });
174
+
175
+ if (received.isErr) {
176
+ console.error(received.error);
177
+ }
178
+
179
+ const sent = await nile.socket.send({
180
+ topic: "tasks.changed",
181
+ scope: { projectId: "project-1" },
182
+ data: { taskId: "task-123", status: "done" },
183
+ });
184
+
185
+ if (received.isOk) {
186
+ await received.value.unsubscribe();
187
+ }
188
+ nile.socket.close();
189
+ ```
190
+
191
+ `send`, `receive` and `unsubscribe` return a Result: a topic may not exist, the scope may be wrong or the server may refuse access. Subscriptions are restored after a reconnect, and a refused one is reported to `socket.onError`. Messages sent while disconnected are not buffered or retried. `socket.url` defaults to the origin of `baseUrl`; set `socket.path` when the server uses a custom Socket.IO path. See [WebSockets](https://nile-js.github.io/nile/guide/features/websockets/) for server setup.
192
+
193
+ ## Uploads
194
+
195
+ ```ts
196
+ const result = await nile.upload({
197
+ service: "documents",
198
+ action: "upload",
199
+ files: { document: file },
200
+ fields: { title: "Invoice" },
201
+ fetchOptions: { timeout: 60000 },
202
+ });
203
+ ```
204
+
205
+ Multipart calls share the same cancellation, deadline and Result behavior as JSON calls. Content-Type is set by the runtime so the multipart boundary is correct.
206
+
207
+ ## Compatibility
208
+
209
+ Version 1.1.0 supports Nile 1.1.x and reworks the socket API introduced in 1.0.x. Topics moved onto `createNileClient` as `nile.socket`, and `createNileSocketClient` and socket `invoke` were removed; the old helper is still exported as a stub that throws with the migration steps. Call actions with `nile.invoke` over HTTP. The HTTP client API is unchanged, so only 1.0.x socket users need to migrate.
120
210
 
121
211
  ## License
122
212
 
package/dist/index.cjs CHANGED
@@ -71,74 +71,63 @@ function safeTry(fn) {
71
71
  }
72
72
  //#endregion
73
73
  //#region src/request.ts
74
- /**
75
- * Handles JSON communication with the Nile /services endpoint.
76
- * Uses fetch + safeTry for crash safety and returns a Result that never throws.
77
- */
78
- async function sendNileRequest(config, intent, service, action, payload, options = {}) {
79
- const { timeout = config.timeout ?? 3e4, ...fetchOptions } = options;
74
+ /** Send one action call; the deadline covers both fetch and reading its JSON body. */
75
+ async function sendRequest(config, body, multipart, options) {
76
+ const { timeout = config.timeout ?? 3e4, headers, signal, ...fetchOptions } = options;
77
+ if (!Number.isSafeInteger(timeout) || timeout < 1 || timeout > 2147483647) return clientErr("Action timeout must be a positive integer up to 2147483647 milliseconds.");
80
78
  const controller = new AbortController();
81
- const timeoutId = setTimeout(() => controller.abort(), timeout);
82
- const requestResult = await safeTry(async () => {
83
- const response = await fetch(`${config.baseUrl}/services`, {
84
- method: "POST",
85
- headers: {
86
- "Content-Type": "application/json",
87
- ...config.headers,
88
- ...fetchOptions.headers
89
- },
90
- credentials: config.credentials,
91
- body: JSON.stringify({
92
- intent,
93
- service,
94
- action,
95
- payload: payload ?? {}
96
- }),
97
- signal: controller.signal,
98
- ...fetchOptions
79
+ const abort = () => controller.abort(signal?.reason);
80
+ if (signal?.aborted) abort();
81
+ else signal?.addEventListener("abort", abort, { once: true });
82
+ let timedOut = false;
83
+ const timeoutId = setTimeout(() => {
84
+ timedOut = true;
85
+ controller.abort();
86
+ }, timeout);
87
+ try {
88
+ const requestResult = await safeTry(async () => {
89
+ const mergedHeaders = new Headers(config.headers);
90
+ new Headers(headers).forEach((value, key) => {
91
+ mergedHeaders.set(key, value);
92
+ });
93
+ if (multipart) mergedHeaders.delete("content-type");
94
+ else mergedHeaders.set("content-type", "application/json");
95
+ const json = await (await fetch(`${config.baseUrl.replace(/\/+$/, "")}/services`, {
96
+ ...fetchOptions,
97
+ method: "POST",
98
+ headers: mergedHeaders,
99
+ credentials: fetchOptions.credentials ?? config.credentials,
100
+ body: body(),
101
+ signal: controller.signal
102
+ })).json();
103
+ if (json === null || typeof json !== "object" || !("status" in json) || typeof json.status !== "boolean" || !("message" in json) || typeof json.message !== "string" || !("data" in json)) throw new Error("Invalid Nile action response.");
104
+ return json;
99
105
  });
106
+ if (requestResult.isErr) {
107
+ if (timedOut) return clientErr("Action timed out.");
108
+ if (signal?.aborted) return clientErr("Action cancelled.");
109
+ return resolveError(requestResult.error);
110
+ }
111
+ const result = requestResult.value;
112
+ if (!result.status) return clientErr(result.message, result.data ?? null);
113
+ return clientOk(result.data, result.message);
114
+ } finally {
100
115
  clearTimeout(timeoutId);
101
- return await response.json();
102
- });
103
- if (requestResult.isErr) return resolveError(requestResult.error);
104
- const result = requestResult.value;
105
- if (!result.status) return clientErr(result.message, result.data ?? null);
106
- return clientOk(result.data, result.message);
116
+ signal?.removeEventListener("abort", abort);
117
+ }
107
118
  }
108
- /**
109
- * Handles multipart form-data uploads to the Nile /services endpoint.
110
- * Builds a FormData body with RPC routing fields (intent, service, action),
111
- * user-provided fields, and file attachments.
112
- *
113
- * Content-Type header is intentionally omitted so the runtime sets the
114
- * correct multipart boundary automatically.
115
- */
116
- async function sendUploadRequest(config, service, action, files, fields, options = {}) {
117
- const { timeout = config.timeout ?? 3e4, ...fetchOptions } = options;
118
- const controller = new AbortController();
119
- const timeoutId = setTimeout(() => controller.abort(), timeout);
120
- const formData = buildFormData(service, action, files, fields);
121
- const requestResult = await safeTry(async () => {
122
- const { "Content-Type": _ct, ...safeHeaders } = config.headers ?? {};
123
- const { "Content-Type": _ct2, ...safeFetchHeaders } = fetchOptions.headers ?? {};
124
- const response = await fetch(`${config.baseUrl}/services`, {
125
- method: "POST",
126
- headers: {
127
- ...safeHeaders,
128
- ...safeFetchHeaders
129
- },
130
- credentials: config.credentials,
131
- body: formData,
132
- signal: controller.signal,
133
- ...fetchOptions
134
- });
135
- clearTimeout(timeoutId);
136
- return await response.json();
137
- });
138
- if (requestResult.isErr) return resolveError(requestResult.error);
139
- const result = requestResult.value;
140
- if (!result.status) return clientErr(result.message, result.data ?? null);
141
- return clientOk(result.data, result.message);
119
+ /** JSON action calls preserve the server's message and failure data unchanged. */
120
+ function sendNileRequest(config, intent, service, action, payload, options = {}) {
121
+ return sendRequest(config, () => JSON.stringify({
122
+ intent,
123
+ service,
124
+ action,
125
+ payload: payload ?? {}
126
+ }), false, options);
127
+ }
128
+ /** Let the runtime set the multipart boundary; retain the same action Result contract. */
129
+ function sendUploadRequest(config, service, action, files, fields, options = {}) {
130
+ return sendRequest(config, () => buildFormData(service, action, files, fields), true, options);
142
131
  }
143
132
  /**
144
133
  * Builds a FormData instance with RPC routing fields and file/field entries.
@@ -154,67 +143,28 @@ function buildFormData(service, action, files, fields) {
154
143
  else formData.append(key, value);
155
144
  return formData;
156
145
  }
157
- /** Resolve error from safeTry into a ClientResult */
146
+ /** Convert local transport errors to the same client failure shape. */
158
147
  function resolveError(error) {
159
- let errorMsg = "An unknown error occurred";
160
- if (error instanceof Error) errorMsg = error.name === "AbortError" ? "Request timed out" : error.message;
161
- else errorMsg = String(error);
162
- return clientErr(errorMsg);
163
- }
164
- //#endregion
165
- //#region src/create-client.ts
166
- /**
167
- * Creates a type-safe Nile client for interacting with backend services.
168
- *
169
- * @param config - The client configuration including baseUrl
170
- * @returns A factory object with invoke, explore, schema, and upload methods
171
- *
172
- * @example
173
- * const nile = createNileClient<ServicePayloads>({ baseUrl: "/api" });
174
- * const result = await nile.invoke({
175
- * service: "tasks",
176
- * action: "create",
177
- * payload: { title: "Buy milk" }
178
- * });
179
- * if (result.isErr) {
180
- * console.error(result.error);
181
- * } else {
182
- * console.log(result.value);
183
- * }
184
- */
185
- function createNileClient(config) {
186
- return {
187
- /** Invoke a service action through the backend engine pipeline */
188
- invoke: async (params) => {
189
- const { service, action, payload, fetchOptions } = params;
190
- return await sendNileRequest(config, "execute", service, action, payload, fetchOptions);
191
- },
192
- /** Discover available services and actions (supports "*" wildcard) */
193
- explore: async (params) => {
194
- const { service, action, fetchOptions } = params;
195
- return await sendNileRequest(config, "explore", service, action, {}, fetchOptions);
196
- },
197
- /** Get action schemas as JSON Schema (supports "*" wildcard) */
198
- schema: async (params) => {
199
- const { service, action, fetchOptions } = params;
200
- return await sendNileRequest(config, "schema", service, action, {}, fetchOptions);
201
- },
202
- /** Upload files to an action via multipart form-data */
203
- upload: async (params) => {
204
- const { service, action, files, fields, fetchOptions } = params;
205
- return await sendUploadRequest(config, service, action, files, fields, fetchOptions);
206
- }
207
- };
148
+ return clientErr(error instanceof Error ? error.message : String(error));
208
149
  }
209
150
  //#endregion
210
151
  //#region src/socket-client.ts
211
- /** Own a lazy Socket.IO connection with no offline write buffering or automatic invocation retries. */
212
- function createNileSocketClient(config) {
152
+ /** The socket connects to an origin; any path in a URL would be read by Socket.IO as a namespace. */
153
+ function socketOrigin(url) {
154
+ try {
155
+ return new URL(url).origin;
156
+ } catch {
157
+ return;
158
+ }
159
+ }
160
+ /** Own a lazy Socket.IO connection with no offline write buffering. */
161
+ function createNileSocket(config) {
213
162
  const timeout = config.timeout ?? 3e4;
214
163
  if (!Number.isSafeInteger(timeout) || timeout < 1) throw new Error("Socket timeout must be positive");
215
164
  let socket;
216
165
  let connecting;
217
166
  let closed = false;
167
+ let everConnected = false;
218
168
  const listeners = /* @__PURE__ */ new Set();
219
169
  const pending = /* @__PURE__ */ new Set();
220
170
  const fail = (error) => clientErr(error);
@@ -263,7 +213,7 @@ function createNileSocketClient(config) {
263
213
  continue;
264
214
  }
265
215
  const message = result.error;
266
- settleCallback(() => config.onSubscriptionError?.({
216
+ settleCallback(() => config.onError?.({
267
217
  topic: listener.topic,
268
218
  error: message
269
219
  }));
@@ -278,7 +228,7 @@ function createNileSocketClient(config) {
278
228
  resolve(fail("Socket client is closed"));
279
229
  return;
280
230
  }
281
- const current = socket ?? io(config.baseUrl, {
231
+ const current = socket ?? io(config.url ? socketOrigin(config.url) : void 0, {
282
232
  path: config.path ?? "/socket.io",
283
233
  autoConnect: false,
284
234
  withCredentials: true,
@@ -289,11 +239,20 @@ function createNileSocketClient(config) {
289
239
  });
290
240
  if (!socket) {
291
241
  socket = current;
292
- current.on("disconnect", () => {
242
+ current.on("disconnect", (reason) => {
293
243
  for (const cancel of [...pending]) cancel();
244
+ settleCallback(() => config.onDisconnect?.(reason));
294
245
  });
295
246
  current.on("connect", () => {
296
- rejoin();
247
+ if (!everConnected) {
248
+ everConnected = true;
249
+ settleCallback(() => config.onConnect?.());
250
+ return;
251
+ }
252
+ rejoin().then(() => settleCallback(() => config.onReconnect?.()));
253
+ });
254
+ current.on("connect_error", () => {
255
+ settleCallback(() => config.onError?.({ error: "Socket connection failed" }));
297
256
  });
298
257
  current.on("nile:message", (message) => dispatchMessage(message, listeners));
299
258
  }
@@ -321,7 +280,6 @@ function createNileSocketClient(config) {
321
280
  return connecting;
322
281
  };
323
282
  return {
324
- invoke: (input) => request("nile:invoke", input),
325
283
  send: (input) => request("nile:send", input),
326
284
  async receive(input) {
327
285
  const listener = {
@@ -340,16 +298,19 @@ function createNileSocketClient(config) {
340
298
  return fail(result.error);
341
299
  }
342
300
  listener.normalizedScope = result.value?.scope;
343
- return clientOk({ unsubscribe: () => {
344
- listener.active = false;
345
- listeners.delete(listener);
346
- const shared = [...listeners].some((item) => item.topic === listener.topic && stableScope(item.scope) === stableScope(listener.scope));
347
- if (shared || closed) return Promise.resolve(clientOk({ subscribed: shared }));
348
- return request("nile:unsubscribe", {
349
- topic: listener.topic,
350
- scope: listener.scope
351
- });
352
- } });
301
+ return clientOk({
302
+ topic: input.topic,
303
+ unsubscribe: () => {
304
+ listener.active = false;
305
+ listeners.delete(listener);
306
+ const shared = [...listeners].some((item) => item.topic === listener.topic && stableScope(item.scope) === stableScope(listener.scope));
307
+ if (shared || closed) return Promise.resolve(clientOk({ subscribed: shared }));
308
+ return request("nile:unsubscribe", {
309
+ topic: listener.topic,
310
+ scope: listener.scope
311
+ });
312
+ }
313
+ });
353
314
  },
354
315
  close() {
355
316
  closed = true;
@@ -378,6 +339,81 @@ function dispatchMessage(message, listeners) {
378
339
  }
379
340
  }
380
341
  //#endregion
342
+ //#region src/create-client.ts
343
+ /**
344
+ * Creates a type-safe Nile client for interacting with backend services.
345
+ *
346
+ * @param config - The client configuration including baseUrl
347
+ * @returns A factory object with invoke, explore, schema and upload methods, plus
348
+ * `socket` for topics. The socket connects on its first `send` or `receive`.
349
+ *
350
+ * @example
351
+ * const nile = createNileClient<ServicePayloads>({ baseUrl: "/api" });
352
+ * const result = await nile.invoke({
353
+ * service: "tasks",
354
+ * action: "create",
355
+ * payload: { title: "Buy milk" }
356
+ * });
357
+ * if (result.isErr) {
358
+ * console.error(result.error);
359
+ * } else {
360
+ * console.log(result.value);
361
+ * }
362
+ *
363
+ * const received = await nile.socket.receive({
364
+ * topic: "tasks.updated",
365
+ * onMessage: (message) => console.log(message.data),
366
+ * });
367
+ */
368
+ function createNileClient(config) {
369
+ return {
370
+ socket: createNileSocket({
371
+ ...config.socket,
372
+ url: config.socket?.url ?? config.baseUrl
373
+ }),
374
+ /** Invoke a service action through the backend engine pipeline */
375
+ invoke: async (params) => {
376
+ const { service, action, payload, fetchOptions } = params;
377
+ return await sendNileRequest(config, "execute", service, action, payload, fetchOptions);
378
+ },
379
+ /** Discover available services and actions (supports "*" wildcard) */
380
+ explore: async (params) => {
381
+ const { service, action, fetchOptions } = params;
382
+ return await sendNileRequest(config, "explore", service, action, {}, fetchOptions);
383
+ },
384
+ /** Get action schemas as JSON Schema (supports "*" wildcard) */
385
+ schema: async (params) => {
386
+ const { service, action, fetchOptions } = params;
387
+ return await sendNileRequest(config, "schema", service, action, {}, fetchOptions);
388
+ },
389
+ /** Upload files to an action via multipart form-data */
390
+ upload: async (params) => {
391
+ const { service, action, files, fields, fetchOptions } = params;
392
+ return await sendUploadRequest(config, service, action, files, fields, fetchOptions);
393
+ }
394
+ };
395
+ }
396
+ //#endregion
397
+ //#region src/deprecated.ts
398
+ const MIGRATION_MESSAGE = [
399
+ "createNileSocketClient was removed in @nilejs/client 1.1.0.",
400
+ "Create one client with createNileClient({ baseUrl, socket: { auth } })",
401
+ "and use nile.socket.send, nile.socket.receive and nile.socket.close.",
402
+ "Socket invoke is gone as well; call actions with nile.invoke over HTTP.",
403
+ "See https://nile-js.github.io/nile/guide/features/websockets/ and the",
404
+ "migration notes in packages/nile/docs/release-migration.md."
405
+ ].join(" ");
406
+ /**
407
+ * Kept as a throwing stub so an upgrade fails with guidance instead of a
408
+ * missing export.
409
+ *
410
+ * @deprecated Use `createNileClient({ baseUrl, socket: { auth } })` and call
411
+ * `socket.send`, `socket.receive` and `socket.close` on the returned client.
412
+ */
413
+ function createNileSocketClient(_config) {
414
+ throw new Error(MIGRATION_MESSAGE);
415
+ }
416
+ //#endregion
381
417
  exports.buildFormData = buildFormData;
382
418
  exports.createNileClient = createNileClient;
383
419
  exports.createNileSocketClient = createNileSocketClient;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/result.ts","../src/safe-try.ts","../src/request.ts","../src/create-client.ts","../src/socket-client.ts"],"sourcesContent":["import type { ClientErr, ClientOk } from \"./types.js\";\n\n/**\n * A successful client result. `data` mirrors `value` so code written for\n * earlier clients keeps working. `message` is present only when the server sent one.\n */\nexport function clientOk<T>(value: T, message?: string): ClientOk<T> {\n return {\n isOk: true,\n isErr: false,\n value: value,\n error: null,\n ...(message === undefined ? {} : { message: message }),\n data: value,\n };\n}\n\n/**\n * A failed client result with a readable message and any data the server\n * attached to the failure. `data` stays null, as it was for earlier clients.\n */\nexport function clientErr(error: string, details: unknown = null): ClientErr {\n return {\n isOk: false,\n isErr: true,\n value: null,\n error: error,\n details: details,\n data: null,\n };\n}\n","/**\n * Internal result type for safeTry\n */\nexport type SafeResult<T, E = unknown> =\n | { isOk: true; value: T; isErr: false; error: null }\n | { isOk: false; value: null; isErr: true; error: E };\n\n/**\n * Gracefully handles both synchronous and asynchronous operations by returning a result object.\n * This replaces the need for external slang-ts dependency and avoids try/catch boilerplate.\n *\n * @param fn - The function to execute (sync or async)\n * @returns A result object containing either the value or the error\n *\n * @example\n * const { isOk, value, error } = await safeTry(() => fetch(url));\n */\nexport function safeTry<T>(\n fn: () => T | Promise<T>\n): Promise<SafeResult<T>> | SafeResult<T> {\n try {\n const result = fn();\n\n if (result instanceof Promise) {\n return result\n .then(\n (value): SafeResult<T> => ({\n isOk: true,\n value,\n isErr: false,\n error: null,\n })\n )\n .catch(\n (error): SafeResult<T> => ({\n isOk: false,\n value: null,\n isErr: true,\n error,\n })\n );\n }\n\n return { isOk: true, value: result, isErr: false, error: null };\n } catch (error) {\n return { isOk: false, value: null, isErr: true, error };\n }\n}\n","import { clientErr, clientOk } from \"./result.js\";\nimport { safeTry } from \"./safe-try.js\";\nimport type {\n ClientResult,\n ExternalResponse,\n FetchOptions,\n NileClientConfig,\n} from \"./types.js\";\n\n/**\n * Handles JSON communication with the Nile /services endpoint.\n * Uses fetch + safeTry for crash safety and returns a Result that never throws.\n */\nexport async function sendNileRequest<T = Record<string, unknown>>(\n config: NileClientConfig,\n intent: \"explore\" | \"execute\" | \"schema\",\n service: string,\n action: string,\n payload: unknown,\n options: FetchOptions = {}\n): Promise<ClientResult<T>> {\n const { timeout = config.timeout ?? 30_000, ...fetchOptions } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const requestResult = await safeTry(async () => {\n const response = await fetch(`${config.baseUrl}/services`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...config.headers,\n ...(fetchOptions.headers as Record<string, string>),\n },\n credentials: config.credentials,\n body: JSON.stringify({\n intent,\n service,\n action,\n payload: payload ?? {},\n }),\n signal: controller.signal,\n ...fetchOptions,\n });\n\n clearTimeout(timeoutId);\n\n const json = (await response.json()) as ExternalResponse<T>;\n return json;\n });\n\n if (requestResult.isErr) {\n return resolveError(requestResult.error);\n }\n\n const result = requestResult.value;\n\n if (!result.status) {\n return clientErr(result.message, result.data ?? null);\n }\n\n return clientOk(result.data, result.message);\n}\n\n/**\n * Handles multipart form-data uploads to the Nile /services endpoint.\n * Builds a FormData body with RPC routing fields (intent, service, action),\n * user-provided fields, and file attachments.\n *\n * Content-Type header is intentionally omitted so the runtime sets the\n * correct multipart boundary automatically.\n */\nexport async function sendUploadRequest<T = Record<string, unknown>>(\n config: NileClientConfig,\n service: string,\n action: string,\n files: Record<string, File | File[]>,\n fields?: Record<string, string>,\n options: FetchOptions = {}\n): Promise<ClientResult<T>> {\n const { timeout = config.timeout ?? 30_000, ...fetchOptions } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeout);\n\n const formData = buildFormData(service, action, files, fields);\n\n const requestResult = await safeTry(async () => {\n // Spread config.headers but strip Content-Type — let the runtime set multipart boundary\n const { \"Content-Type\": _ct, ...safeHeaders } = config.headers ?? {};\n const { \"Content-Type\": _ct2, ...safeFetchHeaders } =\n (fetchOptions.headers as Record<string, string>) ?? {};\n\n const response = await fetch(`${config.baseUrl}/services`, {\n method: \"POST\",\n headers: {\n ...safeHeaders,\n ...safeFetchHeaders,\n },\n credentials: config.credentials,\n body: formData,\n signal: controller.signal,\n ...fetchOptions,\n });\n\n clearTimeout(timeoutId);\n\n const json = (await response.json()) as ExternalResponse<T>;\n return json;\n });\n\n if (requestResult.isErr) {\n return resolveError(requestResult.error);\n }\n\n const result = requestResult.value;\n\n if (!result.status) {\n return clientErr(result.message, result.data ?? null);\n }\n\n return clientOk(result.data, result.message);\n}\n\n/**\n * Builds a FormData instance with RPC routing fields and file/field entries.\n * The server expects 'intent', 'service', 'action' as string fields.\n */\nexport function buildFormData(\n service: string,\n action: string,\n files: Record<string, File | File[]>,\n fields?: Record<string, string>\n): FormData {\n const formData = new FormData();\n\n // RPC routing fields the server expects\n formData.append(\"intent\", \"execute\");\n formData.append(\"service\", service);\n formData.append(\"action\", action);\n\n // User-provided string fields\n if (fields) {\n for (const [key, value] of Object.entries(fields)) {\n formData.append(key, value);\n }\n }\n\n // File attachments — arrays append multiple entries under the same key\n for (const [key, value] of Object.entries(files)) {\n if (Array.isArray(value)) {\n for (const file of value) {\n formData.append(key, file);\n }\n } else {\n formData.append(key, value);\n }\n }\n\n return formData;\n}\n\n/** Resolve error from safeTry into a ClientResult */\nfunction resolveError(error: unknown): ClientResult<never> {\n let errorMsg = \"An unknown error occurred\";\n\n if (error instanceof Error) {\n errorMsg =\n error.name === \"AbortError\" ? \"Request timed out\" : error.message;\n } else {\n errorMsg = String(error);\n }\n\n return clientErr(errorMsg);\n}\n","import { sendNileRequest, sendUploadRequest } from \"./request.js\";\nimport type {\n ClientResult,\n NileClient,\n NileClientConfig,\n UploadParams,\n} from \"./types.js\";\n\n/**\n * Creates a type-safe Nile client for interacting with backend services.\n *\n * @param config - The client configuration including baseUrl\n * @returns A factory object with invoke, explore, schema, and upload methods\n *\n * @example\n * const nile = createNileClient<ServicePayloads>({ baseUrl: \"/api\" });\n * const result = await nile.invoke({\n * service: \"tasks\",\n * action: \"create\",\n * payload: { title: \"Buy milk\" }\n * });\n * if (result.isErr) {\n * console.error(result.error);\n * } else {\n * console.log(result.value);\n * }\n */\nexport function createNileClient<\n TPayloads = Record<string, Record<string, unknown>>,\n>(config: NileClientConfig): NileClient<TPayloads> {\n return {\n /** Invoke a service action through the backend engine pipeline */\n invoke: async <\n S extends keyof TPayloads & string,\n A extends keyof TPayloads[S] & string,\n >(\n params: {\n service: S;\n action: A;\n payload: TPayloads[S][A];\n fetchOptions?: Parameters<typeof sendNileRequest>[5];\n }\n ) => {\n const { service, action, payload, fetchOptions } = params;\n const result = await sendNileRequest<Record<string, unknown>>(\n config,\n \"execute\",\n service,\n action,\n payload,\n fetchOptions\n );\n return result;\n },\n\n /** Discover available services and actions (supports \"*\" wildcard) */\n explore: async (params) => {\n const { service, action, fetchOptions } = params;\n return await sendNileRequest(\n config,\n \"explore\",\n service,\n action,\n {},\n fetchOptions\n );\n },\n\n /** Get action schemas as JSON Schema (supports \"*\" wildcard) */\n schema: async (params) => {\n const { service, action, fetchOptions } = params;\n return await sendNileRequest(\n config,\n \"schema\",\n service,\n action,\n {},\n fetchOptions\n );\n },\n\n /** Upload files to an action via multipart form-data */\n upload: async (params: UploadParams) => {\n const { service, action, files, fields, fetchOptions } = params;\n return await sendUploadRequest(\n config,\n service,\n action,\n files,\n fields,\n fetchOptions\n );\n },\n };\n}\n","import type { Socket } from \"socket.io-client\";\nimport type {\n ActionShape,\n NileSocketClient,\n NileSocketClientConfig,\n SocketActions,\n SocketTopics,\n TopicShape,\n} from \"./socket-types.js\";\nimport { clientErr, clientOk } from \"./result.js\";\nimport type { ClientResult } from \"./types.js\";\n\ntype WireValue = object | string | number | boolean | null | undefined;\n/** Acknowledgements keep the server's `{ error, data }` wire shape; the client turns them into Results. */\ninterface WireResult {\n error: string | null;\n data: unknown;\n}\ninterface Listener {\n topic: string;\n scope?: WireValue;\n onMessage: (message: WireValue) => void;\n active: boolean;\n normalizedScope?: WireValue;\n}\n\n/** Own a lazy Socket.IO connection with no offline write buffering or automatic invocation retries. */\nexport function createNileSocketClient<\n Actions extends ActionShape<Actions> = SocketActions,\n Topics extends TopicShape<Topics> = SocketTopics,\n>(config: NileSocketClientConfig): NileSocketClient<Actions, Topics> {\n const timeout = config.timeout ?? 30_000;\n if (!Number.isSafeInteger(timeout) || timeout < 1) {\n throw new Error(\"Socket timeout must be positive\");\n }\n let socket: Socket | undefined;\n let connecting: Promise<ClientResult<Socket>> | undefined;\n let closed = false;\n const listeners = new Set<Listener>();\n const pending = new Set<() => void>();\n const fail = (error: string) => clientErr(error);\n const settleCallback = (fn: () => void) => {\n Promise.resolve()\n .then(fn)\n .catch(() => undefined);\n };\n const request = async <T>(\n event: string,\n input: WireValue\n ): Promise<ClientResult<T>> => {\n const connected = await connect();\n if (connected.isErr) {\n return fail(connected.error);\n }\n const current = connected.value;\n if (!current.connected || closed) {\n return fail(\"Socket disconnected\");\n }\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: ClientResult<T>) => {\n if (settled) {\n return;\n }\n settled = true;\n pending.delete(cancel);\n clearTimeout(timer);\n resolve(result);\n };\n const cancel = () => finish(fail(\"Socket disconnected\"));\n const timer = setTimeout(\n () => finish(fail(\"Socket acknowledgement timed out\")),\n timeout\n );\n pending.add(cancel);\n // The connection check and emit are synchronous; disconnected calls never enter the offline queue.\n current.emit(event, input, (result: WireResult) => {\n if (\n !result ||\n typeof result !== \"object\" ||\n !(\"error\" in result) ||\n !(\"data\" in result)\n ) {\n finish(fail(\"Invalid socket response\"));\n return;\n }\n if (typeof result.error === \"string\") {\n finish(clientErr(result.error, result.data));\n return;\n }\n // The server validated this acknowledgement for the requested event.\n finish(clientOk(result.data as T));\n });\n });\n };\n const rejoin = async () => {\n for (const listener of listeners) {\n if (!listener.active) {\n continue;\n }\n const result = await request<{ scope?: WireValue }>(\"nile:receive\", {\n topic: listener.topic,\n scope: listener.scope,\n });\n if (result.isOk) {\n listener.normalizedScope = result.value?.scope;\n continue;\n }\n const message = result.error;\n settleCallback(() =>\n config.onSubscriptionError?.({\n topic: listener.topic,\n error: message,\n })\n );\n }\n };\n const connect = (): Promise<ClientResult<Socket>> => {\n if (closed) {\n return Promise.resolve(fail(\"Socket client is closed\"));\n }\n if (socket?.connected) {\n return Promise.resolve(clientOk(socket));\n }\n if (connecting) {\n return connecting;\n }\n connecting = import(\"socket.io-client\")\n .then(\n ({ io }) =>\n new Promise<ClientResult<Socket>>((resolve) => {\n if (closed) {\n resolve(fail(\"Socket client is closed\"));\n return;\n }\n const current =\n socket ??\n io(config.baseUrl, {\n path: config.path ?? \"/socket.io\",\n autoConnect: false,\n withCredentials: true,\n timeout,\n auth: (done) => {\n Promise.resolve()\n .then(() =>\n typeof config.auth === \"function\"\n ? config.auth()\n : (config.auth ?? {})\n )\n .then(done)\n .catch(() => done({}));\n },\n });\n if (!socket) {\n socket = current;\n current.on(\"disconnect\", () => {\n for (const cancel of [...pending]) {\n cancel();\n }\n });\n current.on(\"connect\", () => {\n rejoin();\n });\n current.on(\"nile:message\", (message: WireValue) =>\n dispatchMessage(message, listeners)\n );\n }\n let settled = false;\n const finish = (result: ClientResult<Socket>) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timer);\n current.off(\"connect\", ready);\n current.off(\"connect_error\", failed);\n pending.delete(cancel);\n resolve(result);\n };\n const ready = () => finish(clientOk(current));\n const failed = () => finish(fail(\"Socket connection failed\"));\n const cancel = () => finish(fail(\"Socket client closed\"));\n const timer = setTimeout(\n () => finish(fail(\"Socket connection timed out\")),\n timeout\n );\n pending.add(cancel);\n current.once(\"connect\", ready);\n current.once(\"connect_error\", failed);\n current.connect();\n })\n )\n .catch(() => fail(\"Could not initialize socket client\"))\n .finally(() => {\n connecting = undefined;\n });\n return connecting;\n };\n return {\n invoke: (input) => request(\"nile:invoke\", input),\n send: (input) => request(\"nile:send\", input),\n async receive(input) {\n const listener: Listener = {\n ...input,\n scope: input.scope as WireValue,\n onMessage: input.onMessage as Listener[\"onMessage\"],\n active: true,\n };\n listeners.add(listener);\n const result = await request<{ subscribed: boolean; scope?: WireValue }>(\n \"nile:receive\",\n { topic: input.topic, scope: input.scope }\n );\n if (result.isErr) {\n listeners.delete(listener);\n return fail(result.error);\n }\n listener.normalizedScope = result.value?.scope;\n return clientOk({\n unsubscribe: () => {\n listener.active = false;\n listeners.delete(listener);\n const shared = [...listeners].some(\n (item) =>\n item.topic === listener.topic &&\n stableScope(item.scope) === stableScope(listener.scope)\n );\n if (shared || closed) {\n return Promise.resolve(clientOk({ subscribed: shared }));\n }\n return request(\"nile:unsubscribe\", {\n topic: listener.topic,\n scope: listener.scope,\n });\n },\n });\n },\n close() {\n closed = true;\n listeners.clear();\n for (const cancel of [...pending]) {\n cancel();\n }\n socket?.disconnect();\n socket?.removeAllListeners();\n },\n };\n}\n\n/** Match scoped messages independently of object property insertion order. */\nfunction stableScope(scope: WireValue): string {\n if (scope === undefined || scope === null) {\n return \"null\";\n }\n if (Array.isArray(scope)) {\n return `[${scope.map(stableScope).join(\",\")}]`;\n }\n if (typeof scope === \"object\") {\n return `{${Object.keys(scope)\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${stableScope((scope as Record<string, WireValue>)[key])}`\n )\n .join(\",\")}}`;\n }\n return JSON.stringify(scope);\n}\n\n/** Deliver messages only to listeners for the same validated topic and scope. */\nfunction dispatchMessage(message: WireValue, listeners: Set<Listener>) {\n if (!message || typeof message !== \"object\" || !(\"topic\" in message)) {\n return;\n }\n const scope = stableScope(\n \"scope\" in message ? (message.scope as WireValue) : undefined\n );\n for (const listener of listeners) {\n if (!listener.active || listener.topic !== message.topic) {\n continue;\n }\n if (stableScope(listener.normalizedScope ?? listener.scope) !== scope) {\n continue;\n }\n Promise.resolve()\n .then(() => listener.onMessage(message))\n .catch(() => undefined);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/result.ts","../src/safe-try.ts","../src/request.ts","../src/socket-client.ts","../src/create-client.ts","../src/deprecated.ts"],"sourcesContent":["import type { ClientErr, ClientOk } from \"./types.js\";\n\n/**\n * A successful client result. `data` mirrors `value` so code written for\n * earlier clients keeps working. `message` is present only when the server sent one.\n */\nexport function clientOk<T>(value: T, message?: string): ClientOk<T> {\n return {\n isOk: true,\n isErr: false,\n value: value,\n error: null,\n ...(message === undefined ? {} : { message: message }),\n data: value,\n };\n}\n\n/**\n * A failed client result with a readable message and any data the server\n * attached to the failure. `data` stays null, as it was for earlier clients.\n */\nexport function clientErr(error: string, details: unknown = null): ClientErr {\n return {\n isOk: false,\n isErr: true,\n value: null,\n error: error,\n details: details,\n data: null,\n };\n}\n","/**\n * Internal result type for safeTry\n */\nexport type SafeResult<T, E = unknown> =\n | { isOk: true; value: T; isErr: false; error: null }\n | { isOk: false; value: null; isErr: true; error: E };\n\n/**\n * Gracefully handles both synchronous and asynchronous operations by returning a result object.\n * This replaces the need for external slang-ts dependency and avoids try/catch boilerplate.\n *\n * @param fn - The function to execute (sync or async)\n * @returns A result object containing either the value or the error\n *\n * @example\n * const { isOk, value, error } = await safeTry(() => fetch(url));\n */\nexport function safeTry<T>(\n fn: () => T | Promise<T>\n): Promise<SafeResult<T>> | SafeResult<T> {\n try {\n const result = fn();\n\n if (result instanceof Promise) {\n return result\n .then(\n (value): SafeResult<T> => ({\n isOk: true,\n value,\n isErr: false,\n error: null,\n })\n )\n .catch(\n (error): SafeResult<T> => ({\n isOk: false,\n value: null,\n isErr: true,\n error,\n })\n );\n }\n\n return { isOk: true, value: result, isErr: false, error: null };\n } catch (error) {\n return { isOk: false, value: null, isErr: true, error };\n }\n}\n","import { clientErr, clientOk } from \"./result.js\";\nimport { safeTry } from \"./safe-try.js\";\nimport type {\n ClientResult,\n ExternalResponse,\n FetchOptions,\n NileClientConfig,\n} from \"./types.js\";\n\n/** Send one action call; the deadline covers both fetch and reading its JSON body. */\nasync function sendRequest<T>(\n config: NileClientConfig,\n body: () => BodyInit,\n multipart: boolean,\n options: FetchOptions\n): Promise<ClientResult<T>> {\n const {\n timeout = config.timeout ?? 30_000,\n headers,\n signal,\n ...fetchOptions\n } = options;\n if (\n !Number.isSafeInteger(timeout) ||\n timeout < 1 ||\n timeout > 2_147_483_647\n ) {\n return clientErr(\n \"Action timeout must be a positive integer up to 2147483647 milliseconds.\"\n );\n }\n const controller = new AbortController();\n const abort = () => controller.abort(signal?.reason);\n if (signal?.aborted) {\n abort();\n } else {\n signal?.addEventListener(\"abort\", abort, { once: true });\n }\n let timedOut = false;\n const timeoutId = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeout);\n\n try {\n const requestResult = await safeTry(async () => {\n const mergedHeaders = new Headers(config.headers);\n new Headers(headers).forEach((value, key) => {\n mergedHeaders.set(key, value);\n });\n if (multipart) {\n mergedHeaders.delete(\"content-type\");\n } else {\n mergedHeaders.set(\"content-type\", \"application/json\");\n }\n const response = await fetch(\n `${config.baseUrl.replace(/\\/+$/, \"\")}/services`,\n {\n ...fetchOptions,\n method: \"POST\",\n headers: mergedHeaders,\n credentials: fetchOptions.credentials ?? config.credentials,\n body: body(),\n signal: controller.signal,\n }\n );\n const json: unknown = await response.json();\n if (\n json === null ||\n typeof json !== \"object\" ||\n !(\"status\" in json) ||\n typeof json.status !== \"boolean\" ||\n !(\"message\" in json) ||\n typeof json.message !== \"string\" ||\n !(\"data\" in json)\n ) {\n throw new Error(\"Invalid Nile action response.\");\n }\n return json as ExternalResponse<T>;\n });\n if (requestResult.isErr) {\n if (timedOut) {\n return clientErr(\"Action timed out.\");\n }\n if (signal?.aborted) {\n return clientErr(\"Action cancelled.\");\n }\n return resolveError(requestResult.error);\n }\n const result = requestResult.value;\n if (!result.status) {\n return clientErr(result.message, result.data ?? null);\n }\n return clientOk(result.data, result.message);\n } finally {\n clearTimeout(timeoutId);\n signal?.removeEventListener(\"abort\", abort);\n }\n}\n\n/** JSON action calls preserve the server's message and failure data unchanged. */\nexport function sendNileRequest<T = Record<string, unknown>>(\n config: NileClientConfig,\n intent: \"explore\" | \"execute\" | \"schema\",\n service: string,\n action: string,\n payload: unknown,\n options: FetchOptions = {}\n): Promise<ClientResult<T>> {\n return sendRequest<T>(\n config,\n () =>\n JSON.stringify({\n intent: intent,\n service: service,\n action: action,\n payload: payload ?? {},\n }),\n false,\n options\n );\n}\n\n/** Let the runtime set the multipart boundary; retain the same action Result contract. */\nexport function sendUploadRequest<T = Record<string, unknown>>(\n config: NileClientConfig,\n service: string,\n action: string,\n files: Record<string, File | File[]>,\n fields?: Record<string, string>,\n options: FetchOptions = {}\n): Promise<ClientResult<T>> {\n return sendRequest<T>(\n config,\n () => buildFormData(service, action, files, fields),\n true,\n options\n );\n}\n\n/**\n * Builds a FormData instance with RPC routing fields and file/field entries.\n * The server expects 'intent', 'service', 'action' as string fields.\n */\nexport function buildFormData(\n service: string,\n action: string,\n files: Record<string, File | File[]>,\n fields?: Record<string, string>\n): FormData {\n const formData = new FormData();\n\n // RPC routing fields the server expects\n formData.append(\"intent\", \"execute\");\n formData.append(\"service\", service);\n formData.append(\"action\", action);\n\n // User-provided string fields\n if (fields) {\n for (const [key, value] of Object.entries(fields)) {\n formData.append(key, value);\n }\n }\n\n // File attachments — arrays append multiple entries under the same key\n for (const [key, value] of Object.entries(files)) {\n if (Array.isArray(value)) {\n for (const file of value) {\n formData.append(key, file);\n }\n } else {\n formData.append(key, value);\n }\n }\n\n return formData;\n}\n\n/** Convert local transport errors to the same client failure shape. */\nfunction resolveError(error: unknown): ClientResult<never> {\n return clientErr(error instanceof Error ? error.message : String(error));\n}\n","import type { Socket } from \"socket.io-client\";\nimport { clientErr, clientOk } from \"./result.js\";\nimport type {\n NileSocketClient,\n NileSocketConfig,\n SocketTopics,\n TopicShape,\n} from \"./socket-types.js\";\nimport type { ClientResult } from \"./types.js\";\n\ntype WireValue = object | string | number | boolean | null | undefined;\n/** Acknowledgements keep the server's `{ error, data }` wire shape; the client turns them into Results. */\ninterface WireResult {\n error: string | null;\n data: unknown;\n}\ninterface Listener {\n topic: string;\n scope?: WireValue;\n onMessage: (message: WireValue) => void;\n active: boolean;\n normalizedScope?: WireValue;\n}\n\n/** The socket connects to an origin; any path in a URL would be read by Socket.IO as a namespace. */\nexport function socketOrigin(url: string): string | undefined {\n try {\n return new URL(url).origin;\n } catch {\n // A relative URL such as \"/api\" means the page's own origin.\n return undefined;\n }\n}\n\n/** Own a lazy Socket.IO connection with no offline write buffering. */\nexport function createNileSocket<\n Topics extends TopicShape<Topics> = SocketTopics,\n>(config: NileSocketConfig): NileSocketClient<Topics> {\n const timeout = config.timeout ?? 30_000;\n if (!Number.isSafeInteger(timeout) || timeout < 1) {\n throw new Error(\"Socket timeout must be positive\");\n }\n let socket: Socket | undefined;\n let connecting: Promise<ClientResult<Socket>> | undefined;\n let closed = false;\n let everConnected = false;\n const listeners = new Set<Listener>();\n const pending = new Set<() => void>();\n const fail = (error: string) => clientErr(error);\n const settleCallback = (fn: () => void) => {\n Promise.resolve()\n .then(fn)\n .catch(() => undefined);\n };\n const request = async <T>(\n event: string,\n input: WireValue\n ): Promise<ClientResult<T>> => {\n const connected = await connect();\n if (connected.isErr) {\n return fail(connected.error);\n }\n const current = connected.value;\n if (!current.connected || closed) {\n return fail(\"Socket disconnected\");\n }\n return new Promise((resolve) => {\n let settled = false;\n const finish = (result: ClientResult<T>) => {\n if (settled) {\n return;\n }\n settled = true;\n pending.delete(cancel);\n clearTimeout(timer);\n resolve(result);\n };\n const cancel = () => finish(fail(\"Socket disconnected\"));\n const timer = setTimeout(\n () => finish(fail(\"Socket acknowledgement timed out\")),\n timeout\n );\n pending.add(cancel);\n // The connection check and emit are synchronous; disconnected calls never enter the offline queue.\n current.emit(event, input, (result: WireResult) => {\n if (\n !result ||\n typeof result !== \"object\" ||\n !(\"error\" in result) ||\n !(\"data\" in result)\n ) {\n finish(fail(\"Invalid socket response\"));\n return;\n }\n if (typeof result.error === \"string\") {\n finish(clientErr(result.error, result.data));\n return;\n }\n // The server validated this acknowledgement for the requested event.\n finish(clientOk(result.data as T));\n });\n });\n };\n const rejoin = async () => {\n for (const listener of listeners) {\n if (!listener.active) {\n continue;\n }\n const result = await request<{ scope?: WireValue }>(\"nile:receive\", {\n topic: listener.topic,\n scope: listener.scope,\n });\n if (result.isOk) {\n listener.normalizedScope = result.value?.scope;\n continue;\n }\n const message = result.error;\n settleCallback(() =>\n config.onError?.({ topic: listener.topic, error: message })\n );\n }\n };\n const connect = (): Promise<ClientResult<Socket>> => {\n if (closed) {\n return Promise.resolve(fail(\"Socket client is closed\"));\n }\n if (socket?.connected) {\n return Promise.resolve(clientOk(socket));\n }\n if (connecting) {\n return connecting;\n }\n connecting = import(\"socket.io-client\")\n .then(\n ({ io }) =>\n new Promise<ClientResult<Socket>>((resolve) => {\n if (closed) {\n resolve(fail(\"Socket client is closed\"));\n return;\n }\n const current =\n socket ??\n io(config.url ? socketOrigin(config.url) : undefined, {\n path: config.path ?? \"/socket.io\",\n autoConnect: false,\n withCredentials: true,\n timeout,\n auth: (done) => {\n Promise.resolve()\n .then(() =>\n typeof config.auth === \"function\"\n ? config.auth()\n : (config.auth ?? {})\n )\n .then(done)\n .catch(() => done({}));\n },\n });\n if (!socket) {\n socket = current;\n current.on(\"disconnect\", (reason: string) => {\n for (const cancel of [...pending]) {\n cancel();\n }\n settleCallback(() => config.onDisconnect?.(reason));\n });\n current.on(\"connect\", () => {\n if (!everConnected) {\n everConnected = true;\n settleCallback(() => config.onConnect?.());\n return;\n }\n rejoin().then(() =>\n settleCallback(() => config.onReconnect?.())\n );\n });\n current.on(\"connect_error\", () => {\n settleCallback(() =>\n config.onError?.({ error: \"Socket connection failed\" })\n );\n });\n current.on(\"nile:message\", (message: WireValue) =>\n dispatchMessage(message, listeners)\n );\n }\n let settled = false;\n const finish = (result: ClientResult<Socket>) => {\n if (settled) {\n return;\n }\n settled = true;\n clearTimeout(timer);\n current.off(\"connect\", ready);\n current.off(\"connect_error\", failed);\n pending.delete(cancel);\n resolve(result);\n };\n const ready = () => finish(clientOk(current));\n const failed = () => finish(fail(\"Socket connection failed\"));\n const cancel = () => finish(fail(\"Socket client closed\"));\n const timer = setTimeout(\n () => finish(fail(\"Socket connection timed out\")),\n timeout\n );\n pending.add(cancel);\n current.once(\"connect\", ready);\n current.once(\"connect_error\", failed);\n current.connect();\n })\n )\n .catch(() => fail(\"Could not initialize socket client\"))\n .finally(() => {\n connecting = undefined;\n });\n return connecting;\n };\n return {\n send: (input) => request(\"nile:send\", input),\n async receive(input) {\n const listener: Listener = {\n ...input,\n scope: input.scope as WireValue,\n onMessage: input.onMessage as Listener[\"onMessage\"],\n active: true,\n };\n listeners.add(listener);\n const result = await request<{ subscribed: boolean; scope?: WireValue }>(\n \"nile:receive\",\n { topic: input.topic, scope: input.scope }\n );\n if (result.isErr) {\n listeners.delete(listener);\n return fail(result.error);\n }\n listener.normalizedScope = result.value?.scope;\n return clientOk({\n topic: input.topic,\n unsubscribe: () => {\n listener.active = false;\n listeners.delete(listener);\n const shared = [...listeners].some(\n (item) =>\n item.topic === listener.topic &&\n stableScope(item.scope) === stableScope(listener.scope)\n );\n if (shared || closed) {\n return Promise.resolve(clientOk({ subscribed: shared }));\n }\n return request(\"nile:unsubscribe\", {\n topic: listener.topic,\n scope: listener.scope,\n });\n },\n });\n },\n close() {\n closed = true;\n listeners.clear();\n for (const cancel of [...pending]) {\n cancel();\n }\n socket?.disconnect();\n socket?.removeAllListeners();\n },\n };\n}\n\n/** Match scoped messages independently of object property insertion order. */\nfunction stableScope(scope: WireValue): string {\n if (scope === undefined || scope === null) {\n return \"null\";\n }\n if (Array.isArray(scope)) {\n return `[${scope.map(stableScope).join(\",\")}]`;\n }\n if (typeof scope === \"object\") {\n return `{${Object.keys(scope)\n .sort()\n .map(\n (key) =>\n `${JSON.stringify(key)}:${stableScope((scope as Record<string, WireValue>)[key])}`\n )\n .join(\",\")}}`;\n }\n return JSON.stringify(scope);\n}\n\n/** Deliver messages only to listeners for the same validated topic and scope. */\nfunction dispatchMessage(message: WireValue, listeners: Set<Listener>) {\n if (!message || typeof message !== \"object\" || !(\"topic\" in message)) {\n return;\n }\n const scope = stableScope(\n \"scope\" in message ? (message.scope as WireValue) : undefined\n );\n for (const listener of listeners) {\n if (!listener.active || listener.topic !== message.topic) {\n continue;\n }\n if (stableScope(listener.normalizedScope ?? listener.scope) !== scope) {\n continue;\n }\n Promise.resolve()\n .then(() => listener.onMessage(message))\n .catch(() => undefined);\n }\n}\n","import { sendNileRequest, sendUploadRequest } from \"./request.js\";\nimport { createNileSocket } from \"./socket-client.js\";\nimport type { SocketTopics, TopicShape } from \"./socket-types.js\";\nimport type { NileClient, NileClientConfig, UploadParams } from \"./types.js\";\n\n/**\n * Creates a type-safe Nile client for interacting with backend services.\n *\n * @param config - The client configuration including baseUrl\n * @returns A factory object with invoke, explore, schema and upload methods, plus\n * `socket` for topics. The socket connects on its first `send` or `receive`.\n *\n * @example\n * const nile = createNileClient<ServicePayloads>({ baseUrl: \"/api\" });\n * const result = await nile.invoke({\n * service: \"tasks\",\n * action: \"create\",\n * payload: { title: \"Buy milk\" }\n * });\n * if (result.isErr) {\n * console.error(result.error);\n * } else {\n * console.log(result.value);\n * }\n *\n * const received = await nile.socket.receive({\n * topic: \"tasks.updated\",\n * onMessage: (message) => console.log(message.data),\n * });\n */\nexport function createNileClient<\n TPayloads = Record<string, Record<string, unknown>>,\n TTopics extends TopicShape<TTopics> = SocketTopics,\n>(config: NileClientConfig): NileClient<TPayloads, TTopics> {\n return {\n socket: createNileSocket<TTopics>({\n ...config.socket,\n url: config.socket?.url ?? config.baseUrl,\n }),\n\n /** Invoke a service action through the backend engine pipeline */\n invoke: async <\n S extends keyof TPayloads & string,\n A extends keyof TPayloads[S] & string,\n >(params: {\n service: S;\n action: A;\n payload: TPayloads[S][A];\n fetchOptions?: Parameters<typeof sendNileRequest>[5];\n }) => {\n const { service, action, payload, fetchOptions } = params;\n const result = await sendNileRequest<Record<string, unknown>>(\n config,\n \"execute\",\n service,\n action,\n payload,\n fetchOptions\n );\n return result;\n },\n\n /** Discover available services and actions (supports \"*\" wildcard) */\n explore: async (params) => {\n const { service, action, fetchOptions } = params;\n return await sendNileRequest(\n config,\n \"explore\",\n service,\n action,\n {},\n fetchOptions\n );\n },\n\n /** Get action schemas as JSON Schema (supports \"*\" wildcard) */\n schema: async (params) => {\n const { service, action, fetchOptions } = params;\n return await sendNileRequest(\n config,\n \"schema\",\n service,\n action,\n {},\n fetchOptions\n );\n },\n\n /** Upload files to an action via multipart form-data */\n upload: async (params: UploadParams) => {\n const { service, action, files, fields, fetchOptions } = params;\n return await sendUploadRequest(\n config,\n service,\n action,\n files,\n fields,\n fetchOptions\n );\n },\n };\n}\n","const MIGRATION_MESSAGE = [\n \"createNileSocketClient was removed in @nilejs/client 1.1.0.\",\n \"Create one client with createNileClient({ baseUrl, socket: { auth } })\",\n \"and use nile.socket.send, nile.socket.receive and nile.socket.close.\",\n \"Socket invoke is gone as well; call actions with nile.invoke over HTTP.\",\n \"See https://nile-js.github.io/nile/guide/features/websockets/ and the\",\n \"migration notes in packages/nile/docs/release-migration.md.\",\n].join(\" \");\n\n/**\n * Kept as a throwing stub so an upgrade fails with guidance instead of a\n * missing export.\n *\n * @deprecated Use `createNileClient({ baseUrl, socket: { auth } })` and call\n * `socket.send`, `socket.receive` and `socket.close` on the returned client.\n */\nexport function createNileSocketClient<Actions = unknown, Topics = unknown>(\n _config?: unknown\n): never {\n throw new Error(MIGRATION_MESSAGE);\n}\n"],"mappings":"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"}