about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.golangci.yaml13
-rw-r--r--frontend/static/search.js7
-rw-r--r--frontend/static/style.css4
-rw-r--r--frontend/templates/blocks/options.gotmpl98
-rw-r--r--go.mod31
-rw-r--r--go.sum70
-rw-r--r--gomod2nix.toml91
-rw-r--r--internal/search/search.go123
-rw-r--r--internal/server/server.go43
9 files changed, 409 insertions, 71 deletions
diff --git a/.golangci.yaml b/.golangci.yaml
index 4567686..0a6dbdd 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -21,3 +21,16 @@ linters:
         - sloglint
         - unconvert
         - wrapcheck
+linters-settings:
+    wrapcheck:
+        ignoreSigs:
+            - .Errorf(
+            - errors.New(
+            - errors.Unwrap(
+            - errors.Join(
+            - .Wrap(
+            - .Wrapf(
+            - .WithMessage(
+            - .WithMessagef(
+            - .WithStack(
+            - (context.Context).Err(
diff --git a/frontend/static/search.js b/frontend/static/search.js
index 0388942..f034fe1 100644
--- a/frontend/static/search.js
+++ b/frontend/static/search.js
@@ -38,14 +38,14 @@ search.addEventListener("submit", function (ev) {
       fetch: "true",
     },
   })
-    .then(function (res) {
+    .then(async function (res) {
       state.url = url.toJSON();
       state.opened = [];
-      history.pushState(state, null, url);
       if (res.ok) {
+        history.pushState(state, null, url);
         return res.text();
       } else {
-        throw new Error(res.statusText);
+        throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
       }
     })
     .then(function (html) {
@@ -54,6 +54,7 @@ search.addEventListener("submit", function (ev) {
       addToggleEventListeners();
     })
     .catch(function (error) {
+      results.innerText = error.message;
       console.error("fetch failed", error);
     });
   ev.preventDefault();
diff --git a/frontend/static/style.css b/frontend/static/style.css
index 45cff6a..fd096b0 100644
--- a/frontend/static/style.css
+++ b/frontend/static/style.css
@@ -21,6 +21,10 @@ dd {
   margin-inline-start: 1rem;
 }
 
+dd > p {
+  margin: unset;
+}
+
 dl {
   display: grid;
   align-items: baseline;
diff --git a/frontend/templates/blocks/options.gotmpl b/frontend/templates/blocks/options.gotmpl
index f89a83d..2f9d88a 100644
--- a/frontend/templates/blocks/options.gotmpl
+++ b/frontend/templates/blocks/options.gotmpl
@@ -1,53 +1,59 @@
 {{ template "results" . }}
 {{ define "results" }}
-  {{- range .Results }}
-    <details id="{{ .Option }}">
-      <summary>
-        {{ .Option }}
-      </summary>
-      <p>
-        {{ markdown .Description }}
-      </p>
-      <dl>
-        {{- with .Type }}
-          <dt>Type</dt>
-          <dd><code>{{ . }}</code></dd>
-        {{- end }}
-        {{- with .Default }}
-          <dt>Default</dt>
-          <dd>
-            {{- if .Markdown }}
-              {{ .Markdown }}
-            {{- else }}
-              <pre><code>{{ .Text }}</code></pre>
+  {{- with .Results }}
+    {{- range .Results }}
+      <details id="{{ .Option }}">
+        <summary>
+          {{ .Option }}
+        </summary>
+        <p>
+          {{ markdown .Description }}
+        </p>
+        <dl>
+          {{- with .Type }}
+            <dt>Type</dt>
+            <dd><code>{{ . }}</code></dd>
+          {{- end }}
+          {{- with .Default }}
+            {{- if or .Text .Markdown }}
+              <dt>Default</dt>
+              <dd>
+                {{- if .Markdown }}
+                  {{ markdown .Markdown }}
+                {{- else }}
+                  <pre><code>{{ .Text }}</code></pre>
+                {{- end }}
+              </dd>
+            {{- end }}
+          {{- end }}
+          {{- with .Example }}
+            {{- if or .Text .Markdown }}
+              <dt>Example</dt>
+              <dd>
+                {{- if .Markdown }}
+                  {{ markdown .Markdown }}
+                {{- else }}
+                  <pre><code>{{ .Text }}</code></pre>
+                {{- end }}
+              </dd>
             {{- end }}
-          </dd>
-        {{- end }}
-        {{- with .Example }}
-          {{- if or .Text .Markdown }}
-            <dt>Example</dt>
-            <dd>
-              {{- if .Markdown }}
-                {{ .Markdown }}
-              {{- else }}
-                <pre><code>{{ .Text }}</code></pre>
-              {{- end }}
-            </dd>
           {{- end }}
-        {{- end }}
-        {{- with .RelatedPackages }}
-          <dt>Related Packages</dt>
-          <dd>{{ . }}</dd>
-        {{- end }}
-        {{- with .Declarations }}
-          <dt>Declared</dt>
-          {{- range . }}
-            <dd>
-              <a href="{{ .URL }}">{{ .Name }}</a>
-            </dd>
+          {{- with .RelatedPackages }}
+            <dt>Related Packages</dt>
+            <dd>{{ . }}</dd>
+          {{- end }}
+          {{- with .Declarations }}
+            <dt>Declared</dt>
+            {{- range . }}
+              <dd>
+                <a href="{{ .URL }}">{{ .Name }}</a>
+              </dd>
+            {{- end }}
           {{- end }}
-        {{- end }}
-      </dl>
-    </details>
+        </dl>
+      </details>
+    {{- else }}
+      Nothing found
+    {{- end }}
   {{- end }}
 {{ end }}
diff --git a/go.mod b/go.mod
index 664c1a1..19543b8 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.22.2
 require (
 	github.com/ardanlabs/conf/v3 v3.1.7
 	github.com/bcicen/jstream v1.0.1
+	github.com/blevesearch/bleve/v2 v2.4.0
 	github.com/crewjam/csp v0.0.2
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/getsentry/sentry-go v0.27.0
@@ -17,7 +18,35 @@ require (
 )
 
 require (
+	github.com/RoaringBitmap/roaring v1.9.3 // indirect
+	github.com/bits-and-blooms/bitset v1.13.0 // indirect
+	github.com/blevesearch/bleve_index_api v1.1.6 // indirect
+	github.com/blevesearch/geo v0.1.20 // indirect
+	github.com/blevesearch/go-faiss v1.0.15 // indirect
+	github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
+	github.com/blevesearch/gtreap v0.1.1 // indirect
+	github.com/blevesearch/mmap-go v1.0.4 // indirect
+	github.com/blevesearch/scorch_segment_api/v2 v2.2.11 // indirect
+	github.com/blevesearch/segment v0.9.1 // indirect
+	github.com/blevesearch/snowballstem v0.9.0 // indirect
+	github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
+	github.com/blevesearch/vellum v1.0.10 // indirect
+	github.com/blevesearch/zapx/v11 v11.3.10 // indirect
+	github.com/blevesearch/zapx/v12 v12.3.10 // indirect
+	github.com/blevesearch/zapx/v13 v13.3.10 // indirect
+	github.com/blevesearch/zapx/v14 v14.3.10 // indirect
+	github.com/blevesearch/zapx/v15 v15.3.13 // indirect
+	github.com/blevesearch/zapx/v16 v16.0.12 // indirect
+	github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/go-cmp v0.6.0 // indirect
-	golang.org/x/sys v0.19.0 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/mschoch/smat v0.2.0 // indirect
+	go.etcd.io/bbolt v1.3.10 // indirect
+	golang.org/x/sys v0.20.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 2e315cc..9f5792f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,48 @@
+github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
+github.com/RoaringBitmap/roaring v1.9.3/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
 github.com/ardanlabs/conf/v3 v3.1.7 h1:p232cF68TafoA5U9ZlbxUIhGJtGNdKHBXF80Fdqb5t0=
 github.com/ardanlabs/conf/v3 v3.1.7/go.mod h1:zclexWKe0NVj6LHQ8NgDDZ7bQ1spE0KeKPFficdtAjU=
 github.com/bcicen/jstream v1.0.1 h1:BXY7Cu4rdmc0rhyTVyT3UkxAiX3bnLpKLas9btbH5ck=
 github.com/bcicen/jstream v1.0.1/go.mod h1:9ielPxqFry7Y4Tg3j4BfjPocfJ3TbsRtXOAYXYmRuAQ=
+github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
+github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+github.com/blevesearch/bleve/v2 v2.4.0 h1:2xyg+Wv60CFHYccXc+moGxbL+8QKT/dZK09AewHgKsg=
+github.com/blevesearch/bleve/v2 v2.4.0/go.mod h1:IhQHoFAbHgWKYavb9rQgQEJJVMuY99cKdQ0wPpst2aY=
+github.com/blevesearch/bleve_index_api v1.1.6 h1:orkqDFCBuNU2oHW9hN2YEJmet+TE9orml3FCGbl1cKk=
+github.com/blevesearch/bleve_index_api v1.1.6/go.mod h1:PbcwjIcRmjhGbkS/lJCpfgVSMROV6TRubGGAODaK1W8=
+github.com/blevesearch/geo v0.1.20 h1:paaSpu2Ewh/tn5DKn/FB5SzvH0EWupxHEIwbCk/QPqM=
+github.com/blevesearch/geo v0.1.20/go.mod h1:DVG2QjwHNMFmjo+ZgzrIq2sfCh6rIHzy9d9d0B59I6w=
+github.com/blevesearch/go-faiss v1.0.15 h1:aBrj6fwLuY8CkhECFbvkc4qhLTkrYI84QoEaw9z1jMI=
+github.com/blevesearch/go-faiss v1.0.15/go.mod h1:jrxHrbl42X/RnDPI+wBoZU8joxxuRwedrxqswQ3xfU8=
+github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
+github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
+github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
+github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
+github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
+github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
+github.com/blevesearch/scorch_segment_api/v2 v2.2.11 h1:nb5KTeIhDUu+Ka6He7xXvOXcJOt9Db7c3Vy2ptqJQRs=
+github.com/blevesearch/scorch_segment_api/v2 v2.2.11/go.mod h1:QVakeAECt+Fxe+zu0A4V1bgPdqNeC93wQvzaXDF3NPo=
+github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
+github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
+github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
+github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
+github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
+github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
+github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
+github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
+github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
+github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
+github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
+github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
+github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
+github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
+github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
+github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
+github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ=
+github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
+github.com/blevesearch/zapx/v16 v16.0.12 h1:Uccxvjmn+hQ6ywQP+wIiTpdq9LnAviGoryJOmGwAo/I=
+github.com/blevesearch/zapx/v16 v16.0.12/go.mod h1:MYnOshRfSm4C4drxx1LGRI+MVFByykJ2anDY1fxdk9Q=
 github.com/crewjam/csp v0.0.2 h1:fIq6o0Z6bkABlvLT3kB0XgPnVX9iNXSAGMILs6AqHVw=
 github.com/crewjam/csp v0.0.2/go.mod h1:0tirp4wHwMLZZtV+HXRqGFkUO7uD2ux+1ECvK+7/xFI=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -14,11 +55,27 @@ github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK
 github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
 github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
 github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
+github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
+github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
+github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
 github.com/osdevisnot/sorvor v0.4.4 h1:hcMWsWOKpUtDUE3F7dra1Jf12ftLHfgDcxlyPeVlz0Y=
 github.com/osdevisnot/sorvor v0.4.4/go.mod h1:D/j+vvJEmjIXndJf37uwFWD0Hjcq9DiGojyt4yMo7H0=
 github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
@@ -35,6 +92,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@@ -42,12 +101,19 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
 github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
+go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
+go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
-golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/gomod2nix.toml b/gomod2nix.toml
index 8d7d130..6378cab 100644
--- a/gomod2nix.toml
+++ b/gomod2nix.toml
@@ -1,12 +1,72 @@
 schema = 3
 
 [mod]
+  [mod."github.com/RoaringBitmap/roaring"]
+    version = "v1.9.3"
+    hash = "sha256-LZfRufkU4UhuEcgxuCPd6divX2KIdcHp1FOt79mQV7Q="
   [mod."github.com/ardanlabs/conf/v3"]
     version = "v3.1.7"
     hash = "sha256-7H53l0JN5Q6hkAgBivVQ8lFd03oNmP1IG8ihzLKm2CQ="
   [mod."github.com/bcicen/jstream"]
     version = "v1.0.1"
     hash = "sha256-mm+/BuIEYYj6XOHCCJLxVMKd1XcBXCiRCWA+aTvr1sE="
+  [mod."github.com/bits-and-blooms/bitset"]
+    version = "v1.13.0"
+    hash = "sha256-1jccFEH9In72Jt3wSn4vi4/MJd/UDfDrC9DlmsGVc9o="
+  [mod."github.com/blevesearch/bleve/v2"]
+    version = "v2.4.0"
+    hash = "sha256-5J+rFoUdZmWY/XwV9wHB/gQAYnVqzX8jHtJikUHmieU="
+  [mod."github.com/blevesearch/bleve_index_api"]
+    version = "v1.1.6"
+    hash = "sha256-e1cmYgaKP/11hPpDGU2GV1cRwMx85+yJZSr2TuwvnHc="
+  [mod."github.com/blevesearch/geo"]
+    version = "v0.1.20"
+    hash = "sha256-0Of4hjPdbOwdOOHjQ1BOBgr46XQN0MNBtqklu96lWXs="
+  [mod."github.com/blevesearch/go-faiss"]
+    version = "v1.0.15"
+    hash = "sha256-SXeHdFvDXRePjmnStcuc7HZMLTSljr6DV7CrWhhglXM="
+  [mod."github.com/blevesearch/go-porterstemmer"]
+    version = "v1.0.3"
+    hash = "sha256-hUjo6g1ehUD1awBmta0ji/xoooD2qG7O22HIeSQiRFo="
+  [mod."github.com/blevesearch/gtreap"]
+    version = "v0.1.1"
+    hash = "sha256-B4p/5RnECRfV4yOiSQDLMHb23uI7lsQDePhNK+zjbF4="
+  [mod."github.com/blevesearch/mmap-go"]
+    version = "v1.0.4"
+    hash = "sha256-8y0nMAE9goKjYhR/FFEvtbP7cvM46xneE461L1Jn2Pg="
+  [mod."github.com/blevesearch/scorch_segment_api/v2"]
+    version = "v2.2.11"
+    hash = "sha256-m0vbpW6iTIkgA7+wsMuE8vOJNOE8+AhOdHkuVeV4MnA="
+  [mod."github.com/blevesearch/segment"]
+    version = "v0.9.1"
+    hash = "sha256-0EAT737kNxl8IJFGl2SD9mOzxolONGgpfaYEGr7JXkQ="
+  [mod."github.com/blevesearch/snowballstem"]
+    version = "v0.9.0"
+    hash = "sha256-NQsXrhXcYXn4jQcvwjwLc96SGMRcqVlrR6hYKWGk7/s="
+  [mod."github.com/blevesearch/upsidedown_store_api"]
+    version = "v1.0.2"
+    hash = "sha256-P69Mnh6YR5RI73bD6L7BYDxkVmaqPMNUrjbfSJoKWuo="
+  [mod."github.com/blevesearch/vellum"]
+    version = "v1.0.10"
+    hash = "sha256-dXIzFaw03zMeDu5GlTrw1pw5i9hCsUJL92aWoKsR8so="
+  [mod."github.com/blevesearch/zapx/v11"]
+    version = "v11.3.10"
+    hash = "sha256-GYoW7BOZW+0vMBTIGZfV7+0ve2qxGu9sw3K6cPlh5Oc="
+  [mod."github.com/blevesearch/zapx/v12"]
+    version = "v12.3.10"
+    hash = "sha256-n1HZB4w6id/CFjYlM+89O8US33SEc3Q6mz4mtUHWriY="
+  [mod."github.com/blevesearch/zapx/v13"]
+    version = "v13.3.10"
+    hash = "sha256-Wy/MJLhxtKFc+583MZBmKb8dahyIjN8iHYOm20W/3kM="
+  [mod."github.com/blevesearch/zapx/v14"]
+    version = "v14.3.10"
+    hash = "sha256-pZl5dcmDbiuC8uyOVF8MnEx5bzQFZ1KW6JXVNkPINPs="
+  [mod."github.com/blevesearch/zapx/v15"]
+    version = "v15.3.13"
+    hash = "sha256-mAft3Nv7VvzVQIFc4AJKNf2sW9zf2gX/ISgIshrPXfM="
+  [mod."github.com/blevesearch/zapx/v16"]
+    version = "v16.0.12"
+    hash = "sha256-lOzoml+DdHCxzpOBeTBtqY6xE3VBrYQ25ycaf6MR1qU="
   [mod."github.com/crewjam/csp"]
     version = "v0.0.2"
     hash = "sha256-4vlGmDdQjPiXmueCV51fJH/hRcG8eqhCi9TENCXjzfA="
@@ -16,12 +76,33 @@ schema = 3
   [mod."github.com/getsentry/sentry-go"]
     version = "v0.27.0"
     hash = "sha256-PTkTzVNogqFA/5rc6INLY6RxK5uR1AoJFOO+pOPdE7Q="
+  [mod."github.com/golang/geo"]
+    version = "v0.0.0-20230421003525-6adc56603217"
+    hash = "sha256-j4EFdpNjnCIRM9fFx4zUUipzSdvdAHnC/wmevliJirU="
+  [mod."github.com/golang/protobuf"]
+    version = "v1.5.4"
+    hash = "sha256-N3+Lv9lEZjrdOWdQhFj6Y3Iap4rVLEQeI8/eFFyAMZ0="
+  [mod."github.com/golang/snappy"]
+    version = "v0.0.4"
+    hash = "sha256-Umx+5xHAQCN/Gi4HbtMhnDCSPFAXSsjVbXd8n5LhjAA="
   [mod."github.com/google/go-cmp"]
     version = "v0.6.0"
     hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg="
+  [mod."github.com/json-iterator/go"]
+    version = "v1.1.12"
+    hash = "sha256-To8A0h+lbfZ/6zM+2PpRpY3+L6725OPC66lffq6fUoM="
   [mod."github.com/mitchellh/mapstructure"]
     version = "v1.5.0"
     hash = "sha256-ztVhGQXs67MF8UadVvG72G3ly0ypQW0IRDdOOkjYwoE="
+  [mod."github.com/modern-go/concurrent"]
+    version = "v0.0.0-20180306012644-bacd9c7ef1dd"
+    hash = "sha256-OTySieAgPWR4oJnlohaFTeK1tRaVp/b0d1rYY8xKMzo="
+  [mod."github.com/modern-go/reflect2"]
+    version = "v1.0.2"
+    hash = "sha256-+W9EIW7okXIXjWEgOaMh58eLvBZ7OshW2EhaIpNLSBU="
+  [mod."github.com/mschoch/smat"]
+    version = "v0.2.0"
+    hash = "sha256-DZvUJXjIcta3U+zxzgU3wpoGn/V4lpBY7Xme8aQUi+E="
   [mod."github.com/osdevisnot/sorvor"]
     version = "v0.4.4"
     hash = "sha256-BhyO7bvwxIdEV+c6Eo1uqahhcgsHiS8nJpg2aT8t+8s="
@@ -37,9 +118,15 @@ schema = 3
   [mod."github.com/yuin/goldmark"]
     version = "v1.7.1"
     hash = "sha256-3EUgwoZRRs2jNBWSbB0DGNmfBvx7CeAgEwyUdaRaeR4="
+  [mod."go.etcd.io/bbolt"]
+    version = "v1.3.10"
+    hash = "sha256-uEnz6jmmgT+hlwdZ8ns5NCJSbZcB4i123FF2cn2CbQA="
   [mod."golang.org/x/sys"]
-    version = "v0.19.0"
-    hash = "sha256-cmuL31TYLJmDm/fDnI2Sn0wB88cpdOHV1+urorsJWx4="
+    version = "v0.20.0"
+    hash = "sha256-mowlaoG2k4n1c1rApWef5EMiXd3I77CsUi8jPh6pTYA="
   [mod."golang.org/x/text"]
     version = "v0.14.0"
     hash = "sha256-yh3B0tom1RfzQBf1RNmfdNWF1PtiqxV41jW1GVS6JAg="
+  [mod."google.golang.org/protobuf"]
+    version = "v1.34.1"
+    hash = "sha256-qnHqY6KLZiZDbTVTN6uzF4jedxROYlPCYHoiv6XI0sc="
diff --git a/internal/search/search.go b/internal/search/search.go
new file mode 100644
index 0000000..fc53ad0
--- /dev/null
+++ b/internal/search/search.go
@@ -0,0 +1,123 @@
+package search
+
+import (
+	"context"
+	"log"
+	"os"
+	"path"
+	"sync"
+
+	"searchix/internal/options"
+
+	"github.com/bcicen/jstream"
+	"github.com/blevesearch/bleve/v2"
+	"github.com/mitchellh/mapstructure"
+	"github.com/pkg/errors"
+)
+
+const maxResults = 10
+
+var index bleve.Index
+var docs sync.Map
+
+type Result[T options.NixOption] struct {
+	*bleve.SearchResult
+	Results []T
+}
+
+func Index() error {
+	bleve.SetLog(log.Default())
+	textFieldMapping := bleve.NewTextFieldMapping()
+	nameMapping := bleve.NewTextFieldMapping()
+	// something special for tokenisation?
+	// nameMapping.
+	optionMapping := bleve.NewDocumentStaticMapping()
+
+	optionMapping.AddFieldMappingsAt("Option", nameMapping)
+	optionMapping.AddFieldMappingsAt("RelatedPackages", textFieldMapping)
+	optionMapping.AddFieldMappingsAt("Description", textFieldMapping)
+
+	indexMapping := bleve.NewIndexMapping()
+	indexMapping.AddDocumentMapping("option", optionMapping)
+
+	var err error
+	index, err = bleve.NewMemOnly(indexMapping)
+	// index, err = bleve.New(path.Join(cfg.DataPath, const indexFilename = "index.bleve"), indexMapping)
+
+	if err != nil {
+		return errors.WithMessage(err, "error opening index")
+	}
+	batch := index.NewBatch()
+	_ = batch
+
+	jsonFile, err := os.Open(path.Join("data", "processed", "darwin-options.json"))
+	if err != nil {
+		return errors.WithMessage(err, "error opening json file")
+	}
+
+	dec := jstream.NewDecoder(jsonFile, 1)
+	var opt options.NixOption
+	ms, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+		ErrorUnused: true,
+		ZeroFields:  true,
+		Result:      &opt,
+	})
+	if err != nil {
+		return errors.WithMessage(err, "could not create struct decoder")
+	}
+	for mv := range dec.Stream() {
+		err := ms.Decode(mv.Value) // stores in opt
+
+		if err != nil {
+			return errors.WithMessagef(err, "could not decode object into option, object: %#v", mv.Value)
+		}
+
+		docs.Store(opt.Option, opt)
+
+		err = batch.Index(opt.Option, opt)
+		if err != nil {
+			return errors.WithMessagef(err, "could not index option %s", opt.Option)
+		}
+	}
+	err = index.Batch(batch)
+	if err != nil {
+		return errors.WithMessage(err, "failed to run batch index operation")
+	}
+
+	return nil
+}
+
+func Search[T options.NixOption](ctx context.Context, keyword string) (*Result[T], error) {
+	query := bleve.NewMatchQuery(keyword)
+	search := bleve.NewSearchRequest(query)
+
+	bleveResult, err := index.SearchInContext(ctx, search)
+	select {
+	case <-ctx.Done():
+		return nil, ctx.Err()
+	default:
+		if err != nil {
+			return nil, errors.WithMessage(err, "failed to execute search query")
+		}
+
+		results := make([]T, min(maxResults, bleveResult.Total))
+		for i, result := range bleveResult.Hits {
+			doc, _ := docs.Load(result.ID)
+			results[i] = doc.(T)
+			if i > maxResults {
+				break
+			}
+		}
+
+		return &Result[T]{
+			bleveResult,
+			results,
+		}, nil
+	}
+}
+
+func Load(name string) any {
+	doc, _ := docs.Load(name)
+
+	return doc
+}
diff --git a/internal/server/server.go b/internal/server/server.go
index a6f22e2..3f87b9d 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -2,7 +2,6 @@ package server
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
 	"html/template"
 	"io"
@@ -17,6 +16,7 @@ import (
 
 	cfg "searchix/internal/config"
 	"searchix/internal/options"
+	"searchix/internal/search"
 
 	"github.com/getsentry/sentry-go"
 	sentryhttp "github.com/getsentry/sentry-go/http"
@@ -59,10 +59,10 @@ type TemplateData struct {
 	Query      string
 }
 
-type OptionResultData struct {
+type ResultData[T options.NixOption] struct {
 	TemplateData
 	Query   string
-	Results options.NixOptions
+	Results *search.Result[T]
 }
 
 func applyDevModeOverrides(config *cfg.Config) {
@@ -70,6 +70,13 @@ func applyDevModeOverrides(config *cfg.Config) {
 	config.CSP.ConnectSrc = slices.Insert(config.CSP.ConnectSrc, 0, "'self'")
 }
 
+func init() {
+	err := search.Index()
+	if err != nil {
+		log.Fatalf("could not build search index, error: %#v", err)
+	}
+}
+
 func New(runtimeConfig *Config) (*Server, error) {
 	var err error
 	config, err = cfg.GetConfig()
@@ -113,23 +120,25 @@ func New(runtimeConfig *Config) (*Server, error) {
 		}
 	})
 
-	var nixosOptions = options.NixOptions{}
-	jsonFile, err := os.ReadFile(path.Join(config.DataPath, "test.json"))
-	if err != nil {
-		slog.Error(fmt.Sprintf("error reading json file: %v", err))
-	}
-	err = json.Unmarshal(jsonFile, &nixosOptions)
-	if err != nil {
-		slog.Error(fmt.Sprintf("error parsing json file: %v", err))
-	}
-
+	timeout := 1 * time.Second
 	mux.HandleFunc("/options/results", func(w http.ResponseWriter, r *http.Request) {
-		tdata := OptionResultData{
+		ctx, cancel := context.WithTimeoutCause(r.Context(), timeout, errors.New("timeout"))
+		defer cancel()
+		results, err := search.Search(ctx, r.URL.Query().Get("query"))
+		if err != nil {
+			if err == context.DeadlineExceeded {
+				http.Error(w, "Search timed out", http.StatusInternalServerError)
+
+				return
+			}
+			slog.Error("search error", "error", err)
+			http.Error(w, err.Error(), http.StatusInternalServerError)
+		}
+		tdata := ResultData[options.NixOption]{
 			TemplateData: indexData,
 			Query:        r.URL.Query().Get("query"),
-			Results:      nixosOptions,
+			Results:      results,
 		}
-		var err error
 		if r.Header.Get("Fetch") == "true" {
 			w.Header().Add("Content-Type", "text/html; charset=utf-8")
 			err = templates["options"].Execute(w, tdata)
@@ -137,7 +146,7 @@ func New(runtimeConfig *Config) (*Server, error) {
 			err = templates["options"].ExecuteTemplate(w, "index.gotmpl", tdata)
 		}
 		if err != nil {
-			slog.Error(fmt.Sprintf("template error: %v", err))
+			slog.Error("template error", "template", "options", "error", err)
 			http.Error(w, err.Error(), http.StatusInternalServerError)
 		}
 	})