@effected/images 0.4.0 → 0.6.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/ImageBackend.js CHANGED
@@ -3,7 +3,7 @@ import { ImageBackendError } from "./ImageBackendError.js";
3
3
  import { DIGEST_PATTERN } from "./internal/digest.js";
4
4
  import { Context, Effect, FileSystem, Layer, Option, Path, Random } from "effect";
5
5
 
6
- //#region src/ImageBackend.ts
6
+ // src/ImageBackend.ts
7
7
  /** `[extension, media type]` pairs, in the fixed order `get` probes them, from the package's one extension table. */
8
8
  const FILES = Object.entries(EXTENSIONS).map(([format, ext]) => [ext, MIME_TYPES[format]]);
9
9
  /** Media type to extension. Read only through `Object.hasOwn`: a content type is caller input. */
@@ -50,6 +50,7 @@ var ImageBackend = class ImageBackend extends Context.Service()("@effected/image
50
50
  for (const [ext, contentType] of FILES) {
51
51
  const bytes = yield* fs.readFile(fileFor(key, ext)).pipe(Effect.map(Option.some), Effect.catchReason("PlatformError", "NotFound", () => Effect.succeedNone), Effect.mapError((cause) => new ImageBackendError({
52
52
  operation: "get",
53
+ reason: "backend",
53
54
  key,
54
55
  cause
55
56
  })));
@@ -65,6 +66,7 @@ var ImageBackend = class ImageBackend extends Context.Service()("@effected/image
65
66
  const ext = params.contentType !== void 0 && Object.hasOwn(EXTENSION_BY_TYPE, params.contentType) ? EXTENSION_BY_TYPE[params.contentType] : void 0;
66
67
  if (ext === void 0) return yield* new ImageBackendError({
67
68
  operation: "set",
69
+ reason: "unsupported-content-type",
68
70
  key: params.key,
69
71
  cause: /* @__PURE__ */ new Error(`unsupported content type ${JSON.stringify(params.contentType ?? null)}`)
70
72
  });
@@ -78,6 +80,7 @@ var ImageBackend = class ImageBackend extends Context.Service()("@effected/image
78
80
  yield* fs.rename(temp, target);
79
81
  }).pipe(Effect.onError(() => fs.remove(temp, { force: true }).pipe(Effect.ignore)), Effect.mapError((cause) => new ImageBackendError({
80
82
  operation: "set",
83
+ reason: "backend",
81
84
  key: params.key,
82
85
  cause
83
86
  })));
@@ -101,11 +104,13 @@ var ImageBackend = class ImageBackend extends Context.Service()("@effected/image
101
104
  contentType: entry.contentType
102
105
  }))), Effect.mapError((cause) => new ImageBackendError({
103
106
  operation: "get",
107
+ reason: "backend",
104
108
  key: k,
105
109
  cause
106
110
  })), Effect.withSpan("ImageBackend.get")),
107
111
  set: (params) => source.set(params).pipe(Effect.mapError((cause) => new ImageBackendError({
108
112
  operation: "set",
113
+ reason: "backend",
109
114
  key: params.key,
110
115
  cause
111
116
  })), Effect.withSpan("ImageBackend.set"))
@@ -113,5 +118,4 @@ var ImageBackend = class ImageBackend extends Context.Service()("@effected/image
113
118
  }
114
119
  };
115
120
 
116
- //#endregion
117
121
  export { ImageBackend };
@@ -1,6 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
 
