@nilejs/client 1.0.1 → 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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","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/** 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 { 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.js","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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nilejs/client",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Standalone, type-safe client for Nile backend framework",
6
6
  "main": "./dist/index.cjs",
@@ -16,6 +16,31 @@
16
16
  "files": [
17
17
  "dist"
18
18
  ],
19
+ "homepage": "https://nile-js.github.io/nile/",
20
+ "bugs": {
21
+ "url": "https://github.com/nile-js/nile/issues"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/nile-js/nile.git",
26
+ "directory": "packages/client"
27
+ },
28
+ "author": "Hussein Kizz",
29
+ "license": "MIT",
30
+ "keywords": [
31
+ "nile",
32
+ "client",
33
+ "backend",
34
+ "typescript",
35
+ "type-safe",
36
+ "fetch",
37
+ "result",
38
+ "socket.io",
39
+ "realtime"
40
+ ],
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
19
44
  "devDependencies": {
20
45
  "tsdown": "^0.23.1",
21
46
  "typescript": "^5",