@aigne/afs-http 1.11.0-beta.3
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.md +26 -0
- package/README.md +318 -0
- package/dist/adapters/express.cjs +74 -0
- package/dist/adapters/express.d.cts +56 -0
- package/dist/adapters/express.d.cts.map +1 -0
- package/dist/adapters/express.d.mts +56 -0
- package/dist/adapters/express.d.mts.map +1 -0
- package/dist/adapters/express.mjs +74 -0
- package/dist/adapters/express.mjs.map +1 -0
- package/dist/adapters/koa.cjs +73 -0
- package/dist/adapters/koa.d.cts +56 -0
- package/dist/adapters/koa.d.cts.map +1 -0
- package/dist/adapters/koa.d.mts +56 -0
- package/dist/adapters/koa.d.mts.map +1 -0
- package/dist/adapters/koa.mjs +73 -0
- package/dist/adapters/koa.mjs.map +1 -0
- package/dist/client.cjs +143 -0
- package/dist/client.d.cts +70 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.mts +70 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +144 -0
- package/dist/client.mjs.map +1 -0
- package/dist/errors.cjs +105 -0
- package/dist/errors.d.cts +63 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +63 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +98 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/handler.cjs +126 -0
- package/dist/handler.d.cts +43 -0
- package/dist/handler.d.cts.map +1 -0
- package/dist/handler.d.mts +43 -0
- package/dist/handler.d.mts.map +1 -0
- package/dist/handler.mjs +127 -0
- package/dist/handler.mjs.map +1 -0
- package/dist/index.cjs +33 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +9 -0
- package/dist/protocol.cjs +68 -0
- package/dist/protocol.d.cts +119 -0
- package/dist/protocol.d.cts.map +1 -0
- package/dist/protocol.d.mts +119 -0
- package/dist/protocol.d.mts.map +1 -0
- package/dist/protocol.mjs +64 -0
- package/dist/protocol.mjs.map +1 -0
- package/dist/retry.cjs +111 -0
- package/dist/retry.d.cts +57 -0
- package/dist/retry.d.cts.map +1 -0
- package/dist/retry.d.mts +57 -0
- package/dist/retry.d.mts.map +1 -0
- package/dist/retry.mjs +105 -0
- package/dist/retry.mjs.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/koa.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Koa-compatible context interface
|
|
6
|
+
*/
|
|
7
|
+
interface KoaContext {
|
|
8
|
+
method: string;
|
|
9
|
+
protocol: string;
|
|
10
|
+
host: string;
|
|
11
|
+
originalUrl: string;
|
|
12
|
+
request: {
|
|
13
|
+
body?: unknown;
|
|
14
|
+
headers: Record<string, string | string[] | undefined>;
|
|
15
|
+
};
|
|
16
|
+
req: Readable;
|
|
17
|
+
status: number;
|
|
18
|
+
body: string;
|
|
19
|
+
set(name: string, value: string): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Koa-compatible next function
|
|
23
|
+
*/
|
|
24
|
+
type KoaNext = () => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Koa-compatible middleware
|
|
27
|
+
*/
|
|
28
|
+
type KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Koa adapter for AFS HTTP handler
|
|
31
|
+
*
|
|
32
|
+
* This adapter converts between Koa context and Web Standard Request/Response
|
|
33
|
+
* objects, handling both scenarios where body parsing middleware is configured
|
|
34
|
+
* (like koa-bodyparser) and where it isn't.
|
|
35
|
+
*
|
|
36
|
+
* @param handler - The AFS HTTP handler function
|
|
37
|
+
* @returns A Koa-compatible middleware
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import Koa from "koa";
|
|
42
|
+
* import Router from "@koa/router";
|
|
43
|
+
* import { createAFSHttpHandler, koaAdapter } from "@aigne/afs-http";
|
|
44
|
+
*
|
|
45
|
+
* const handler = createAFSHttpHandler({ module: provider });
|
|
46
|
+
* const app = new Koa();
|
|
47
|
+
* const router = new Router();
|
|
48
|
+
*
|
|
49
|
+
* router.post("/afs/rpc", koaAdapter(handler));
|
|
50
|
+
* app.use(router.routes());
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function koaAdapter(handler: (request: Request) => Promise<Response>): KoaMiddleware;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { koaAdapter };
|
|
56
|
+
//# sourceMappingURL=koa.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"koa.d.cts","names":[],"sources":["../../src/adapters/koa.ts"],"mappings":";;;;AAA4C;AAc7B;UATL,UAAA;EAAA,MAAA;EAAA,QAAA;EAAA,IAAA;EAAA,WAAA;EAAA,OAAA;IAAA,IAAA;IAAA,OAAA,EAOG,MAAA;EAAA;EAAA,GAAA,EAEN,QAAA;EAAA,MAAA;EAAA,IAAA;EAAA,GAAA,CAAA,IAAA,UAAA,KAAA;AAAA;AAAA;AAAQ;AASa;AATrB,KASF,OAAA,SAAgB,OAAA;AAAA;AAAO;;AAAP,KAKhB,aAAA,IAAA,GAAA,EAAsB,UAAA,EAAA,IAAA,EAAkB,OAAA,KAAY,OAAA;AAAA;;AAwDzD;;;;;;;;;;;;;;;;;;;;;;AAxDyD,iBAwDzC,UAAA,CAAA,OAAA,GAAA,OAAA,EAA8B,OAAA,KAAY,OAAA,CAAQ,QAAA,IAAY,aAAA"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/koa.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Koa-compatible context interface
|
|
6
|
+
*/
|
|
7
|
+
interface KoaContext {
|
|
8
|
+
method: string;
|
|
9
|
+
protocol: string;
|
|
10
|
+
host: string;
|
|
11
|
+
originalUrl: string;
|
|
12
|
+
request: {
|
|
13
|
+
body?: unknown;
|
|
14
|
+
headers: Record<string, string | string[] | undefined>;
|
|
15
|
+
};
|
|
16
|
+
req: Readable;
|
|
17
|
+
status: number;
|
|
18
|
+
body: string;
|
|
19
|
+
set(name: string, value: string): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Koa-compatible next function
|
|
23
|
+
*/
|
|
24
|
+
type KoaNext = () => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Koa-compatible middleware
|
|
27
|
+
*/
|
|
28
|
+
type KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Koa adapter for AFS HTTP handler
|
|
31
|
+
*
|
|
32
|
+
* This adapter converts between Koa context and Web Standard Request/Response
|
|
33
|
+
* objects, handling both scenarios where body parsing middleware is configured
|
|
34
|
+
* (like koa-bodyparser) and where it isn't.
|
|
35
|
+
*
|
|
36
|
+
* @param handler - The AFS HTTP handler function
|
|
37
|
+
* @returns A Koa-compatible middleware
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import Koa from "koa";
|
|
42
|
+
* import Router from "@koa/router";
|
|
43
|
+
* import { createAFSHttpHandler, koaAdapter } from "@aigne/afs-http";
|
|
44
|
+
*
|
|
45
|
+
* const handler = createAFSHttpHandler({ module: provider });
|
|
46
|
+
* const app = new Koa();
|
|
47
|
+
* const router = new Router();
|
|
48
|
+
*
|
|
49
|
+
* router.post("/afs/rpc", koaAdapter(handler));
|
|
50
|
+
* app.use(router.routes());
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function koaAdapter(handler: (request: Request) => Promise<Response>): KoaMiddleware;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { koaAdapter };
|
|
56
|
+
//# sourceMappingURL=koa.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"koa.d.mts","names":[],"sources":["../../src/adapters/koa.ts"],"mappings":";;;;AAA4C;AAc7B;UATL,UAAA;EAAA,MAAA;EAAA,QAAA;EAAA,IAAA;EAAA,WAAA;EAAA,OAAA;IAAA,IAAA;IAAA,OAAA,EAOG,MAAA;EAAA;EAAA,GAAA,EAEN,QAAA;EAAA,MAAA;EAAA,IAAA;EAAA,GAAA,CAAA,IAAA,UAAA,KAAA;AAAA;AAAA;AAAQ;AASa;AATrB,KASF,OAAA,SAAgB,OAAA;AAAA;AAAO;;AAAP,KAKhB,aAAA,IAAA,GAAA,EAAsB,UAAA,EAAA,IAAA,EAAkB,OAAA,KAAY,OAAA;AAAA;;AAwDzD;;;;;;;;;;;;;;;;;;;;;;AAxDyD,iBAwDzC,UAAA,CAAA,OAAA,GAAA,OAAA,EAA8B,OAAA,KAAY,OAAA,CAAQ,QAAA,IAAY,aAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//#region src/adapters/koa.ts
|
|
2
|
+
/**
|
|
3
|
+
* Collect stream data into a string
|
|
4
|
+
*/
|
|
5
|
+
async function streamToString(stream) {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
9
|
+
stream.on("error", reject);
|
|
10
|
+
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Convert Koa headers to Headers object
|
|
15
|
+
*/
|
|
16
|
+
function convertHeaders(headers) {
|
|
17
|
+
const result = new Headers();
|
|
18
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
19
|
+
if (value === void 0) continue;
|
|
20
|
+
if (Array.isArray(value)) for (const v of value) result.append(key, v);
|
|
21
|
+
else result.set(key, value);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Koa adapter for AFS HTTP handler
|
|
27
|
+
*
|
|
28
|
+
* This adapter converts between Koa context and Web Standard Request/Response
|
|
29
|
+
* objects, handling both scenarios where body parsing middleware is configured
|
|
30
|
+
* (like koa-bodyparser) and where it isn't.
|
|
31
|
+
*
|
|
32
|
+
* @param handler - The AFS HTTP handler function
|
|
33
|
+
* @returns A Koa-compatible middleware
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import Koa from "koa";
|
|
38
|
+
* import Router from "@koa/router";
|
|
39
|
+
* import { createAFSHttpHandler, koaAdapter } from "@aigne/afs-http";
|
|
40
|
+
*
|
|
41
|
+
* const handler = createAFSHttpHandler({ module: provider });
|
|
42
|
+
* const app = new Koa();
|
|
43
|
+
* const router = new Router();
|
|
44
|
+
*
|
|
45
|
+
* router.post("/afs/rpc", koaAdapter(handler));
|
|
46
|
+
* app.use(router.routes());
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
function koaAdapter(handler) {
|
|
50
|
+
return async (ctx, _next) => {
|
|
51
|
+
const url = `${ctx.protocol}://${ctx.host}${ctx.originalUrl}`;
|
|
52
|
+
let body;
|
|
53
|
+
if (ctx.method === "POST" || ctx.method === "PUT" || ctx.method === "PATCH") {
|
|
54
|
+
const requestBody = ctx.request.body;
|
|
55
|
+
if (requestBody !== void 0 && requestBody !== null && typeof requestBody === "object" && Object.keys(requestBody).length > 0) body = JSON.stringify(requestBody);
|
|
56
|
+
else body = await streamToString(ctx.req);
|
|
57
|
+
}
|
|
58
|
+
const response = await handler(new Request(url, {
|
|
59
|
+
method: ctx.method,
|
|
60
|
+
headers: convertHeaders(ctx.request.headers),
|
|
61
|
+
body
|
|
62
|
+
}));
|
|
63
|
+
ctx.status = response.status;
|
|
64
|
+
response.headers.forEach((value, key) => {
|
|
65
|
+
ctx.set(key, value);
|
|
66
|
+
});
|
|
67
|
+
ctx.body = await response.text();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { koaAdapter };
|
|
73
|
+
//# sourceMappingURL=koa.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"koa.mjs","names":[],"sources":["../../src/adapters/koa.ts"],"sourcesContent":["import type { Readable } from \"node:stream\";\n\n/**\n * Koa-compatible context interface\n */\ninterface KoaContext {\n method: string;\n protocol: string;\n host: string;\n originalUrl: string;\n request: {\n body?: unknown;\n headers: Record<string, string | string[] | undefined>;\n };\n req: Readable;\n status: number;\n body: string;\n set(name: string, value: string): void;\n}\n\n/**\n * Koa-compatible next function\n */\ntype KoaNext = () => Promise<void>;\n\n/**\n * Koa-compatible middleware\n */\ntype KoaMiddleware = (ctx: KoaContext, next: KoaNext) => Promise<void>;\n\n/**\n * Collect stream data into a string\n */\nasync function streamToString(stream: Readable): Promise<string> {\n const chunks: Buffer[] = [];\n return new Promise((resolve, reject) => {\n stream.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n stream.on(\"error\", reject);\n stream.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf8\")));\n });\n}\n\n/**\n * Convert Koa headers to Headers object\n */\nfunction convertHeaders(headers: Record<string, string | string[] | undefined>): Headers {\n const result = new Headers();\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const v of value) {\n result.append(key, v);\n }\n } else {\n result.set(key, value);\n }\n }\n return result;\n}\n\n/**\n * Koa adapter for AFS HTTP handler\n *\n * This adapter converts between Koa context and Web Standard Request/Response\n * objects, handling both scenarios where body parsing middleware is configured\n * (like koa-bodyparser) and where it isn't.\n *\n * @param handler - The AFS HTTP handler function\n * @returns A Koa-compatible middleware\n *\n * @example\n * ```typescript\n * import Koa from \"koa\";\n * import Router from \"@koa/router\";\n * import { createAFSHttpHandler, koaAdapter } from \"@aigne/afs-http\";\n *\n * const handler = createAFSHttpHandler({ module: provider });\n * const app = new Koa();\n * const router = new Router();\n *\n * router.post(\"/afs/rpc\", koaAdapter(handler));\n * app.use(router.routes());\n * ```\n */\nexport function koaAdapter(handler: (request: Request) => Promise<Response>): KoaMiddleware {\n return async (ctx, _next) => {\n // Build URL from context\n const url = `${ctx.protocol}://${ctx.host}${ctx.originalUrl}`;\n\n // Get body - handle both parsed and unparsed scenarios\n let body: string | undefined;\n\n if (ctx.method === \"POST\" || ctx.method === \"PUT\" || ctx.method === \"PATCH\") {\n const requestBody = ctx.request.body;\n if (\n requestBody !== undefined &&\n requestBody !== null &&\n typeof requestBody === \"object\" &&\n Object.keys(requestBody).length > 0\n ) {\n // Scenario 1: Body already parsed by middleware (e.g., koa-bodyparser)\n body = JSON.stringify(requestBody);\n } else {\n // Scenario 2: Body not parsed, read from stream\n body = await streamToString(ctx.req);\n }\n }\n\n // Create Web Standard Request\n const request = new Request(url, {\n method: ctx.method,\n headers: convertHeaders(ctx.request.headers),\n body,\n });\n\n // Call the handler\n const response = await handler(request);\n\n // Write response back to Koa context\n ctx.status = response.status;\n\n // Copy headers\n response.headers.forEach((value, key) => {\n ctx.set(key, value);\n });\n\n // Set body\n ctx.body = await response.text();\n };\n}\n"],"mappings":";;;;AAiCA,eAAe,eAAe,QAAmC;CAC/D,MAAM,SAAmB,EAAE;AAC3B,QAAO,IAAI,SAAS,SAAS,WAAW;AACtC,SAAO,GAAG,SAAS,UAAkB,OAAO,KAAK,MAAM,CAAC;AACxD,SAAO,GAAG,SAAS,OAAO;AAC1B,SAAO,GAAG,aAAa,QAAQ,OAAO,OAAO,OAAO,CAAC,SAAS,OAAO,CAAC,CAAC;GACvE;;;;;AAMJ,SAAS,eAAe,SAAiE;CACvF,MAAM,SAAS,IAAI,SAAS;AAC5B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;AAClD,MAAI,UAAU,OAAW;AACzB,MAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,KAAK,MACd,QAAO,OAAO,KAAK,EAAE;MAGvB,QAAO,IAAI,KAAK,MAAM;;AAG1B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,WAAW,SAAiE;AAC1F,QAAO,OAAO,KAAK,UAAU;EAE3B,MAAM,MAAM,GAAG,IAAI,SAAS,KAAK,IAAI,OAAO,IAAI;EAGhD,IAAI;AAEJ,MAAI,IAAI,WAAW,UAAU,IAAI,WAAW,SAAS,IAAI,WAAW,SAAS;GAC3E,MAAM,cAAc,IAAI,QAAQ;AAChC,OACE,gBAAgB,UAChB,gBAAgB,QAChB,OAAO,gBAAgB,YACvB,OAAO,KAAK,YAAY,CAAC,SAAS,EAGlC,QAAO,KAAK,UAAU,YAAY;OAGlC,QAAO,MAAM,eAAe,IAAI,IAAI;;EAYxC,MAAM,WAAW,MAAM,QAPP,IAAI,QAAQ,KAAK;GAC/B,QAAQ,IAAI;GACZ,SAAS,eAAe,IAAI,QAAQ,QAAQ;GAC5C;GACD,CAAC,CAGqC;AAGvC,MAAI,SAAS,SAAS;AAGtB,WAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,OAAI,IAAI,KAAK,MAAM;IACnB;AAGF,MAAI,OAAO,MAAM,SAAS,MAAM"}
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const require_errors = require('./errors.cjs');
|
|
2
|
+
const require_retry = require('./retry.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/client.ts
|
|
5
|
+
/**
|
|
6
|
+
* Default client options
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_CLIENT_OPTIONS = {
|
|
9
|
+
timeout: 3e4,
|
|
10
|
+
maxBodySize: 10 * 1024 * 1024,
|
|
11
|
+
accessMode: "readwrite"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* AFS HTTP Client
|
|
15
|
+
*
|
|
16
|
+
* Implements the AFSModule interface, forwarding all operations to a remote
|
|
17
|
+
* AFS server via HTTP RPC calls.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { AFS } from "@aigne/afs";
|
|
22
|
+
* import { AFSHttpClient } from "@aigne/afs-http";
|
|
23
|
+
*
|
|
24
|
+
* const afs = new AFS();
|
|
25
|
+
* afs.mount(
|
|
26
|
+
* new AFSHttpClient({
|
|
27
|
+
* url: "https://remote-afs.example.com/afs/rpc",
|
|
28
|
+
* name: "remote",
|
|
29
|
+
* })
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* // Use like any local provider
|
|
33
|
+
* const result = await afs.list("/modules/remote/some-path");
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
var AFSHttpClient = class {
|
|
37
|
+
name;
|
|
38
|
+
description;
|
|
39
|
+
accessMode;
|
|
40
|
+
url;
|
|
41
|
+
timeout;
|
|
42
|
+
maxBodySize;
|
|
43
|
+
retryOptions;
|
|
44
|
+
constructor(options) {
|
|
45
|
+
if (!options.url) throw new Error("AFSHttpClient requires a url option");
|
|
46
|
+
if (!options.name) throw new Error("AFSHttpClient requires a name option");
|
|
47
|
+
this.url = options.url.endsWith("/rpc") ? options.url : `${options.url}/rpc`;
|
|
48
|
+
this.name = options.name;
|
|
49
|
+
this.description = options.description;
|
|
50
|
+
this.accessMode = options.accessMode ?? DEFAULT_CLIENT_OPTIONS.accessMode;
|
|
51
|
+
this.timeout = options.timeout ?? DEFAULT_CLIENT_OPTIONS.timeout;
|
|
52
|
+
this.maxBodySize = options.maxBodySize ?? DEFAULT_CLIENT_OPTIONS.maxBodySize;
|
|
53
|
+
this.retryOptions = {
|
|
54
|
+
...require_retry.DEFAULT_RETRY_OPTIONS,
|
|
55
|
+
...options.retry
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Make an RPC call to the remote server
|
|
60
|
+
*/
|
|
61
|
+
async rpc(method, params) {
|
|
62
|
+
const body = JSON.stringify({
|
|
63
|
+
method,
|
|
64
|
+
params
|
|
65
|
+
});
|
|
66
|
+
if (body.length > this.maxBodySize) throw new require_errors.AFSRuntimeError(`Request body too large: ${body.length} bytes exceeds limit of ${this.maxBodySize} bytes`);
|
|
67
|
+
let response;
|
|
68
|
+
try {
|
|
69
|
+
response = await require_retry.fetchWithRetry(this.url, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "Content-Type": "application/json" },
|
|
72
|
+
body,
|
|
73
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
74
|
+
}, this.retryOptions);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new require_errors.AFSNetworkError(`Failed to connect to ${this.url}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
|
|
77
|
+
}
|
|
78
|
+
let result;
|
|
79
|
+
try {
|
|
80
|
+
result = await response.json();
|
|
81
|
+
} catch {
|
|
82
|
+
throw new require_errors.AFSRuntimeError(`Invalid response from server: expected JSON, got ${response.headers.get("content-type")}`);
|
|
83
|
+
}
|
|
84
|
+
if (!result.success) {
|
|
85
|
+
const error = result.error;
|
|
86
|
+
if (error) throw new require_errors.AFSRuntimeError(error.message || "Unknown error", {
|
|
87
|
+
code: error.code,
|
|
88
|
+
details: error.details
|
|
89
|
+
});
|
|
90
|
+
throw new require_errors.AFSRuntimeError("Request failed without error details");
|
|
91
|
+
}
|
|
92
|
+
return result.data;
|
|
93
|
+
}
|
|
94
|
+
async list(path, options) {
|
|
95
|
+
return this.rpc("list", {
|
|
96
|
+
path,
|
|
97
|
+
options
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async read(path, options) {
|
|
101
|
+
return this.rpc("read", {
|
|
102
|
+
path,
|
|
103
|
+
options
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async write(path, content, options) {
|
|
107
|
+
return this.rpc("write", {
|
|
108
|
+
path,
|
|
109
|
+
content,
|
|
110
|
+
options
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async delete(path, options) {
|
|
114
|
+
return this.rpc("delete", {
|
|
115
|
+
path,
|
|
116
|
+
options
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async rename(oldPath, newPath, options) {
|
|
120
|
+
return this.rpc("rename", {
|
|
121
|
+
oldPath,
|
|
122
|
+
newPath,
|
|
123
|
+
options
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async search(path, query, options) {
|
|
127
|
+
return this.rpc("search", {
|
|
128
|
+
path,
|
|
129
|
+
query,
|
|
130
|
+
options
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async exec(path, args, options) {
|
|
134
|
+
return this.rpc("exec", {
|
|
135
|
+
path,
|
|
136
|
+
args,
|
|
137
|
+
options
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
exports.AFSHttpClient = AFSHttpClient;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { RetryOptions } from "./retry.cjs";
|
|
2
|
+
import { AFSAccessMode, AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecResult, AFSListOptions, AFSListResult, AFSModule, AFSReadOptions, AFSReadResult, AFSRenameOptions, AFSRenameResult, AFSSearchOptions, AFSSearchResult, AFSWriteEntryPayload, AFSWriteOptions, AFSWriteResult } from "@aigne/afs";
|
|
3
|
+
|
|
4
|
+
//#region src/client.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Options for AFSHttpClient
|
|
7
|
+
*/
|
|
8
|
+
interface AFSHttpClientOptions {
|
|
9
|
+
/** Server URL (required) */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Module name for mounting to AFS (required) */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Module description (optional) */
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Access mode (default: "readwrite") */
|
|
16
|
+
accessMode?: AFSAccessMode;
|
|
17
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
18
|
+
timeout?: number;
|
|
19
|
+
/** Maximum request body size in bytes (default: 10MB) */
|
|
20
|
+
maxBodySize?: number;
|
|
21
|
+
/** Retry configuration */
|
|
22
|
+
retry?: RetryOptions;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* AFS HTTP Client
|
|
26
|
+
*
|
|
27
|
+
* Implements the AFSModule interface, forwarding all operations to a remote
|
|
28
|
+
* AFS server via HTTP RPC calls.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { AFS } from "@aigne/afs";
|
|
33
|
+
* import { AFSHttpClient } from "@aigne/afs-http";
|
|
34
|
+
*
|
|
35
|
+
* const afs = new AFS();
|
|
36
|
+
* afs.mount(
|
|
37
|
+
* new AFSHttpClient({
|
|
38
|
+
* url: "https://remote-afs.example.com/afs/rpc",
|
|
39
|
+
* name: "remote",
|
|
40
|
+
* })
|
|
41
|
+
* );
|
|
42
|
+
*
|
|
43
|
+
* // Use like any local provider
|
|
44
|
+
* const result = await afs.list("/modules/remote/some-path");
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class AFSHttpClient implements AFSModule {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly description?: string;
|
|
50
|
+
readonly accessMode: AFSAccessMode;
|
|
51
|
+
private readonly url;
|
|
52
|
+
private readonly timeout;
|
|
53
|
+
private readonly maxBodySize;
|
|
54
|
+
private readonly retryOptions;
|
|
55
|
+
constructor(options: AFSHttpClientOptions);
|
|
56
|
+
/**
|
|
57
|
+
* Make an RPC call to the remote server
|
|
58
|
+
*/
|
|
59
|
+
private rpc;
|
|
60
|
+
list(path: string, options?: AFSListOptions): Promise<AFSListResult>;
|
|
61
|
+
read(path: string, options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
62
|
+
write(path: string, content: AFSWriteEntryPayload, options?: AFSWriteOptions): Promise<AFSWriteResult>;
|
|
63
|
+
delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
|
|
64
|
+
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<AFSRenameResult>;
|
|
65
|
+
search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
66
|
+
exec(path: string, args: Record<string, unknown>, options: AFSExecOptions): Promise<AFSExecResult>;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { AFSHttpClient, AFSHttpClientOptions };
|
|
70
|
+
//# sourceMappingURL=client.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;AA0BA;AAiDA;UAjDiB,oBAAA;EAAA;EAAA,GAAA;EAAA;EAAA,IAAA;EAAA;EAAA,WAAA;EAAA;EAAA,UAAA,GAQF,aAAA;EAAA;EAAA,OAAA;EAAA;EAAA,WAAA;EAAA;EAAA,KAAA,GAML,YAAA;AAAA;AAAA;AAmCV;;;;;;;;;;;;;;;;;;;;;;AAnCU,cAmCG,aAAA,YAAyB,SAAA;EAAA,SAAA,IAAA;EAAA,SAAA,WAAA;EAAA,SAAA,UAAA,EAGf,aAAA;EAAA,iBAAA,GAAA;EAAA,iBAAA,OAAA;EAAA,iBAAA,WAAA;EAAA,iBAAA,YAAA;EAAA,YAAA,OAAA,EAOA,oBAAA;EAAA;;;EAAA,QAAA,GAAA;EAAA,KAAA,IAAA,UAAA,OAAA,GAoFc,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,KAAA,IAAA,UAAA,OAAA,GAIzB,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,MAAA,IAAA,UAAA,OAAA,EAMjD,oBAAA,EAAA,OAAA,GACC,eAAA,GACT,OAAA,CAAQ,cAAA;EAAA,OAAA,IAAA,UAAA,OAAA,GAI0B,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,OAAA,OAAA,UAAA,OAAA,UAAA,OAAA,GAOpD,gBAAA,GACT,OAAA,CAAQ,eAAA;EAAA,OAAA,IAAA,UAAA,KAAA,UAAA,OAAA,GAIyC,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,KAAA,IAAA,UAAA,IAAA,EAMvE,MAAA,mBAAA,OAAA,EACG,cAAA,GACR,OAAA,CAAQ,aAAA;AAAA"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { RetryOptions } from "./retry.mjs";
|
|
2
|
+
import { AFSAccessMode, AFSDeleteOptions, AFSDeleteResult, AFSExecOptions, AFSExecResult, AFSListOptions, AFSListResult, AFSModule, AFSReadOptions, AFSReadResult, AFSRenameOptions, AFSRenameResult, AFSSearchOptions, AFSSearchResult, AFSWriteEntryPayload, AFSWriteOptions, AFSWriteResult } from "@aigne/afs";
|
|
3
|
+
|
|
4
|
+
//#region src/client.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Options for AFSHttpClient
|
|
7
|
+
*/
|
|
8
|
+
interface AFSHttpClientOptions {
|
|
9
|
+
/** Server URL (required) */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Module name for mounting to AFS (required) */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Module description (optional) */
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Access mode (default: "readwrite") */
|
|
16
|
+
accessMode?: AFSAccessMode;
|
|
17
|
+
/** Request timeout in milliseconds (default: 30000) */
|
|
18
|
+
timeout?: number;
|
|
19
|
+
/** Maximum request body size in bytes (default: 10MB) */
|
|
20
|
+
maxBodySize?: number;
|
|
21
|
+
/** Retry configuration */
|
|
22
|
+
retry?: RetryOptions;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* AFS HTTP Client
|
|
26
|
+
*
|
|
27
|
+
* Implements the AFSModule interface, forwarding all operations to a remote
|
|
28
|
+
* AFS server via HTTP RPC calls.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { AFS } from "@aigne/afs";
|
|
33
|
+
* import { AFSHttpClient } from "@aigne/afs-http";
|
|
34
|
+
*
|
|
35
|
+
* const afs = new AFS();
|
|
36
|
+
* afs.mount(
|
|
37
|
+
* new AFSHttpClient({
|
|
38
|
+
* url: "https://remote-afs.example.com/afs/rpc",
|
|
39
|
+
* name: "remote",
|
|
40
|
+
* })
|
|
41
|
+
* );
|
|
42
|
+
*
|
|
43
|
+
* // Use like any local provider
|
|
44
|
+
* const result = await afs.list("/modules/remote/some-path");
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class AFSHttpClient implements AFSModule {
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly description?: string;
|
|
50
|
+
readonly accessMode: AFSAccessMode;
|
|
51
|
+
private readonly url;
|
|
52
|
+
private readonly timeout;
|
|
53
|
+
private readonly maxBodySize;
|
|
54
|
+
private readonly retryOptions;
|
|
55
|
+
constructor(options: AFSHttpClientOptions);
|
|
56
|
+
/**
|
|
57
|
+
* Make an RPC call to the remote server
|
|
58
|
+
*/
|
|
59
|
+
private rpc;
|
|
60
|
+
list(path: string, options?: AFSListOptions): Promise<AFSListResult>;
|
|
61
|
+
read(path: string, options?: AFSReadOptions): Promise<AFSReadResult>;
|
|
62
|
+
write(path: string, content: AFSWriteEntryPayload, options?: AFSWriteOptions): Promise<AFSWriteResult>;
|
|
63
|
+
delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult>;
|
|
64
|
+
rename(oldPath: string, newPath: string, options?: AFSRenameOptions): Promise<AFSRenameResult>;
|
|
65
|
+
search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult>;
|
|
66
|
+
exec(path: string, args: Record<string, unknown>, options: AFSExecOptions): Promise<AFSExecResult>;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { AFSHttpClient, AFSHttpClientOptions };
|
|
70
|
+
//# sourceMappingURL=client.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client.ts"],"mappings":";;;;;AA0BA;AAiDA;UAjDiB,oBAAA;EAAA;EAAA,GAAA;EAAA;EAAA,IAAA;EAAA;EAAA,WAAA;EAAA;EAAA,UAAA,GAQF,aAAA;EAAA;EAAA,OAAA;EAAA;EAAA,WAAA;EAAA;EAAA,KAAA,GAML,YAAA;AAAA;AAAA;AAmCV;;;;;;;;;;;;;;;;;;;;;;AAnCU,cAmCG,aAAA,YAAyB,SAAA;EAAA,SAAA,IAAA;EAAA,SAAA,WAAA;EAAA,SAAA,UAAA,EAGf,aAAA;EAAA,iBAAA,GAAA;EAAA,iBAAA,OAAA;EAAA,iBAAA,WAAA;EAAA,iBAAA,YAAA;EAAA,YAAA,OAAA,EAOA,oBAAA;EAAA;;;EAAA,QAAA,GAAA;EAAA,KAAA,IAAA,UAAA,OAAA,GAoFc,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,KAAA,IAAA,UAAA,OAAA,GAIzB,cAAA,GAAiB,OAAA,CAAQ,aAAA;EAAA,MAAA,IAAA,UAAA,OAAA,EAMjD,oBAAA,EAAA,OAAA,GACC,eAAA,GACT,OAAA,CAAQ,cAAA;EAAA,OAAA,IAAA,UAAA,OAAA,GAI0B,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,OAAA,OAAA,UAAA,OAAA,UAAA,OAAA,GAOpD,gBAAA,GACT,OAAA,CAAQ,eAAA;EAAA,OAAA,IAAA,UAAA,KAAA,UAAA,OAAA,GAIyC,gBAAA,GAAmB,OAAA,CAAQ,eAAA;EAAA,KAAA,IAAA,UAAA,IAAA,EAMvE,MAAA,mBAAA,OAAA,EACG,cAAA,GACR,OAAA,CAAQ,aAAA;AAAA"}
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { AFSNetworkError, AFSRuntimeError } from "./errors.mjs";
|
|
2
|
+
import { DEFAULT_RETRY_OPTIONS, fetchWithRetry } from "./retry.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/client.ts
|
|
5
|
+
/**
|
|
6
|
+
* Default client options
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_CLIENT_OPTIONS = {
|
|
9
|
+
timeout: 3e4,
|
|
10
|
+
maxBodySize: 10 * 1024 * 1024,
|
|
11
|
+
accessMode: "readwrite"
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* AFS HTTP Client
|
|
15
|
+
*
|
|
16
|
+
* Implements the AFSModule interface, forwarding all operations to a remote
|
|
17
|
+
* AFS server via HTTP RPC calls.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { AFS } from "@aigne/afs";
|
|
22
|
+
* import { AFSHttpClient } from "@aigne/afs-http";
|
|
23
|
+
*
|
|
24
|
+
* const afs = new AFS();
|
|
25
|
+
* afs.mount(
|
|
26
|
+
* new AFSHttpClient({
|
|
27
|
+
* url: "https://remote-afs.example.com/afs/rpc",
|
|
28
|
+
* name: "remote",
|
|
29
|
+
* })
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* // Use like any local provider
|
|
33
|
+
* const result = await afs.list("/modules/remote/some-path");
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
var AFSHttpClient = class {
|
|
37
|
+
name;
|
|
38
|
+
description;
|
|
39
|
+
accessMode;
|
|
40
|
+
url;
|
|
41
|
+
timeout;
|
|
42
|
+
maxBodySize;
|
|
43
|
+
retryOptions;
|
|
44
|
+
constructor(options) {
|
|
45
|
+
if (!options.url) throw new Error("AFSHttpClient requires a url option");
|
|
46
|
+
if (!options.name) throw new Error("AFSHttpClient requires a name option");
|
|
47
|
+
this.url = options.url.endsWith("/rpc") ? options.url : `${options.url}/rpc`;
|
|
48
|
+
this.name = options.name;
|
|
49
|
+
this.description = options.description;
|
|
50
|
+
this.accessMode = options.accessMode ?? DEFAULT_CLIENT_OPTIONS.accessMode;
|
|
51
|
+
this.timeout = options.timeout ?? DEFAULT_CLIENT_OPTIONS.timeout;
|
|
52
|
+
this.maxBodySize = options.maxBodySize ?? DEFAULT_CLIENT_OPTIONS.maxBodySize;
|
|
53
|
+
this.retryOptions = {
|
|
54
|
+
...DEFAULT_RETRY_OPTIONS,
|
|
55
|
+
...options.retry
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Make an RPC call to the remote server
|
|
60
|
+
*/
|
|
61
|
+
async rpc(method, params) {
|
|
62
|
+
const body = JSON.stringify({
|
|
63
|
+
method,
|
|
64
|
+
params
|
|
65
|
+
});
|
|
66
|
+
if (body.length > this.maxBodySize) throw new AFSRuntimeError(`Request body too large: ${body.length} bytes exceeds limit of ${this.maxBodySize} bytes`);
|
|
67
|
+
let response;
|
|
68
|
+
try {
|
|
69
|
+
response = await fetchWithRetry(this.url, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "Content-Type": "application/json" },
|
|
72
|
+
body,
|
|
73
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
74
|
+
}, this.retryOptions);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new AFSNetworkError(`Failed to connect to ${this.url}: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : void 0);
|
|
77
|
+
}
|
|
78
|
+
let result;
|
|
79
|
+
try {
|
|
80
|
+
result = await response.json();
|
|
81
|
+
} catch {
|
|
82
|
+
throw new AFSRuntimeError(`Invalid response from server: expected JSON, got ${response.headers.get("content-type")}`);
|
|
83
|
+
}
|
|
84
|
+
if (!result.success) {
|
|
85
|
+
const error = result.error;
|
|
86
|
+
if (error) throw new AFSRuntimeError(error.message || "Unknown error", {
|
|
87
|
+
code: error.code,
|
|
88
|
+
details: error.details
|
|
89
|
+
});
|
|
90
|
+
throw new AFSRuntimeError("Request failed without error details");
|
|
91
|
+
}
|
|
92
|
+
return result.data;
|
|
93
|
+
}
|
|
94
|
+
async list(path, options) {
|
|
95
|
+
return this.rpc("list", {
|
|
96
|
+
path,
|
|
97
|
+
options
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async read(path, options) {
|
|
101
|
+
return this.rpc("read", {
|
|
102
|
+
path,
|
|
103
|
+
options
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async write(path, content, options) {
|
|
107
|
+
return this.rpc("write", {
|
|
108
|
+
path,
|
|
109
|
+
content,
|
|
110
|
+
options
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async delete(path, options) {
|
|
114
|
+
return this.rpc("delete", {
|
|
115
|
+
path,
|
|
116
|
+
options
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async rename(oldPath, newPath, options) {
|
|
120
|
+
return this.rpc("rename", {
|
|
121
|
+
oldPath,
|
|
122
|
+
newPath,
|
|
123
|
+
options
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async search(path, query, options) {
|
|
127
|
+
return this.rpc("search", {
|
|
128
|
+
path,
|
|
129
|
+
query,
|
|
130
|
+
options
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async exec(path, args, options) {
|
|
134
|
+
return this.rpc("exec", {
|
|
135
|
+
path,
|
|
136
|
+
args,
|
|
137
|
+
options
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
export { AFSHttpClient };
|
|
144
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type {\n AFSAccessMode,\n AFSDeleteOptions,\n AFSDeleteResult,\n AFSExecOptions,\n AFSExecResult,\n AFSListOptions,\n AFSListResult,\n AFSModule,\n AFSReadOptions,\n AFSReadResult,\n AFSRenameOptions,\n AFSRenameResult,\n AFSSearchOptions,\n AFSSearchResult,\n AFSWriteEntryPayload,\n AFSWriteOptions,\n AFSWriteResult,\n} from \"@aigne/afs\";\nimport { AFSNetworkError, AFSRuntimeError } from \"./errors.js\";\nimport type { AFSRpcMethod, AFSRpcResponse, AFSRpcResults } from \"./protocol.js\";\nimport { DEFAULT_RETRY_OPTIONS, fetchWithRetry, type RetryOptions } from \"./retry.js\";\n\n/**\n * Options for AFSHttpClient\n */\nexport interface AFSHttpClientOptions {\n /** Server URL (required) */\n url: string;\n /** Module name for mounting to AFS (required) */\n name: string;\n /** Module description (optional) */\n description?: string;\n /** Access mode (default: \"readwrite\") */\n accessMode?: AFSAccessMode;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Maximum request body size in bytes (default: 10MB) */\n maxBodySize?: number;\n /** Retry configuration */\n retry?: RetryOptions;\n}\n\n/**\n * Default client options\n */\nconst DEFAULT_CLIENT_OPTIONS = {\n timeout: 30000,\n maxBodySize: 10 * 1024 * 1024, // 10MB\n accessMode: \"readwrite\" as AFSAccessMode,\n};\n\n/**\n * AFS HTTP Client\n *\n * Implements the AFSModule interface, forwarding all operations to a remote\n * AFS server via HTTP RPC calls.\n *\n * @example\n * ```typescript\n * import { AFS } from \"@aigne/afs\";\n * import { AFSHttpClient } from \"@aigne/afs-http\";\n *\n * const afs = new AFS();\n * afs.mount(\n * new AFSHttpClient({\n * url: \"https://remote-afs.example.com/afs/rpc\",\n * name: \"remote\",\n * })\n * );\n *\n * // Use like any local provider\n * const result = await afs.list(\"/modules/remote/some-path\");\n * ```\n */\nexport class AFSHttpClient implements AFSModule {\n readonly name: string;\n readonly description?: string;\n readonly accessMode: AFSAccessMode;\n\n private readonly url: string;\n private readonly timeout: number;\n private readonly maxBodySize: number;\n private readonly retryOptions: RetryOptions;\n\n constructor(options: AFSHttpClientOptions) {\n if (!options.url) {\n throw new Error(\"AFSHttpClient requires a url option\");\n }\n if (!options.name) {\n throw new Error(\"AFSHttpClient requires a name option\");\n }\n\n this.url = options.url.endsWith(\"/rpc\") ? options.url : `${options.url}/rpc`;\n this.name = options.name;\n this.description = options.description;\n this.accessMode = options.accessMode ?? DEFAULT_CLIENT_OPTIONS.accessMode;\n this.timeout = options.timeout ?? DEFAULT_CLIENT_OPTIONS.timeout;\n this.maxBodySize = options.maxBodySize ?? DEFAULT_CLIENT_OPTIONS.maxBodySize;\n this.retryOptions = {\n ...DEFAULT_RETRY_OPTIONS,\n ...options.retry,\n };\n }\n\n /**\n * Make an RPC call to the remote server\n */\n private async rpc<M extends AFSRpcMethod>(\n method: M,\n params: Record<string, unknown>,\n ): Promise<AFSRpcResults[M]> {\n const body = JSON.stringify({ method, params });\n\n // Check body size before sending\n if (body.length > this.maxBodySize) {\n throw new AFSRuntimeError(\n `Request body too large: ${body.length} bytes exceeds limit of ${this.maxBodySize} bytes`,\n );\n }\n\n let response: Response;\n try {\n response = await fetchWithRetry(\n this.url,\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body,\n signal: AbortSignal.timeout(this.timeout),\n },\n this.retryOptions,\n );\n } catch (error) {\n throw new AFSNetworkError(\n `Failed to connect to ${this.url}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined,\n );\n }\n\n // Parse response\n let result: AFSRpcResponse<AFSRpcResults[M]>;\n try {\n result = await response.json();\n } catch {\n throw new AFSRuntimeError(\n `Invalid response from server: expected JSON, got ${response.headers.get(\"content-type\")}`,\n );\n }\n\n // Check for RPC-level errors\n if (!result.success) {\n const error = result.error;\n if (error) {\n // Re-throw with the appropriate error type based on code\n const errorMessage = error.message || \"Unknown error\";\n throw new AFSRuntimeError(errorMessage, {\n code: error.code,\n details: error.details,\n });\n }\n throw new AFSRuntimeError(\"Request failed without error details\");\n }\n\n return result.data as AFSRpcResults[M];\n }\n\n async list(path: string, options?: AFSListOptions): Promise<AFSListResult> {\n return this.rpc(\"list\", { path, options });\n }\n\n async read(path: string, options?: AFSReadOptions): Promise<AFSReadResult> {\n return this.rpc(\"read\", { path, options });\n }\n\n async write(\n path: string,\n content: AFSWriteEntryPayload,\n options?: AFSWriteOptions,\n ): Promise<AFSWriteResult> {\n return this.rpc(\"write\", { path, content, options });\n }\n\n async delete(path: string, options?: AFSDeleteOptions): Promise<AFSDeleteResult> {\n return this.rpc(\"delete\", { path, options });\n }\n\n async rename(\n oldPath: string,\n newPath: string,\n options?: AFSRenameOptions,\n ): Promise<AFSRenameResult> {\n return this.rpc(\"rename\", { oldPath, newPath, options });\n }\n\n async search(path: string, query: string, options?: AFSSearchOptions): Promise<AFSSearchResult> {\n return this.rpc(\"search\", { path, query, options });\n }\n\n async exec(\n path: string,\n args: Record<string, unknown>,\n options: AFSExecOptions,\n ): Promise<AFSExecResult> {\n return this.rpc(\"exec\", { path, args, options });\n }\n}\n"],"mappings":";;;;;;;AA8CA,MAAM,yBAAyB;CAC7B,SAAS;CACT,aAAa,KAAK,OAAO;CACzB,YAAY;CACb;;;;;;;;;;;;;;;;;;;;;;;;AAyBD,IAAa,gBAAb,MAAgD;CAC9C,AAAS;CACT,AAAS;CACT,AAAS;CAET,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YAAY,SAA+B;AACzC,MAAI,CAAC,QAAQ,IACX,OAAM,IAAI,MAAM,sCAAsC;AAExD,MAAI,CAAC,QAAQ,KACX,OAAM,IAAI,MAAM,uCAAuC;AAGzD,OAAK,MAAM,QAAQ,IAAI,SAAS,OAAO,GAAG,QAAQ,MAAM,GAAG,QAAQ,IAAI;AACvE,OAAK,OAAO,QAAQ;AACpB,OAAK,cAAc,QAAQ;AAC3B,OAAK,aAAa,QAAQ,cAAc,uBAAuB;AAC/D,OAAK,UAAU,QAAQ,WAAW,uBAAuB;AACzD,OAAK,cAAc,QAAQ,eAAe,uBAAuB;AACjE,OAAK,eAAe;GAClB,GAAG;GACH,GAAG,QAAQ;GACZ;;;;;CAMH,MAAc,IACZ,QACA,QAC2B;EAC3B,MAAM,OAAO,KAAK,UAAU;GAAE;GAAQ;GAAQ,CAAC;AAG/C,MAAI,KAAK,SAAS,KAAK,YACrB,OAAM,IAAI,gBACR,2BAA2B,KAAK,OAAO,0BAA0B,KAAK,YAAY,QACnF;EAGH,IAAI;AACJ,MAAI;AACF,cAAW,MAAM,eACf,KAAK,KACL;IACE,QAAQ;IACR,SAAS,EACP,gBAAgB,oBACjB;IACD;IACA,QAAQ,YAAY,QAAQ,KAAK,QAAQ;IAC1C,EACD,KAAK,aACN;WACM,OAAO;AACd,SAAM,IAAI,gBACR,wBAAwB,KAAK,IAAI,IAAI,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAC3F,iBAAiB,QAAQ,QAAQ,OAClC;;EAIH,IAAI;AACJ,MAAI;AACF,YAAS,MAAM,SAAS,MAAM;UACxB;AACN,SAAM,IAAI,gBACR,oDAAoD,SAAS,QAAQ,IAAI,eAAe,GACzF;;AAIH,MAAI,CAAC,OAAO,SAAS;GACnB,MAAM,QAAQ,OAAO;AACrB,OAAI,MAGF,OAAM,IAAI,gBADW,MAAM,WAAW,iBACE;IACtC,MAAM,MAAM;IACZ,SAAS,MAAM;IAChB,CAAC;AAEJ,SAAM,IAAI,gBAAgB,uCAAuC;;AAGnE,SAAO,OAAO;;CAGhB,MAAM,KAAK,MAAc,SAAkD;AACzE,SAAO,KAAK,IAAI,QAAQ;GAAE;GAAM;GAAS,CAAC;;CAG5C,MAAM,KAAK,MAAc,SAAkD;AACzE,SAAO,KAAK,IAAI,QAAQ;GAAE;GAAM;GAAS,CAAC;;CAG5C,MAAM,MACJ,MACA,SACA,SACyB;AACzB,SAAO,KAAK,IAAI,SAAS;GAAE;GAAM;GAAS;GAAS,CAAC;;CAGtD,MAAM,OAAO,MAAc,SAAsD;AAC/E,SAAO,KAAK,IAAI,UAAU;GAAE;GAAM;GAAS,CAAC;;CAG9C,MAAM,OACJ,SACA,SACA,SAC0B;AAC1B,SAAO,KAAK,IAAI,UAAU;GAAE;GAAS;GAAS;GAAS,CAAC;;CAG1D,MAAM,OAAO,MAAc,OAAe,SAAsD;AAC9F,SAAO,KAAK,IAAI,UAAU;GAAE;GAAM;GAAO;GAAS,CAAC;;CAGrD,MAAM,KACJ,MACA,MACA,SACwB;AACxB,SAAO,KAAK,IAAI,QAAQ;GAAE;GAAM;GAAM;GAAS,CAAC"}
|