3
- //#region src/ImageBackendError.ts
3
+ // src/ImageBackendError.ts
4
4
  const causeMessage = (cause) => {
5
5
  let current = cause;
6
6
  for (let depth = 0; depth < 8 && current instanceof Error && current.cause instanceof Error; depth++) current = current.cause;
@@ -9,20 +9,32 @@ const causeMessage = (cause) => {
9
9
  /**
10
10
  * An image backend read or write failed.
11
11
  *
12
+ * @remarks
13
+ * `reason` says why, so a caller branches on a literal rather than reading
14
+ * the message: `backend` is a failure of the store underneath (the file
15
+ * system for `layerDirectory`, the adapted service for `layerFrom`), and
16
+ * `unsupported-content-type` is a `set` whose content type the directory
17
+ * backend has no file extension for. `backend` names the source rather than
18
+ * claiming file IO, because `layerFrom` wraps any source failure — a store
19
+ * `Cache` error included — under it.
20
+ *
21
+ * `reason` is required: a hand-written backend states which case it hit.
22
+ *
12
23
  * @public
13
24
  */
14
25
  var ImageBackendError = class extends Schema.TaggedError()("ImageBackendError", {
15
26
  /** The backend operation that failed. */
16
27
  operation: Schema.Literals(["get", "set"]),
28
+ /** Why it failed: the store underneath, or a content type the backend cannot store. */
29
+ reason: Schema.Literals(["backend", "unsupported-content-type"]),
17
30
  /** The key involved. */
18
31
  key: Schema.String,
19
32
  /** The underlying failure, preserved structurally. */
20
33
  cause: Schema.Defect()
21
34
  }) {
22
35
  get message() {
23
- return `Image backend ${this.operation} failed for key "${this.key}": ${causeMessage(this.cause)}`;
36
+ return `Image backend ${this.operation} failed for key "${this.key}" (${this.reason}): ${causeMessage(this.cause)}`;
24
37
  }
25
38
  };
26
39
 
27
- //#endregion
28
40
  export { ImageBackendError };
package/ImageCache.js CHANGED
@@ -3,7 +3,7 @@ import { ImageBackend } from "./ImageBackend.js";
3
3
  import { ImageGenerateError } from "./ImageGenerateError.js";
4
4
  import { Context, Effect, Layer, Option, Result } from "effect";
5
5
 
6
- //#region src/ImageCache.ts
6
+ // src/ImageCache.ts
7
7
  /**
8
8
  * True when `accept` admits the facts' format. With `accept` omitted the call's `F` is every format, so admitting
9
9
  * everything is exactly the narrowing the guard claims.
@@ -66,5 +66,4 @@ var ImageCache = class ImageCache extends Context.Service()("@effected/images/Im
66
66
  }));
67
67
  };
68
68
 
69
- //#endregion
70
69
  export { ImageCache };
package/ImageCacheKey.js CHANGED
@@ -4,7 +4,7 @@ import { canonicalJson } from "./internal/canonical.js";
4
4
  import { Crypto, Effect, Result, Schema } from "effect";
5
5
  import * as Hex from "effect/encoding/Hex";
6
6
 
7
- //#region src/ImageCacheKey.ts
7
+ // src/ImageCacheKey.ts
8
8
  /**
9
9
  * The identity of one generated image: a SHA-256 digest of the salt and the schema-encoded parameters.
10
10
  *
@@ -52,5 +52,4 @@ var ImageCacheKey = class ImageCacheKey extends Schema.Class("ImageCacheKey")({
52
52
  }
53
53
  };
54
54
 
55
- //#endregion
56
55
  export { ImageCacheKey };
@@ -1,6 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
 
3
- //#region src/ImageCacheKeyError.ts
3
+ // src/ImageCacheKeyError.ts
4
4
  /**
5
5
  * Cache-key parameters that could not be turned into a key.
6
6
  *
@@ -27,5 +27,4 @@ var ImageCacheKeyError = class extends Schema.TaggedError()("ImageCacheKeyError"
27
27
  }
28
28
  };
29
29
 
30
- //#endregion
31
30
  export { ImageCacheKeyError };
package/ImageFacts.js CHANGED
@@ -8,7 +8,7 @@ import { readPng } from "./internal/png.js";
8
8
  import { readWebp } from "./internal/webp.js";
9
9
  import { Effect, Result, Schema } from "effect";
10
10
 
11
- //#region src/ImageFacts.ts
11
+ // src/ImageFacts.ts
12
12
  const READERS = {
13
13
  png: readPng,
14
14
  jpeg: readJpeg,
@@ -74,5 +74,4 @@ var ImageFacts = class ImageFacts extends Schema.Class("ImageFacts")({
74
74
  static fromBytes = Effect.fn("ImageFacts.fromBytes")((bytes) => Effect.fromResult(ImageFacts.fromBytesResult(bytes)));
75
75
  };
76
76
 
77
- //#endregion
78
77
  export { ImageFacts };
package/ImageFormat.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
 
3
- //#region src/ImageFormat.ts
3
+ // src/ImageFormat.ts
4
4
  /**
5
5
  * The image formats `@effected/images` reads.
6
6
  *
@@ -51,5 +51,4 @@ const ImageMimeTypeSchema = Schema.Literals([
51
51
  MIME_TYPES.avif
52
52
  ]);
53
53
 
54
- //#endregion
55
54
  export { EXTENSIONS, ImageFormat, ImageMimeTypeSchema, MIME_TYPES };
@@ -1,7 +1,7 @@
1
1
  import { ImageFormat } from "./ImageFormat.js";
2
2
  import { Schema } from "effect";
3
3
 
4
- //#region src/ImageGenerateError.ts
4
+ // src/ImageGenerateError.ts
5
5
  /**
6
6
  * A generator produced bytes the cache will not store.
7
7
  *
@@ -30,5 +30,4 @@ var ImageGenerateError = class extends Schema.TaggedError()("ImageGenerateError"
30
30
  }
31
31
  };
32
32
 
33
- //#endregion
34
33
  export { ImageGenerateError };
@@ -1,7 +1,7 @@
1
1
  import { ImageFormat } from "./ImageFormat.js";
2
2
  import { Schema } from "effect";
3
3
 
4
- //#region src/ImageParseError.ts
4
+ // src/ImageParseError.ts
5
5
  /**
6
6
  * Bytes that could not be read as an image header.
7
7
  *
@@ -31,5 +31,4 @@ var ImageParseError = class extends Schema.TaggedError()("ImageParseError", {
31
31
  }
32
32
  };
33
33
 
34
- //#endregion
35
34
  export { ImageParseError };
package/README.md CHANGED
@@ -142,7 +142,7 @@ The directory backend stores `<directory>/<key>.<ext>` through a temp file and a
142
142
  - `ImageFormat` — the literal union `png | jpeg | gif | webp | avif`.
143
143
  - `ImageParseError` — the one parse failure, with a `reason` and, once a signature matched, the `format`.
144
144
  - `ImageCacheKey` / `ImageCacheKeyError` — content-addressed keys from schema-encoded parameters; failure reasons `encode`, `non-json` and `digest`.
145
- - `ImageBackend` / `ImageBackendError` — the storage port, a directory backend, a no-store backend and a structural adapter.
145
+ - `ImageBackend` / `ImageBackendError` — the storage port, a directory backend, a no-store backend and a structural adapter; failure reasons `backend` and `unsupported-content-type`.
146
146
  - `ImageCache` / `ImageGenerateError` — `getOrGenerate` with an optional `accept` format list that narrows the result's format.
147
147
 
148
148
  SVG, BMP, ICO, TIFF, HEIC, JPEG XL, EXIF orientation, image transformation and cache eviction are out of scope.
package/cache.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { Context, Crypto, Effect, FileSystem, Layer, Option, Path, Schema } from "effect";
2
2
  import * as Images from "@effected/images";
3
- //#region src/ImageBackendError.d.ts
4
3
  declare const ImageBackendError_base: Schema.Class<ImageBackendError, Schema.TaggedStruct<"ImageBackendError", {
5
4
  /** The backend operation that failed. */
6
5
  readonly operation: Schema.Literals<readonly ["get", "set"]>;
6
+ /** Why it failed: the store underneath, or a content type the backend cannot store. */
7
+ readonly reason: Schema.Literals<readonly ["backend", "unsupported-content-type"]>;
7
8
  /** The key involved. */
8
9
  readonly key: Schema.String;
9
10
  /** The underlying failure, preserved structurally. */
@@ -12,13 +13,22 @@ declare const ImageBackendError_base: Schema.Class<ImageBackendError, Schema.Tag
12
13
  /**
13
14
  * An image backend read or write failed.
14
15
  *
16
+ * @remarks
17
+ * `reason` says why, so a caller branches on a literal rather than reading
18
+ * the message: `backend` is a failure of the store underneath (the file
19
+ * system for `layerDirectory`, the adapted service for `layerFrom`), and
20
+ * `unsupported-content-type` is a `set` whose content type the directory
21
+ * backend has no file extension for. `backend` names the source rather than
22
+ * claiming file IO, because `layerFrom` wraps any source failure — a store
23
+ * `Cache` error included — under it.
24
+ *
25
+ * `reason` is required: a hand-written backend states which case it hit.
26
+ *
15
27
  * @public
16
28
  */
17
29
  export declare class ImageBackendError extends ImageBackendError_base {
18
30
  get message(): string;
19
31
  }
20
- //#endregion
21
- //#region src/ImageBackend.d.ts
22
32
  /**
23
33
  * Bytes and media type a backend holds for one key.
24
34
  *
@@ -107,8 +117,6 @@ export declare class ImageBackend extends ImageBackend_base {
107
117
  */
108
118
  static layerFrom<I>(key: Context.Key<I, ImageBackendSource>): Layer.Layer<ImageBackend, never, I>;
109
119
  }
110
- //#endregion
111
- //#region src/ImageCacheKeyError.d.ts
112
120
  declare const ImageCacheKeyError_base: Schema.Class<ImageCacheKeyError, Schema.TaggedStruct<"ImageCacheKeyError", {
113
121
  /** Which step failed. */
114
122
  readonly reason: Schema.Literals<readonly ["encode", "non-json", "digest"]>;
@@ -129,8 +137,6 @@ declare const ImageCacheKeyError_base: Schema.Class<ImageCacheKeyError, Schema.T
129
137
  export declare class ImageCacheKeyError extends ImageCacheKeyError_base {
130
138
  get message(): string;
131
139
  }
132
- //#endregion
133
- //#region src/ImageCacheKey.d.ts
134
140
  /**
135
141
  * Options for {@link ImageCacheKey.fromParams}.
136
142
  *
@@ -166,8 +172,6 @@ export declare class ImageCacheKey extends ImageCacheKey_base {
166
172
  */
167
173
  static fromParams<S extends Schema.ConstraintEncoder<unknown>>(schema: S, params: S["Type"], options: ImageCacheKeyOptions): Effect.Effect<ImageCacheKey, ImageCacheKeyError, Crypto.Crypto>;
168
174
  }
169
- //#endregion
170
- //#region src/ImageGenerateError.d.ts
171
175
  declare const ImageGenerateError_base: Schema.Class<ImageGenerateError, Schema.TaggedStruct<"ImageGenerateError", {
172
176
  /** Why the output was refused. */
173
177
  readonly reason: Schema.Literals<readonly ["empty", "unparseable", "rejected-format"]>;
@@ -190,8 +194,6 @@ declare const ImageGenerateError_base: Schema.Class<ImageGenerateError, Schema.T
190
194
  export declare class ImageGenerateError extends ImageGenerateError_base {
191
195
  get message(): string;
192
196
  }
193
- //#endregion
194
- //#region src/ImageCache.d.ts
195
197
  /**
196
198
  * Options for {@link ImageCacheShape.getOrGenerate}.
197
199
  *
@@ -252,6 +254,5 @@ export declare class ImageCache extends ImageCache_base {
252
254
  /** The cache over whichever {@link ImageBackend} the app provides. */
253
255
  static readonly layer: Layer.Layer<ImageCache, never, ImageBackend>;
254
256
  }
255
- //#endregion
256
257
  export type { GetOrGenerateOptions, ImageBackendSetParams, ImageBackendShape, ImageBackendSource, ImageCacheKeyOptions, ImageCacheResult, ImageCacheShape, StoredImage };
257
258
  //# sourceMappingURL=cache.d.ts.map
package/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Effect, Result, Schema } from "effect";
2
- //#region src/ImageParseError.d.ts
3
2
  declare const ImageParseError_base: Schema.Class<ImageParseError, Schema.TaggedStruct<"ImageParseError", {
4
3
  /** Why the header could not be read. */
5
4
  readonly reason: Schema.Literals<readonly ["unrecognized", "truncated", "malformed"]>;
@@ -22,8 +21,6 @@ declare const ImageParseError_base: Schema.Class<ImageParseError, Schema.TaggedS
22
21
  export declare class ImageParseError extends ImageParseError_base {
23
22
  get message(): string;
24
23
  }
25
- //#endregion
26
- //#region src/ImageFacts.d.ts
27
24
  /**
28
25
  * One of the five media types `ImageFacts.mimeType` holds, one per `ImageFormat`.
29
26
  *
@@ -77,8 +74,6 @@ export declare class ImageFacts extends ImageFacts_base {
77
74
  */
78
75
  static readonly fromBytes: (bytes: Uint8Array) => Effect.Effect<ImageFacts, ImageParseError>;
79
76
  }
