scrabble-solver 2.18.3 → 2.18.4
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/.nx/workspace-data/d/server-process.json +3 -3
- package/.nx/workspace-data/file-map.json +737 -704
- package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
- package/.nx/workspace-data/lockfile-nodes.hash +1 -1
- package/.nx/workspace-data/nx_files.nxt +0 -0
- package/.nx/workspace-data/parsed-lock-file.dependencies.json +528 -713
- package/.nx/workspace-data/parsed-lock-file.nodes.json +965 -1370
- package/.nx/workspace-data/project-graph.json +1559 -2155
- package/.nx/workspace-data/source-maps.json +44 -8
- package/.oxlintrc.json +0 -1
- package/CLAUDE.md +9 -7
- package/bun.lock +230 -279
- package/lerna.json +1 -1
- package/package.json +5 -5
- package/packages/configs/package.json +3 -3
- package/packages/constants/package.json +1 -1
- package/packages/dictionaries/bin/update-dictionaries.js +2 -5
- package/packages/dictionaries/build/Dictionaries.d.ts +1 -1
- package/packages/dictionaries/build/Dictionaries.js +9 -4
- package/packages/dictionaries/build/lib/DiskCache.js +1 -1
- package/packages/dictionaries/build/lib/downloadDictionary.d.ts +1 -1
- package/packages/dictionaries/build/lib/downloadDictionary.js +12 -7
- package/packages/dictionaries/package.json +4 -4
- package/packages/dictionaries/src/Dictionaries.ts +13 -6
- package/packages/dictionaries/src/lib/DiskCache.ts +2 -2
- package/packages/dictionaries/src/lib/downloadDictionary.ts +14 -6
- package/packages/logger/README.md +18 -0
- package/packages/logger/build/constants.d.ts +3 -0
- package/packages/logger/build/constants.js +4 -1
- package/packages/logger/build/describeError.d.ts +5 -0
- package/packages/logger/build/describeError.js +23 -0
- package/packages/logger/build/events.d.ts +92 -0
- package/packages/logger/build/events.js +13 -0
- package/packages/logger/build/formatCsvRow.d.ts +2 -0
- package/packages/logger/build/formatCsvRow.js +20 -0
- package/packages/logger/build/index.d.ts +5 -1
- package/packages/logger/build/index.js +13 -3
- package/packages/logger/build/logError.d.ts +4 -0
- package/packages/logger/build/logError.js +17 -0
- package/packages/logger/build/logEvent.d.ts +3 -0
- package/packages/logger/build/logEvent.js +29 -0
- package/packages/logger/package.json +7 -8
- package/packages/logger/scripts/export-csv.ts +113 -0
- package/packages/logger/scripts/readEvents.ts +34 -0
- package/packages/logger/scripts/tsconfig.json +10 -0
- package/packages/logger/src/constants.ts +9 -0
- package/packages/logger/src/describeError.test.ts +44 -0
- package/packages/logger/src/describeError.ts +30 -0
- package/packages/logger/src/events.ts +103 -0
- package/packages/logger/src/formatCsvRow.test.ts +41 -0
- package/packages/logger/src/formatCsvRow.ts +24 -0
- package/packages/logger/src/index.ts +13 -1
- package/packages/logger/src/logError.ts +24 -0
- package/packages/logger/src/logEvent.test.ts +59 -0
- package/packages/logger/src/logEvent.ts +26 -0
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +15 -15
- package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
- package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
- package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
- package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
- package/packages/scrabble-solver/.next/dynamic-css-manifest.json +1 -1
- package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
- package/packages/scrabble-solver/.next/react-loadable-manifest.json +6 -6
- package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
- package/packages/scrabble-solver/.next/server/chunks/546.js +1 -1
- package/packages/scrabble-solver/.next/server/chunks/612.js +5 -0
- package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
- package/packages/scrabble-solver/.next/server/chunks/715.js +1 -0
- package/packages/scrabble-solver/.next/server/chunks/961.js +1 -1
- package/packages/scrabble-solver/.next/server/dynamic-css-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/_document.js +2 -2
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +2 -2
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/verify.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/visit.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/index.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/not-found.html +2 -2
- package/packages/scrabble-solver/.next/static/{8WnIUTe-yJvPD6stltvYI → TAqjh9l5VJQOfJ4SzgPk2}/_buildManifest.js +1 -1
- package/packages/scrabble-solver/.next/static/chunks/101-f341f70c10d71212.js +3 -0
- package/packages/scrabble-solver/.next/static/chunks/693.d295da7c7b873986.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/980.dd35e807b18063c1.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/{main-7e52da35a4beea0b.js → main-6e9ac37e28d2b899.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-9340d51f5a6787fc.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-ea21d96a88f25000.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/{webpack-d7138f1ef6b8c9d6.js → webpack-d73aa4aae49cd800.js} +1 -1
- package/packages/scrabble-solver/.next/static/css/{92f85ce358a0a468.css → 4dbcacc1122ac6b9.css} +1 -1
- package/packages/scrabble-solver/.next/static/css/8b8087461767360a.css +1 -0
- package/packages/scrabble-solver/.next/static/css/{65bcc9440e067113.css → 98051ed25076b896.css} +1 -1
- package/packages/scrabble-solver/.next/trace +22 -22
- package/packages/scrabble-solver/.next/trace-build +1 -1
- package/packages/scrabble-solver/package.json +10 -10
- package/packages/scrabble-solver/public/service-worker.js +1 -1
- package/packages/scrabble-solver/src/api/BadRequestError.ts +6 -0
- package/packages/scrabble-solver/src/api/index.ts +2 -1
- package/packages/scrabble-solver/src/api/withApiLog.ts +85 -0
- package/packages/scrabble-solver/src/components/Button/Button.module.scss +1 -1
- package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +4 -4
- package/packages/scrabble-solver/src/components/Solver/Solver.tsx +2 -2
- package/packages/scrabble-solver/src/hooks/useColumns.ts +5 -7
- package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +2 -2
- package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss +1 -1
- package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.tsx +2 -2
- package/packages/scrabble-solver/src/modals/WordsModal/components/WordsTable/WordsTable.tsx +2 -2
- package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +22 -30
- package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/index.ts +36 -42
- package/packages/scrabble-solver/src/pages/api/solve.ts +32 -42
- package/packages/scrabble-solver/src/pages/api/verify.ts +35 -43
- package/packages/scrabble-solver/src/pages/api/visit.ts +38 -13
- package/packages/scrabble-solver/src/pages/index.tsx +2 -2
- package/packages/scrabble-solver/src/parameters/index.ts +0 -2
- package/packages/scrabble-solver/src/sdk/solve.ts +3 -9
- package/packages/scrabble-solver/src/sdk/verify.ts +2 -10
- package/packages/scrabble-solver/src/sdk/visit.ts +8 -3
- package/packages/scrabble-solver/src/state/sagas.ts +7 -1
- package/packages/scrabble-solver/src/styles/_tokens.scss +0 -2
- package/packages/scrabble-solver/src/styles/mixins.scss +0 -5
- package/packages/scrabble-solver/src/styles/variables.module.scss +0 -1
- package/packages/scrabble-solver/src/styles/variables.module.scss.d.ts +0 -1
- package/packages/scrabble-solver/src/styles/variables.scss +1 -1
- package/packages/scrabble-solver/src/types/api.ts +7 -0
- package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
- package/packages/solver/package.json +5 -5
- package/packages/types/package.json +2 -2
- package/packages/word-definitions/package.json +2 -2
- package/packages/word-lists/package.json +2 -2
- package/playwright-report/index.html +1 -1
- package/packages/logger/build/logger.d.ts +0 -1
- package/packages/logger/build/logger.js +0 -28
- package/packages/logger/scripts/stats.js +0 -73
- package/packages/logger/src/logger.ts +0 -30
- package/packages/scrabble-solver/.next/server/chunks/392.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/101-829ab8a8d310e7a0.js +0 -3
- package/packages/scrabble-solver/.next/static/chunks/693.677c88e0eb1340b5.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/980.7ce17464fc56648a.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-e3fe880e24c024ab.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/index-b073a1686818dd26.js +0 -1
- package/packages/scrabble-solver/.next/static/css/a691348b788f8bc0.css +0 -1
- package/packages/scrabble-solver/src/api/getServerLoggingData.ts +0 -17
- package/packages/scrabble-solver/src/hooks/useIsCompactLayout.ts +0 -14
- /package/packages/scrabble-solver/.next/static/{8WnIUTe-yJvPD6stltvYI → TAqjh9l5VJQOfJ4SzgPk2}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-typescript","duration":
|
|
1
|
+
[{"name":"run-typescript","duration":168729,"timestamp":5383943044,"id":13,"parentId":1,"tags":{},"startTime":1787864119994,"traceId":"29d4e0b0fa58c2a5"},{"name":"run-webpack","duration":5660378,"timestamp":5384114003,"id":15,"parentId":1,"tags":{},"startTime":1787864120165,"traceId":"29d4e0b0fa58c2a5"},{"name":"static-check","duration":171566,"timestamp":5389837607,"id":2109,"parentId":1,"tags":{},"startTime":1787864125889,"traceId":"29d4e0b0fa58c2a5"},{"name":"static-generation","duration":249480,"timestamp":5390019708,"id":2127,"parentId":1,"tags":{},"startTime":1787864126071,"traceId":"29d4e0b0fa58c2a5"},{"name":"collect-build-traces","duration":4113081,"timestamp":5390009545,"id":2124,"parentId":1,"tags":{},"startTime":1787864126061,"traceId":"29d4e0b0fa58c2a5"},{"name":"telemetry-flush","duration":19,"timestamp":5394123827,"id":2134,"parentId":1,"tags":{},"startTime":1787864130175,"traceId":"29d4e0b0fa58c2a5"},{"name":"next-build","duration":10414286,"timestamp":5383709563,"id":1,"tags":{"buildMode":"default","version":"16.3.3","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1787864119761,"traceId":"29d4e0b0fa58c2a5"}]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.18.
|
|
3
|
+
"version": "2.18.4",
|
|
4
4
|
"description": "Scrabble Solver web app: free word finder and board solver for Scrabble, Super Scrabble, Literaki, and more.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scrabble",
|
|
@@ -57,18 +57,18 @@
|
|
|
57
57
|
"@floating-ui/react": "^0.27.20",
|
|
58
58
|
"@kamilmielnik/gaddag": "^2.0.0",
|
|
59
59
|
"@reduxjs/toolkit": "^2.12.0",
|
|
60
|
-
"@scrabble-solver/configs": "^2.18.
|
|
61
|
-
"@scrabble-solver/constants": "^2.18.
|
|
62
|
-
"@scrabble-solver/dictionaries": "^2.18.
|
|
63
|
-
"@scrabble-solver/logger": "^2.18.
|
|
64
|
-
"@scrabble-solver/solver": "^2.18.
|
|
65
|
-
"@scrabble-solver/types": "^2.18.
|
|
66
|
-
"@scrabble-solver/word-definitions": "^2.18.
|
|
60
|
+
"@scrabble-solver/configs": "^2.18.4",
|
|
61
|
+
"@scrabble-solver/constants": "^2.18.4",
|
|
62
|
+
"@scrabble-solver/dictionaries": "^2.18.4",
|
|
63
|
+
"@scrabble-solver/logger": "^2.18.4",
|
|
64
|
+
"@scrabble-solver/solver": "^2.18.4",
|
|
65
|
+
"@scrabble-solver/types": "^2.18.4",
|
|
66
|
+
"@scrabble-solver/word-definitions": "^2.18.4",
|
|
67
67
|
"classnames": "^2.5.1",
|
|
68
68
|
"env-cmd": "^11.0.0",
|
|
69
69
|
"include-media": "^2.0.0",
|
|
70
70
|
"include-media-query-builder": "^1.1.0",
|
|
71
|
-
"next": "^16.3.
|
|
71
|
+
"next": "^16.3.3",
|
|
72
72
|
"normalize.css": "^8.0.1",
|
|
73
73
|
"react": "^19.2.8",
|
|
74
74
|
"react-cool-onclickoutside": "^1.7.0",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@svgr/webpack": "^8.1.0",
|
|
91
91
|
"@types/react": "^19.2.18",
|
|
92
|
-
"@types/react-dom": "^19.2.
|
|
92
|
+
"@types/react-dom": "^19.2.5",
|
|
93
93
|
"@types/react-highlight-words": "^0.20.1",
|
|
94
94
|
"@types/react-modal": "^3.16.3",
|
|
95
95
|
"@types/react-portal": "^4.0.7",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
(()=>{"use strict";let e,t,s;var a,r={303:()=>{try{self["workbox:strategies:7.4.0"]&&_()}catch(e){}},448:()=>{try{self["workbox:routing:7.4.0"]&&_()}catch(e){}},693:()=>{try{self["workbox:core:7.4.0"]&&_()}catch(e){}},802:()=>{try{self["workbox:precaching:7.4.0"]&&_()}catch(e){}}},i={};function n(e){var t=i[e];if(void 0!==t)return t.exports;var s=i[e]={exports:{}},a=!0;try{r[e](s,s.exports,n),a=!1}finally{a&&delete i[e]}return s.exports}n(693);class l extends Error{constructor(e,t){super(((e,...t)=>{let s=e;return t.length>0&&(s+=` :: ${JSON.stringify(t)}`),s})(e,t)),this.name=e,this.details=t}}let c={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"u">typeof registration?registration.scope:""},h=e=>[c.prefix,e,c.suffix].filter(e=>e&&e.length>0).join("-"),o=e=>e||h(c.precache);function u(e,t){let s=t();return e.waitUntil(s),s}n(802);class f{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if("install"===e.type&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;s?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return s}}}class d{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let s=(null==t?void 0:t.cacheKey)||this._precacheController.getCacheKeyForURL(e.url);return s?new Request(s,{headers:e.headers}):e},this._precacheController=e}}async function p(t,s){let a=null;if(t.url&&(a=new URL(t.url).origin),a!==self.location.origin)throw new l("cross-origin-copy-response",{origin:a});let r=t.clone(),i={headers:new Headers(r.headers),status:r.status,statusText:r.statusText},n=s?s(i):i,c=!function(){if(void 0===e){let t=new Response("");if("body"in t)try{new Response(t.body),e=!0}catch(t){e=!1}e=!1}return e}()?await r.blob():r.body;return new Response(c,n)}function w(e,t){let s=new URL(e);for(let e of t)s.searchParams.delete(e);return s.href}async function y(e,t,s,a){let r=w(t.url,s);if(t.url===r)return e.match(t,a);let i=Object.assign(Object.assign({},a),{ignoreSearch:!0});for(let n of(await e.keys(t,i)))if(r===w(n.url,s))return e.match(n,a)}class g{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}let m=new Set;async function R(){for(let e of m)await e()}function C(e){return"string"==typeof e?new Request(e):e}n(303);class b{constructor(e,t){for(const s of(this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new g,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map,this._plugins))this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:t}=this,s=C(e);if("navigate"===s.mode&&t instanceof FetchEvent&&t.preloadResponse){let e=await t.preloadResponse;if(e)return e}let a=this.hasCallback("fetchDidFail")?s.clone():null;try{for(let e of this.iterateCallbacks("requestWillFetch"))s=await e({request:s.clone(),event:t})}catch(e){if(e instanceof Error)throw new l("plugin-error-request-will-fetch",{thrownErrorMessage:e.message})}let r=s.clone();try{let e;for(let a of(e=await fetch(s,"navigate"===s.mode?void 0:this._strategy.fetchOptions),this.iterateCallbacks("fetchDidSucceed")))e=await a({event:t,request:r,response:e});return e}catch(e){throw a&&await this.runCallbacks("fetchDidFail",{error:e,event:t,originalRequest:a.clone(),request:r.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){let t,s=C(e),{cacheName:a,matchOptions:r}=this._strategy,i=await this.getCacheKey(s,"read"),n=Object.assign(Object.assign({},r),{cacheName:a});for(let e of(t=await caches.match(i,n),this.iterateCallbacks("cachedResponseWillBeUsed")))t=await e({cacheName:a,matchOptions:r,cachedResponse:t,request:i,event:this.event})||void 0;return t}async cachePut(e,t){let s=C(e);await new Promise(e=>setTimeout(e,0));let a=await this.getCacheKey(s,"write");if(!t)throw new l("cache-put-with-no-response",{url:new URL(String(a.url),location.href).href.replace(RegExp(`^${location.origin}`),"")});let r=await this._ensureResponseSafeToCache(t);if(!r)return!1;let{cacheName:i,matchOptions:n}=this._strategy,c=await self.caches.open(i),h=this.hasCallback("cacheDidUpdate"),o=h?await y(c,a.clone(),["__WB_REVISION__"],n):null;try{await c.put(a,h?r.clone():r)}catch(e){if(e instanceof Error)throw"QuotaExceededError"===e.name&&await R(),e}for(let e of this.iterateCallbacks("cacheDidUpdate"))await e({cacheName:i,oldResponse:o,newResponse:r.clone(),request:a,event:this.event});return!0}async getCacheKey(e,t){let s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let a=e;for(let e of this.iterateCallbacks("cacheKeyWillBeUsed"))a=C(await e({mode:t,request:a,event:this.event,params:this.params}));this._cacheKeys[s]=a}return this._cacheKeys[s]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if("function"==typeof t[e]){let s=this._pluginStateMap.get(t),a=a=>{let r=Object.assign(Object.assign({},a),{state:s});return t[e](r)};yield a}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>"rejected"===e.status);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(let e of this.iterateCallbacks("cacheWillUpdate"))if(t=await e({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return!s&&t&&200!==t.status&&(t=void 0),t}}class v{constructor(e={}){this.cacheName=(e=>e||h(c.runtime))(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,s="string"==typeof e.request?new Request(e.request):e.request,a=new b(this,{event:t,request:s,params:"params"in e?e.params:void 0}),r=this._getResponse(a,s,t),i=this._awaitComplete(r,a,s,t);return[r,i]}async _getResponse(e,t,s){let a;await e.runCallbacks("handlerWillStart",{event:s,request:t});try{if(!(a=await this._handle(t,e))||"error"===a.type)throw new l("no-response",{url:t.url})}catch(r){if(r instanceof Error){for(let i of e.iterateCallbacks("handlerDidError"))if(a=await i({error:r,event:s,request:t}))break}if(a);else throw r}for(let r of e.iterateCallbacks("handlerWillRespond"))a=await r({event:s,request:t,response:a});return a}async _awaitComplete(e,t,s,a){let r,i;try{r=await e}catch(e){}try{await t.runCallbacks("handlerDidRespond",{event:a,request:s,response:r}),await t.doneWaiting()}catch(e){e instanceof Error&&(i=e)}if(await t.runCallbacks("handlerDidComplete",{event:a,request:s,response:r,error:i}),t.destroy(),i)throw i}}class L extends v{constructor(e={}){e.cacheName=o(e.cacheName),super(e),this._fallbackToNetwork=!1!==e.fallbackToNetwork,this.plugins.push(L.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){let s=await t.cacheMatch(e);return s||(t.event&&"install"===t.event.type?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s,a=t.params||{};if(this._fallbackToNetwork){let r=a.integrity,i=e.integrity,n=!i||i===r;s=await t.fetch(new Request(e,{integrity:"no-cors"!==e.mode?i||r:void 0})),r&&n&&"no-cors"!==e.mode&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new l("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();let s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new l("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(let[s,a]of this.plugins.entries())a!==L.copyRedirectedCacheableResponsesPlugin&&(a===L.defaultPrecacheCacheabilityPlugin&&(e=s),a.cacheWillUpdate&&t++);0===t?this.plugins.push(L.defaultPrecacheCacheabilityPlugin):t>1&&null!==e&&this.plugins.splice(e,1)}}L.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:e})=>!e||e.status>=400?null:e},L.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:e})=>e.redirected?await p(e):e};class U{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new L({cacheName:o(e),plugins:[...t,new d({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){let t=[];for(let s of e){"string"==typeof s?t.push(s):s&&void 0===s.revision&&t.push(s.url);let{cacheKey:e,url:a}=function(e){if(!e)throw new l("add-to-cache-list-unexpected-type",{entry:e});if("string"==typeof e){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:t,url:s}=e;if(!s)throw new l("add-to-cache-list-unexpected-type",{entry:e});if(!t){let e=new URL(s,location.href);return{cacheKey:e.href,url:e.href}}let a=new URL(s,location.href),r=new URL(s,location.href);return a.searchParams.set("__WB_REVISION__",t),{cacheKey:a.href,url:r.href}}(s),r="string"!=typeof s&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(a)&&this._urlsToCacheKeys.get(a)!==e)throw new l("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(a),secondEntry:e});if("string"!=typeof s&&s.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==s.integrity)throw new l("add-to-cache-list-conflicting-integrities",{url:a});this._cacheKeysToIntegrities.set(e,s.integrity)}this._urlsToCacheKeys.set(a,e),this._urlsToCacheModes.set(a,r),t.length>0&&console.warn(`Workbox is precaching URLs without revision info: ${t.join(", ")}
|
|
2
|
-
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`)}}install(e){return u(e,async()=>{let t=new f;for(let[s,a]of(this.strategy.plugins.push(t),this._urlsToCacheKeys)){let t=this._cacheKeysToIntegrities.get(a),r=this._urlsToCacheModes.get(s),i=new Request(s,{integrity:t,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:a},request:i,event:e}))}let{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return u(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),s=new Set(this._urlsToCacheKeys.values()),a=[];for(let r of t)s.has(r.url)||(await e.delete(r),a.push(r.url));return{deletedURLs:a}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){let t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k=()=>(t||(t=new U),t);n(448);let T=e=>e&&"object"==typeof e?e:{handle:e};class K{constructor(e,t,s="GET"){this.handler=T(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=T(e)}}class x extends K{constructor(e,t,s){super(({url:t})=>{let s=e.exec(t.href);if(s)return t.origin!==location.origin&&0!==s.index?void 0:s.slice(1)},t,s)}}class P{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",e=>{let{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)})}addCacheListener(){self.addEventListener("message",e=>{if(e.data&&"CACHE_URLS"===e.data.type){let{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(t=>{"string"==typeof t&&(t=[t]);let s=new Request(...t);return this.handleRequest({request:s,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){let s,a=new URL(e.url,location.href);if(!a.protocol.startsWith("http"))return;let r=a.origin===location.origin,{params:i,route:n}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:a}),l=n&&n.handler,c=e.method;if(!l&&this._defaultHandlerMap.has(c)&&(l=this._defaultHandlerMap.get(c)),!l)return;try{s=l.handle({url:a,request:e,event:t,params:i})}catch(e){s=Promise.reject(e)}let h=n&&n.catchHandler;return s instanceof Promise&&(this._catchHandler||h)&&(s=s.catch(async s=>{if(h)try{return await h.handle({url:a,request:e,event:t,params:i})}catch(e){e instanceof Error&&(s=e)}if(this._catchHandler)return this._catchHandler.handle({url:a,request:e,event:t});throw s})),s}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){for(let r of this._routes.get(s.method)||[]){let i,n=r.match({url:e,sameOrigin:t,request:s,event:a});if(n)return Array.isArray(i=n)&&0===i.length||n.constructor===Object&&0===Object.keys(n).length?i=void 0:"boolean"==typeof n&&(i=void 0),{route:r,params:i}}return{}}setDefaultHandler(e,t="GET"){this._defaultHandlerMap.set(t,T(e))}setCatchHandler(e){this._catchHandler=T(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new l("unregister-route-but-not-found-with-method",{method:e.method});let t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}function q(e,t,a){let r;if("string"==typeof e){let s=new URL(e,location.href);r=new K(({url:e})=>e.href===s.href,t,a)}else if(e instanceof RegExp)r=new x(e,t,a);else if("function"==typeof e)r=new K(e,t,a);else if(e instanceof K)r=e;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return(!s&&((s=new P).addFetchListener(),s.addCacheListener()),s).registerRoute(r),r}class E extends K{constructor(e,t){super(({request:s})=>{let a=e.getURLsToCacheKeys();for(let r of function*(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:a=!0,urlManipulation:r}={}){let i=new URL(e,location.href);i.hash="",yield i.href;let n=function(e,t=[]){for(let s of[...e.searchParams.keys()])t.some(e=>e.test(s))&&e.searchParams.delete(s);return e}(i,t);if(yield n.href,s&&n.pathname.endsWith("/")){let e=new URL(n.href);e.pathname+=s,yield e.href}if(a){let e=new URL(n.href);e.pathname+=".html",yield e.href}if(r)for(let e of r({url:i}))yield e.href}(s.url,t)){let t=a.get(r);if(t){let s=e.getIntegrityForCacheKey(t);return{cacheKey:t,integrity:s}}}},e.strategy)}}let M="-precache-",N=async(e,t=M)=>{let s=(await self.caches.keys()).filter(s=>s.includes(t)&&s.includes(self.registration.scope)&&s!==e);return await Promise.all(s.map(e=>self.caches.delete(e))),s};class W extends K{constructor(e,{allowlist:t=[/./],denylist:s=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=s}_match({url:e,request:t}){if(t&&"navigate"!==t.mode)return!1;let s=e.pathname+e.search;for(let e of this._denylist)if(e.test(s))return!1;return!!this._allowlist.some(e=>e.test(s))}}let O="app-shell";async function I(){let e=await caches.open(O);await e.add("/")}async function S(e){let{request:t}=e;try{return await fetch(t)}catch(s){let e=await caches.open(O),t=await e.match("/");if(!t)throw s;return t}}self.addEventListener("install",e=>{e.waitUntil(I()),self.skipWaiting()}),self.addEventListener("activate",()=>{self.clients.claim()}),self.addEventListener("activate",e=>{let t=o();e.waitUntil(N(t).then(e=>{}))}),a=[{'revision':'
|
|
2
|
+
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`)}}install(e){return u(e,async()=>{let t=new f;for(let[s,a]of(this.strategy.plugins.push(t),this._urlsToCacheKeys)){let t=this._cacheKeysToIntegrities.get(a),r=this._urlsToCacheModes.get(s),i=new Request(s,{integrity:t,cache:r,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:a},request:i,event:e}))}let{updatedURLs:s,notUpdatedURLs:a}=t;return{updatedURLs:s,notUpdatedURLs:a}})}activate(e){return u(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),s=new Set(this._urlsToCacheKeys.values()),a=[];for(let r of t)s.has(r.url)||(await e.delete(r),a.push(r.url));return{deletedURLs:a}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){let t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let k=()=>(t||(t=new U),t);n(448);let T=e=>e&&"object"==typeof e?e:{handle:e};class K{constructor(e,t,s="GET"){this.handler=T(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=T(e)}}class x extends K{constructor(e,t,s){super(({url:t})=>{let s=e.exec(t.href);if(s)return t.origin!==location.origin&&0!==s.index?void 0:s.slice(1)},t,s)}}class P{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",e=>{let{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)})}addCacheListener(){self.addEventListener("message",e=>{if(e.data&&"CACHE_URLS"===e.data.type){let{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(t=>{"string"==typeof t&&(t=[t]);let s=new Request(...t);return this.handleRequest({request:s,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){let s,a=new URL(e.url,location.href);if(!a.protocol.startsWith("http"))return;let r=a.origin===location.origin,{params:i,route:n}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:a}),l=n&&n.handler,c=e.method;if(!l&&this._defaultHandlerMap.has(c)&&(l=this._defaultHandlerMap.get(c)),!l)return;try{s=l.handle({url:a,request:e,event:t,params:i})}catch(e){s=Promise.reject(e)}let h=n&&n.catchHandler;return s instanceof Promise&&(this._catchHandler||h)&&(s=s.catch(async s=>{if(h)try{return await h.handle({url:a,request:e,event:t,params:i})}catch(e){e instanceof Error&&(s=e)}if(this._catchHandler)return this._catchHandler.handle({url:a,request:e,event:t});throw s})),s}findMatchingRoute({url:e,sameOrigin:t,request:s,event:a}){for(let r of this._routes.get(s.method)||[]){let i,n=r.match({url:e,sameOrigin:t,request:s,event:a});if(n)return Array.isArray(i=n)&&0===i.length||n.constructor===Object&&0===Object.keys(n).length?i=void 0:"boolean"==typeof n&&(i=void 0),{route:r,params:i}}return{}}setDefaultHandler(e,t="GET"){this._defaultHandlerMap.set(t,T(e))}setCatchHandler(e){this._catchHandler=T(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new l("unregister-route-but-not-found-with-method",{method:e.method});let t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}function q(e,t,a){let r;if("string"==typeof e){let s=new URL(e,location.href);r=new K(({url:e})=>e.href===s.href,t,a)}else if(e instanceof RegExp)r=new x(e,t,a);else if("function"==typeof e)r=new K(e,t,a);else if(e instanceof K)r=e;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return(!s&&((s=new P).addFetchListener(),s.addCacheListener()),s).registerRoute(r),r}class E extends K{constructor(e,t){super(({request:s})=>{let a=e.getURLsToCacheKeys();for(let r of function*(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:a=!0,urlManipulation:r}={}){let i=new URL(e,location.href);i.hash="",yield i.href;let n=function(e,t=[]){for(let s of[...e.searchParams.keys()])t.some(e=>e.test(s))&&e.searchParams.delete(s);return e}(i,t);if(yield n.href,s&&n.pathname.endsWith("/")){let e=new URL(n.href);e.pathname+=s,yield e.href}if(a){let e=new URL(n.href);e.pathname+=".html",yield e.href}if(r)for(let e of r({url:i}))yield e.href}(s.url,t)){let t=a.get(r);if(t){let s=e.getIntegrityForCacheKey(t);return{cacheKey:t,integrity:s}}}},e.strategy)}}let M="-precache-",N=async(e,t=M)=>{let s=(await self.caches.keys()).filter(s=>s.includes(t)&&s.includes(self.registration.scope)&&s!==e);return await Promise.all(s.map(e=>self.caches.delete(e))),s};class W extends K{constructor(e,{allowlist:t=[/./],denylist:s=[]}={}){super(e=>this._match(e),e),this._allowlist=t,this._denylist=s}_match({url:e,request:t}){if(t&&"navigate"!==t.mode)return!1;let s=e.pathname+e.search;for(let e of this._denylist)if(e.test(s))return!1;return!!this._allowlist.some(e=>e.test(s))}}let O="app-shell";async function I(){let e=await caches.open(O);await e.add("/")}async function S(e){let{request:t}=e;try{return await fetch(t)}catch(s){let e=await caches.open(O),t=await e.match("/");if(!t)throw s;return t}}self.addEventListener("install",e=>{e.waitUntil(I()),self.skipWaiting()}),self.addEventListener("activate",()=>{self.clients.claim()}),self.addEventListener("activate",e=>{let t=o();e.waitUntil(N(t).then(e=>{}))}),a=[{'revision':'ecc788e7dc3a2c453bd7b19b936c3aa1','url':'/_next/build-manifest.json'},{'revision':'723d2f2242ccfe388715c8fc0db5a426','url':'/_next/dynamic-css-manifest.json'},{'revision':'ea88ba9aba20e7d1d2b3d045cbb9e85d','url':'/_next/react-loadable-manifest.json'},{'revision':'d1fc43aadf1fda3b5fe27546d7621f66','url':'/_next/server/dynamic-css-manifest.js'},{'revision':'b29d73bb910f1c88bdf3322c448f2894','url':'/_next/server/middleware-build-manifest.js'},{'revision':'3c03dfac5c2ba3615ebb74d6620de7ff','url':'/_next/server/middleware-react-loadable-manifest.js'},{'revision':'9fce7989bff5d35b01e177447faca50d','url':'/_next/server/next-font-manifest.js'},{'revision':'d51420cd4aa5d37d6719849cf36d0d6f','url':'/_next/server/next-font-manifest.json'},{'revision':'00a54b17bbad331c966ff64d0c683e4c','url':'/_next/static/TAqjh9l5VJQOfJ4SzgPk2/_buildManifest.js'},{'revision':'b6652df95db52feb4daf4eca35380933','url':'/_next/static/TAqjh9l5VJQOfJ4SzgPk2/_ssgManifest.js'},{'revision':null,'url':'/_next/static/chunks/100.305bdf9c75b1758f.js'},{'revision':null,'url':'/_next/static/chunks/101-f341f70c10d71212.js'},{'revision':null,'url':'/_next/static/chunks/256.6a20b23a766cfebd.js'},{'revision':null,'url':'/_next/static/chunks/277.6785dfaf22fdb0b7.js'},{'revision':null,'url':'/_next/static/chunks/307.89e6100f58443679.js'},{'revision':null,'url':'/_next/static/chunks/341.79535ba85bf1986f.js'},{'revision':null,'url':'/_next/static/chunks/363.cec65b1f9668a75e.js'},{'revision':null,'url':'/_next/static/chunks/422.3e55b4c94e328701.js'},{'revision':null,'url':'/_next/static/chunks/541.dcd9e8b804f88da5.js'},{'revision':null,'url':'/_next/static/chunks/631.98d42abad0a60af6.js'},{'revision':null,'url':'/_next/static/chunks/663.bd2c45413fbca518.js'},{'revision':null,'url':'/_next/static/chunks/693.d295da7c7b873986.js'},{'revision':null,'url':'/_next/static/chunks/744.b06c9a3de4a1d877.js'},{'revision':null,'url':'/_next/static/chunks/820.823e9e885ebb66af.js'},{'revision':null,'url':'/_next/static/chunks/827.c81b92b383a748c9.js'},{'revision':null,'url':'/_next/static/chunks/865.59baba8296c9b116.js'},{'revision':null,'url':'/_next/static/chunks/912.077603e4f0bc6cca.js'},{'revision':null,'url':'/_next/static/chunks/939.2ad86b50405c7897.js'},{'revision':null,'url':'/_next/static/chunks/980.dd35e807b18063c1.js'},{'revision':null,'url':'/_next/static/chunks/990.61296e5870b91f11.js'},{'revision':null,'url':'/_next/static/chunks/framework-626cbd48ac5aca57.js'},{'revision':null,'url':'/_next/static/chunks/main-6e9ac37e28d2b899.js'},{'revision':null,'url':'/_next/static/chunks/pages/_app-9340d51f5a6787fc.js'},{'revision':null,'url':'/_next/static/chunks/pages/_error-f0290a98eb3ed0ec.js'},{'revision':null,'url':'/_next/static/chunks/pages/index-ea21d96a88f25000.js'},{'revision':null,'url':'/_next/static/chunks/pages/not-found-a59f9deeeff63d17.js'},{'revision':'846118c33b2c0e922d7b3a7676f81f6f','url':'/_next/static/chunks/polyfills-42372ed130431b0a.js'},{'revision':null,'url':'/_next/static/chunks/webpack-d73aa4aae49cd800.js'}],k().precache(a),q(new E(k(),void 0)),q(new W(S,{denylist:[/^\/api\//]}))})();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { BadRequestError } from './BadRequestError';
|
|
2
2
|
export { isBoardValid } from './isBoardValid';
|
|
3
3
|
export { isCellValid } from './isCellValid';
|
|
4
4
|
export { isCharacterValid } from './isCharacterValid';
|
|
5
5
|
export { isRowValid } from './isRowValid';
|
|
6
|
+
export { type ApiContext, withApiLog } from './withApiLog';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type Operation, logError, logEvent } from '@scrabble-solver/logger';
|
|
2
|
+
import { isObject } from '@scrabble-solver/types';
|
|
3
|
+
import { type NextApiHandler, type NextApiRequest, type NextApiResponse } from 'next';
|
|
4
|
+
|
|
5
|
+
import { BadRequestError } from './BadRequestError';
|
|
6
|
+
|
|
7
|
+
export interface ApiContext {
|
|
8
|
+
ip: string | undefined;
|
|
9
|
+
ua: string | undefined;
|
|
10
|
+
getElapsedMs: () => number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ApiHandler = (request: NextApiRequest, response: NextApiResponse, context: ApiContext) => Promise<void> | void;
|
|
14
|
+
|
|
15
|
+
interface ErrorResponse {
|
|
16
|
+
error: string;
|
|
17
|
+
message: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const INPUT_EXCERPT_LENGTH = 1024;
|
|
21
|
+
const UA_MAX_LENGTH = 512;
|
|
22
|
+
|
|
23
|
+
export function withApiLog(operation: Operation, handler: ApiHandler): NextApiHandler {
|
|
24
|
+
return async (request, response) => {
|
|
25
|
+
const startedAt = performance.now();
|
|
26
|
+
const context: ApiContext = {
|
|
27
|
+
ip: getClientIp(request),
|
|
28
|
+
ua: request.headers['user-agent']?.slice(0, UA_MAX_LENGTH),
|
|
29
|
+
getElapsedMs: () => Math.round(performance.now() - startedAt),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await handler(request, response, context);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const { ip, ua } = context;
|
|
36
|
+
const input = getInputExcerpt(request);
|
|
37
|
+
|
|
38
|
+
if (error instanceof BadRequestError) {
|
|
39
|
+
logEvent({ type: 'error', level: 'warn', operation, ip, ua, message: error.message, input });
|
|
40
|
+
respond(response, 400, { error: 'Bad request', message: error.message });
|
|
41
|
+
} else {
|
|
42
|
+
logError(operation, error, { ip, ua, input });
|
|
43
|
+
respond(response, 500, {
|
|
44
|
+
error: 'Server error',
|
|
45
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function respond(response: NextApiResponse, status: number, body: ErrorResponse): void {
|
|
53
|
+
if (!response.headersSent) {
|
|
54
|
+
response.status(status).send(body);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getClientIp(request: NextApiRequest): string | undefined {
|
|
59
|
+
const forwardedFor = request.headers['x-forwarded-for'];
|
|
60
|
+
const header = Array.isArray(forwardedFor) ? forwardedFor.at(-1) : forwardedFor;
|
|
61
|
+
const ip = header?.split(',').at(-1)?.trim() || request.socket.remoteAddress;
|
|
62
|
+
return ip?.replace(/^::ffff:/, '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getInputExcerpt(request: NextApiRequest): string {
|
|
66
|
+
const input: unknown = request.method === 'GET' ? request.query : request.body;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return JSON.stringify(sortFieldsBySize(input) ?? null).slice(0, INPUT_EXCERPT_LENGTH);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return `(unserializable: ${String(error)})`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function sortFieldsBySize(input: unknown): unknown {
|
|
76
|
+
if (!isObject(input) || Array.isArray(input)) {
|
|
77
|
+
return input;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return Object.fromEntries(Object.entries(input).sort(([, a], [, b]) => getJsonSize(a) - getJsonSize(b)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getJsonSize(value: unknown): number {
|
|
84
|
+
return JSON.stringify(value ?? null).length;
|
|
85
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
10
10
|
|
|
11
|
-
@include media('
|
|
11
|
+
@include media('<l', 'touch') {
|
|
12
12
|
margin-bottom: calc(3 * var(--spacing--l) + var(--button--icon--size));
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
height: 100%;
|
|
32
32
|
margin: 0 auto;
|
|
33
33
|
|
|
34
|
-
@include media('
|
|
34
|
+
@include media('<l') {
|
|
35
35
|
grid-template-columns: 1fr;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
flex: 0 0 var(--solver-column--width);
|
|
55
55
|
gap: var(--spacing);
|
|
56
56
|
|
|
57
|
-
@include media('
|
|
57
|
+
@include media('<l') {
|
|
58
58
|
display: none;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
width: 100%;
|
|
107
107
|
max-width: var(--controls-max-width);
|
|
108
108
|
|
|
109
|
-
@include media('
|
|
109
|
+
@include media('>=l') {
|
|
110
110
|
display: none;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -3,8 +3,8 @@ import classNames from 'classnames';
|
|
|
3
3
|
import { type FunctionComponent, memo, type SyntheticEvent, useEffect, useMemo } from 'react';
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
5
|
|
|
6
|
-
import { useIsCompactLayout } from '@/hooks/useIsCompactLayout';
|
|
7
6
|
import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
|
|
7
|
+
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
8
8
|
import {
|
|
9
9
|
resultsSlice,
|
|
10
10
|
selectAreResultsOutdated,
|
|
@@ -37,7 +37,7 @@ const SolverBase: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
|
37
37
|
const dispatch = useDispatch();
|
|
38
38
|
const translate = useTranslate();
|
|
39
39
|
const isTouchDevice = useIsTouchDevice();
|
|
40
|
-
const showCompactControls =
|
|
40
|
+
const showCompactControls = useMediaQuery('<l');
|
|
41
41
|
const error = useTypedSelector(selectSolveError);
|
|
42
42
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
43
43
|
const results = useTypedSelector(selectProcessedResults);
|
|
@@ -5,7 +5,6 @@ import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
|
5
5
|
import { selectLocale, useTypedSelector } from '@/state';
|
|
6
6
|
import { ResultColumnId } from '@/types';
|
|
7
7
|
|
|
8
|
-
import { useIsCompactLayout } from './useIsCompactLayout';
|
|
9
8
|
import { useMediaQueries } from './useMediaQueries';
|
|
10
9
|
|
|
11
10
|
const COLUMNS_XS = [ResultColumnId.Coordinates, ResultColumnId.Word, ResultColumnId.Points];
|
|
@@ -14,7 +13,7 @@ const COLUMNS_S = [...COLUMNS_XS, ResultColumnId.BlanksCount, ResultColumnId.Wor
|
|
|
14
13
|
|
|
15
14
|
const COLUMNS_M = [...COLUMNS_XS];
|
|
16
15
|
|
|
17
|
-
const
|
|
16
|
+
const COLUMNS_L = [...COLUMNS_XS];
|
|
18
17
|
|
|
19
18
|
const selectColumns = createSelector([selectLocale], (locale) => {
|
|
20
19
|
const { consonants, vowels } = LOCALE_FEATURES[locale];
|
|
@@ -40,8 +39,7 @@ const selectColumns = createSelector([selectLocale], (locale) => {
|
|
|
40
39
|
|
|
41
40
|
export const useColumns = (): Partial<Record<ResultColumnId, boolean>> => {
|
|
42
41
|
const columns = useTypedSelector(selectColumns);
|
|
43
|
-
const { isLessThanXs, isLessThanS, isLessThanM } = useMediaQueries();
|
|
44
|
-
const isCompactLayout = useIsCompactLayout();
|
|
42
|
+
const { isLessThanXs, isLessThanS, isLessThanM, isLessThanL } = useMediaQueries();
|
|
45
43
|
|
|
46
44
|
const filteredColumns = useMemo(() => {
|
|
47
45
|
if (isLessThanXs) {
|
|
@@ -56,12 +54,12 @@ export const useColumns = (): Partial<Record<ResultColumnId, boolean>> => {
|
|
|
56
54
|
return columns.filter((columnId) => COLUMNS_M.includes(columnId));
|
|
57
55
|
}
|
|
58
56
|
|
|
59
|
-
if (
|
|
60
|
-
return columns.filter((columnId) =>
|
|
57
|
+
if (isLessThanL) {
|
|
58
|
+
return columns.filter((columnId) => COLUMNS_L.includes(columnId));
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
return columns;
|
|
64
|
-
}, [columns, isLessThanXs, isLessThanS, isLessThanM,
|
|
62
|
+
}, [columns, isLessThanXs, isLessThanS, isLessThanM, isLessThanL]);
|
|
65
63
|
|
|
66
64
|
const columnsMap = useMemo(() => {
|
|
67
65
|
return Object.fromEntries(filteredColumns.map((column) => [column, true]));
|
|
@@ -6,8 +6,8 @@ import { Button } from '@/components/Button';
|
|
|
6
6
|
import { Dictionary } from '@/components/Dictionary';
|
|
7
7
|
import { Modal } from '@/components/Modal';
|
|
8
8
|
import { Results } from '@/components/Results';
|
|
9
|
-
import { useIsCompactLayout } from '@/hooks/useIsCompactLayout';
|
|
10
9
|
import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
|
|
10
|
+
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
11
11
|
import Check from '@/icons/Check.svg';
|
|
12
12
|
import EyeFill from '@/icons/EyeFill.svg';
|
|
13
13
|
import {
|
|
@@ -29,7 +29,7 @@ interface Props {
|
|
|
29
29
|
const ResultsModalBase: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
|
|
30
30
|
const dispatch = useDispatch();
|
|
31
31
|
const translate = useTranslate();
|
|
32
|
-
const showResultsInModal =
|
|
32
|
+
const showResultsInModal = useMediaQuery('<l');
|
|
33
33
|
const previewsOnRepeatedClick = useIsTouchDevice();
|
|
34
34
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
35
35
|
const highlightedIndex = useTypedSelector(selectResultCandidateIndex);
|
|
@@ -4,7 +4,7 @@ import { useDispatch } from 'react-redux';
|
|
|
4
4
|
import { Button } from '@/components/Button';
|
|
5
5
|
import { Dictionary } from '@/components/Dictionary';
|
|
6
6
|
import { Modal } from '@/components/Modal';
|
|
7
|
-
import {
|
|
7
|
+
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
8
8
|
import EyeFill from '@/icons/EyeFill.svg';
|
|
9
9
|
import { hoveredWordSlice, selectHoveredWord, useTranslate, useTypedSelector } from '@/state';
|
|
10
10
|
|
|
@@ -22,7 +22,7 @@ const WordsModalBase: FunctionComponent<Props> = ({ className, isOpen, onClose,
|
|
|
22
22
|
const dispatch = useDispatch();
|
|
23
23
|
const translate = useTranslate();
|
|
24
24
|
const hoveredWord = useTypedSelector(selectHoveredWord);
|
|
25
|
-
const showsPreviewButton =
|
|
25
|
+
const showsPreviewButton = useMediaQuery('<l');
|
|
26
26
|
const keepsHighlightOnClose = useRef(false);
|
|
27
27
|
|
|
28
28
|
const handlePreview = useCallback(() => {
|
|
@@ -6,8 +6,8 @@ import { List } from 'react-window';
|
|
|
6
6
|
|
|
7
7
|
import { EmptyState } from '@/components/EmptyState';
|
|
8
8
|
import { Header, HeaderButton, Search } from '@/components/Table';
|
|
9
|
-
import { useIsCompactLayout } from '@/hooks/useIsCompactLayout';
|
|
10
9
|
import { useIsTouchDevice } from '@/hooks/useIsTouchDevice';
|
|
10
|
+
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
11
11
|
import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
12
12
|
import GeoAlt from '@/icons/GeoAlt.svg';
|
|
13
13
|
import QuestionSquare from '@/icons/QuestionSquare.svg';
|
|
@@ -38,7 +38,7 @@ export const WordsTable: FunctionComponent<Props> = ({ className, isOpen, onPrev
|
|
|
38
38
|
const dispatch = useDispatch();
|
|
39
39
|
const translate = useTranslate();
|
|
40
40
|
const isTouchDevice = useIsTouchDevice();
|
|
41
|
-
const isCompactLayout =
|
|
41
|
+
const isCompactLayout = useMediaQuery('<l');
|
|
42
42
|
const highlightFollowsPointer = !isTouchDevice && !isCompactLayout;
|
|
43
43
|
const locale = useTypedSelector(selectLocale);
|
|
44
44
|
const { direction } = LOCALE_FEATURES[locale];
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { games } from '@scrabble-solver/configs';
|
|
2
2
|
import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
|
|
3
3
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
4
|
-
import {
|
|
4
|
+
import { logEvent } from '@scrabble-solver/logger';
|
|
5
5
|
import { type Locale, isLocale } from '@scrabble-solver/types';
|
|
6
6
|
import { getWordDefinition } from '@scrabble-solver/word-definitions';
|
|
7
7
|
import { type NextApiRequest, type NextApiResponse } from 'next';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { type ApiContext, BadRequestError, withApiLog } from '@/api';
|
|
10
10
|
|
|
11
11
|
interface RequestData {
|
|
12
12
|
locale: Locale;
|
|
@@ -16,39 +16,33 @@ interface RequestData {
|
|
|
16
16
|
const MAXIMUM_COLLISIONS_COUNT = Object.values(games).reduce((result, game) => Math.max(result, game.rackSize), 0);
|
|
17
17
|
const MAXIMUM_WORDS_COUNT = MAXIMUM_COLLISIONS_COUNT + 1;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const meta = getServerLoggingData(request);
|
|
19
|
+
export default withApiLog('definition', dictionary);
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
async function dictionary(request: NextApiRequest, response: NextApiResponse, { ip, getElapsedMs }: ApiContext) {
|
|
22
|
+
const { locale, words } = parseRequest(request);
|
|
23
|
+
const gaddag = await dictionaries.get(locale);
|
|
24
|
+
const results = await Promise.all(words.map((word) => getWordDefinition(locale, word, gaddag.has(word))));
|
|
25
|
+
response.status(200).send(results.map((result) => result.toJson()));
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const results = await Promise.all(words.map((word) => getWordDefinition(locale, word, gaddag.has(word))));
|
|
35
|
-
response.status(200).send(results.map((result) => result.toJson()));
|
|
36
|
-
} catch (error) {
|
|
37
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
38
|
-
logger.error('dictionary - error', { error, meta });
|
|
39
|
-
response.status(500).send({ error: 'Server error', message });
|
|
40
|
-
}
|
|
41
|
-
};
|
|
27
|
+
logEvent({
|
|
28
|
+
type: 'definition',
|
|
29
|
+
ip,
|
|
30
|
+
ms: getElapsedMs(),
|
|
31
|
+
locale,
|
|
32
|
+
words: words.join(','),
|
|
33
|
+
found: results.filter((result) => result.definitions.length > 0).length,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
42
36
|
|
|
43
|
-
|
|
37
|
+
function parseRequest(request: NextApiRequest): RequestData {
|
|
44
38
|
const { locale, word } = request.query;
|
|
45
39
|
|
|
46
40
|
if (!isLocale(locale)) {
|
|
47
|
-
throw new
|
|
41
|
+
throw new BadRequestError('Invalid "locale" parameter');
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
if (typeof word !== 'string' || word.length === 0) {
|
|
51
|
-
throw new
|
|
45
|
+
throw new BadRequestError('Invalid "word" parameter');
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
const words = Array.from(
|
|
@@ -62,13 +56,11 @@ const parseRequest = (request: NextApiRequest): RequestData => {
|
|
|
62
56
|
);
|
|
63
57
|
|
|
64
58
|
if (words.length > MAXIMUM_WORDS_COUNT) {
|
|
65
|
-
throw new
|
|
59
|
+
throw new BadRequestError('Invalid "word" parameter');
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
return {
|
|
69
63
|
locale,
|
|
70
64
|
words,
|
|
71
65
|
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export default dictionary;
|
|
66
|
+
}
|
|
@@ -1,65 +1,61 @@
|
|
|
1
1
|
import { type Gaddag } from '@kamilmielnik/gaddag';
|
|
2
2
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
3
|
-
import {
|
|
3
|
+
import { logEvent } from '@scrabble-solver/logger';
|
|
4
4
|
import { isLocale, type Locale } from '@scrabble-solver/types';
|
|
5
5
|
import { type NextApiRequest, type NextApiResponse } from 'next';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
7
|
import { gzip } from 'zlib';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { type ApiContext, BadRequestError, withApiLog } from '@/api';
|
|
10
10
|
|
|
11
11
|
interface RequestData {
|
|
12
12
|
locale: Locale;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const meta = getServerLoggingData(request);
|
|
15
|
+
export default withApiLog('download', dictionary);
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
async function dictionary(request: NextApiRequest, response: NextApiResponse, { ip, getElapsedMs }: ApiContext) {
|
|
18
|
+
const { locale } = parseRequest(request);
|
|
19
|
+
const gaddag = await dictionaries.get(locale);
|
|
20
|
+
const encoding = acceptsGzip(request) ? 'gzip' : 'identity';
|
|
21
|
+
const body = encoding === 'gzip' ? await getCompressedDictionary(gaddag) : getSerializedDictionary(gaddag);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
});
|
|
23
|
+
// no-cache makes clients store the payload but revalidate it, so unchanged
|
|
24
|
+
// re-downloads become 304s, answered by Next's built-in ETag handling.
|
|
25
|
+
response.setHeader('Cache-Control', 'no-cache');
|
|
26
|
+
response.setHeader('Content-Type', 'application/octet-stream');
|
|
27
|
+
response.setHeader('Vary', 'Accept-Encoding');
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
if (encoding === 'gzip') {
|
|
30
|
+
response.setHeader('Content-Encoding', 'gzip');
|
|
31
|
+
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
// re-downloads become 304s, answered by Next's built-in ETag handling.
|
|
34
|
-
response.setHeader('Cache-Control', 'no-cache');
|
|
35
|
-
response.setHeader('Content-Type', 'application/octet-stream');
|
|
36
|
-
response.setHeader('Vary', 'Accept-Encoding');
|
|
33
|
+
response.status(200).send(body);
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
};
|
|
35
|
+
logEvent({
|
|
36
|
+
type: 'download',
|
|
37
|
+
ip,
|
|
38
|
+
ms: getElapsedMs(),
|
|
39
|
+
locale,
|
|
40
|
+
status: response.statusCode,
|
|
41
|
+
encoding,
|
|
42
|
+
bytes: response.statusCode === 304 ? 0 : body.byteLength,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
function parseRequest(request: NextApiRequest): RequestData {
|
|
51
47
|
const { locale } = request.query;
|
|
52
48
|
|
|
53
49
|
if (!isLocale(locale)) {
|
|
54
|
-
throw new
|
|
50
|
+
throw new BadRequestError('Invalid "locale" parameter');
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
return {
|
|
58
54
|
locale,
|
|
59
55
|
};
|
|
60
|
-
}
|
|
56
|
+
}
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
function acceptsGzip(request: NextApiRequest): boolean {
|
|
63
59
|
const acceptEncoding = request.headers['accept-encoding'];
|
|
64
60
|
const header = Array.isArray(acceptEncoding) ? acceptEncoding.join(',') : acceptEncoding;
|
|
65
61
|
|
|
@@ -86,14 +82,14 @@ const acceptsGzip = (request: NextApiRequest): boolean => {
|
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
return wildcardAccepts;
|
|
89
|
-
}
|
|
85
|
+
}
|
|
90
86
|
|
|
91
87
|
// Dictionaries rebuild at most daily, so each response variant is produced
|
|
92
88
|
// once and lives exactly as long as the in-memory dictionary it was made from.
|
|
93
89
|
const serializedCache = new WeakMap<Gaddag, Buffer>();
|
|
94
90
|
const compressedCache = new WeakMap<Gaddag, Promise<Buffer>>();
|
|
95
91
|
|
|
96
|
-
|
|
92
|
+
function getSerializedDictionary(gaddag: Gaddag): Buffer {
|
|
97
93
|
let serialized = serializedCache.get(gaddag);
|
|
98
94
|
|
|
99
95
|
if (!serialized) {
|
|
@@ -104,11 +100,11 @@ const getSerializedDictionary = (gaddag: Gaddag): Buffer => {
|
|
|
104
100
|
}
|
|
105
101
|
|
|
106
102
|
return serialized;
|
|
107
|
-
}
|
|
103
|
+
}
|
|
108
104
|
|
|
109
105
|
const gzipAsync = promisify(gzip);
|
|
110
106
|
|
|
111
|
-
|
|
107
|
+
function getCompressedDictionary(gaddag: Gaddag): Promise<Buffer> {
|
|
112
108
|
let compressed = compressedCache.get(gaddag);
|
|
113
109
|
|
|
114
110
|
if (!compressed) {
|
|
@@ -118,12 +114,10 @@ const getCompressedDictionary = (gaddag: Gaddag): Promise<Buffer> => {
|
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
return compressed;
|
|
121
|
-
}
|
|
117
|
+
}
|
|
122
118
|
|
|
123
119
|
export const config = {
|
|
124
120
|
api: {
|
|
125
121
|
responseLimit: '50mb',
|
|
126
122
|
},
|
|
127
123
|
};
|
|
128
|
-
|
|
129
|
-
export default dictionary;
|