diff --git a/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue b/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue index 7426b8439..b1d495e55 100644 --- a/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue +++ b/nezha-fronted/src/components/page/dashboard/explore/exploreItem.vue @@ -3535,6 +3535,8 @@ import logTab from './logTab' import promqlInputMixin from '@/components/common/mixin/promqlInput' import chartRightBox from '@/components/common/rightBox/chart/chartRightBox' import copy from '@/components/common/copy' +import './promqlparser/wasm_exec.js' + export default { name: 'exploreItem', components: { @@ -3624,19 +3626,33 @@ export default { scrollbarWrap: null } }, - created () { + async created () { this.getPanelData() this.resetExpression() + await this.loadWebAssembly() + this.initQueryFromPath() }, mounted () { this.scrollbarWrap = this.$refs.exploreScrollbar this.scrollbarWrap.addEventListener('scroll', this.onScroll) - this.initQueryFromPath() }, beforeDestroy () { this.scrollbarWrap.removeEventListener('scroll', this.onScroll) }, methods: { + async loadWebAssembly () { + try { + // eslint-disable-next-line no-undef + const go = new Go() + const result = await WebAssembly.instantiateStreaming(fetch('/static/promqlparser.wasm'), go.importObject) + go.run(result.instance) + // eslint-disable-next-line no-undef + this.parsePromQL = parsePromQL + } catch (error) { + console.error(error) + } + }, + parsePromQL () {}, onScroll: bus.debounce(function () { this.showTopBtn = this.scrollbarWrap.scrollTop > 50 }, 300), @@ -3953,7 +3969,7 @@ export default { } else { if (response.error) { this.$refs['promql-' + promqlIndex][0].setError(response.error) - } else if(response.msg) { + } else if (response.msg) { this.$refs['promql-' + promqlIndex][0].setError(response.msg) } else { this.$refs['promql-' + promqlIndex][0].setError(response) @@ -4102,6 +4118,24 @@ export default { this.setSearchTime(nowTimeType.type, nowTimeType.value) } if (this.expressions && this.expressions.length >= 1) { + let error = false + // 对 promql 格式校验及format + if (this.showMetrics) { + this.expressions.forEach((item, index) => { + if (item != '' && this.promqlKeys[index].state) { + const res = this.parsePromQL(item) + if (res.status === 'error') { + error = true + this.$refs['promql-' + index][0].setError(res.message) + } else { + this.$set(this.expressions, index, res.result) + this.$refs['promql-' + index][0].prettyCode() + this.$refs['promql-' + index][0].setError('') + } + } + }) + } + if (error) { return } if (this.showMetrics) { this.queryTableData() this.queryChartData() diff --git a/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue b/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue index eb8478484..acb67fda3 100644 --- a/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue +++ b/nezha-fronted/src/components/page/dashboard/explore/promqlInput.vue @@ -1158,10 +1158,20 @@ export default { this.expressionList[this.index] = insertTxt this.codeMirrorValue[this.index] = insertTxt this.initCodeMirror() - } + }, /* setMsg:function(){ this.appendMsg } */ + prettyCode () { + this.$nextTick(() => { + const text = this.newView.state.doc.toString() + this.newView.dispatch( + this.newView.state.update({ + changes: { from: 0, to: text.length, insert: this.codeMirrorValue[this.index] } + }) + ) + }) + } }, watch: { dropDownVisible (n, o) { diff --git a/nezha-fronted/src/components/page/dashboard/explore/promqlparser/go.mod b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/go.mod new file mode 100644 index 000000000..8ab16c81d --- /dev/null +++ b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/go.mod @@ -0,0 +1,30 @@ +module test.go + +go 1.20 + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dennwc/varint v1.0.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.16.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.0 // indirect + github.com/prometheus/prometheus v0.46.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/goleak v1.2.1 // indirect + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect + golang.org/x/sys v0.10.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/nezha-fronted/src/components/page/dashboard/explore/promqlparser/go.sum b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/go.sum new file mode 100644 index 000000000..265df2532 --- /dev/null +++ b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/go.sum @@ -0,0 +1,91 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= +github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= +github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk= +github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/prometheus v0.46.0 h1:9JSdXnsuT6YsbODEhSQMwxNkGwPExfmzqG73vCMk/Kw= +github.com/prometheus/prometheus v0.46.0/go.mod h1:10L5IJE5CEsjee1FnOcVswYXlPIscDWWt3IJ2UDYrz4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/nezha-fronted/src/components/page/dashboard/explore/promqlparser/promqlparser.go b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/promqlparser.go new file mode 100644 index 000000000..54e16b2e0 --- /dev/null +++ b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/promqlparser.go @@ -0,0 +1,33 @@ +// 编译为wasm文件 在页面中引入 +package main + +import ( + "syscall/js" + + "github.com/prometheus/prometheus/promql/parser" +) + +func parsePromQL(this js.Value, args []js.Value) interface{} { + promql := args[0].String() + expr, err := parser.ParseExpr(promql) + if err != nil { + return map[string]interface{}{ + "status": "error", + "message": err.Error(), + } + } + + return map[string]interface{}{ + "status": "success", + "result": expr.Pretty(0), + } +} + +func main() { + c := make(chan struct{}, 0) + + // 注册函数 + js.Global().Set("parsePromQL", js.FuncOf(parsePromQL)) + + <-c +} diff --git a/nezha-fronted/src/components/page/dashboard/explore/promqlparser/wasm_exec.js b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/wasm_exec.js new file mode 100644 index 000000000..2f279c750 --- /dev/null +++ b/nezha-fronted/src/components/page/dashboard/explore/promqlparser/wasm_exec.js @@ -0,0 +1,554 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +'use strict'; + +(() => { + const enosys = () => { + const err = new Error('not implemented') + err.code = 'ENOSYS' + return err + } + + if (!globalThis.fs) { + let outputBuf = '' + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync (fd, buf) { + outputBuf += decoder.decode(buf) + const nl = outputBuf.lastIndexOf('\n') + if (nl != -1) { + console.log(outputBuf.substring(0, nl)) + outputBuf = outputBuf.substring(nl + 1) + } + return buf.length + }, + write (fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()) + return + } + const n = this.writeSync(fd, buf) + callback(null, n) + }, + chmod (path, mode, callback) { callback(enosys()) }, + chown (path, uid, gid, callback) { callback(enosys()) }, + close (fd, callback) { callback(enosys()) }, + fchmod (fd, mode, callback) { callback(enosys()) }, + fchown (fd, uid, gid, callback) { callback(enosys()) }, + fstat (fd, callback) { callback(enosys()) }, + fsync (fd, callback) { callback(null) }, + ftruncate (fd, length, callback) { callback(enosys()) }, + lchown (path, uid, gid, callback) { callback(enosys()) }, + link (path, link, callback) { callback(enosys()) }, + lstat (path, callback) { callback(enosys()) }, + mkdir (path, perm, callback) { callback(enosys()) }, + open (path, flags, mode, callback) { callback(enosys()) }, + read (fd, buffer, offset, length, position, callback) { callback(enosys()) }, + readdir (path, callback) { callback(enosys()) }, + readlink (path, callback) { callback(enosys()) }, + rename (from, to, callback) { callback(enosys()) }, + rmdir (path, callback) { callback(enosys()) }, + stat (path, callback) { callback(enosys()) }, + symlink (path, link, callback) { callback(enosys()) }, + truncate (path, length, callback) { callback(enosys()) }, + unlink (path, callback) { callback(enosys()) }, + utimes (path, atime, mtime, callback) { callback(enosys()) } + } + } + + if (!globalThis.process) { + globalThis.process = { + getuid () { return -1 }, + getgid () { return -1 }, + geteuid () { return -1 }, + getegid () { return -1 }, + getgroups () { throw enosys() }, + pid: -1, + ppid: -1, + umask () { throw enosys() }, + cwd () { throw enosys() }, + chdir () { throw enosys() } + } + } + + if (!globalThis.crypto) { + throw new Error('globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)') + } + + if (!globalThis.performance) { + throw new Error('globalThis.performance is not available, polyfill required (performance.now only)') + } + + if (!globalThis.TextEncoder) { + throw new Error('globalThis.TextEncoder is not available, polyfill required') + } + + if (!globalThis.TextDecoder) { + throw new Error('globalThis.TextDecoder is not available, polyfill required') + } + + const encoder = new TextEncoder('utf-8') + const decoder = new TextDecoder('utf-8') + + globalThis.Go = class { + constructor () { + this.argv = ['js'] + this.env = {} + this.exit = (code) => { + if (code !== 0) { + console.warn('exit code:', code) + } + } + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve + }) + this._pendingEvent = null + this._scheduledTimeouts = new Map() + this._nextCallbackTimeoutID = 1 + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true) + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true) + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true) + const high = this.mem.getInt32(addr + 4, true) + return low + high * 4294967296 + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true) + if (f === 0) { + return undefined + } + if (!isNaN(f)) { + return f + } + + const id = this.mem.getUint32(addr, true) + return this._values[id] + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000 + + if (typeof v === 'number' && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true) + this.mem.setUint32(addr, 0, true) + return + } + this.mem.setFloat64(addr, v, true) + return + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true) + return + } + + let id = this._ids.get(v) + if (id === undefined) { + id = this._idPool.pop() + if (id === undefined) { + id = this._values.length + } + this._values[id] = v + this._goRefCounts[id] = 0 + this._ids.set(v, id) + } + this._goRefCounts[id]++ + let typeFlag = 0 + switch (typeof v) { + case 'object': + if (v !== null) { + typeFlag = 1 + } + break + case 'string': + typeFlag = 2 + break + case 'symbol': + typeFlag = 3 + break + case 'function': + typeFlag = 4 + break + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true) + this.mem.setUint32(addr, id, true) + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0) + const len = getInt64(addr + 8) + return new Uint8Array(this._inst.exports.mem.buffer, array, len) + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0) + const len = getInt64(addr + 8) + const a = new Array(len) + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8) + } + return a + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0) + const len = getInt64(addr + 8) + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)) + } + + const timeOrigin = Date.now() - performance.now() + this.importObject = { + go: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + 'runtime.wasmExit': (sp) => { + sp >>>= 0 + const code = this.mem.getInt32(sp + 8, true) + this.exited = true + delete this._inst + delete this._values + delete this._goRefCounts + delete this._ids + delete this._idPool + this.exit(code) + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + 'runtime.wasmWrite': (sp) => { + sp >>>= 0 + const fd = getInt64(sp + 8) + const p = getInt64(sp + 16) + const n = this.mem.getInt32(sp + 24, true) + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)) + }, + + // func resetMemoryDataView() + 'runtime.resetMemoryDataView': (sp) => { + sp >>>= 0 + this.mem = new DataView(this._inst.exports.mem.buffer) + }, + + // func nanotime1() int64 + 'runtime.nanotime1': (sp) => { + sp >>>= 0 + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000) + }, + + // func walltime() (sec int64, nsec int32) + 'runtime.walltime': (sp) => { + sp >>>= 0 + const msec = (new Date()).getTime() + setInt64(sp + 8, msec / 1000) + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true) + }, + + // func scheduleTimeoutEvent(delay int64) int32 + 'runtime.scheduleTimeoutEvent': (sp) => { + sp >>>= 0 + const id = this._nextCallbackTimeoutID + this._nextCallbackTimeoutID++ + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume() + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn('scheduleTimeoutEvent: missed timeout event') + this._resume() + } + }, + getInt64(sp + 8) + 1 // setTimeout has been seen to fire up to 1 millisecond early + )) + this.mem.setInt32(sp + 16, id, true) + }, + + // func clearTimeoutEvent(id int32) + 'runtime.clearTimeoutEvent': (sp) => { + sp >>>= 0 + const id = this.mem.getInt32(sp + 8, true) + clearTimeout(this._scheduledTimeouts.get(id)) + this._scheduledTimeouts.delete(id) + }, + + // func getRandomData(r []byte) + 'runtime.getRandomData': (sp) => { + sp >>>= 0 + crypto.getRandomValues(loadSlice(sp + 8)) + }, + + // func finalizeRef(v ref) + 'syscall/js.finalizeRef': (sp) => { + sp >>>= 0 + const id = this.mem.getUint32(sp + 8, true) + this._goRefCounts[id]-- + if (this._goRefCounts[id] === 0) { + const v = this._values[id] + this._values[id] = null + this._ids.delete(v) + this._idPool.push(id) + } + }, + + // func stringVal(value string) ref + 'syscall/js.stringVal': (sp) => { + sp >>>= 0 + storeValue(sp + 24, loadString(sp + 8)) + }, + + // func valueGet(v ref, p string) ref + 'syscall/js.valueGet': (sp) => { + sp >>>= 0 + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 32, result) + }, + + // func valueSet(v ref, p string, x ref) + 'syscall/js.valueSet': (sp) => { + sp >>>= 0 + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)) + }, + + // func valueDelete(v ref, p string) + 'syscall/js.valueDelete': (sp) => { + sp >>>= 0 + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)) + }, + + // func valueIndex(v ref, i int) ref + 'syscall/js.valueIndex': (sp) => { + sp >>>= 0 + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))) + }, + + // valueSetIndex(v ref, i int, x ref) + 'syscall/js.valueSetIndex': (sp) => { + sp >>>= 0 + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)) + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + 'syscall/js.valueCall': (sp) => { + sp >>>= 0 + try { + const v = loadValue(sp + 8) + const m = Reflect.get(v, loadString(sp + 16)) + const args = loadSliceOfValues(sp + 32) + const result = Reflect.apply(m, v, args) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 56, result) + this.mem.setUint8(sp + 64, 1) + } catch (err) { + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 56, err) + this.mem.setUint8(sp + 64, 0) + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + 'syscall/js.valueInvoke': (sp) => { + sp >>>= 0 + try { + const v = loadValue(sp + 8) + const args = loadSliceOfValues(sp + 16) + const result = Reflect.apply(v, undefined, args) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, result) + this.mem.setUint8(sp + 48, 1) + } catch (err) { + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, err) + this.mem.setUint8(sp + 48, 0) + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + 'syscall/js.valueNew': (sp) => { + sp >>>= 0 + try { + const v = loadValue(sp + 8) + const args = loadSliceOfValues(sp + 16) + const result = Reflect.construct(v, args) + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, result) + this.mem.setUint8(sp + 48, 1) + } catch (err) { + sp = this._inst.exports.getsp() >>> 0 // see comment above + storeValue(sp + 40, err) + this.mem.setUint8(sp + 48, 0) + } + }, + + // func valueLength(v ref) int + 'syscall/js.valueLength': (sp) => { + sp >>>= 0 + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)) + }, + + // valuePrepareString(v ref) (ref, int) + 'syscall/js.valuePrepareString': (sp) => { + sp >>>= 0 + const str = encoder.encode(String(loadValue(sp + 8))) + storeValue(sp + 16, str) + setInt64(sp + 24, str.length) + }, + + // valueLoadString(v ref, b []byte) + 'syscall/js.valueLoadString': (sp) => { + sp >>>= 0 + const str = loadValue(sp + 8) + loadSlice(sp + 16).set(str) + }, + + // func valueInstanceOf(v ref, t ref) bool + 'syscall/js.valueInstanceOf': (sp) => { + sp >>>= 0 + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0) + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + 'syscall/js.copyBytesToGo': (sp) => { + sp >>>= 0 + const dst = loadSlice(sp + 8) + const src = loadValue(sp + 32) + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0) + return + } + const toCopy = src.subarray(0, dst.length) + dst.set(toCopy) + setInt64(sp + 40, toCopy.length) + this.mem.setUint8(sp + 48, 1) + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + 'syscall/js.copyBytesToJS': (sp) => { + sp >>>= 0 + const dst = loadValue(sp + 8) + const src = loadSlice(sp + 16) + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0) + return + } + const toCopy = src.subarray(0, dst.length) + dst.set(toCopy) + setInt64(sp + 40, toCopy.length) + this.mem.setUint8(sp + 48, 1) + }, + + debug: (value) => { + console.log(value) + } + } + } + } + + async run (instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error('Go.run: WebAssembly.Instance expected') + } + this._inst = instance + this.mem = new DataView(this._inst.exports.mem.buffer) + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + globalThis, + this + ] + this._goRefCounts = new Array(this._values.length).fill(Infinity) // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [globalThis, 5], + [this, 6] + ]) + this._idPool = [] // unused ids that have been garbage collected + this.exited = false // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096 + + const strPtr = (str) => { + const ptr = offset + const bytes = encoder.encode(str + '\0') + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes) + offset += bytes.length + if (offset % 8 !== 0) { + offset += 8 - (offset % 8) + } + return ptr + } + + const argc = this.argv.length + + const argvPtrs = [] + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)) + }) + argvPtrs.push(0) + + const keys = Object.keys(this.env).sort() + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)) + }) + argvPtrs.push(0) + + const argv = offset + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true) + this.mem.setUint32(offset + 4, 0, true) + offset += 8 + }) + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192 + if (offset >= wasmMinDataAddr) { + throw new Error('total length of command line and environment variables exceeds limit') + } + + this._inst.exports.run(argc, argv) + if (this.exited) { + this._resolveExitPromise() + } + await this._exitPromise + } + + _resume () { + if (this.exited) { + throw new Error('Go program has already exited') + } + this._inst.exports.resume() + if (this.exited) { + this._resolveExitPromise() + } + } + + _makeFuncWrapper (id) { + const go = this + return function () { + const event = { id: id, this: this, args: arguments } + go._pendingEvent = event + go._resume() + return event.result + } + } + } +})()