@cogenta/render 0.1.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.
Files changed (205) hide show
  1. package/dist/astro/integration.d.ts +57 -0
  2. package/dist/astro/integration.d.ts.map +1 -0
  3. package/dist/astro/integration.js +72 -0
  4. package/dist/astro/integration.js.map +1 -0
  5. package/dist/build/index.d.ts +7 -0
  6. package/dist/build/index.d.ts.map +1 -0
  7. package/dist/build/index.js +7 -0
  8. package/dist/build/index.js.map +1 -0
  9. package/dist/build/integration.d.ts +25 -0
  10. package/dist/build/integration.d.ts.map +1 -0
  11. package/dist/build/integration.js +41 -0
  12. package/dist/build/integration.js.map +1 -0
  13. package/dist/build/plan.d.ts +41 -0
  14. package/dist/build/plan.d.ts.map +1 -0
  15. package/dist/build/plan.js +48 -0
  16. package/dist/build/plan.js.map +1 -0
  17. package/dist/build/refusal.d.ts +14 -0
  18. package/dist/build/refusal.d.ts.map +1 -0
  19. package/dist/build/refusal.js +110 -0
  20. package/dist/build/refusal.js.map +1 -0
  21. package/dist/build/requirements.d.ts +59 -0
  22. package/dist/build/requirements.d.ts.map +1 -0
  23. package/dist/build/requirements.js +74 -0
  24. package/dist/build/requirements.js.map +1 -0
  25. package/dist/build/run-build.d.ts +43 -0
  26. package/dist/build/run-build.d.ts.map +1 -0
  27. package/dist/build/run-build.js +73 -0
  28. package/dist/build/run-build.js.map +1 -0
  29. package/dist/build/targets.d.ts +47 -0
  30. package/dist/build/targets.d.ts.map +1 -0
  31. package/dist/build/targets.js +69 -0
  32. package/dist/build/targets.js.map +1 -0
  33. package/dist/cache/index.d.ts +7 -0
  34. package/dist/cache/index.d.ts.map +1 -0
  35. package/dist/cache/index.js +4 -0
  36. package/dist/cache/index.js.map +1 -0
  37. package/dist/cache/page-cache.d.ts +64 -0
  38. package/dist/cache/page-cache.d.ts.map +1 -0
  39. package/dist/cache/page-cache.js +128 -0
  40. package/dist/cache/page-cache.js.map +1 -0
  41. package/dist/cache/recorder.d.ts +49 -0
  42. package/dist/cache/recorder.d.ts.map +1 -0
  43. package/dist/cache/recorder.js +77 -0
  44. package/dist/cache/recorder.js.map +1 -0
  45. package/dist/cache/tags.d.ts +55 -0
  46. package/dist/cache/tags.d.ts.map +1 -0
  47. package/dist/cache/tags.js +62 -0
  48. package/dist/cache/tags.js.map +1 -0
  49. package/dist/config.d.ts +48 -0
  50. package/dist/config.d.ts.map +1 -0
  51. package/dist/config.js +44 -0
  52. package/dist/config.js.map +1 -0
  53. package/dist/content/client.d.ts +35 -0
  54. package/dist/content/client.d.ts.map +1 -0
  55. package/dist/content/client.js +155 -0
  56. package/dist/content/client.js.map +1 -0
  57. package/dist/content/types.d.ts +104 -0
  58. package/dist/content/types.d.ts.map +1 -0
  59. package/dist/content/types.js +16 -0
  60. package/dist/content/types.js.map +1 -0
  61. package/dist/context/render-context.d.ts +31 -0
  62. package/dist/context/render-context.d.ts.map +1 -0
  63. package/dist/context/render-context.js +152 -0
  64. package/dist/context/render-context.js.map +1 -0
  65. package/dist/context/types.d.ts +49 -0
  66. package/dist/context/types.d.ts.map +1 -0
  67. package/dist/context/types.js +2 -0
  68. package/dist/context/types.js.map +1 -0
  69. package/dist/images/errors.d.ts +20 -0
  70. package/dist/images/errors.d.ts.map +1 -0
  71. package/dist/images/errors.js +58 -0
  72. package/dist/images/errors.js.map +1 -0
  73. package/dist/images/format.d.ts +18 -0
  74. package/dist/images/format.d.ts.map +1 -0
  75. package/dist/images/format.js +34 -0
  76. package/dist/images/format.js.map +1 -0
  77. package/dist/images/geometry.d.ts +46 -0
  78. package/dist/images/geometry.d.ts.map +1 -0
  79. package/dist/images/geometry.js +127 -0
  80. package/dist/images/geometry.js.map +1 -0
  81. package/dist/images/index.d.ts +29 -0
  82. package/dist/images/index.d.ts.map +1 -0
  83. package/dist/images/index.js +30 -0
  84. package/dist/images/index.js.map +1 -0
  85. package/dist/images/pipeline.d.ts +51 -0
  86. package/dist/images/pipeline.d.ts.map +1 -0
  87. package/dist/images/pipeline.js +105 -0
  88. package/dist/images/pipeline.js.map +1 -0
  89. package/dist/images/sharp.d.ts +57 -0
  90. package/dist/images/sharp.d.ts.map +1 -0
  91. package/dist/images/sharp.js +139 -0
  92. package/dist/images/sharp.js.map +1 -0
  93. package/dist/images/srcset.d.ts +33 -0
  94. package/dist/images/srcset.d.ts.map +1 -0
  95. package/dist/images/srcset.js +94 -0
  96. package/dist/images/srcset.js.map +1 -0
  97. package/dist/images/store.d.ts +32 -0
  98. package/dist/images/store.d.ts.map +1 -0
  99. package/dist/images/store.js +54 -0
  100. package/dist/images/store.js.map +1 -0
  101. package/dist/images/types.d.ts +128 -0
  102. package/dist/images/types.d.ts.map +1 -0
  103. package/dist/images/types.js +22 -0
  104. package/dist/images/types.js.map +1 -0
  105. package/dist/images/wasm.d.ts +43 -0
  106. package/dist/images/wasm.d.ts.map +1 -0
  107. package/dist/images/wasm.js +128 -0
  108. package/dist/images/wasm.js.map +1 -0
  109. package/dist/index.d.ts +29 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +29 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/pwa/build.d.ts +39 -0
  114. package/dist/pwa/build.d.ts.map +1 -0
  115. package/dist/pwa/build.js +38 -0
  116. package/dist/pwa/build.js.map +1 -0
  117. package/dist/pwa/client.d.ts +90 -0
  118. package/dist/pwa/client.d.ts.map +1 -0
  119. package/dist/pwa/client.js +76 -0
  120. package/dist/pwa/client.js.map +1 -0
  121. package/dist/pwa/index.d.ts +29 -0
  122. package/dist/pwa/index.d.ts.map +1 -0
  123. package/dist/pwa/index.js +22 -0
  124. package/dist/pwa/index.js.map +1 -0
  125. package/dist/pwa/manifest.d.ts +69 -0
  126. package/dist/pwa/manifest.d.ts.map +1 -0
  127. package/dist/pwa/manifest.js +108 -0
  128. package/dist/pwa/manifest.js.map +1 -0
  129. package/dist/pwa/offline.d.ts +44 -0
  130. package/dist/pwa/offline.d.ts.map +1 -0
  131. package/dist/pwa/offline.js +94 -0
  132. package/dist/pwa/offline.js.map +1 -0
  133. package/dist/pwa/service-worker.d.ts +71 -0
  134. package/dist/pwa/service-worker.d.ts.map +1 -0
  135. package/dist/pwa/service-worker.js +119 -0
  136. package/dist/pwa/service-worker.js.map +1 -0
  137. package/dist/pwa/strategy.d.ts +36 -0
  138. package/dist/pwa/strategy.d.ts.map +1 -0
  139. package/dist/pwa/strategy.js +180 -0
  140. package/dist/pwa/strategy.js.map +1 -0
  141. package/dist/pwa/sw-runtime.d.ts +109 -0
  142. package/dist/pwa/sw-runtime.d.ts.map +1 -0
  143. package/dist/pwa/sw-runtime.js +214 -0
  144. package/dist/pwa/sw-runtime.js.map +1 -0
  145. package/dist/pwa/types.d.ts +107 -0
  146. package/dist/pwa/types.d.ts.map +1 -0
  147. package/dist/pwa/types.js +18 -0
  148. package/dist/pwa/types.js.map +1 -0
  149. package/dist/pwa/version.d.ts +45 -0
  150. package/dist/pwa/version.d.ts.map +1 -0
  151. package/dist/pwa/version.js +89 -0
  152. package/dist/pwa/version.js.map +1 -0
  153. package/dist/skin/color.d.ts +50 -0
  154. package/dist/skin/color.d.ts.map +1 -0
  155. package/dist/skin/color.js +176 -0
  156. package/dist/skin/color.js.map +1 -0
  157. package/dist/skin/css.d.ts +19 -0
  158. package/dist/skin/css.d.ts.map +1 -0
  159. package/dist/skin/css.js +69 -0
  160. package/dist/skin/css.js.map +1 -0
  161. package/dist/skin/errors.d.ts +22 -0
  162. package/dist/skin/errors.d.ts.map +1 -0
  163. package/dist/skin/errors.js +59 -0
  164. package/dist/skin/errors.js.map +1 -0
  165. package/dist/skin/index.d.ts +15 -0
  166. package/dist/skin/index.d.ts.map +1 -0
  167. package/dist/skin/index.js +11 -0
  168. package/dist/skin/index.js.map +1 -0
  169. package/dist/skin/sheet.d.ts +42 -0
  170. package/dist/skin/sheet.d.ts.map +1 -0
  171. package/dist/skin/sheet.js +36 -0
  172. package/dist/skin/sheet.js.map +1 -0
  173. package/dist/skin/tokens.d.ts +90 -0
  174. package/dist/skin/tokens.d.ts.map +1 -0
  175. package/dist/skin/tokens.js +56 -0
  176. package/dist/skin/tokens.js.map +1 -0
  177. package/dist/skin/validate.d.ts +14 -0
  178. package/dist/skin/validate.d.ts.map +1 -0
  179. package/dist/skin/validate.js +272 -0
  180. package/dist/skin/validate.js.map +1 -0
  181. package/dist/theme/load-theme.d.ts +37 -0
  182. package/dist/theme/load-theme.d.ts.map +1 -0
  183. package/dist/theme/load-theme.js +102 -0
  184. package/dist/theme/load-theme.js.map +1 -0
  185. package/dist/theme/manifest.d.ts +48 -0
  186. package/dist/theme/manifest.d.ts.map +1 -0
  187. package/dist/theme/manifest.js +36 -0
  188. package/dist/theme/manifest.js.map +1 -0
  189. package/dist/theme/verify/forbidden.d.ts +39 -0
  190. package/dist/theme/verify/forbidden.d.ts.map +1 -0
  191. package/dist/theme/verify/forbidden.js +107 -0
  192. package/dist/theme/verify/forbidden.js.map +1 -0
  193. package/dist/theme/verify/scan-file.d.ts +15 -0
  194. package/dist/theme/verify/scan-file.d.ts.map +1 -0
  195. package/dist/theme/verify/scan-file.js +151 -0
  196. package/dist/theme/verify/scan-file.js.map +1 -0
  197. package/dist/theme/verify/scanner.d.ts +52 -0
  198. package/dist/theme/verify/scanner.d.ts.map +1 -0
  199. package/dist/theme/verify/scanner.js +304 -0
  200. package/dist/theme/verify/scanner.js.map +1 -0
  201. package/dist/theme/verify/verify-theme.d.ts +44 -0
  202. package/dist/theme/verify/verify-theme.d.ts.map +1 -0
  203. package/dist/theme/verify/verify-theme.js +179 -0
  204. package/dist/theme/verify/verify-theme.js.map +1 -0
  205. package/package.json +56 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/images/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAoC,MAAM,eAAe,CAAA;AACtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE3C,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE1F,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAEpG,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AACvF,OAAO,EAAE,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAEtF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEvF,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAgBrD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAM5E;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAO,GAAyB,EAAE;IAElC,MAAM,QAAQ,GAAG,oBAAoB,CAAgC;QACnE,IAAI,EAAE,QAAQ;QACd,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;KACpE,CAAC,CAAA;IAEF,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAA;IACrC,QAAQ,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAA;IAEpC,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { type SourceOptions } from './srcset.js';
