scrabble-solver 2.16.7 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. package/.nx/workspace-data/d/server-process.json +2 -2
  2. package/.nx/workspace-data/file-map.json +548 -572
  3. package/.nx/workspace-data/lockfile-dependencies.hash +1 -1
  4. package/.nx/workspace-data/lockfile-nodes.hash +1 -1
  5. package/.nx/workspace-data/nx_files.nxt +0 -0
  6. package/.nx/workspace-data/parsed-lock-file.dependencies.json +185 -160
  7. package/.nx/workspace-data/parsed-lock-file.nodes.json +406 -370
  8. package/.nx/workspace-data/project-graph.json +656 -552
  9. package/.nx/workspace-data/source-maps.json +96 -0
  10. package/.oxlintrc.json +6 -0
  11. package/CLAUDE.md +22 -17
  12. package/README.md +9 -0
  13. package/bun.lock +107 -133
  14. package/cypress/e2e/app.spec.cy.ts +4 -4
  15. package/cypress/e2e/features/solver-paths.ts +66 -0
  16. package/cypress/e2e/features.spec.cy.ts +1 -0
  17. package/cypress/support/lib/actions.ts +28 -9
  18. package/lerna.json +1 -1
  19. package/package.json +8 -7
  20. package/packages/configs/package.json +3 -3
  21. package/packages/constants/package.json +1 -1
  22. package/packages/dictionaries/README.md +1 -1
  23. package/packages/dictionaries/build/Dictionaries.d.ts +2 -2
  24. package/packages/dictionaries/build/Dictionaries.js +6 -6
  25. package/packages/dictionaries/build/lib/DiskCache.d.ts +11 -4
  26. package/packages/dictionaries/build/lib/DiskCache.js +27 -10
  27. package/packages/dictionaries/build/lib/LayeredCache.d.ts +4 -4
  28. package/packages/dictionaries/build/lib/LayeredCache.js +3 -3
  29. package/packages/dictionaries/build/lib/MemoryCache.d.ts +4 -4
  30. package/packages/dictionaries/build/lib/MemoryCache.js +2 -2
  31. package/packages/dictionaries/build/lib/createCacheTimestampComparator.d.ts +2 -2
  32. package/packages/dictionaries/build/lib/downloadDictionary.d.ts +2 -2
  33. package/packages/dictionaries/build/lib/downloadDictionary.js +4 -3
  34. package/packages/dictionaries/build/lib/getDictionaryFilepath.d.ts +3 -1
  35. package/packages/dictionaries/build/lib/getDictionaryFilepath.js +8 -3
  36. package/packages/dictionaries/package.json +7 -6
  37. package/packages/dictionaries/src/Dictionaries.ts +12 -12
  38. package/packages/dictionaries/src/lib/DiskCache.test.ts +85 -0
  39. package/packages/dictionaries/src/lib/DiskCache.ts +34 -14
  40. package/packages/dictionaries/src/lib/LayeredCache.ts +7 -7
  41. package/packages/dictionaries/src/lib/MemoryCache.ts +6 -6
  42. package/packages/dictionaries/src/lib/createCacheTimestampComparator.ts +2 -2
  43. package/packages/dictionaries/src/lib/downloadDictionary.ts +5 -4
  44. package/packages/dictionaries/src/lib/getDictionaryFilepath.ts +7 -2
  45. package/packages/gaddag/build/Gaddag.d.ts +39 -0
  46. package/packages/gaddag/build/Gaddag.js +128 -0
  47. package/packages/gaddag/build/buildGaddag.d.ts +10 -0
  48. package/packages/gaddag/build/buildGaddag.js +360 -0
  49. package/packages/gaddag/build/constants.d.ts +12 -0
  50. package/packages/gaddag/build/constants.js +15 -0
  51. package/packages/gaddag/build/index.d.ts +3 -0
  52. package/packages/gaddag/build/index.js +13 -0
  53. package/packages/logger/package.json +1 -1
  54. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  55. package/packages/scrabble-solver/.next/build-manifest.json +13 -13
  56. package/packages/scrabble-solver/.next/cache/.previewinfo +1 -1
  57. package/packages/scrabble-solver/.next/cache/.rscinfo +1 -1
  58. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  59. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  60. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  61. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack.old +0 -0
  62. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  63. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  64. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  65. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  66. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack.old +0 -0
  67. package/packages/scrabble-solver/.next/diagnostics/framework.json +1 -1
  68. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  69. package/packages/scrabble-solver/.next/prerender-manifest.json +4 -4
  70. package/packages/scrabble-solver/.next/required-server-files.js +3 -2
  71. package/packages/scrabble-solver/.next/required-server-files.json +3 -2
  72. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  73. package/packages/scrabble-solver/.next/server/chunks/129.js +1 -0
  74. package/packages/scrabble-solver/.next/server/chunks/30.js +1 -1
  75. package/packages/scrabble-solver/.next/server/chunks/392.js +1 -1
  76. package/packages/scrabble-solver/.next/server/chunks/671.js +1 -1
  77. package/packages/scrabble-solver/.next/server/chunks/712.js +1 -1
  78. package/packages/scrabble-solver/.next/server/chunks/785.js +1 -0
  79. package/packages/scrabble-solver/.next/server/chunks/791.js +1 -1
  80. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  81. package/packages/scrabble-solver/.next/server/pages/404.html +1 -1
  82. package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
  83. package/packages/scrabble-solver/.next/server/pages/_app.js +1 -1
  84. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  85. package/packages/scrabble-solver/.next/server/pages/_error.js +1 -1
  86. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  87. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
  88. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  89. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +1 -1
  90. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  91. package/packages/scrabble-solver/.next/server/pages/api/solve.js +1 -1
  92. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  93. package/packages/scrabble-solver/.next/server/pages/api/verify.js +1 -1
  94. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  95. package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
  96. package/packages/scrabble-solver/.next/server/pages/index.js +1 -1
  97. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  98. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  99. package/packages/scrabble-solver/.next/server/pages/not-found.html +1 -1
  100. package/packages/scrabble-solver/.next/server/pages/not-found.js.nft.json +1 -1
  101. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
  102. package/packages/scrabble-solver/.next/server/webpack-runtime.js +1 -1
  103. package/packages/scrabble-solver/.next/static/{vYpMmuC_yEurZGcyGVnLf → OLE8_ZInlGH7ENU0NIOpf}/_buildManifest.js +1 -1
  104. package/packages/scrabble-solver/.next/static/chunks/583.7a46807be14c377e.js +1 -0
  105. package/packages/scrabble-solver/.next/static/chunks/830.661816bf0de8c2f1.js +1 -0
  106. package/packages/scrabble-solver/.next/static/chunks/{main-852475ca502b4e49.js → main-5b999f3f24ebfa84.js} +2 -2
  107. package/packages/scrabble-solver/.next/static/chunks/pages/_app-7024e7a27ffbb2fa.js +7 -0
  108. package/packages/scrabble-solver/.next/static/chunks/pages/{index-ec0f6aeeaf7be227.js → index-8b398b0a5d870773.js} +1 -1
  109. package/packages/scrabble-solver/.next/static/chunks/webpack-3943d624bbb47a64.js +1 -0
  110. package/packages/scrabble-solver/.next/static/css/aedeaada218d7280.css +2 -0
  111. package/packages/scrabble-solver/.next/trace +21 -21
  112. package/packages/scrabble-solver/.next/trace-build +1 -1
  113. package/packages/scrabble-solver/next.config.js +0 -6
  114. package/packages/scrabble-solver/package.json +10 -10
  115. package/packages/scrabble-solver/public/service-worker.js +2 -2
  116. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +4 -1
  117. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +17 -6
  118. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +7 -22
  119. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +1 -8
  120. package/packages/scrabble-solver/src/components/Results/Result.tsx +0 -3
  121. package/packages/scrabble-solver/src/components/Results/Results.module.scss +0 -1
  122. package/packages/scrabble-solver/src/components/Results/Results.tsx +10 -2
  123. package/packages/scrabble-solver/src/components/Results/types.ts +3 -3
  124. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +4 -3
  125. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.ts +1 -1
  126. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +2 -2
  127. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/index.ts +80 -3
  128. package/packages/scrabble-solver/src/pages/api/solve.ts +2 -2
  129. package/packages/scrabble-solver/src/pages/api/verify.ts +12 -3
  130. package/packages/scrabble-solver/src/sdk/solve.ts +13 -7
  131. package/packages/scrabble-solver/src/sdk/verify.ts +12 -6
  132. package/packages/scrabble-solver/src/service-worker/appShell.test.ts +86 -0
  133. package/packages/scrabble-solver/src/service-worker/appShell.ts +39 -0
  134. package/packages/scrabble-solver/src/service-worker/index.ts +4 -5
  135. package/packages/scrabble-solver/src/serviceWorkerManager.ts +1 -18
  136. package/packages/scrabble-solver/src/solver-worker/client.test.ts +151 -0
  137. package/packages/scrabble-solver/src/solver-worker/client.ts +106 -0
  138. package/packages/scrabble-solver/src/solver-worker/dictionaries/constants.ts +9 -0
  139. package/packages/scrabble-solver/src/solver-worker/dictionaries/getDictionary.ts +21 -0
  140. package/packages/scrabble-solver/src/solver-worker/dictionaries/index.ts +2 -0
  141. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidateDictionary.test.ts +199 -0
  142. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidateDictionary.ts +86 -0
  143. package/packages/scrabble-solver/src/solver-worker/dictionaries/revalidatedAt.ts +43 -0
  144. package/packages/scrabble-solver/src/solver-worker/getGaddag.test.ts +109 -0
  145. package/packages/scrabble-solver/src/solver-worker/getGaddag.ts +62 -0
  146. package/packages/scrabble-solver/src/solver-worker/index.ts +1 -0
  147. package/packages/scrabble-solver/src/solver-worker/index.worker.ts +124 -0
  148. package/packages/scrabble-solver/src/solver-worker/messages.ts +20 -0
  149. package/packages/scrabble-solver/src/state/index.ts +1 -0
  150. package/packages/scrabble-solver/src/state/sagas.ts +19 -9
  151. package/packages/scrabble-solver/src/state/useTypedStore.ts +6 -0
  152. package/packages/scrabble-solver/src/types/api.ts +1 -2
  153. package/packages/scrabble-solver/tsconfig.json +4 -0
  154. package/packages/scrabble-solver/tsconfig.tsbuildinfo +1 -1
  155. package/packages/solver/README.md +24 -0
  156. package/packages/solver/benchmarks/index.ts +15 -0
  157. package/packages/solver/benchmarks/lib.ts +31 -0
  158. package/packages/solver/benchmarks/renderChart.ts +165 -0
  159. package/packages/solver/benchmarks/results/chart.svg +65 -0
  160. package/packages/solver/benchmarks/runBenchmarks.ts +66 -0
  161. package/packages/solver/benchmarks/scenarios.ts +64 -0
  162. package/packages/solver/benchmarks/types.ts +16 -0
  163. package/packages/solver/benchmarks/updateReadme.ts +53 -0
  164. package/packages/solver/build/MoveGenerator.d.ts +116 -0
  165. package/packages/solver/build/MoveGenerator.js +686 -0
  166. package/packages/solver/build/index.d.ts +0 -13
  167. package/packages/solver/build/index.js +1 -27
  168. package/packages/solver/build/solve.d.ts +2 -2
  169. package/packages/solver/build/solve.js +3 -19
  170. package/packages/solver/package.json +7 -6
  171. package/packages/solver/src/MoveGenerator.test.ts +1009 -0
  172. package/packages/solver/src/MoveGenerator.ts +926 -0
  173. package/packages/solver/src/ReferenceSolver.ts +401 -0
  174. package/packages/solver/src/index.ts +0 -13
  175. package/packages/solver/src/solve.test.ts +21 -21
  176. package/packages/solver/src/solve.ts +4 -22
  177. package/packages/solver/tsconfig.json +1 -1
  178. package/packages/types/build/Cell.d.ts +1 -0
  179. package/packages/types/build/Cell.js +3 -0
  180. package/packages/types/build/Result.d.ts +3 -2
  181. package/packages/types/build/Result.js +11 -6
  182. package/packages/types/build/ResultJson.d.ts +5 -4
  183. package/packages/types/build/index.d.ts +0 -6
  184. package/packages/types/build/index.js +1 -9
  185. package/packages/types/build/readResultCells.d.ts +5 -0
  186. package/packages/types/build/readResultCells.js +75 -0
  187. package/packages/types/package.json +4 -3
  188. package/packages/types/src/Cell.ts +4 -0
  189. package/packages/types/src/Result.ts +15 -6
  190. package/packages/types/src/ResultJson.ts +5 -5
  191. package/packages/types/src/index.ts +0 -6
  192. package/packages/types/src/readResultCells.test.ts +140 -0
  193. package/packages/types/src/readResultCells.ts +90 -0
  194. package/packages/word-definitions/package.json +2 -2
  195. package/packages/word-lists/package.json +2 -2
  196. package/packages/scrabble-solver/.next/static/chunks/pages/_app-a5bbe02710f13d05.js +0 -7
  197. package/packages/scrabble-solver/.next/static/chunks/webpack-1a7a166b34ed8d24.js +0 -1
  198. package/packages/scrabble-solver/.next/static/css/0af35adb4bab56f0.css +0 -2
  199. package/packages/scrabble-solver/src/sdk/getDictionary.ts +0 -9
  200. package/packages/scrabble-solver/src/service-worker/average.ts +0 -7
  201. package/packages/scrabble-solver/src/service-worker/dictionaries/constants.ts +0 -5
  202. package/packages/scrabble-solver/src/service-worker/dictionaries/getDictionary.ts +0 -20
  203. package/packages/scrabble-solver/src/service-worker/dictionaries/index.ts +0 -2
  204. package/packages/scrabble-solver/src/service-worker/dictionaries/revalidateDictionary.ts +0 -33
  205. package/packages/scrabble-solver/src/service-worker/getTrie.ts +0 -24
  206. package/packages/scrabble-solver/src/service-worker/routeSolveRequests.ts +0 -91
  207. package/packages/scrabble-solver/src/service-worker/routeVerifyRequests.ts +0 -37
  208. package/packages/solver/build/areDigraphsValid.d.ts +0 -2
  209. package/packages/solver/build/areDigraphsValid.js +0 -17
  210. package/packages/solver/build/fillPattern.d.ts +0 -6
  211. package/packages/solver/build/fillPattern.js +0 -52
  212. package/packages/solver/build/generateEndIndices.d.ts +0 -2
  213. package/packages/solver/build/generateEndIndices.js +0 -17
  214. package/packages/solver/build/generateHorizontalPatterns.d.ts +0 -2
  215. package/packages/solver/build/generateHorizontalPatterns.js +0 -16
  216. package/packages/solver/build/generatePattern.d.ts +0 -7
  217. package/packages/solver/build/generatePattern.js +0 -20
  218. package/packages/solver/build/generatePatterns.d.ts +0 -2
  219. package/packages/solver/build/generatePatterns.js +0 -11
  220. package/packages/solver/build/generateStartIndices.d.ts +0 -2
  221. package/packages/solver/build/generateStartIndices.js +0 -16
  222. package/packages/solver/build/generateVectors.d.ts +0 -7
  223. package/packages/solver/build/generateVectors.js +0 -7
  224. package/packages/solver/build/generateVerticalPatterns.d.ts +0 -2
  225. package/packages/solver/build/generateVerticalPatterns.js +0 -16
  226. package/packages/solver/build/getCellsScore.d.ts +0 -2
  227. package/packages/solver/build/getCellsScore.js +0 -18
  228. package/packages/solver/build/getPatternHash.d.ts +0 -2
  229. package/packages/solver/build/getPatternHash.js +0 -14
  230. package/packages/solver/build/getPatternScore.d.ts +0 -2
  231. package/packages/solver/build/getPatternScore.js +0 -22
  232. package/packages/solver/build/getUniquePatterns.d.ts +0 -2
  233. package/packages/solver/build/getUniquePatterns.js +0 -17
  234. package/packages/solver/src/areDigraphsValid.ts +0 -18
  235. package/packages/solver/src/fillPattern.test.ts +0 -107
  236. package/packages/solver/src/fillPattern.ts +0 -68
  237. package/packages/solver/src/generateEndIndices.test.ts +0 -23
  238. package/packages/solver/src/generateEndIndices.ts +0 -19
  239. package/packages/solver/src/generateHorizontalPatterns.test.ts +0 -31
  240. package/packages/solver/src/generateHorizontalPatterns.ts +0 -15
  241. package/packages/solver/src/generatePattern.ts +0 -33
  242. package/packages/solver/src/generatePatterns.test.ts +0 -13
  243. package/packages/solver/src/generatePatterns.ts +0 -11
  244. package/packages/solver/src/generateStartIndices.test.ts +0 -19
  245. package/packages/solver/src/generateStartIndices.ts +0 -17
  246. package/packages/solver/src/generateVectors.test.ts +0 -12
  247. package/packages/solver/src/generateVectors.ts +0 -10
  248. package/packages/solver/src/generateVerticalPatterns.test.ts +0 -32
  249. package/packages/solver/src/generateVerticalPatterns.ts +0 -15
  250. package/packages/solver/src/getCellsScore.ts +0 -21
  251. package/packages/solver/src/getPatternHash.test.ts +0 -17
  252. package/packages/solver/src/getPatternHash.ts +0 -12
  253. package/packages/solver/src/getPatternScore.test.ts +0 -204
  254. package/packages/solver/src/getPatternScore.ts +0 -23
  255. package/packages/solver/src/getUniquePatterns.ts +0 -19
  256. package/packages/types/build/CollisionJson.d.ts +0 -2
  257. package/packages/types/build/CollisionJson.js +0 -2
  258. package/packages/types/build/FinalPattern.d.ts +0 -6
  259. package/packages/types/build/FinalPattern.js +0 -14
  260. package/packages/types/build/HorizontalPattern.d.ts +0 -5
  261. package/packages/types/build/HorizontalPattern.js +0 -34
  262. package/packages/types/build/Pattern.d.ts +0 -20
  263. package/packages/types/build/Pattern.js +0 -50
  264. package/packages/types/build/PatternJson.d.ts +0 -6
  265. package/packages/types/build/PatternJson.js +0 -2
  266. package/packages/types/build/VerticalPattern.d.ts +0 -5
  267. package/packages/types/build/VerticalPattern.js +0 -34
  268. package/packages/types/src/CollisionJson.ts +0 -3
  269. package/packages/types/src/FinalPattern.ts +0 -14
  270. package/packages/types/src/HorizontalPattern.ts +0 -37
  271. package/packages/types/src/Pattern.ts +0 -71
  272. package/packages/types/src/PatternJson.ts +0 -7
  273. package/packages/types/src/VerticalPattern.ts +0 -37
  274. /package/packages/scrabble-solver/.next/static/{vYpMmuC_yEurZGcyGVnLf → OLE8_ZInlGH7ENU0NIOpf}/_ssgManifest.js +0 -0
  275. /package/packages/scrabble-solver/src/{service-worker → solver-worker}/dictionaries/expirationManager.ts +0 -0
  276. /package/packages/scrabble-solver/src/{service-worker → solver-worker}/dictionaries/getDictionaryUrl.ts +0 -0
