@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 +21 -0
- package/README.md +92 -2
- package/dist/index.cjs +167 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +95 -64
- package/dist/index.d.ts +95 -64
- package/dist/index.js +167 -131
- package/dist/index.js.map +1 -1
- package/package.json +26 -1
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:**
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
/**
|
|
146
|
+
/** Convert local transport errors to the same client failure shape. */
|
|
158
147
|
function resolveError(error) {
|
|
159
|
-
|
|
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
|
-
/**
|
|
212
|
-
function
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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({
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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;
|
package/dist/index.cjs.map
CHANGED
|
@@ -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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"}
|