2
+ import { type VariantStore } from './store.js';
3
+ import { type FocalPoint, type ImageFit, type ImageFormat, type ImageOptions, type ImageSource, type ImageTransformer, type MediaAsset, type RenderedVariant } from './types.js';
4
+ /**
5
+ * The lazy half of the pipeline.
6
+ *
7
+ * `source()` does no work at all — it returns URLs — and `variant()` does the
8
+ * work the first time a browser follows one of them. That split is the answer to
9
+ * the spec's warning: nothing is generated at build time, so a site with ten
10
+ * thousand images and five widths does not have fifty thousand files to produce
11
+ * before it can deploy. What it has is a cache that fills with the variants
12
+ * someone actually looked at.
13
+ */
14
+ export interface VariantParameters {
15
+ readonly id: string;
16
+ readonly width?: number | undefined;
17
+ readonly height?: number | undefined;
18
+ readonly format?: ImageFormat | undefined;
19
+ readonly fit?: ImageFit | undefined;
20
+ /**
21
+ * From the media entity, resolved by the caller. It is not a URL parameter:
22
+ * see the note in `srcset.ts`.
23
+ */
24
+ readonly focal?: FocalPoint | null | undefined;
25
+ }
26
+ export interface ImagePipelineOptions {
27
+ readonly transformer: ImageTransformer;
28
+ /** Reads the original bytes. The pipeline never touches storage itself. */
29
+ readonly load: (id: string) => Promise<Uint8Array>;
30
+ readonly store?: VariantStore | undefined;
31
+ readonly source?: SourceOptions | undefined;
32
+ readonly quality?: number | undefined;
33
+ }
34
+ export interface ImagePipeline {
35
+ readonly driver: string;
36
+ /** Contract D's `ctx.image()`. Pure: builds URLs, generates nothing. */
37
+ source(media: MediaAsset, options?: ImageOptions): ImageSource;
38
+ /** What `/_image` calls. Generates on first ask, then serves from the store. */
39
+ variant(parameters: VariantParameters): Promise<RenderedVariant>;
40
+ }
41
+ export declare function variantKey(parameters: VariantParameters, quality: number): string;
42
+ export declare function createImagePipeline(options: ImagePipelineOptions): ImagePipeline;
43
+ /**
44
+ * The query string of `/_image`, turned into parameters, or refused.
45
+ *
46
+ * Kept next to `variantUrl` in spirit: one function writes the URL, this one
47
+ * reads it, and a public URL is untrusted input like any other — `w=1e9` and
48
+ * `f=../../etc/passwd` arrive here, not at a decoder.
49
+ */
50
+ export declare function parseVariantParameters(query: URLSearchParams): VariantParameters;
51
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/images/pipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAC/D,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,EACL,KAAK,UAAU,EAEf,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,eAAe,EACrB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;GASG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IACnC;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI,GAAG,SAAS,CAAA;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAA;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,wEAAwE;IACxE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CAAA;IAC9D,gFAAgF;IAChF,OAAO,CAAC,UAAU,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAA;CACjE;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAWjF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAqDhF;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,GAAG,iBAAiB,CA0BhF"}
@@ -0,0 +1,105 @@
1
+ import { invalidOptionError } from './errors.js';
2
+ import { DEFAULT_QUALITY, planTransform } from './geometry.js';
3
+ import { describeMedia } from './srcset.js';
4
+ import { createMemoryVariantStore } from './store.js';
5
+ import { IMAGE_FORMATS, } from './types.js';
6
+ export function variantKey(parameters, quality) {
7
+ const focal = parameters.focal;
8
+ return [
9
+ parameters.id,
10
+ `w=${parameters.width ?? ''}`,
11
+ `h=${parameters.height ?? ''}`,
12
+ `f=${parameters.format ?? ''}`,
13
+ `fit=${parameters.fit ?? ''}`,
14
+ `q=${quality}`,
15
+ `focal=${focal === undefined || focal === null ? '' : `${focal.x},${focal.y}`}`,
16
+ ].join('|');
17
+ }
18
+ export function createImagePipeline(options) {
19
+ const store = options.store ?? createMemoryVariantStore();
20
+ const quality = options.quality ?? DEFAULT_QUALITY;
21
+ const sourceOptions = options.source ?? {};
22
+ const { transformer } = options;
23
+ /**
24
+ * Requests for a variant that is being generated right now.
25
+ *
26
+ * Without this, a page with one hero image and twenty simultaneous visitors
27
+ * decodes that image twenty times. It is the difference between a cold cache
28
+ * costing one render and costing as many renders as there are readers.
29
+ */
30
+ const inFlight = new Map();
31
+ async function generate(key, parameters) {
32
+ const bytes = await options.load(parameters.id);
33
+ const metadata = await transformer.metadata(bytes);
34
+ const request = {
35
+ width: parameters.width,
36
+ height: parameters.height,
37
+ format: parameters.format,
38
+ fit: parameters.fit,
39
+ focal: parameters.focal,
40
+ quality,
41
+ };
42
+ const variant = await transformer.transform(bytes, planTransform(metadata, request));
43
+ await store.set(key, variant);
44
+ return variant;
45
+ }
46
+ return {
47
+ driver: transformer.name,
48
+ source: (media, imageOptions) => describeMedia(media, imageOptions ?? {}, sourceOptions),
49
+ variant: async (parameters) => {
50
+ const key = variantKey(parameters, quality);
51
+ const cached = await store.get(key);
52
+ if (cached !== null)
53
+ return cached;
54
+ const running = inFlight.get(key);
55
+ if (running !== undefined)
56
+ return running;
57
+ const work = generate(key, parameters).finally(() => {
58
+ inFlight.delete(key);
59
+ });
60
+ inFlight.set(key, work);
61
+ return work;
62
+ },
63
+ };
64
+ }
65
+ /**
66
+ * The query string of `/_image`, turned into parameters, or refused.
67
+ *
68
+ * Kept next to `variantUrl` in spirit: one function writes the URL, this one
69
+ * reads it, and a public URL is untrusted input like any other — `w=1e9` and
70
+ * `f=../../etc/passwd` arrive here, not at a decoder.
71
+ */
72
+ export function parseVariantParameters(query) {
73
+ const id = query.get('id');
74
+ if (id === null || id === '') {
75
+ throw invalidOptionError('An image request must name the media it wants.', { query: 'id' });
76
+ }
77
+ const format = query.get('f');
78
+ if (format !== null && !IMAGE_FORMATS.includes(format)) {
79
+ throw invalidOptionError(`"${format}" is not a format Cogenta can produce.`, {
80
+ format,
81
+ supported: IMAGE_FORMATS,
82
+ });
83
+ }
84
+ const fit = query.get('fit');
85
+ if (fit !== null && fit !== 'cover' && fit !== 'contain') {
86
+ throw invalidOptionError(`"${fit}" is not an image fit. Use "cover" or "contain".`, { fit });
87
+ }
88
+ return {
89
+ id,
90
+ ...(query.has('w') ? { width: readInteger(query.get('w'), 'w') } : {}),
91
+ ...(query.has('h') ? { height: readInteger(query.get('h'), 'h') } : {}),
92
+ ...(format === null ? {} : { format: format }),
93
+ ...(fit === null ? {} : { fit }),
94
+ };
95
+ }
96
+ function readInteger(raw, name) {
97
+ const value = Number(raw);
98
+ if (raw === null || raw.trim() === '' || !Number.isInteger(value)) {
99
+ throw invalidOptionError(`The "${name}" of an image request must be a whole number.`, {
100
+ [name]: raw,
101
+ });
102
+ }
103
+ return value;
104
+ }
105
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/images/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { type Driver } from '@cogenta/core';
2
+ import type { ImageConfig, ImageTransformer } from './types.js';
3
+ /**
4
+ * The optimal tier: `sharp`, which links a native libvips.
5
+ *
6
+ * An **optional peer dependency**, loaded dynamically, and never a hard one.
7
+ * Rule R10 is explicit and the L3 spec repeats it: `sharp` does not install on
8
+ * ARM, on musl, or on shared hosting. Depending on it directly would mean a
9
+ * `pnpm install` that fails on the hosts Cogenta exists to run on, so its
10
+ * absence is a normal outcome here — it is what makes the registry fall through
11
+ * to the WASM tier.
12
+ *
13
+ * Its types are declared structurally rather than imported, so that a site
14
+ * without `sharp` installed can still typecheck against `@cogenta/render`.
15
+ */
16
+ interface SharpMetadataLike {
17
+ readonly width?: number | undefined;
18
+ readonly height?: number | undefined;
19
+ readonly hasAlpha?: boolean | undefined;
20
+ }
21
+ interface SharpInstanceLike {
22
+ metadata(): Promise<SharpMetadataLike>;
23
+ extract(rect: {
24
+ left: number;
25
+ top: number;
26
+ width: number;
27
+ height: number;
28
+ }): SharpInstanceLike;
29
+ resize(options: {
30
+ width: number;
31
+ height: number;
32
+ fit: 'fill';
33
+ }): SharpInstanceLike;
34
+ flatten(options: {
35
+ background: string;
36
+ }): SharpInstanceLike;
37
+ toFormat(format: string, options?: Readonly<Record<string, unknown>>): SharpInstanceLike;
38
+ toBuffer(options: {
39
+ resolveWithObject: true;
40
+ }): Promise<{
41
+ data: Uint8Array;
42
+ info: {
43
+ width: number;
44
+ height: number;
45
+ };
46
+ }>;
47
+ }
48
+ type SharpFactory = (input: Uint8Array) => SharpInstanceLike;
49
+ /**
50
+ * Loads `sharp` if the host application installed it. `null` is a normal
51
+ * answer, not an error.
52
+ */
53
+ export declare function loadSharpModule(): Promise<SharpFactory | null>;
54
+ export declare function createSharpTransformer(sharp: SharpFactory): ImageTransformer;
55
+ export declare function sharpImageDriver(): Driver<ImageTransformer, ImageConfig>;
56
+ export {};
57
+ //# sourceMappingURL=sharp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharp.d.ts","sourceRoot":"","sources":["../../src/images/sharp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAqB,MAAM,eAAe,CAAA;AAG5E,OAAO,KAAK,EACV,WAAW,EAGX,gBAAgB,EAGjB,MAAM,YAAY,CAAA;AAEnB;;;;;;;;;;;;GAYG;AAEH,UAAU,iBAAiB;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CACxC;AAED,UAAU,iBAAiB;IACzB,QAAQ,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACtC,OAAO,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,iBAAiB,CAAA;IAC9F,MAAM,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,iBAAiB,CAAA;IAClF,OAAO,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,iBAAiB,CAAA;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAA;IACxF,QAAQ,CAAC,OAAO,EAAE;QAChB,iBAAiB,EAAE,IAAI,CAAA;KACxB,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;CAC3E;AAED,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,UAAU,KAAK,iBAAiB,CAAA;AAE5D;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAOpE;AAMD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAiF5E;AAgBD,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAiCxE"}
@@ -0,0 +1,139 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { decodeFailedError, encodeFailedError, unsupportedFormatError } from './errors.js';
3
+ import { contentTypeOf, sniffImageFormat } from './format.js';
4
+ /**
5
+ * Loads `sharp` if the host application installed it. `null` is a normal
6
+ * answer, not an error.
7
+ */
8
+ export async function loadSharpModule() {
9
+ try {
10
+ const module = (await import('sharp'));
11
+ return module.default;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
17
+ function encoderOptions(format, quality) {
18
+ return format === 'png' ? { compressionLevel: 9 } : { quality };
19
+ }
20
+ export function createSharpTransformer(sharp) {
21
+ function open(bytes, mediaId) {
22
+ if (sniffImageFormat(bytes) === null)
23
+ throw unsupportedFormatError(bytes, mediaId);
24
+ return sharp(bytes);
25
+ }
26
+ return {
27
+ name: 'sharp',
28
+ tier: 'optimal',
29
+ metadata: async (bytes) => {
30
+ const format = sniffImageFormat(bytes);
31
+ if (format === null)
32
+ throw unsupportedFormatError(bytes, 'this file');
33
+ let described;
34
+ try {
35
+ described = await open(bytes, 'this file').metadata();
36
+ }
37
+ catch (error) {
38
+ throw decodeFailedError('this file', error);
39
+ }
40
+ const { width, height } = described;
41
+ if (width === undefined || height === undefined) {
42
+ throw decodeFailedError('this file', 'sharp reported no intrinsic size');
43
+ }
44
+ return { width, height, format };
45
+ },
46
+ transform: async (bytes, operation) => {
47
+ let pipeline = open(bytes, 'this file');
48
+ let described;
49
+ try {
50
+ described = await pipeline.metadata();
51
+ }
52
+ catch (error) {
53
+ throw decodeFailedError('this file', error);
54
+ }
55
+ const { crop, resize } = operation;
56
+ if (crop !== null)
57
+ pipeline = pipeline.extract(crop);
58
+ if (resize !== null) {
59
+ // `fill` on purpose: the crop rectangle already carries the aspect
60
+ // ratio, decided by the shared geometry. Letting sharp fit the box a
61
+ // second time would let the two tiers disagree.
62
+ pipeline = pipeline.resize({ width: resize.width, height: resize.height, fit: 'fill' });
63
+ }
64
+ // JPEG has no alpha. sharp drops the channel silently, which turns a
65
+ // transparent background black; the WASM tier flattens onto white. Doing
66
+ // it explicitly makes both tiers produce the same picture.
67
+ if (operation.format === 'jpeg' && described.hasAlpha === true) {
68
+ pipeline = pipeline.flatten({ background: '#ffffff' });
69
+ }
70
+ try {
71
+ const { data, info } = await pipeline
72
+ .toFormat(operation.format, encoderOptions(operation.format, operation.quality))
73
+ .toBuffer({ resolveWithObject: true });
74
+ return {
75
+ bytes: data,
76
+ width: info.width,
77
+ height: info.height,
78
+ format: operation.format,
79
+ contentType: contentTypeOf(operation.format),
80
+ };
81
+ }
82
+ catch (error) {
83
+ // A truncated file survives `metadata()` — the header is intact — and
84
+ // fails here, when the pixels are actually read.
85
+ throw isDecodeFailure(error)
86
+ ? decodeFailedError('this file', error)
87
+ : encodeFailedError('this file', operation.format, 'sharp', error);
88
+ }
89
+ },
90
+ dispose: async () => {
91
+ // sharp holds a process-wide thread pool that other selections share.
92
+ },
93
+ };
94
+ }
95
+ /** libvips says so in words, and the words are the only signal sharp gives. */
96
+ function isDecodeFailure(error) {
97
+ const message = error instanceof Error ? error.message.toLowerCase() : '';
98
+ return (message.includes('unsupported image format') ||
99
+ message.includes('input buffer') ||
100
+ message.includes('corrupt') ||
101
+ message.includes('premature') ||
102
+ message.includes('bad seek') ||
103
+ message.includes('truncated') ||
104
+ message.includes('not enough data'));
105
+ }
106
+ export function sharpImageDriver() {
107
+ let instance;
108
+ return {
109
+ name: 'sharp',
110
+ tier: 'optimal',
111
+ available: async () => (await loadSharpModule()) !== null,
112
+ init: async () => {
113
+ if (instance === undefined) {
114
+ const sharp = await loadSharpModule();
115
+ if (sharp === null) {
116
+ throw new CogentaError({
117
+ code: 'DRIVER_UNAVAILABLE',
118
+ message: 'The sharp image driver was asked for, but sharp is not installed.',
119
+ hint: 'Run `pnpm add sharp`, or leave images.driver on "auto" to use the WebAssembly pipeline, which needs nothing.',
120
+ details: { need: 'images', driver: 'sharp' },
121
+ });
122
+ }
123
+ instance = createSharpTransformer(sharp);
124
+ }
125
+ return instance;
126
+ },
127
+ dispose: async () => {
128
+ await instance?.dispose();
129
+ instance = undefined;
130
+ },
131
+ health: async () => ({
132
+ status: 'ok',
133
+ driver: 'sharp',
134
+ tier: 'optimal',
135
+ message: 'Images are resized by the native libvips behind sharp.',
136
+ }),
137
+ };
138
+ }
139
+ //# sourceMappingURL=sharp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sharp.js","sourceRoot":"","sources":["../../src/images/sharp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { ImageOptions, ImageSource, MediaAsset } from './types.js';
2
+ /**
3
+ * What `ctx.image()` returns, and it returns it **without generating anything**.
4
+ *
5
+ * The spec's warning is the whole design here: variants are N formats × M widths
6
+ * × K contents, and a build that materialises that product does not finish. So a
7
+ * theme only ever gets URLs, and a variant is rendered the first time a browser
8
+ * asks for it — then cached. A page that nobody visits costs nothing.
9
+ *
10
+ * The widths are a fixed ladder rather than anything derived from the request,
11
+ * for the same reason: a countable set of variants can be cached, warmed and
12
+ * purged, and an open-ended one cannot.
13
+ */
14
+ export declare const SRCSET_WIDTHS: readonly number[];
15
+ export interface SourceOptions {
16
+ /** Where image variants are served from. */
17
+ readonly endpoint?: string | undefined;
18
+ /** Where originals are served from — videos, and anything not resized. */
19
+ readonly mediaEndpoint?: string | undefined;
20
+ readonly widths?: readonly number[] | undefined;
21
+ }
22
+ export declare function variantUrl(endpoint: string, media: MediaAsset, width: number, options: ImageOptions): string;
23
+ /** The ladder, capped at the intrinsic width, with the asked-for width always in it. */
24
+ export declare function candidateWidths(requested: number, intrinsic: number | undefined, ladder?: readonly number[]): readonly number[];
25
+ /**
26
+ * Contract D's `ImageSource` for one media entity.
27
+ *
28
+ * A video is not resized. It returns `kind: 'video'`, an empty `srcset` — there
29
+ * is no responsive source set to offer for a video element — and its poster if
30
+ * the media has one.
31
+ */
32
+ export declare function describeMedia(media: MediaAsset, options?: ImageOptions, config?: SourceOptions): ImageSource;
33
+ //# sourceMappingURL=srcset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"srcset.d.ts","sourceRoot":"","sources":["../../src/images/srcset.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAAgC,CAAA;AAE3E,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CAChD;AASD,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,GACpB,MAAM,CAUR;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,GAAE,SAAS,MAAM,EAAkB,GACxC,SAAS,MAAM,EAAE,CAInB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,YAAiB,EAC1B,MAAM,GAAE,aAAkB,GACzB,WAAW,CA8Bb"}
@@ -0,0 +1,94 @@
1
+ import { missingSizeError } from './errors.js';
2
+ /**
3
+ * What `ctx.image()` returns, and it returns it **without generating anything**.
4
+ *
5
+ * The spec's warning is the whole design here: variants are N formats × M widths
6
+ * × K contents, and a build that materialises that product does not finish. So a
7
+ * theme only ever gets URLs, and a variant is rendered the first time a browser
8
+ * asks for it — then cached. A page that nobody visits costs nothing.
9
+ *
10
+ * The widths are a fixed ladder rather than anything derived from the request,
11
+ * for the same reason: a countable set of variants can be cached, warmed and
12
+ * purged, and an open-ended one cannot.
13
+ */
14
+ export const SRCSET_WIDTHS = [320, 640, 960, 1280, 1920];
15
+ const DEFAULT_ENDPOINT = '/_image';
16
+ const DEFAULT_MEDIA_ENDPOINT = '/_media';
17
+ function originalUrl(media, mediaEndpoint) {
18
+ return media.url ?? `${mediaEndpoint}/${encodeURIComponent(media.id)}`;
19
+ }
20
+ export function variantUrl(endpoint, media, width, options) {
21
+ const parameters = new URLSearchParams({ id: media.id, w: String(width) });
22
+ if (options.height !== undefined)
23
+ parameters.set('h', String(options.height));
24
+ if (options.format !== undefined)
25
+ parameters.set('f', options.format);
26
+ if (options.fit !== undefined)
27
+ parameters.set('fit', options.fit);
28
+ // The focal point is deliberately absent: the endpoint reads it from the media
29
+ // entity. Putting it in the URL would let a visitor choose the crop, and would
30
+ // multiply the cache keys of every image by the number of points someone
31
+ // cares to try.
32
+ return `${endpoint}?${parameters.toString()}`;
33
+ }
34
+ /** The ladder, capped at the intrinsic width, with the asked-for width always in it. */
35
+ export function candidateWidths(requested, intrinsic, ladder = SRCSET_WIDTHS) {
36
+ const ceiling = intrinsic ?? requested;
37
+ const offered = ladder.filter((width) => width <= ceiling);
38
+ return [...new Set([...offered, Math.min(requested, ceiling)])].sort((a, b) => a - b);
39
+ }
40
+ /**
41
+ * Contract D's `ImageSource` for one media entity.
42
+ *
43
+ * A video is not resized. It returns `kind: 'video'`, an empty `srcset` — there
44
+ * is no responsive source set to offer for a video element — and its poster if
45
+ * the media has one.
46
+ */
47
+ export function describeMedia(media, options = {}, config = {}) {
48
+ const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
49
+ const mediaEndpoint = config.mediaEndpoint ?? DEFAULT_MEDIA_ENDPOINT;
50
+ const { width, height } = declaredSize(media, options);
51
+ if (media.kind === 'video') {
52
+ const poster = media.poster;
53
+ return {
54
+ kind: 'video',
55
+ src: originalUrl(media, mediaEndpoint),
56
+ srcset: '',
57
+ width,
58
+ height,
59
+ alt: media.alt ?? '',
60
+ focal: media.focal ?? null,
61
+ ...(poster === undefined ? {} : { poster }),
62
+ };
63
+ }
64
+ const widths = candidateWidths(width, media.width, config.widths ?? SRCSET_WIDTHS);
65
+ return {
66
+ kind: 'image',
67
+ src: variantUrl(endpoint, media, width, options),
68
+ srcset: widths.map((w) => `${variantUrl(endpoint, media, w, options)} ${w}w`).join(', '),
69
+ width,
70
+ height,
71
+ alt: media.alt ?? '',
72
+ focal: media.focal ?? null,
73
+ };
74
+ }
75
+ /**
76
+ * The size the `<img>` or `<video>` declares, which is what reserves the space
77
+ * before the file arrives. Refusing to guess is the point: a missing size is a
78
+ * layout shift, and a layout shift is a Lighthouse failure the site owner cannot
79
+ * diagnose from the rendered page.
80
+ */
81
+ function declaredSize(media, options) {
82
+ const width = options.width ?? media.width;
83
+ const height = options.height ??
84
+ (width !== undefined &&
85
+ media.width !== undefined &&
86
+ media.height !== undefined &&
87
+ media.width > 0
88
+ ? Math.round((media.height / media.width) * width)
89
+ : media.height);
90
+ if (width === undefined || height === undefined)
91
+ throw missingSizeError(media.id);
92
+ return { width, height };
93
+ }
94
+ //# sourceMappingURL=srcset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"srcset.js","sourceRoot":"","sources":["../../src/images/srcset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { RenderedVariant } from './types.js';
2
+ /**
3
+ * Where a generated variant lands so it is never generated twice.
4
+ *
5
+ * An interface rather than a concrete cache, because the right answer differs
6
+ * per deployment: a single Node process wants memory, a static build wants the
7
+ * output directory, a fleet wants the object store. The default below is the one
8
+ * that needs nothing, which is the same reasoning rule R1 applies everywhere
9
+ * else.
10
+ *
11
+ * `CacheDriver` from `@cogenta/core` is deliberately *not* reused: it serialises
12
+ * its values as JSON, and a JPEG round-tripped through JSON is no longer a JPEG.
13
+ */
14
+ export interface VariantStore {
15
+ get(key: string): Promise<RenderedVariant | null>;
16
+ set(key: string, variant: RenderedVariant): Promise<void>;
17
+ clear(): Promise<void>;
18
+ }
19
+ export interface MemoryVariantStoreOptions {
20
+ /** Total bytes of variants held before the least recently used are dropped. */
21
+ readonly maxBytes?: number;
22
+ }
23
+ /**
24
+ * In-process, least-recently-used, bounded by bytes rather than by entry count.
25
+ *
26
+ * Bounded by bytes because that is the thing that runs out: a thousand thumbnails
27
+ * and a thousand hero images are the same number of entries and two orders of
28
+ * magnitude apart in memory. The spec's warning about variants exploding is
29
+ * about exactly this.
30
+ */
31
+ export declare function createMemoryVariantStore(options?: MemoryVariantStoreOptions): VariantStore;
32
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/images/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IACjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,yBAAyB;IACxC,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,yBAA8B,GAAG,YAAY,CA4C9F"}
@@ -0,0 +1,54 @@
1
+ const DEFAULT_MAX_BYTES = 64 * 1024 * 1024;
2
+ /**
3
+ * In-process, least-recently-used, bounded by bytes rather than by entry count.
4
+ *
5
+ * Bounded by bytes because that is the thing that runs out: a thousand thumbnails
6
+ * and a thousand hero images are the same number of entries and two orders of
7
+ * magnitude apart in memory. The spec's warning about variants exploding is
8
+ * about exactly this.
9
+ */
10
+ export function createMemoryVariantStore(options = {}) {
11
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
12
+ const entries = new Map();
13
+ let held = 0;
14
+ function drop(key) {
15
+ const existing = entries.get(key);
16
+ if (existing === undefined)
17
+ return;
18
+ held -= existing.bytes.byteLength;
19
+ entries.delete(key);
20
+ }
21
+ function evict() {
22
+ for (const key of entries.keys()) {
23
+ if (held <= maxBytes)
24
+ return;
25
+ drop(key);
26
+ }
27
+ }
28
+ return {
29
+ get: async (key) => {
30
+ const found = entries.get(key);
31
+ if (found === undefined)
32
+ return null;
33
+ // Re-inserting moves it to the end, which is what makes the Map an LRU.
34
+ entries.delete(key);
35
+ entries.set(key, found);
36
+ return found;
37
+ },
38
+ set: async (key, variant) => {
39
+ drop(key);
40
+ // A variant larger than the whole budget is served but not kept: caching
41
+ // it would evict everything else to hold one file.
42
+ if (variant.bytes.byteLength > maxBytes)
43
+ return;
44
+ entries.set(key, variant);
45
+ held += variant.bytes.byteLength;
46
+ evict();
47
+ },
48
+ clear: async () => {
49
+ entries.clear();
50
+ held = 0;
51
+ },
52
+ };
53
+ }
54
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/images/store.ts"],"names":[],"mappings":"AAyBA,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAE1C;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAO,GAA8B,EAAE;IAC9E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAA;IACtD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAA;IAClD,IAAI,IAAI,GAAG,CAAC,CAAA;IAEZ,SAAS,IAAI,CAAC,GAAW;QACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAM;QAClC,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAA;QACjC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACrB,CAAC;IAED,SAAS,KAAK;QACZ,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACjC,IAAI,IAAI,IAAI,QAAQ;gBAAE,OAAM;YAC5B,IAAI,CAAC,GAAG,CAAC,CAAA;QACX,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,GAAW,EAAmC,EAAE;YAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC9B,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAA;YACpC,wEAAwE;YACxE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACnB,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YACvB,OAAO,KAAK,CAAA;QACd,CAAC;QAED,GAAG,EAAE,KAAK,EAAE,GAAW,EAAE,OAAwB,EAAiB,EAAE;YAClE,IAAI,CAAC,GAAG,CAAC,CAAA;YACT,yEAAyE;YACzE,mDAAmD;YACnD,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ;gBAAE,OAAM;YAC/C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;YACzB,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,UAAU,CAAA;YAChC,KAAK,EAAE,CAAA;QACT,CAAC;QAED,KAAK,EAAE,KAAK,IAAmB,EAAE;YAC/B,OAAO,CAAC,KAAK,EAAE,CAAA;YACf,IAAI,GAAG,CAAC,CAAA;QACV,CAAC;KACF,CAAA;AACH,CAAC"}