Skip to content

Commit 7769f85

Browse files
authored
Merge pull request #2 from OnedocLabs/dsinghvi/patch-sdk
(fix, do not merge): patch sdk for pdf generation
2 parents d74779f + 9d217a1 commit 7769f85

File tree

8 files changed

+4590
-100
lines changed

8 files changed

+4590
-100
lines changed

.fernignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Specify files that shouldn't be modified by Fern
22

3-
src/Client.ts
43
src/core/form-data-utils/FormDataWrapper.ts
54
src/core/form-data-utils/index.ts
65
src/core/index.ts

output.pdf

30.5 KB
Binary file not shown.

package-lock.json

Lines changed: 4442 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Client.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export declare namespace FileForgeClient {
2121
timeoutInSeconds?: number;
2222
maxRetries?: number;
2323
}
24+
25+
interface Response{
26+
file?: string;
27+
url?:string;
28+
}
2429
}
2530

2631
export class FileForgeClient {
@@ -37,20 +42,15 @@ export class FileForgeClient {
3742
files: File[] | fs.ReadStream[],
3843
request: FileForge.GenerateRequest,
3944
requestOptions?: FileForgeClient.RequestOptions
40-
): Promise<stream.Readable> {
45+
): Promise<FileForgeClient.Response>{
4146
const _request = core.newFormData();
4247
const options = await serializers.GenerateRequestOptions.jsonOrThrow(request.options, {
4348
unrecognizedObjectKeys: "passthrough",
4449
allowUnrecognizedUnionMembers: false,
4550
allowUnrecognizedEnumValues: false,
4651
breadcrumbsPrefix: [""],
4752
});
48-
console.log(options);
49-
await _request.append(
50-
"options",
51-
JSON.stringify(options),
52-
{ contentType: "application/json" }
53-
);
53+
await _request.append("options", new Blob([JSON.stringify(options)], { type: "application/json" }));
5454
for (const _file of files) {
5555
await _request.append("files", _file);
5656
}
@@ -74,10 +74,21 @@ export class FileForgeClient {
7474
timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000,
7575
maxRetries: requestOptions?.maxRetries,
7676
});
77-
console.log("_response", JSON.stringify(_response))
77+
7878
if (_response.ok) {
79-
console.log("_response.body", _response.body);
80-
return _response.body;
79+
const chunks: any[] = [];
80+
81+
for await (let chunk of _response.body) {
82+
chunks.push(chunk);
83+
}
84+
85+
const buffer: Buffer = Buffer.concat(chunks);
86+
87+
if (request.options?.host !== true){
88+
return {"file": buffer.toString()};
89+
}else{
90+
return JSON.parse(buffer.toString())
91+
}
8192
}
8293

8394
if (_response.error.reason === "status-code") {

src/core/fetcher/Fetcher.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,39 +66,25 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
6666
: args.url;
6767

6868
let body: BodyInit | undefined = undefined;
69-
const maybeStringifyBody = (body: any) => {
69+
const maybeStringifyBody = async (body: any) => {
7070
if (body instanceof Uint8Array) {
7171
return body;
72+
} else if (body instanceof FormData
73+
|| body instanceof (await import("formdata-node")).FormData
74+
|| body instanceof (await import("form-data")).default) {
75+
return body as any;
7276
} else {
7377
return JSON.stringify(body);
7478
}
7579
};
7680

77-
if (RUNTIME.type === "node") {
78-
if (args.body instanceof (await import("formdata-node")).FormData) {
79-
// @ts-expect-error
80-
body = args.body;
81-
} else {
82-
body = maybeStringifyBody(args.body);
83-
}
84-
} else {
85-
if (args.body instanceof (await import("form-data")).default) {
86-
// @ts-expect-error
87-
body = args.body;
88-
} else {
89-
body = maybeStringifyBody(args.body);
90-
}
91-
}
81+
body = await maybeStringifyBody(args.body);
9282

9383
// In Node.js environments, the SDK always uses`node-fetch`.
9484
// If not in Node.js the SDK uses global fetch if available,
9585
// and falls back to node-fetch.
9686
const fetchFn =
97-
RUNTIME.type === "node"
98-
? // `.default` is required due to this issue:
99-
// https://github.com/node-fetch/node-fetch/issues/450#issuecomment-387045223
100-
((await import("node-fetch")).default as any)
101-
: typeof fetch == "function"
87+
typeof fetch == "function"
10288
? fetch
10389
: ((await import("node-fetch")).default as any);
10490

@@ -208,4 +194,4 @@ async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse
208194
}
209195
}
210196

211-
export const fetcher: FetchFunction = fetcherImpl;
197+
export const fetcher: FetchFunction = fetcherImpl;

src/core/form-data-utils/FormData.ts

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { RUNTIME } from "../runtime";
22

3-
export function newFormData(): FormData {
3+
export function newFormData(): CrossRuntimeFormData {
44
if (RUNTIME.type === "node") {
55
return new NodeFormData();
66
} else {
77
return new WebFormData();
88
}
99
}
1010

11-
export declare namespace FormData {
11+
export declare namespace CrossRuntimeFormData {
1212
interface AppendOptions {
1313
contentType?: string;
1414
}
1515
}
1616

17-
export abstract class FormData {
17+
export abstract class CrossRuntimeFormData {
1818
public abstract append(key: string, value: any, options?: { contentType?: string }): Promise<void>;
1919

2020
/**
@@ -28,48 +28,35 @@ export abstract class FormData {
2828
public abstract getHeaders(): Promise<Record<string, string>>;
2929
}
3030

31-
class NodeFormData implements FormData {
32-
private encoder: any | undefined;
31+
class NodeFormData implements CrossRuntimeFormData {
3332
private fd: any | undefined;
3433

35-
public async initialize(): Promise<void> {
36-
this.fd = new (await import("formdata-node")).FormData();
37-
this.encoder = new (await import("form-data-encoder")).FormDataEncoder(this.fd);
34+
public constructor() {
35+
this.fd = new FormData();
3836
}
3937

4038
public async append(
4139
key: string,
4240
value: any,
4341
options?: { contentType?: string | undefined } | undefined
4442
): Promise<void> {
45-
if (this.fd == null) {
46-
await this.initialize();
47-
}
48-
if (options?.contentType == null) {
49-
this.fd.append(key, value);
43+
if (options?.contentType != null) {
44+
this.fd.append(key, new Blob([value], { type: options?.contentType }));
5045
} else {
51-
this.fd.append(key, new Blob([value], { type: options.contentType }));
46+
this.fd.append(key, value);
5247
}
5348
}
54-
49+
5550
public async getBody(): Promise<any> {
56-
if (this.encoder == null) {
57-
await this.initialize();
58-
}
59-
return (await import("node:stream")).Readable.from(this.encoder);
51+
return this.fd;
6052
}
6153

6254
public async getHeaders(): Promise<Record<string, string>> {
63-
if (this.encoder == null) {
64-
await this.initialize();
65-
}
66-
return {
67-
"Content-Length": this.encoder.length,
68-
};
55+
return {};
6956
}
7057
}
7158

72-
class WebFormData implements FormData {
59+
class WebFormData implements CrossRuntimeFormData {
7360
private fd: any;
7461

7562
public async initialize(): Promise<void> {

tests/custom.test.ts

Lines changed: 106 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
11
import stream from "stream";
2+
import * as core from "../src/core";
23
import { FileForgeClient } from "../src";
4+
import * as error from "../src/errors/index";
35
import fs from "fs";
6+
import { writeFile } from "fs/promises";
47

8+
const FILEFORGE_API_KEY = process.env.FILEFORGE_API_KEY!;
9+
10+
const HTML = `<!DOCTYPE html>
11+
<html>
12+
<head>
13+
<title>My First Web Page</title>
14+
<link href="style.css" rel="stylesheet" />
15+
</head>
16+
<body>
17+
<h1>Hello World!</h1>
18+
</body>
19+
</html>
20+
`;
21+
22+
const CSS =`body{
23+
background-color: lightblue;
24+
}
25+
`;
526
/**
627
* This is a custom test file, if you wish to add more tests
728
* to your SDK.
@@ -11,45 +32,89 @@ import fs from "fs";
1132
* you will have tests automatically generated for you.
1233
*/
1334
describe("test", () => {
14-
it("should generate a PDF", async () => {
15-
const apiKeySupplier = async () => "ebec0c4c-214f-4796-afd2-b5c9b12281b6"; // Replace with your actual API key supplier
16-
const environmentSupplier = async () => "https://api.fileforge.com"; // Replace with your actual environment endpoint
17-
18-
const client = new FileForgeClient({
19-
apiKey: apiKeySupplier,
20-
environment: environmentSupplier,
21-
});
22-
23-
// Write HTML content to a file
24-
const htmlContent = "<h1>Hello World!</h1>";
25-
const blob = new Blob([htmlContent], { type: "text/html" });
26-
const file = new File([blob], "index.html", { type: "text/html" });
27-
28-
const request = {
29-
options: {
30-
fileName: "test.pdf",
31-
test: false,
32-
host: false,
33-
},
34-
};
35-
36-
const pdfStream: stream.Readable = await client.generate([file], request);
37-
console.log(pdfStream);
38-
const pdfFilePath = "output.pdf";
39-
const writeStream = fs.createWriteStream(pdfFilePath);
40-
41-
pdfStream.pipe(writeStream);
42-
43-
return new Promise((resolve, reject) => {
44-
writeStream.on("finish", () => {
45-
console.log("PDF generated and saved to", pdfFilePath);
46-
resolve(true);
47-
});
48-
49-
writeStream.on("error", (error) => {
50-
console.error("Error generating PDF:", error);
51-
reject(error);
52-
});
53-
});
54-
});
35+
it("should generate a PDF buffer", async () => {
36+
const htmlBlob = new Blob([HTML], {
37+
type: "text/html",
38+
});
39+
const cssBlob = new Blob([CSS], {
40+
type: "text/css",
41+
});
42+
const htmlFile = new File([htmlBlob], "index.html", { type: "text/html" });
43+
const cssFile = new File([cssBlob], "style.css", { type: "text/css" });
44+
45+
const ff = new FileForgeClient({
46+
apiKey: FILEFORGE_API_KEY
47+
});
48+
49+
const pdf:FileForgeClient.Response = await ff.generate(
50+
[htmlFile, cssFile],
51+
{
52+
options: {}
53+
}
54+
);
55+
56+
await writeFile("output.pdf", pdf.file!);
57+
}, 10_000_000);
58+
59+
60+
it("should generate a PDF link", async () => {
61+
const htmlBlob = new Blob([HTML], {
62+
type: "text/html",
63+
});
64+
const cssBlob = new Blob([CSS], {
65+
type: "text/css",
66+
});
67+
const htmlFile = new File([htmlBlob], "index.html", { type: "text/html" });
68+
const cssFile = new File([cssBlob], "style.css", { type: "text/css" });
69+
70+
const ff = new FileForgeClient({
71+
apiKey: FILEFORGE_API_KEY
72+
});
73+
74+
const pdf:FileForgeClient.Response = await ff.generate(
75+
[htmlFile, cssFile],
76+
{
77+
options: {
78+
host: true,
79+
}
80+
}
81+
);
82+
83+
expect(pdf.url).not.toBeNull();
84+
85+
}, 10_000_000);
86+
87+
it("should fail because of invalid api key", async () => {
88+
const htmlBlob = new Blob([HTML], {
89+
type: "text/html",
90+
});
91+
const cssBlob = new Blob([CSS], {
92+
type: "text/css",
93+
});
94+
const htmlFile = new File([htmlBlob], "index.html", { type: "text/html" });
95+
const cssFile = new File([cssBlob], "style.css", { type: "text/css" });
96+
97+
const ff = new FileForgeClient({
98+
apiKey: "blabla_invalid_key"
99+
});
100+
try {
101+
const pdf = await ff.generate(
102+
[htmlFile, cssFile],
103+
{
104+
options: {
105+
host: true,
106+
}
107+
}
108+
);
109+
110+
}catch(e){
111+
expect(e).not.toBeNull();
112+
if (e instanceof error.FileForgeError) {
113+
expect(e.statusCode).toBe(401);
114+
}
115+
}
116+
117+
}, 10_000_000);
118+
119+
55120
});

tests/test.html

Whitespace-only changes.

0 commit comments

Comments
 (0)