80
- //#endregion
81
- //#region src/ImageFormat.d.ts
82
77
  /**
83
78
  * The image formats `@effected/images` reads.
84
79
  *
@@ -91,6 +86,5 @@ export declare const ImageFormat: Schema.Literals<readonly ["png", "jpeg", "gif"
91
86
  * @public
92
87
  */
93
88
  export type ImageFormat = typeof ImageFormat.Type;
94
- //#endregion
95
89
  export type { ImageExtension, ImageMimeType };
96
90
  //# sourceMappingURL=index.d.ts.map
package/internal/avif.js CHANGED
@@ -2,7 +2,7 @@ import { ascii, u32be } from "./bytes.js";
2
2
  import { dimensions, malformed, truncated } from "./result.js";
3
3
  import { Result } from "effect";
4
4
 
5
- //#region src/internal/avif.ts
5
+ // src/internal/avif.ts
6
6
  /** Boxes the walk may visit before it gives up. Hostile input cannot loop past it. */
7
7
  const AVIF_BOX_BUDGET = 1024;
8
8
  /** Brands scanned in the ftyp box, at most. */
@@ -91,5 +91,4 @@ const readAvif = (b) => {
91
91
  return best === void 0 ? malformed("no ispe property") : dimensions(best.width, best.height);
92
92
  };
