diff --git a/__snapshots__/mod_test.ts.snap b/__snapshots__/mod_test.ts.snap
index 37a3f94..c71ab9b 100644
--- a/__snapshots__/mod_test.ts.snap
+++ b/__snapshots__/mod_test.ts.snap
@@ -8,3 +8,12 @@ snapshot[`should archive a markdown file with all images 1`] = `
'
`;
+
+snapshot[`should use a fallback image if downloading the image failed 1`] = `
+'# Testing
+
+![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADN0lEQVR4nO2Y3U9SYRzH+T/qstq6sYv+gmKkKaAmaLalazJNjdNoikAo0PSyteWFF3mRlk0Kp64ZDGsElq/LeLtpxYbrwoZ4lq04cG5+7XnwIfDEi43ToTy/7bs9nPNs5/N5Xsb2k0jEEkssscQqdzVq9cebKNMzldb0TUWZQNBoMcPMJcp8snR4rXFXcHAqN4hJrTMdKyqAV54ygW10HCLRLaBpWtBEoltgHX1AROxFBcixqQR4OksC7wJl+lpcYH/LhIamD4RwHV2B0y8SfzW0KED/Zzug8CVA4WPgvIeBKtfvP4qeo/dortzHgLTAXJTrKwH4EhuG7Z1h6FwJ8itQv8RkQuCQkNzLwDkPA2fdaejseWRulZMLf8aVgFh8GGBPjxOLj+TM41XgsKnzpkUOysazBHbiI+kF8aV3sKIE8sXmD2IJBI/G2e94FVB6PoNsSgOyJxo85kOO5k3Al4AL9j6QPqzGaXDcAksoCcYAC+1ryRyIlmUGdO9TMBRiYSiUAt1mCprfCiRgCLBgCbPQ7p7OwJNoXk2DLcyCNcxiyMFQCsxBBM3i59lBIh3rSRjwp8X0gRS0rf4Sb1tl8LuyC6CP6zc+gWxSwRGQTcqhf/0DB/YwsYRZvEjkd9kFLMEfoLR3ceBJFHYNDIW+c8Bu+7dBvdALqoUbeFyqEF1ugTbnWF54knbnWC5IKAVXXGaQzzbgtLqMYA0nhRGQTtQUFUBzdMsbGYjuJUcGnqT7jYN7LwIx6PXYoMdzB4/5ESgGv5/aqVYY9O+CYTMCynk1R0A5p4aBdx9zdknrvQediz04Wu9dvEuCCaA0z1tBvXCTA0+C7oNl/770r7ky8CR9ay5hBVBqniryCqBoXo+D2R+FrpcURwA9E1wA3Ye6mfr8EnON0LE4wIEnEV4A/T88vlhwF+rnmkDj7q5cAXyUpuUFJVTPL1e2gHSiGmodyoISV53XKlgAHaVHNSCfzX8flOg+uLv4FRA6qiMj0EQZ9/711uIMmowaqpUgEYlugeV+urmLGs9FBVp6DadUlCnOd7v8z9rrhhOSUgr14VErmxwnQcEp4x5a+ZLhxRJLLLHEkhyifgJu5rlgZTGQ8gAAAABJRU5ErkJggg==)
+
+
+'
+`;
diff --git a/deno.json b/deno.json
index 671293c..775905f 100644
--- a/deno.json
+++ b/deno.json
@@ -1,7 +1,7 @@
{
"name": "@openformation/markdown-archiver",
"description": "Library for creating self-contained markdown files by embedding images.",
- "version": "1.0.3",
+ "version": "1.0.4",
"exports": "./mod.ts",
"imports": {
"@std/encoding": "jsr:@std/encoding@^0.224.2",
@@ -21,4 +21,4 @@
"__snapshots__"
]
}
-}
+}
\ No newline at end of file
diff --git a/lib/image.ts b/lib/image.ts
index eba6858..7f20bb9 100644
--- a/lib/image.ts
+++ b/lib/image.ts
@@ -28,9 +28,7 @@ const ImageDataUri = Brand.refined(
export function fetchImage(url: string) {
return Effect.gen(function* () {
const response = yield* Effect.tryPromise({
- try: () => {
- return fetch(url);
- },
+ try: () => fetch(url),
catch: (cause: unknown) =>
new FetchImageError(`Failed to fetch image at ${url}: ${cause}`, {
cause,
@@ -38,6 +36,10 @@ export function fetchImage(url: string) {
});
if (!response.ok) {
+ if (response.body) {
+ yield* Effect.promise(response.body.cancel.bind(response.body));
+ }
+
return yield* Effect.fail(
new FetchImageError(
`Failed to fetch image at ${url} as the server responded with a status code of ${response.status}.`,
@@ -76,10 +78,22 @@ export class ImageService extends Context.Tag("ImageService")<
readonly fetch: (
url: string,
) => Effect.Effect;
+ readonly getFallbackImageDataUri: () => Effect.Effect<
+ ImageDataUri
+ >;
}
>() {}
+function getFallbackImageDataUri() {
+ return Effect.succeed(
+ ImageDataUri(
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAADN0lEQVR4nO2Y3U9SYRzH+T/qstq6sYv+gmKkKaAmaLalazJNjdNoikAo0PSyteWFF3mRlk0Kp64ZDGsElq/LeLtpxYbrwoZ4lq04cG5+7XnwIfDEi43ToTy/7bs9nPNs5/N5Xsb2k0jEEkssscQqdzVq9cebKNMzldb0TUWZQNBoMcPMJcp8snR4rXFXcHAqN4hJrTMdKyqAV54ygW10HCLRLaBpWtBEoltgHX1AROxFBcixqQR4OksC7wJl+lpcYH/LhIamD4RwHV2B0y8SfzW0KED/Zzug8CVA4WPgvIeBKtfvP4qeo/dortzHgLTAXJTrKwH4EhuG7Z1h6FwJ8itQv8RkQuCQkNzLwDkPA2fdaejseWRulZMLf8aVgFh8GGBPjxOLj+TM41XgsKnzpkUOysazBHbiI+kF8aV3sKIE8sXmD2IJBI/G2e94FVB6PoNsSgOyJxo85kOO5k3Al4AL9j6QPqzGaXDcAksoCcYAC+1ryRyIlmUGdO9TMBRiYSiUAt1mCprfCiRgCLBgCbPQ7p7OwJNoXk2DLcyCNcxiyMFQCsxBBM3i59lBIh3rSRjwp8X0gRS0rf4Sb1tl8LuyC6CP6zc+gWxSwRGQTcqhf/0DB/YwsYRZvEjkd9kFLMEfoLR3ceBJFHYNDIW+c8Bu+7dBvdALqoUbeFyqEF1ugTbnWF54knbnWC5IKAVXXGaQzzbgtLqMYA0nhRGQTtQUFUBzdMsbGYjuJUcGnqT7jYN7LwIx6PXYoMdzB4/5ESgGv5/aqVYY9O+CYTMCynk1R0A5p4aBdx9zdknrvQediz04Wu9dvEuCCaA0z1tBvXCTA0+C7oNl/770r7ky8CR9ay5hBVBqniryCqBoXo+D2R+FrpcURwA9E1wA3Ye6mfr8EnON0LE4wIEnEV4A/T88vlhwF+rnmkDj7q5cAXyUpuUFJVTPL1e2gHSiGmodyoISV53XKlgAHaVHNSCfzX8flOg+uLv4FRA6qiMj0EQZ9/711uIMmowaqpUgEYlugeV+urmLGs9FBVp6DadUlCnOd7v8z9rrhhOSUgr14VErmxwnQcEp4x5a+ZLhxRJLLLHEkhyifgJu5rlgZTGQ8gAAAABJRU5ErkJggg==",
+ ),
+ );
+}
+
const ImageServiceServer = ImageService.of({
+ getFallbackImageDataUri,
fetch(url: string) {
return Effect.gen(function* () {
const { buffer, mimeType } = yield* fetchImage(url);
@@ -92,6 +106,7 @@ const ImageServiceServer = ImageService.of({
});
const ImageServiceBrowser = ImageService.of({
+ getFallbackImageDataUri,
fetch(url: string) {
return Effect.gen(function* () {
const { buffer } = yield* fetchImage(url);
diff --git a/lib/mod.ts b/lib/mod.ts
index 23babb3..a3a86cf 100644
--- a/lib/mod.ts
+++ b/lib/mod.ts
@@ -20,19 +20,15 @@ function makeServices() {
return Layer.mergeAll(imageService, markdownService, transformerService);
}
-/**
- * The archiver result type. The effect contains either a `string` with
- * the archived markdown or a `FetchImageError` if fetching an image failed.
- */
-export type Archiver = Effect.Effect;
-
/**
* Takes source markdown contents and makes it self-contained by embedding images.
*
* @param markdown {string} The markdown to archive.
* @returns {string} The markdown with embedded images.
*/
-export function archive(markdown: string): Archiver {
+export function archive(
+ markdown: string,
+): Effect.Effect {
const run = Effect.gen(function* () {
const markdownService = yield* MarkdownService;
diff --git a/lib/transformer.ts b/lib/transformer.ts
index 564567a..04d2f22 100644
--- a/lib/transformer.ts
+++ b/lib/transformer.ts
@@ -1,5 +1,4 @@
import type { Html, Image, Root } from "mdast";
-import type { FetchImageError } from "./image.ts";
import { Context, Effect, Layer } from "effect";
import { visit } from "unist-util-visit";
@@ -11,7 +10,7 @@ export class TransformerService extends Context.Tag("TransformerService")<
{
readonly embedImages: (
ast: Root,
- ) => Effect.Effect;
+ ) => Effect.Effect;
}
>() {}
@@ -19,7 +18,10 @@ function processImage(image: Image) {
return Effect.gen(function* () {
const imageService = yield* ImageService;
- const dataUri = yield* imageService.fetch(image.url);
+ const dataUri = yield* Effect.orElse(
+ imageService.fetch(image.url),
+ () => imageService.getFallbackImageDataUri(),
+ );
image.url = dataUri;
});
@@ -37,7 +39,10 @@ function processHtmlImage({
if (match) {
const url = match[1];
- const dataUri = yield* imageService.fetch(url);
+ const dataUri = yield* Effect.orElse(
+ imageService.fetch(url),
+ () => imageService.getFallbackImageDataUri(),
+ );
node.value = node.value.replace(url, dataUri);
}
diff --git a/mod.ts b/mod.ts
index 5a076bd..1dfdb5a 100644
--- a/mod.ts
+++ b/mod.ts
@@ -1,4 +1 @@
-export type { FetchImageError } from "./lib/image.ts";
-export type { Archiver } from "./lib/mod.ts";
-
export { archive } from "./lib/mod.ts";
diff --git a/mod_test.ts b/mod_test.ts
index f5dd446..ce70df8 100644
--- a/mod_test.ts
+++ b/mod_test.ts
@@ -15,3 +15,17 @@ Deno.test("should archive a markdown file with all images", async (t) => {
await assertSnapshot(t, result);
});
+
+Deno.test("should use a fallback image if downloading the image failed", async (t) => {
+ const program = archive(`
+# Testing
+
+![](https://example.com/not-found.jpg)
+
+
+`);
+
+ const result = await Effect.runPromise(program);
+
+ await assertSnapshot(t, result);
+});