Forráskód Böngészése

Merge pull request #2 from saul-jb/feat/isomorphic

feat: Isomorphic Fetch
Saul 1 éve
szülő
commit
99bb6fc300
4 módosított fájl, 38 hozzáadás és 20 törlés
  1. 0 3
      package.json
  2. 25 7
      src/index.ts
  3. 4 1
      src/interfaces.ts
  4. 9 9
      src/utils.ts

+ 0 - 3
package.json

@@ -28,8 +28,5 @@
     "jest": "^29.3.0",
     "ts-jest": "^29.0.3",
     "typescript": "^4.8.4"
-  },
-  "dependencies": {
-    "node-fetch": "^3.3.2"
   }
 }

+ 25 - 7
src/index.ts

@@ -1,6 +1,7 @@
 import * as utils from "./utils.js";
 
 import type {
+	Fetch,
 	Config,
 	TagsResponse,
 	Tag,
@@ -19,15 +20,32 @@ import type {
 
 export class Ollama {
 	private readonly config: Config;
+	private readonly fetch: Fetch;
 
 	constructor (config?: Partial<Config>) {
 		this.config = {
 			address: config?.address ?? "http://localhost:11434"
 		};
+
+		let f: Fetch | null = null;
+
+		if (config?.fetch != null) {
+			f = config.fetch;
+		} else if (typeof fetch !== "undefined") {
+			f = fetch;
+		} else if (typeof window !== "undefined") {
+			f = window.fetch;
+		}
+
+		if (f == null) {
+			throw new Error("unable to find fetch - please define it via 'config.fetch'");
+		}
+
+		this.fetch = f;
 	}
 
 	async tags (): Promise<Tag[]> {
-		const response = await utils.get(`${this.config.address}/api/tags`);
+		const response = await utils.get(this.fetch, `${this.config.address}/api/tags`);
 		const json = await response.json() as TagsResponse;
 
 		return json.models.map(m => ({
@@ -48,7 +66,7 @@ export class Ollama {
 			request.options = parameters;
 		}
 
-		const response = await utils.post(`${this.config.address}/api/generate`, { ...request });
+		const response = await utils.post(this.fetch, `${this.config.address}/api/generate`, { ...request });
 
 		if (!response.body) {
 			throw new Error("Missing body");
@@ -77,7 +95,7 @@ export class Ollama {
 	}
 
 	async * create (name: string, path: string): AsyncGenerator<CreateStatus> {
-		const response = await utils.post(`${this.config.address}/api/create`, { name, path });
+		const response = await utils.post(this.fetch, `${this.config.address}/api/create`, { name, path });
 
 		if (!response.body) {
 			throw new Error("Missing body");
@@ -91,18 +109,18 @@ export class Ollama {
 	}
 
 	async copy (source: string, destination: string): Promise<void> {
-		await utils.post(`${this.config.address}/api/copy`, {
+		await utils.post(this.fetch, `${this.config.address}/api/copy`, {
 			source,
 			destination
 		});
 	}
 
 	async delete (name: string): Promise<void> {
-		await utils.del(`${this.config.address}/api/delete`, { name });
+		await utils.del(this.fetch, `${this.config.address}/api/delete`, { name });
 	}
 
 	async * pull (name: string): AsyncGenerator<PullResult> {
-		const response = await utils.post(`${this.config.address}/api/pull`, { name });
+		const response = await utils.post(this.fetch, `${this.config.address}/api/pull`, { name });
 
 		if (!response.body) {
 			throw new Error("Missing body");
@@ -121,7 +139,7 @@ export class Ollama {
 	}
 
 	async embeddings (model: string, prompt: string, parameters?: Partial<ModelParameters>): Promise<number[]> {
-		const response = await utils.post(`${this.config.address}/api/embeddings`, {
+		const response = await utils.post(this.fetch, `${this.config.address}/api/embeddings`, {
 			model,
 			prompt,
 			options: parameters ?? {}

+ 4 - 1
src/interfaces.ts

@@ -1,5 +1,8 @@
+export type Fetch = typeof fetch
+
 export interface Config {
-	address: string
+	address: string,
+	fetch?: Fetch
 }
 
 export interface ModelParameters {

+ 9 - 9
src/utils.ts

@@ -1,6 +1,4 @@
-import fetch from "node-fetch";
-import type { Response } from "node-fetch";
-import type { ErrorResponse } from "./interfaces.js";
+import type { Fetch, ErrorResponse } from "./interfaces.js";
 
 export const formatAddress = (address: string): string => {
 	if (!address.startsWith("http://") && !address.startsWith("https://")) {
@@ -28,7 +26,7 @@ const checkOk = async (response: Response): Promise<void> => {
 	}
 };
 
-export const get = async (address: string): Promise<Response> => {
+export const get = async (fetch: Fetch, address: string): Promise<Response> => {
 	const response = await fetch(formatAddress(address));
 
 	await checkOk(response);
@@ -36,7 +34,7 @@ export const get = async (address: string): Promise<Response> => {
 	return response;
 };
 
-export const post = async (address: string, data?: Record<string, unknown>): Promise<Response> => {
+export const post = async (fetch: Fetch, address: string, data?: Record<string, unknown>): Promise<Response> => {
 	const response = await fetch(formatAddress(address), {
 		method: "POST",
 		body: JSON.stringify(data)
@@ -47,7 +45,7 @@ export const post = async (address: string, data?: Record<string, unknown>): Pro
 	return response;
 };
 
-export const del = async (address: string, data?: Record<string, unknown>): Promise<Response> => {
+export const del = async (fetch: Fetch, address: string, data?: Record<string, unknown>): Promise<Response> => {
 	const response = await fetch(formatAddress(address), {
 		method: "DELETE",
 		body: JSON.stringify(data)
@@ -58,11 +56,13 @@ export const del = async (address: string, data?: Record<string, unknown>): Prom
 	return response;
 };
 
-export const parseJSON = async function * <T = unknown>(itr: Iterable<{ toString: () => string }> | AsyncIterable<{ toString: () => string }>): AsyncGenerator<T> {
+export const parseJSON = async function * <T = unknown>(itr: ReadableStream<Uint8Array>): AsyncGenerator<T> {
+	const decoder = new TextDecoder("utf-8");
 	let buffer = "";
 
-	for await (const chunk of itr) {
-		buffer += chunk;
+	// TS is a bit strange here, ReadableStreams are AsyncIterable but TS doesn't see it.
+	for await (const chunk of itr as unknown as AsyncIterable<Uint8Array>) {
+		buffer += decoder.decode(chunk);
 
 		const parts = buffer.split("\n");