93
93
 
94
- //#endregion
95
94
  export { AVIF_BOX_BUDGET, isAvifBrand, readAvif };
package/internal/bytes.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/internal/bytes.ts
1
+ // src/internal/bytes.ts
2
2
  const u16be = (b, o) => b[o] << 8 | b[o + 1];
3
3
  const u16le = (b, o) => b[o] | b[o + 1] << 8;
4
4
  const u24le = (b, o) => b[o] | b[o + 1] << 8 | b[o + 2] << 16;
@@ -10,5 +10,4 @@ const ascii = (b, o, length) => {
10
10
  };
11
11
  const matches = (b, o, signature) => b.length >= o + signature.length && signature.every((byte, i) => b[o + i] === byte);
12
12
 
13
- //#endregion
14
13
  export { ascii, matches, u16be, u16le, u24le, u32be };
@@ -1,6 +1,6 @@
1
1
  import { Result } from "effect";
2
2
 
3
- //#region src/internal/canonical.ts
3
+ // src/internal/canonical.ts
4
4
  const MAX_DEPTH = 256;
5
5
  const isPlainObject = (value) => {
6
6
  const proto = Object.getPrototypeOf(value);
@@ -44,5 +44,4 @@ const encode = (value, depth) => {
44
44
  */
45
45
  const canonicalJson = (value) => encode(value, 0);
46
46
 
47
- //#endregion
48
47
  export { canonicalJson };
@@ -1,7 +1,7 @@
1
1
  import { ascii, matches } from "./bytes.js";
2
2
  import { isAvifBrand } from "./avif.js";
3
3
 
4
- //#region src/internal/detect.ts
4
+ // src/internal/detect.ts
5
5
  const PNG = [
6
6
  137,
7
7
  80,
@@ -27,5 +27,4 @@ const detectFormat = (b) => {
27
27
  if (isAvifBrand(b)) return "avif";
28
28
  };
29
29
 
30
- //#endregion
31
30
  export { detectFormat };
@@ -1,6 +1,5 @@
1
- //#region src/internal/digest.ts
1
+ // src/internal/digest.ts
2
2
  /** A lowercase hex SHA-256 digest: the shape of every image cache key, and the only name a directory backend writes. */
3
3
  const DIGEST_PATTERN = /^[0-9a-f]{64}$/u;
4
4
 
5
- //#endregion
6
5
  export { DIGEST_PATTERN };
package/internal/gif.js CHANGED
@@ -1,9 +1,8 @@
1
1
  import { u16le } from "./bytes.js";
2
2
  import { dimensions, truncated } from "./result.js";
3
3
 
4
- //#region src/internal/gif.ts
4
+ // src/internal/gif.ts
5
5
  /** The logical screen descriptor follows the 6-byte signature: width(2 LE), height(2 LE). */
6
6
  const readGif = (b) => b.length < 10 ? truncated(`need 10 bytes for the screen descriptor, have ${b.length}`) : dimensions(u16le(b, 6), u16le(b, 8));
7
7
 
8
- //#endregion
9
8
  export { readGif };
package/internal/jpeg.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { u16be } from "./bytes.js";
2
2
  import { dimensions, malformed, truncated } from "./result.js";
3
3
 
4
- //#region src/internal/jpeg.ts
4
+ // src/internal/jpeg.ts
5
5
  /** Markers plus fill bytes the walk may consume before it gives up. Hostile input cannot loop past it. */
6
6
  const JPEG_STEP_BUDGET = 4096;
7
7
  /** SOFn frame headers: C0-CF except C4 (DHT), C8 (JPG) and CC (DAC). */
@@ -37,5 +37,4 @@ const readJpeg = (b) => {
37
37
  }
38
38
  };
39
39
 
40
- //#endregion
41
40
  export { JPEG_STEP_BUDGET, readJpeg };
package/internal/png.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ascii, u32be } from "./bytes.js";
2
2
  import { dimensions, malformed, truncated } from "./result.js";
3
3
 
4
- //#region src/internal/png.ts
4
+ // src/internal/png.ts
5
5
  /** IHDR is the first chunk: length(4)=13, type(4)="IHDR", width(4), height(4) after the 8-byte signature. */
6
6
  const readPng = (b) => {
7
7
  if (b.length < 24) return truncated(`need 24 bytes for IHDR dimensions, have ${b.length}`);
@@ -11,5 +11,4 @@ const readPng = (b) => {
11
11
  return dimensions(u32be(b, 16), u32be(b, 20));
12
12
  };
13
13
 
14
- //#endregion
15
14
  export { readPng };
@@ -1,6 +1,6 @@
1
1
  import { Result } from "effect";
2
2
 
3
- //#region src/internal/result.ts
3
+ // src/internal/result.ts
4
4
  const MAX_DIMENSION = 2147483647;
5
5
  const truncated = (detail) => Result.fail({
6
6
  reason: "truncated",
@@ -17,5 +17,4 @@ const dimensions = (width, height) => legal(width) && legal(height) ? Result.suc
17
17
  height
18
18
  }) : malformed(`dimensions ${width}x${height} out of range 1..${MAX_DIMENSION}`);
19
19
 
20
- //#endregion
21
20
  export { MAX_DIMENSION, dimensions, malformed, truncated };
package/internal/webp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ascii, u16le, u24le } from "./bytes.js";
2
2
  import { dimensions, malformed, truncated } from "./result.js";
3
3
 
4
- //#region src/internal/webp.ts
4
+ // src/internal/webp.ts
5
5
  const PAYLOAD = 20;
6
6
  const readWebp = (b) => {
7
7
  if (b.length < 16) return truncated("need the first chunk's fourcc");
@@ -29,5 +29,4 @@ const readWebp = (b) => {
29
29
  }
30
30
  };
31
31
 
32
- //#endregion
33
32
  export { readWebp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/images",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "description": "Image facts (format, MIME type, dimensions) from bytes, and a generated-image cache, for Effect.",
6
6
  "keywords": [