@valbuild/mcp 0.123.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/CHANGELOG.md +109 -0
- package/README.md +79 -0
- package/dist/asyncToGenerator-500f022f.esm.js +137 -0
- package/dist/asyncToGenerator-8e5c36c8.cjs.prod.js +140 -0
- package/dist/asyncToGenerator-c3823d62.cjs.dev.js +140 -0
- package/dist/declarations/src/images/imageTools.d.ts +14 -0
- package/dist/declarations/src/images/index.d.ts +2 -0
- package/dist/declarations/src/images/remoteUploadTarget.d.ts +70 -0
- package/dist/declarations/src/images/types.d.ts +60 -0
- package/dist/declarations/src/index.d.ts +24 -0
- package/dist/declarations/src/initValMcp.d.ts +95 -0
- package/dist/declarations/src/sharp/index.d.ts +46 -0
- package/dist/declarations/src/tools/createValTools.d.ts +47 -0
- package/dist/declarations/src/tools/defineTool.d.ts +79 -0
- package/dist/declarations/src/tools/index.d.ts +7 -0
- package/dist/declarations/src/tools/types.d.ts +172 -0
- package/dist/declarations/src/tools/writePath.d.ts +160 -0
- package/dist/declarations/src/valAccessToken.d.ts +93 -0
- package/dist/declarations/src/valMcpMetadata.d.ts +47 -0
- package/dist/valbuild-mcp.cjs.d.ts +2 -0
- package/dist/valbuild-mcp.cjs.dev.js +4399 -0
- package/dist/valbuild-mcp.cjs.js +7 -0
- package/dist/valbuild-mcp.cjs.prod.js +4399 -0
- package/dist/valbuild-mcp.esm.js +4381 -0
- package/package.json +66 -0
- package/sharp/dist/valbuild-mcp-sharp.cjs.d.ts +2 -0
- package/sharp/dist/valbuild-mcp-sharp.cjs.dev.js +157 -0
- package/sharp/dist/valbuild-mcp-sharp.cjs.js +7 -0
- package/sharp/dist/valbuild-mcp-sharp.cjs.prod.js +157 -0
- package/sharp/dist/valbuild-mcp-sharp.esm.js +153 -0
- package/sharp/package.json +4 -0
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@valbuild/mcp",
|
|
3
|
+
"private": false,
|
|
4
|
+
"description": "Val - content tools for MCP hosts",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/valbuild/val.git"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/valbuild-mcp.cjs.js",
|
|
10
|
+
"module": "dist/valbuild-mcp.esm.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"module": "./dist/valbuild-mcp.esm.js",
|
|
14
|
+
"default": "./dist/valbuild-mcp.cjs.js"
|
|
15
|
+
},
|
|
16
|
+
"./sharp": {
|
|
17
|
+
"module": "./sharp/dist/valbuild-mcp-sharp.esm.js",
|
|
18
|
+
"default": "./sharp/dist/valbuild-mcp-sharp.cjs.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"types": "dist/valbuild-mcp.cjs.d.ts",
|
|
23
|
+
"version": "0.123.0",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "jest",
|
|
27
|
+
"test:watch": "jest --watch"
|
|
28
|
+
},
|
|
29
|
+
"preconstruct": {
|
|
30
|
+
"entrypoints": [
|
|
31
|
+
"./index.ts",
|
|
32
|
+
"./sharp/index.ts"
|
|
33
|
+
],
|
|
34
|
+
"exports": true
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@valbuild/core": "workspace:*",
|
|
38
|
+
"@valbuild/server": "workspace:*",
|
|
39
|
+
"@valbuild/shared": "workspace:*",
|
|
40
|
+
"minimatch": "^10.2.6",
|
|
41
|
+
"zod": "^4.4.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@prettier/sync": "^0.6.1",
|
|
45
|
+
"@types/jest": "^30.0.0",
|
|
46
|
+
"prettier": "~3.8.5",
|
|
47
|
+
"sharp": "^0.35.4",
|
|
48
|
+
"sucrase": "^3.35.1",
|
|
49
|
+
"typescript": "^6.0.3"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": "^20.19.0 || >=22"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"CMS",
|
|
56
|
+
"mcp",
|
|
57
|
+
"val",
|
|
58
|
+
"valbuild"
|
|
59
|
+
],
|
|
60
|
+
"files": [
|
|
61
|
+
"CHANGELOG.md",
|
|
62
|
+
"dist",
|
|
63
|
+
"sharp/dist",
|
|
64
|
+
"sharp/package.json"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export * from "../../dist/declarations/src/sharp/index.js";
|
|
2
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsYnVpbGQtbWNwLXNoYXJwLmNqcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vZGlzdC9kZWNsYXJhdGlvbnMvc3JjL3NoYXJwL2luZGV4LmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEifQ==
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var asyncToGenerator = require('../../dist/asyncToGenerator-c3823d62.cjs.dev.js');
|
|
6
|
+
var core = require('@valbuild/core');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `sharp`, as the image tool wants it.
|
|
10
|
+
*
|
|
11
|
+
* The library is passed IN rather than imported, and that is the whole point of
|
|
12
|
+
* this file existing separately. `sharp` ships a compiled binary per platform;
|
|
13
|
+
* importing it here would put one in every project that installs Val, for a
|
|
14
|
+
* feature most of them will not mount. Instead the project installs `sharp`
|
|
15
|
+
* itself and writes:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import sharp from "sharp";
|
|
19
|
+
* import { createValImageTools } from "@valbuild/mcp";
|
|
20
|
+
* import { sharpImageProcessor } from "@valbuild/mcp/sharp";
|
|
21
|
+
*
|
|
22
|
+
* const tools = createValImageTools(sharpImageProcessor(sharp));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* The parameter is typed structurally — {@link SharpLike} — rather than as
|
|
26
|
+
* `import("sharp")`, for the same reason: a type-only import of a package that
|
|
27
|
+
* is not installed is still a compile error, and this package must typecheck in
|
|
28
|
+
* a project that has never heard of sharp. The real `sharp` satisfies it, and
|
|
29
|
+
* `sharpImageProcessor.test.ts` asserts as much against the actual library so
|
|
30
|
+
* the structural type cannot drift away from it unnoticed.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** What `sharp(bytes)` gives back, narrowed to what this file calls. */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Which sharp formats are worth calling an image.
|
|
37
|
+
*
|
|
38
|
+
* Mapped explicitly rather than by pasting `image/` in front of
|
|
39
|
+
* `metadata.format`, because sharp's names and the mime types disagree in the
|
|
40
|
+
* cases that matter — `jpeg` vs `image/jpeg` is fine, but `heif` covers AVIF,
|
|
41
|
+
* and `svg` is a format sharp reads and Val stores as text. An unmapped format
|
|
42
|
+
* is reported as "not an image this library can read", which is the honest
|
|
43
|
+
* answer and is recoverable: the caller can convert it themselves.
|
|
44
|
+
*/
|
|
45
|
+
var MIME_TYPE_OF_FORMAT = {
|
|
46
|
+
avif: "image/avif",
|
|
47
|
+
gif: "image/gif",
|
|
48
|
+
jpeg: "image/jpeg",
|
|
49
|
+
jpg: "image/jpeg",
|
|
50
|
+
jp2: "image/jp2",
|
|
51
|
+
png: "image/png",
|
|
52
|
+
svg: "image/svg+xml",
|
|
53
|
+
tiff: "image/tiff",
|
|
54
|
+
webp: "image/webp"
|
|
55
|
+
};
|
|
56
|
+
function sharpImageProcessor(sharp) {
|
|
57
|
+
return {
|
|
58
|
+
read: function read(bytes) {
|
|
59
|
+
return asyncToGenerator._asyncToGenerator(/*#__PURE__*/asyncToGenerator._regenerator().m(function _callee() {
|
|
60
|
+
var metadata, _metadata, width, height, format, mimeType;
|
|
61
|
+
return asyncToGenerator._regenerator().w(function (_context) {
|
|
62
|
+
while (1) switch (_context.p = _context.n) {
|
|
63
|
+
case 0:
|
|
64
|
+
_context.p = 0;
|
|
65
|
+
_context.n = 1;
|
|
66
|
+
return sharp(bytes).metadata();
|
|
67
|
+
case 1:
|
|
68
|
+
metadata = _context.v;
|
|
69
|
+
_context.n = 3;
|
|
70
|
+
break;
|
|
71
|
+
case 2:
|
|
72
|
+
_context.p = 2;
|
|
73
|
+
_context.v;
|
|
74
|
+
return _context.a(2, null);
|
|
75
|
+
case 3:
|
|
76
|
+
_metadata = metadata, width = _metadata.width, height = _metadata.height, format = _metadata.format;
|
|
77
|
+
if (!(!width || !height || !format)) {
|
|
78
|
+
_context.n = 4;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
return _context.a(2, null);
|
|
82
|
+
case 4:
|
|
83
|
+
mimeType = MIME_TYPE_OF_FORMAT[format];
|
|
84
|
+
if (mimeType) {
|
|
85
|
+
_context.n = 5;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return _context.a(2, null);
|
|
89
|
+
case 5:
|
|
90
|
+
if (core.Internal.MIME_TYPES_TO_EXT[mimeType]) {
|
|
91
|
+
_context.n = 6;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return _context.a(2, null);
|
|
95
|
+
case 6:
|
|
96
|
+
return _context.a(2, {
|
|
97
|
+
width: width,
|
|
98
|
+
height: height,
|
|
99
|
+
mimeType: mimeType
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}, _callee, null, [[0, 2]]);
|
|
103
|
+
}))();
|
|
104
|
+
},
|
|
105
|
+
encode: function encode(bytes, request) {
|
|
106
|
+
return asyncToGenerator._asyncToGenerator(/*#__PURE__*/asyncToGenerator._regenerator().m(function _callee2() {
|
|
107
|
+
var pipeline, encoded;
|
|
108
|
+
return asyncToGenerator._regenerator().w(function (_context2) {
|
|
109
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
110
|
+
case 0:
|
|
111
|
+
if (!(request.mimeType !== "image/webp")) {
|
|
112
|
+
_context2.n = 1;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
return _context2.a(2, null);
|
|
116
|
+
case 1:
|
|
117
|
+
_context2.p = 1;
|
|
118
|
+
// `rotate()` with no argument applies the EXIF orientation, and it has
|
|
119
|
+
// to be here: the WebP carries no EXIF, so a rotation the source only
|
|
120
|
+
// described would otherwise be lost and the image would land on its
|
|
121
|
+
// side. It is also what makes the dimensions the tool re-reads
|
|
122
|
+
// afterwards the ones a browser would show.
|
|
123
|
+
pipeline = sharp(bytes).rotate();
|
|
124
|
+
if (request.resizeTo) {
|
|
125
|
+
pipeline = pipeline.resize({
|
|
126
|
+
width: request.resizeTo.width,
|
|
127
|
+
height: request.resizeTo.height,
|
|
128
|
+
fit: "inside",
|
|
129
|
+
// Belt and braces: `fitWithin` never asks for an upscale, and this
|
|
130
|
+
// means a future caller that does cannot get one either.
|
|
131
|
+
withoutEnlargement: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
_context2.n = 2;
|
|
135
|
+
return pipeline
|
|
136
|
+
// sharp takes 1-100 where the schema (and `canvas.toBlob`) say 0-1.
|
|
137
|
+
.webp({
|
|
138
|
+
quality: Math.round(request.quality * 100)
|
|
139
|
+
}).toBuffer();
|
|
140
|
+
case 2:
|
|
141
|
+
encoded = _context2.v;
|
|
142
|
+
return _context2.a(2, {
|
|
143
|
+
bytes: new Uint8Array(encoded),
|
|
144
|
+
mimeType: "image/webp"
|
|
145
|
+
});
|
|
146
|
+
case 3:
|
|
147
|
+
_context2.p = 3;
|
|
148
|
+
_context2.v;
|
|
149
|
+
return _context2.a(2, null);
|
|
150
|
+
}
|
|
151
|
+
}, _callee2, null, [[1, 3]]);
|
|
152
|
+
}))();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
exports.sharpImageProcessor = sharpImageProcessor;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var asyncToGenerator = require('../../dist/asyncToGenerator-8e5c36c8.cjs.prod.js');
|
|
6
|
+
var core = require('@valbuild/core');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `sharp`, as the image tool wants it.
|
|
10
|
+
*
|
|
11
|
+
* The library is passed IN rather than imported, and that is the whole point of
|
|
12
|
+
* this file existing separately. `sharp` ships a compiled binary per platform;
|
|
13
|
+
* importing it here would put one in every project that installs Val, for a
|
|
14
|
+
* feature most of them will not mount. Instead the project installs `sharp`
|
|
15
|
+
* itself and writes:
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import sharp from "sharp";
|
|
19
|
+
* import { createValImageTools } from "@valbuild/mcp";
|
|
20
|
+
* import { sharpImageProcessor } from "@valbuild/mcp/sharp";
|
|
21
|
+
*
|
|
22
|
+
* const tools = createValImageTools(sharpImageProcessor(sharp));
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* The parameter is typed structurally — {@link SharpLike} — rather than as
|
|
26
|
+
* `import("sharp")`, for the same reason: a type-only import of a package that
|
|
27
|
+
* is not installed is still a compile error, and this package must typecheck in
|
|
28
|
+
* a project that has never heard of sharp. The real `sharp` satisfies it, and
|
|
29
|
+
* `sharpImageProcessor.test.ts` asserts as much against the actual library so
|
|
30
|
+
* the structural type cannot drift away from it unnoticed.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** What `sharp(bytes)` gives back, narrowed to what this file calls. */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Which sharp formats are worth calling an image.
|
|
37
|
+
*
|
|
38
|
+
* Mapped explicitly rather than by pasting `image/` in front of
|
|
39
|
+
* `metadata.format`, because sharp's names and the mime types disagree in the
|
|
40
|
+
* cases that matter — `jpeg` vs `image/jpeg` is fine, but `heif` covers AVIF,
|
|
41
|
+
* and `svg` is a format sharp reads and Val stores as text. An unmapped format
|
|
42
|
+
* is reported as "not an image this library can read", which is the honest
|
|
43
|
+
* answer and is recoverable: the caller can convert it themselves.
|
|
44
|
+
*/
|
|
45
|
+
var MIME_TYPE_OF_FORMAT = {
|
|
46
|
+
avif: "image/avif",
|
|
47
|
+
gif: "image/gif",
|
|
48
|
+
jpeg: "image/jpeg",
|
|
49
|
+
jpg: "image/jpeg",
|
|
50
|
+
jp2: "image/jp2",
|
|
51
|
+
png: "image/png",
|
|
52
|
+
svg: "image/svg+xml",
|
|
53
|
+
tiff: "image/tiff",
|
|
54
|
+
webp: "image/webp"
|
|
55
|
+
};
|
|
56
|
+
function sharpImageProcessor(sharp) {
|
|
57
|
+
return {
|
|
58
|
+
read: function read(bytes) {
|
|
59
|
+
return asyncToGenerator._asyncToGenerator(/*#__PURE__*/asyncToGenerator._regenerator().m(function _callee() {
|
|
60
|
+
var metadata, _metadata, width, height, format, mimeType;
|
|
61
|
+
return asyncToGenerator._regenerator().w(function (_context) {
|
|
62
|
+
while (1) switch (_context.p = _context.n) {
|
|
63
|
+
case 0:
|
|
64
|
+
_context.p = 0;
|
|
65
|
+
_context.n = 1;
|
|
66
|
+
return sharp(bytes).metadata();
|
|
67
|
+
case 1:
|
|
68
|
+
metadata = _context.v;
|
|
69
|
+
_context.n = 3;
|
|
70
|
+
break;
|
|
71
|
+
case 2:
|
|
72
|
+
_context.p = 2;
|
|
73
|
+
_context.v;
|
|
74
|
+
return _context.a(2, null);
|
|
75
|
+
case 3:
|
|
76
|
+
_metadata = metadata, width = _metadata.width, height = _metadata.height, format = _metadata.format;
|
|
77
|
+
if (!(!width || !height || !format)) {
|
|
78
|
+
_context.n = 4;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
return _context.a(2, null);
|
|
82
|
+
case 4:
|
|
83
|
+
mimeType = MIME_TYPE_OF_FORMAT[format];
|
|
84
|
+
if (mimeType) {
|
|
85
|
+
_context.n = 5;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
return _context.a(2, null);
|
|
89
|
+
case 5:
|
|
90
|
+
if (core.Internal.MIME_TYPES_TO_EXT[mimeType]) {
|
|
91
|
+
_context.n = 6;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
return _context.a(2, null);
|
|
95
|
+
case 6:
|
|
96
|
+
return _context.a(2, {
|
|
97
|
+
width: width,
|
|
98
|
+
height: height,
|
|
99
|
+
mimeType: mimeType
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}, _callee, null, [[0, 2]]);
|
|
103
|
+
}))();
|
|
104
|
+
},
|
|
105
|
+
encode: function encode(bytes, request) {
|
|
106
|
+
return asyncToGenerator._asyncToGenerator(/*#__PURE__*/asyncToGenerator._regenerator().m(function _callee2() {
|
|
107
|
+
var pipeline, encoded;
|
|
108
|
+
return asyncToGenerator._regenerator().w(function (_context2) {
|
|
109
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
110
|
+
case 0:
|
|
111
|
+
if (!(request.mimeType !== "image/webp")) {
|
|
112
|
+
_context2.n = 1;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
return _context2.a(2, null);
|
|
116
|
+
case 1:
|
|
117
|
+
_context2.p = 1;
|
|
118
|
+
// `rotate()` with no argument applies the EXIF orientation, and it has
|
|
119
|
+
// to be here: the WebP carries no EXIF, so a rotation the source only
|
|
120
|
+
// described would otherwise be lost and the image would land on its
|
|
121
|
+
// side. It is also what makes the dimensions the tool re-reads
|
|
122
|
+
// afterwards the ones a browser would show.
|
|
123
|
+
pipeline = sharp(bytes).rotate();
|
|
124
|
+
if (request.resizeTo) {
|
|
125
|
+
pipeline = pipeline.resize({
|
|
126
|
+
width: request.resizeTo.width,
|
|
127
|
+
height: request.resizeTo.height,
|
|
128
|
+
fit: "inside",
|
|
129
|
+
// Belt and braces: `fitWithin` never asks for an upscale, and this
|
|
130
|
+
// means a future caller that does cannot get one either.
|
|
131
|
+
withoutEnlargement: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
_context2.n = 2;
|
|
135
|
+
return pipeline
|
|
136
|
+
// sharp takes 1-100 where the schema (and `canvas.toBlob`) say 0-1.
|
|
137
|
+
.webp({
|
|
138
|
+
quality: Math.round(request.quality * 100)
|
|
139
|
+
}).toBuffer();
|
|
140
|
+
case 2:
|
|
141
|
+
encoded = _context2.v;
|
|
142
|
+
return _context2.a(2, {
|
|
143
|
+
bytes: new Uint8Array(encoded),
|
|
144
|
+
mimeType: "image/webp"
|
|
145
|
+
});
|
|
146
|
+
case 3:
|
|
147
|
+
_context2.p = 3;
|
|
148
|
+
_context2.v;
|
|
149
|
+
return _context2.a(2, null);
|
|
150
|
+
}
|
|
151
|
+
}, _callee2, null, [[1, 3]]);
|
|
152
|
+
}))();
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
exports.sharpImageProcessor = sharpImageProcessor;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { _ as _asyncToGenerator, a as _regenerator } from '../../dist/asyncToGenerator-500f022f.esm.js';
|
|
2
|
+
import { Internal } from '@valbuild/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `sharp`, as the image tool wants it.
|
|
6
|
+
*
|
|
7
|
+
* The library is passed IN rather than imported, and that is the whole point of
|
|
8
|
+
* this file existing separately. `sharp` ships a compiled binary per platform;
|
|
9
|
+
* importing it here would put one in every project that installs Val, for a
|
|
10
|
+
* feature most of them will not mount. Instead the project installs `sharp`
|
|
11
|
+
* itself and writes:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import sharp from "sharp";
|
|
15
|
+
* import { createValImageTools } from "@valbuild/mcp";
|
|
16
|
+
* import { sharpImageProcessor } from "@valbuild/mcp/sharp";
|
|
17
|
+
*
|
|
18
|
+
* const tools = createValImageTools(sharpImageProcessor(sharp));
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The parameter is typed structurally — {@link SharpLike} — rather than as
|
|
22
|
+
* `import("sharp")`, for the same reason: a type-only import of a package that
|
|
23
|
+
* is not installed is still a compile error, and this package must typecheck in
|
|
24
|
+
* a project that has never heard of sharp. The real `sharp` satisfies it, and
|
|
25
|
+
* `sharpImageProcessor.test.ts` asserts as much against the actual library so
|
|
26
|
+
* the structural type cannot drift away from it unnoticed.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** What `sharp(bytes)` gives back, narrowed to what this file calls. */
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Which sharp formats are worth calling an image.
|
|
33
|
+
*
|
|
34
|
+
* Mapped explicitly rather than by pasting `image/` in front of
|
|
35
|
+
* `metadata.format`, because sharp's names and the mime types disagree in the
|
|
36
|
+
* cases that matter — `jpeg` vs `image/jpeg` is fine, but `heif` covers AVIF,
|
|
37
|
+
* and `svg` is a format sharp reads and Val stores as text. An unmapped format
|
|
38
|
+
* is reported as "not an image this library can read", which is the honest
|
|
39
|
+
* answer and is recoverable: the caller can convert it themselves.
|
|
40
|
+
*/
|
|
41
|
+
var MIME_TYPE_OF_FORMAT = {
|
|
42
|
+
avif: "image/avif",
|
|
43
|
+
gif: "image/gif",
|
|
44
|
+
jpeg: "image/jpeg",
|
|
45
|
+
jpg: "image/jpeg",
|
|
46
|
+
jp2: "image/jp2",
|
|
47
|
+
png: "image/png",
|
|
48
|
+
svg: "image/svg+xml",
|
|
49
|
+
tiff: "image/tiff",
|
|
50
|
+
webp: "image/webp"
|
|
51
|
+
};
|
|
52
|
+
function sharpImageProcessor(sharp) {
|
|
53
|
+
return {
|
|
54
|
+
read: function read(bytes) {
|
|
55
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
56
|
+
var metadata, _metadata, width, height, format, mimeType;
|
|
57
|
+
return _regenerator().w(function (_context) {
|
|
58
|
+
while (1) switch (_context.p = _context.n) {
|
|
59
|
+
case 0:
|
|
60
|
+
_context.p = 0;
|
|
61
|
+
_context.n = 1;
|
|
62
|
+
return sharp(bytes).metadata();
|
|
63
|
+
case 1:
|
|
64
|
+
metadata = _context.v;
|
|
65
|
+
_context.n = 3;
|
|
66
|
+
break;
|
|
67
|
+
case 2:
|
|
68
|
+
_context.p = 2;
|
|
69
|
+
_context.v;
|
|
70
|
+
return _context.a(2, null);
|
|
71
|
+
case 3:
|
|
72
|
+
_metadata = metadata, width = _metadata.width, height = _metadata.height, format = _metadata.format;
|
|
73
|
+
if (!(!width || !height || !format)) {
|
|
74
|
+
_context.n = 4;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
return _context.a(2, null);
|
|
78
|
+
case 4:
|
|
79
|
+
mimeType = MIME_TYPE_OF_FORMAT[format];
|
|
80
|
+
if (mimeType) {
|
|
81
|
+
_context.n = 5;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
return _context.a(2, null);
|
|
85
|
+
case 5:
|
|
86
|
+
if (Internal.MIME_TYPES_TO_EXT[mimeType]) {
|
|
87
|
+
_context.n = 6;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return _context.a(2, null);
|
|
91
|
+
case 6:
|
|
92
|
+
return _context.a(2, {
|
|
93
|
+
width: width,
|
|
94
|
+
height: height,
|
|
95
|
+
mimeType: mimeType
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}, _callee, null, [[0, 2]]);
|
|
99
|
+
}))();
|
|
100
|
+
},
|
|
101
|
+
encode: function encode(bytes, request) {
|
|
102
|
+
return _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2() {
|
|
103
|
+
var pipeline, encoded;
|
|
104
|
+
return _regenerator().w(function (_context2) {
|
|
105
|
+
while (1) switch (_context2.p = _context2.n) {
|
|
106
|
+
case 0:
|
|
107
|
+
if (!(request.mimeType !== "image/webp")) {
|
|
108
|
+
_context2.n = 1;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return _context2.a(2, null);
|
|
112
|
+
case 1:
|
|
113
|
+
_context2.p = 1;
|
|
114
|
+
// `rotate()` with no argument applies the EXIF orientation, and it has
|
|
115
|
+
// to be here: the WebP carries no EXIF, so a rotation the source only
|
|
116
|
+
// described would otherwise be lost and the image would land on its
|
|
117
|
+
// side. It is also what makes the dimensions the tool re-reads
|
|
118
|
+
// afterwards the ones a browser would show.
|
|
119
|
+
pipeline = sharp(bytes).rotate();
|
|
120
|
+
if (request.resizeTo) {
|
|
121
|
+
pipeline = pipeline.resize({
|
|
122
|
+
width: request.resizeTo.width,
|
|
123
|
+
height: request.resizeTo.height,
|
|
124
|
+
fit: "inside",
|
|
125
|
+
// Belt and braces: `fitWithin` never asks for an upscale, and this
|
|
126
|
+
// means a future caller that does cannot get one either.
|
|
127
|
+
withoutEnlargement: true
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
_context2.n = 2;
|
|
131
|
+
return pipeline
|
|
132
|
+
// sharp takes 1-100 where the schema (and `canvas.toBlob`) say 0-1.
|
|
133
|
+
.webp({
|
|
134
|
+
quality: Math.round(request.quality * 100)
|
|
135
|
+
}).toBuffer();
|
|
136
|
+
case 2:
|
|
137
|
+
encoded = _context2.v;
|
|
138
|
+
return _context2.a(2, {
|
|
139
|
+
bytes: new Uint8Array(encoded),
|
|
140
|
+
mimeType: "image/webp"
|
|
141
|
+
});
|
|
142
|
+
case 3:
|
|
143
|
+
_context2.p = 3;
|
|
144
|
+
_context2.v;
|
|
145
|
+
return _context2.a(2, null);
|
|
146
|
+
}
|
|
147
|
+
}, _callee2, null, [[1, 3]]);
|
|
148
|
+
}))();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export { sharpImageProcessor };
|