@@ -0,0 +1,124 @@
1
+ /// <reference lib="WebWorker" />
2
+
3
+ import { getConfig } from '@scrabble-solver/configs';
4
+ import { BLANK } from '@scrabble-solver/constants';
5
+ import { solve } from '@scrabble-solver/solver';
6
+ import { Board, type Locale, Tile } from '@scrabble-solver/types';
7
+
8
+ import { type SolveRequestPayload, type VerifyRequestPayload } from '@/types';
9
+
10
+ import { revalidateDictionary } from './dictionaries';
11
+ import { getGaddag } from './getGaddag';
12
+ import { type SolverWorkerRequest, type SolverWorkerResponse } from './messages';
13
+
14
+ declare const self: DedicatedWorkerGlobalScope;
15
+
16
+ /**
17
+ * Solves run synchronously on the single worker thread, so a burst of requests
18
+ * would queue stale solves ahead of the latest one.
19
+ */
20
+ let latestSolveId = 0;
21
+
22
+ self.addEventListener('message', ({ data }: MessageEvent<SolverWorkerRequest>) => {
23
+ if (data.type === 'solve') {
24
+ latestSolveId = data.id;
25
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
26
+ respond(data.id, () => handleSolve(data.id, data.payload));
27
+ } else if (data.type === 'verify') {
28
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
29
+ respond(data.id, () => handleVerify(data.id, data.payload));
30
+ } else if (data.type === 'prefetch') {
31
+ prefetchDictionary(data.locale).catch(() => undefined);
32
+ }
33
+ });
34
+
35
+ async function handleSolve(id: number, payload: SolveRequestPayload): Promise<SolverWorkerResponse> {
36
+ const { board, characters, game, locale } = payload;
37
+ const gaddag = await getGaddag(locale);
38
+ revalidateInBackground(locale);
39
+
40
+ if (!gaddag) {
41
+ return { id, outcome: 'unavailable' };
42
+ }
43
+
44
+ await yieldToQueuedMessages();
45
+
46
+ if (latestSolveId !== id) {
47
+ return { id, outcome: 'superseded' };
48
+ }
49
+
50
+ const config = getConfig(game, locale);
51
+ const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK }));
52
+ return { data: solve(gaddag, config, Board.fromJson(board), tiles), id, outcome: 'answered' };
53
+ }
54
+
55
+ async function handleVerify(
56
+ id: number,
57
+ { board: boardJson, locale }: VerifyRequestPayload,
58
+ ): Promise<SolverWorkerResponse> {
59
+ const gaddag = await getGaddag(locale);
60
+ revalidateInBackground(locale);
61
+
62
+ if (!gaddag) {
63
+ return { id, outcome: 'unavailable' };
64
+ }
65
+
66
+ const board = Board.fromJson(boardJson);
67
+ const words = board.getWords().sort((a, b) => a.localeCompare(b, locale));
68
+ const invalidWords: string[] = [];
69
+ const validWords: string[] = [];
70
+
71
+ for (const word of words) {
72
+ if (gaddag.has(word)) {
73
+ validWords.push(word);
74
+ } else {
75
+ invalidWords.push(word);
76
+ }
77
+ }
78
+
79
+ return { data: { invalidWords, validWords }, id, outcome: 'answered' };
80
+ }
81
+
82
+ /**
83
+ * Deserializes the dictionary ahead of the first solve, so it does not pay the
84
+ * cold-start cost. Warms even when revalidation fails (e.g. offline) - a
85
+ * previously cached dictionary can still be deserialized.
86
+ */
87
+ async function prefetchDictionary(locale: Locale): Promise<void> {
88
+ try {
89
+ await revalidateDictionary(locale);
90
+ } catch {
91
+ // Solving falls back to the server until a later revalidation succeeds.
92
+ }
93
+
94
+ await getGaddag(locale);
95
+ }
96
+
97
+ /**
98
+ * Revalidation failures are expected (offline, server down) and must not
99
+ * surface as unhandled rejections - solving keeps using the cached dictionary
100
+ * until a later revalidation succeeds.
101
+ */
102
+ function revalidateInBackground(locale: Locale): void {
103
+ revalidateDictionary(locale).catch(() => undefined);
104
+ }
105
+
106
+ async function respond(id: number, handle: () => Promise<SolverWorkerResponse>): Promise<void> {
107
+ try {
108
+ self.postMessage(await handle());
109
+ } catch {
110
+ self.postMessage({ id, outcome: 'unavailable' } satisfies SolverWorkerResponse);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Messages queued behind this handler dispatch only between macrotasks - a
116
+ * MessageChannel round-trip lets them register before an expensive solve.
117
+ */
118
+ function yieldToQueuedMessages(): Promise<void> {
119
+ return new Promise((resolve) => {
120
+ const { port1, port2 } = new MessageChannel();
121
+ port1.onmessage = () => resolve();
122
+ port2.postMessage(null);
123
+ });
124
+ }
@@ -0,0 +1,20 @@
1
+ import { type Locale, type ResultJson } from '@scrabble-solver/types';
2
+
3
+ import { type SolveRequestPayload, type VerifyRequestPayload } from '@/types';
4
+
5
+ export interface VerifyResult {
6
+ invalidWords: string[];
7
+ validWords: string[];
8
+ }
9
+
10
+ export type SolverWorkerRequest =
11
+ | { id: number; type: 'prefetch'; locale: Locale }
12
+ | { id: number; type: 'solve'; payload: SolveRequestPayload }
13
+ | { id: number; type: 'verify'; payload: VerifyRequestPayload };
14
+
15
+ export type SolverWorkerResponse =
16
+ | { id: number; outcome: 'answered'; data: ResultJson[] | VerifyResult }
17
+ /** No cached dictionary, or an internal error - the caller falls back to the server. */
18
+ | { id: number; outcome: 'unavailable' }
19
+ /** A newer solve replaced this one - the newer answer is the one worth waiting for. */
20
+ | { id: number; outcome: 'superseded' };
@@ -12,4 +12,5 @@ export * from './solve';
12
12
  export * from './store';
13
13
  export * from './useTranslate';
14
14
  export * from './useTypedSelector';
15
+ export * from './useTypedStore';
15
16
  export * from './verify';
@@ -6,11 +6,12 @@
6
6
  import { type PayloadAction } from '@reduxjs/toolkit';
7
7
  import { hasConfig, languages } from '@scrabble-solver/configs';
8
8
  import { Board, type Locale, type Result } from '@scrabble-solver/types';
9
- import { call, delay, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
9
+ import { call, delay, put, select, spawn, takeEvery, takeLatest } from 'redux-saga/effects';
10
10
 
11
11
  import { LOCALE_FEATURES } from '@/i18n';
12
12
  import { memoize } from '@/lib';
13
13
  import { findWordDefinitions, solve, verify, visit } from '@/sdk';
14
+ import { prefetchDictionary } from '@/solver-worker';
14
15
 
15
16
  import { initialize, reset } from './actions';
16
17
  import { boardSlice, selectBoard } from './board';
@@ -41,7 +42,7 @@ export function* rootSaga(): AnyGenerator {
41
42
  yield takeEvery(boardSlice.actions.changeCellValue.type, onCellValueChange);
42
43
  yield takeEvery([rackSlice.actions.changeCharacter.type, rackSlice.actions.changeCharacters.type], onRackValueChange);
43
44
  yield takeEvery(resultsSlice.actions.applyResult.type, onApplyResult);
44
- yield takeEvery(resultsSlice.actions.changeResultCandidate.type, onResultCandidateChange);
45
+ yield takeLatest(resultsSlice.actions.changeResultCandidate.type, onResultCandidateChange);
45
46
  yield takeEvery(settingsSlice.actions.changeGame.type, onGameChange);
46
47
  yield takeEvery(settingsSlice.actions.changeLocale.type, onLocaleChange);
47
48
  yield takeLatest(dictionarySlice.actions.submit.type, onDictionarySubmit);
@@ -120,8 +121,11 @@ function* onDictionarySubmit(): AnyGenerator {
120
121
 
121
122
  function* onInitialize(): AnyGenerator {
122
123
  const board = yield select(selectBoard);
124
+ const locale = yield select(selectLocale);
123
125
 
124
- yield call(visit);
126
+ yield spawn(prefetchDictionary, locale);
127
+ // Detached: offline, a failed visit would otherwise tear down the whole root saga.
128
+ yield spawn(visit);
125
129
 
126
130
  if (!board.isEmpty()) {
127
131
  yield* resetRack();
@@ -142,6 +146,8 @@ function* onReset(): AnyGenerator {
142
146
  }
143
147
 
144
148
  function* onLocaleChange({ payload: locale }: PayloadAction<Locale>): AnyGenerator {
149
+ yield spawn(prefetchDictionary, locale);
150
+
145
151
  const game = yield select(selectGame);
146
152
 
147
153
  if (!hasConfig(game, locale)) {
@@ -166,13 +172,17 @@ function* onLocaleChange({ payload: locale }: PayloadAction<Locale>): AnyGenerat
166
172
  }
167
173
 
168
174
  function* onResultCandidateChange({ payload: result }: PayloadAction<Result | null>): AnyGenerator {
169
- if (result) {
170
- const locale: Locale = yield select(selectLocale);
171
- const uniqueWords = Array.from(new Set(result.words));
172
- const input = uniqueWords.join(LOCALE_FEATURES[locale].separator);
173
- yield put(dictionarySlice.actions.changeInput(input));
174
- yield put(dictionarySlice.actions.submit());
175
+ if (!result) {
176
+ return;
175
177
  }
178
+
179
+ yield delay(SUBMIT_DELAY);
180
+
181
+ const locale: Locale = yield select(selectLocale);
182
+ const uniqueWords = Array.from(new Set(result.words));
183
+ const input = uniqueWords.join(LOCALE_FEATURES[locale].separator);
184
+ yield put(dictionarySlice.actions.changeInput(input));
185
+ yield put(dictionarySlice.actions.submit());
176
186
  }
177
187
 
178
188
  function* onSolve(): AnyGenerator {
@@ -0,0 +1,6 @@
1
+ import { type Store } from '@reduxjs/toolkit';
2
+ import { useStore } from 'react-redux';
3
+
4
+ import type { RootState } from './types';
5
+
6
+ export const useTypedStore = (): Store<RootState> => useStore<RootState>();
@@ -1,9 +1,8 @@
1
- import { type BoardJson, type ConfigJson, type Game, type Locale } from '@scrabble-solver/types';
1
+ import { type BoardJson, type Game, type Locale } from '@scrabble-solver/types';
2
2
 
3
3
  export interface SolveRequestPayload {
4
4
  board: BoardJson;
5
5
  characters: string[];
6
- config: ConfigJson;
7
6
  game: Game;
8
7
  locale: Locale;
9
8
  }
@@ -7,6 +7,10 @@
7
7
  "isolatedModules": true,
8
8
  "jsx": "preserve",
9
9
  "lib": ["dom", "dom.iterable", "esnext"],
10
+ // Webpack bundles this package (import.meta worker URLs need an ES module
11
+ // target), unlike the nodenext library packages.
12
+ "module": "esnext",
13
+ "moduleResolution": "bundler",
10
14
  "noEmit": true,
11
15
  "paths": {
12
16
  "@/*": ["./src/*"]