@orieljs/framework 0.1.0-alpha.5 → 0.1.0-alpha.7

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 (87) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-WNJP46B7.js → chunk-4MWG3B7G.js} +1785 -626
  5. package/dist/chunk-4MWG3B7G.js.map +1 -0
  6. package/dist/chunk-7ZQZYDOC.js +3253 -0
  7. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  8. package/dist/{chunk-BSQKOLSK.js → chunk-GVNLZZTN.js} +1 -1
  9. package/dist/chunk-GVNLZZTN.js.map +1 -0
  10. package/dist/{chunk-A5XRLEWM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +535 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +2202 -5952
  16. package/dist/index.js +4900 -944
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-WKTl3EEw.d.ts → jsx-runtime-6um4Mwfb.d.ts} +97 -51
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BErengYY.d.ts → testing-CKwfWJGn.d.ts} +313 -994
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +19 -5
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +651 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +276 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/facades.md +278 -0
  46. package/docs/hashing.md +118 -0
  47. package/docs/helpers.md +1281 -0
  48. package/docs/http-tests.md +386 -0
  49. package/docs/inertia.md +485 -0
  50. package/docs/installation.md +209 -0
  51. package/docs/lifecycle.md +234 -0
  52. package/docs/live-tests.md +226 -0
  53. package/docs/live.md +910 -0
  54. package/docs/logging.md +255 -0
  55. package/docs/mail.md +619 -0
  56. package/docs/middleware.md +384 -0
  57. package/docs/migrations.md +416 -0
  58. package/docs/mocking.md +264 -0
  59. package/docs/navigation.json +273 -0
  60. package/docs/notifications.md +225 -0
  61. package/docs/pagination.md +247 -0
  62. package/docs/passwords.md +207 -0
  63. package/docs/providers.md +187 -0
  64. package/docs/queries.md +600 -0
  65. package/docs/queues.md +330 -0
  66. package/docs/requests.md +533 -0
  67. package/docs/responses.md +444 -0
  68. package/docs/routing.md +738 -0
  69. package/docs/scheduling.md +277 -0
  70. package/docs/seeding.md +122 -0
  71. package/docs/session.md +409 -0
  72. package/docs/starter-kits.md +396 -0
  73. package/docs/storage.md +192 -0
  74. package/docs/structure.md +264 -0
  75. package/docs/testing.md +165 -0
  76. package/docs/time-travel.md +138 -0
  77. package/docs/validation.md +1792 -0
  78. package/docs/verification.md +152 -0
  79. package/docs/views.md +566 -0
  80. package/docs/vite.md +322 -0
  81. package/package.json +34 -32
  82. package/dist/chunk-A5XRLEWM.js.map +0 -1
  83. package/dist/chunk-BSQKOLSK.js.map +0 -1
  84. package/dist/chunk-PM5YTG57.js +0 -880
  85. package/dist/chunk-PM5YTG57.js.map +0 -1
  86. package/dist/chunk-WNJP46B7.js.map +0 -1
  87. package/dist/index.d-DOlTeG-8.d.ts +0 -99
@@ -0,0 +1,278 @@
1
+ # Facades
2
+
3
+ Facades give you Oriel's services by name, as `Cache.get('key')`, resolved from the current request or the
4
+ application each time you use them.
5
+
6
+ ## Introduction
7
+
8
+ Code throughout these pages reaches Oriel's features through facades. A facade gives you a service of the [service
9
+ container](container.md) under a short name, as if its methods were static, and forwards each call to the service the
10
+ container holds. Oriel has a facade for most of its features, and exports them all from `@orieljs/framework`:
11
+
12
+ ```ts title="routes/web.ts"
13
+ import { Cache, Route } from '@orieljs/framework'
14
+
15
+ Route.get('/cache', () => Cache.get('key'))
16
+ ```
17
+
18
+ #### Helper functions
19
+
20
+ Alongside the facades, Oriel has helper functions for the features you use most: `view()`, `response()`, `config()`,
21
+ `request()`, `session()` and others. Each feature's page documents its helper, and [Helpers](helpers.md) lists them all.
22
+ A helper and its facade reach the same service, so these two are the same:
23
+
24
+ ```ts
25
+ const fromFacade = View.make('profile')
26
+
27
+ const fromHelper = view('profile')
28
+ ```
29
+
30
+ ## When to use facades
31
+
32
+ Facades are short to write: a feature's name, then its method, with nothing to inject or configure. Because they ask
33
+ for nothing, a class can come to use many of them, and grow past one job without you noticing. When a class reaches
34
+ for many facades, consider splitting it into smaller classes.
35
+
36
+ ### Facades vs. dependency injection
37
+
38
+ A service you inject can be replaced in a test by a stand-in. A facade's service can be replaced the same way, since the
39
+ facade resolves it from the container at each call: your tests give the container a fake, and the code under test uses
40
+ it through the facade.
41
+
42
+ The facades of the services you most often check in a test have a `fake()`: `Mail.fake()`, `Queue.fake()`,
43
+ `Bus.fake()`, `Event.fake()`, `Storage.fake()` and `Notification.fake()`. Each puts a fake in place of the service
44
+ and returns it, and you check what your code did on the fake it returned:
45
+
46
+ ```ts title="tests/Feature/ShipOrder.test.ts"
47
+ // @filename: tests/TestCase.ts
48
+ import { TestCase as BaseTestCase } from '@orieljs/framework/testing'
49
+ export class TestCase extends BaseTestCase {}
50
+ // @filename: app/Mail/OrderShipped.ts
51
+ import { Mailable } from '@orieljs/framework'
52
+ export class OrderShipped extends Mailable {}
53
+ // ---cut---
54
+ import { Mail } from '@orieljs/framework'
55
+ import { test } from 'vitest'
56
+ import { OrderShipped } from '../../app/Mail/OrderShipped'
57
+ import { TestCase } from '../TestCase'
58
+
59
+ const t = TestCase.boot()
60
+
61
+ test('shipping an order sends the mail', async () => {
62
+ const mail = Mail.fake()
63
+
64
+ await t.post('/orders/1/ship')
65
+
66
+ mail.assertSent(OrderShipped)
67
+ })
68
+ ```
69
+
70
+ `Storage.fake('avatars')` returns the fake disk. [Fakes](mocking.md) lists every fake and its assertions.
71
+
72
+ For any other facade, `swap()` puts an object of your own in place of the service:
73
+
74
+ ```ts title="tests/Feature/PublishPodcast.test.ts"
75
+ // @filename: tests/TestCase.ts
76
+ import { TestCase as BaseTestCase } from '@orieljs/framework/testing'
77
+ export class TestCase extends BaseTestCase {}
78
+ // @filename: app/Facades/Podcasts.ts
79
+ import { facade } from '@orieljs/framework'
80
+ export declare class PodcastPublisher {
81
+ publish(podcast: import('../Models/Podcast').Podcast): Promise<void>
82
+ }
83
+ export const Podcasts = facade(PodcastPublisher)
84
+ // ---cut---
85
+ import { RefreshDatabase } from '@orieljs/framework/testing'
86
+ import { expect, test } from 'vitest'
87
+ import { Podcasts } from '../../app/Facades/Podcasts'
88
+ import { Podcast } from '../../app/Models/Podcast'
89
+ import { TestCase } from '../TestCase'
90
+
91
+ const t = TestCase.boot({ uses: [RefreshDatabase] })
92
+
93
+ test('a podcast can be published', async () => {
94
+ const podcast = await Podcast.create({ title: 'Episode 1', audio_path: 'podcasts/1.mp3' })
95
+ const published: number[] = []
96
+ Podcasts.swap({ publish: async (episode) => void published.push(episode.id) })
97
+
98
+ await t.post(`/podcasts/${podcast.id}/publish`)
99
+
100
+ expect(published).toEqual([podcast.id])
101
+ })
102
+ ```
103
+
104
+ `swap()` binds the object as an instance on the current container. In a test, outside any request, that is the test's
105
+ application, so every request the test makes uses it. Inside a request, it replaces the service for that request
106
+ only.
107
+
108
+ > [!NOTE]
109
+ > Laravel's facades can be mocked with Mockery: `Cache::shouldReceive('get')`, `spy()`, `partialMock()`. Oriel's
110
+ > have no mocking methods. Use the facade's `fake()` where it has one, and `swap()` with a stand-in of your own
111
+ > otherwise.
112
+
113
+ ### Facades vs. helper functions
114
+
115
+ A helper does what its facade does: `view('profile')` and `View.make('profile')` both ask the view factory for the
116
+ view. Both resolve the same service from the container, so a fake or a `swap()` reaches the code that uses the
117
+ helper too:
118
+
119
+ ```ts
120
+ Route.get('/cache', () => cache('key'))
121
+ ```
122
+
123
+ `cache('key')` calls `get()` on the cache manager the `Cache` facade stands for, so a test that swaps the `Cache`
124
+ facade's service changes what this route returns.
125
+
126
+ ## How facades work
127
+
128
+ A facade is an object that `facade()` makes from a class or a
129
+ [token](container.md#binding-interfaces-to-implementations). Each time you read one of its properties, it resolves the
130
+ service from the current container and returns the service's property, its methods bound to the service. The current
131
+ container is the one `inject()` uses (see [Injecting with `inject()`](container.md#injecting-with-inject)): inside a
132
+ request, a queued job or a command, its scope; otherwise, the application. So `Session` reaches the current request's
133
+ session, and `Mail` the mail manager your app shares.
134
+
135
+ You can make a facade for a service of your own:
136
+
137
+ ```ts title="app/Facades/Podcasts.ts"
138
+ // @filename: app/Services/PodcastPublisher.ts
139
+ export declare class PodcastPublisher {
140
+ publish(podcast: import('../Models/Podcast').Podcast): Promise<void>
141
+ }
142
+ // ---cut---
143
+ import { facade } from '@orieljs/framework'
144
+ import { PodcastPublisher } from '../Services/PodcastPublisher'
145
+
146
+ export const Podcasts = facade(PodcastPublisher)
147
+ ```
148
+
149
+ From then on, `Podcasts.publish(podcast)` calls `publish()` on the `PodcastPublisher` the container gives you, a new
150
+ one each time unless you bind it as a singleton. `facade()` takes a second argument, an object of extra methods,
151
+ which is how `Mail` has `fake()` and `Hash` has `driver()`.
152
+
153
+ > [!NOTE]
154
+ > A Laravel facade is a class whose `getFacadeAccessor()` names a binding, and whose static calls `__callStatic()`
155
+ > forwards to it. Oriel's is an object made by `facade()`, which forwards every property you read, and needs no
156
+ > class of its own.
157
+
158
+ ### Typing
159
+
160
+ A facade is typed as the service it stands for, with its extra methods and two of its own: `getFacadeRoot()`, which
161
+ returns the service, and `swap()`. TypeScript checks every call:
162
+
163
+ ```ts
164
+ Route.get('/podcasts', () => 'Podcasts')
165
+
166
+ // @ts-expect-error: the router has no post2()
167
+ Route.post2('/podcasts', () => 'Created')
168
+ ```
169
+
170
+ `Route` is typed as Oriel's router, so a misspelt method or a wrong argument fails the type check, and your editor
171
+ completes the facade's methods. The `Facade<T>` type, exported from `@orieljs/framework`, names the type of a facade
172
+ you write yourself.
173
+
174
+ ### Facades at a module's top level
175
+
176
+ A facade resolves its service when you use it, so it works only while your app is running. Don't use one at a
177
+ module's top level, outside any function:
178
+
179
+ ```ts title="app/Services/Greeting.ts"
180
+ import { Config } from '@orieljs/framework'
181
+
182
+ // Runs when the module is first imported: before your app exists, this throws.
183
+ const appName = Config.string('app.name')
184
+
185
+ export function greeting(): string {
186
+ return `Welcome to ${appName}`
187
+ }
188
+ ```
189
+
190
+ A module's top level runs once, when the module is first imported. Your providers, and what they import, are imported
191
+ with `bootstrap/app.ts`, before `createApplication()` has made the app, and a test file's imports run before its first
192
+ test has an app. Then no container is available, and the facade throws a `ContainerNotAvailableError`: "Cannot resolve
193
+ [Config.Repository]: no container is available. inject(), helpers and facades only work while the application is
194
+ running (inside a request, job, command, provider or app.run()). Don't call them at module top level."
195
+
196
+ When an app does exist, the top level resolves that app's service, once, and keeps it: your tests make a new app for
197
+ each test, and every test would see the first test's value. Use the facade inside the function instead:
198
+
199
+ ```ts title="app/Services/Greeting.ts"
200
+ import { Config } from '@orieljs/framework'
201
+
202
+ export function greeting(): string {
203
+ return `Welcome to ${Config.string('app.name')}`
204
+ }
205
+ ```
206
+
207
+ The route files are the exception: the app imports each one itself, afresh for every app and inside it, so a route
208
+ file calls the `Route` facade at its top level.
209
+
210
+ ## Real-time facades
211
+
212
+ Oriel has no real-time facades, which Laravel makes from any class you import with a `Facades\` namespace prefix.
213
+ Make a facade for your class with `facade()`, as in [How facades work](#how-facades-work).
214
+
215
+ ## Facade class reference
216
+
217
+ Every facade, and what it resolves. The bindings are the tokens of `@orieljs/framework/contracts`, which `inject()`
218
+ and `app().make()` also take:
219
+
220
+ | Facade | Resolves to | Binding |
221
+ | --- | --- | --- |
222
+ | `Artisan` | The console kernel: `call()` runs a command | `ConsoleKernel` |
223
+ | `Auth` | The auth manager: `guard()`, and the default guard's `user()`, `check()`, `attempt()`… | `AuthFactory` |
224
+ | `Bus` | The command bus: `dispatch()`, `chain()`, `batch()` | `BusDispatcher` |
225
+ | `Cache` | The cache manager: `store()`, and the default store's methods | `CacheManager` |
226
+ | `Config` | The configuration repository (see [The `Config` facade](#the-config-facade)) | `ConfigRepository` |
227
+ | `Cookie` | The current request's cookie jar, plus `get()` and `has()` for the request's cookies | `CookieJar` (scoped) |
228
+ | `Crypt` | The encrypter | `Encrypter` |
229
+ | `DB` | The database manager: `connection()`, and the default connection's methods; `DB.raw()` | `DatabaseManager` |
230
+ | `Event` | The event dispatcher | `EventDispatcher` |
231
+ | `Gate` | The authorization gate | `Gate` |
232
+ | `Hash` | The hash manager, which hashes with the default driver; `driver()` | `Hasher` |
233
+ | `Inertia` | The current request's Inertia response factory | `InertiaFactory` (scoped) |
234
+ | `Live` | The Live component manager: `Live.component()` registers a component; `Live.test()` mounts one in a test | `LiveManager` |
235
+ | `Log` | The log manager: `channel()`, and the default channel's methods | `LogManager` |
236
+ | `Mail` | The mail manager: `mailer()`, and the default mailer's methods | `MailManager` |
237
+ | `Notification` | The notification dispatcher, through the statics of the `Notification` class | `NotificationDispatcher` |
238
+ | `Password` | The default password broker; `broker()` | `PasswordBroker` |
239
+ | `Queue` | The queue manager: `connection()`, and the default connection's methods | `QueueManager` |
240
+ | `RateLimiter` | The rate limiter, which `throttle` uses: `hit()`, `tooManyAttempts()`, `clear()` | `RateLimiter` |
241
+ | `Route` | The router | `Router` |
242
+ | `Schedule` | The schedule: `command()`, `job()`, `call()` | `Schedule` |
243
+ | `Schema` | The default database connection's schema builder | `SchemaBuilderToken`, from `@orieljs/framework` |
244
+ | `Session` | The current request's session; `Session.extend()` | `Session` (scoped) |
245
+ | `Storage` | The filesystem manager: `disk()`, and the default disk's methods | `FilesystemManager` |
246
+ | `Validator` | The validation factory: `make()`, `extend()` | `ValidationFactory` |
247
+ | `View` | The view factory: `make()`, `share()`, `composer()` | `ViewFactory` |
248
+
249
+ A scoped facade needs a scope. Outside any, `Cookie` and `Session` throw a `BindingResolutionError` ("Scoped
250
+ binding [Session.Store] was resolved outside a scope. …"), and `Inertia`, outside a request, uses the application's
251
+ own response factory.
252
+
253
+ `Inertia` and `Live` are written by hand rather than with `facade()`, and `Live` is also the `<Live>` element of your
254
+ views. `Notification` is the base class of your notifications, whose statics are the facade (`NotificationFacade`,
255
+ also exported, is a plain facade over the same dispatcher). None of the three has `getFacadeRoot()`, and only
256
+ `Notification` has `swap()`.
257
+
258
+ > [!NOTE]
259
+ > Laravel's `App`, `Request`, `Response`, `Redirect`, `URL`, `Lang` and `Vite` facades are helpers in Oriel: `app()`,
260
+ > `request()`, `response()`, `redirect()`, `url()`, `__()` and `vite()`. Oriel has no facade for Laravel's `Blade`,
261
+ > `Broadcast`, `Context`, `Date`, `Exceptions`, `File`, `Http`, `Pipeline`, `Process` or `Redis`.
262
+
263
+ ### The `Config` facade
264
+
265
+ `Config` resolves your app's configuration, as the `config()` helper does. It has the repository's `get()`, `set()`,
266
+ `has()`, `push()` and `all()`, and adds `prepend()`, `getMany()` and typed getters:
267
+
268
+ ```ts
269
+ const name = Config.string('app.name')
270
+
271
+ const debug = Config.boolean('app.debug', false)
272
+
273
+ const values = Config.getMany(['app.name', 'app.env'])
274
+ ```
275
+
276
+ `string()`, `integer()`, `float()`, `boolean()` and `array()` return the value typed, or throw a `TypeError` when it
277
+ isn't of that type: "Configuration value for key [app.name] must be a string, undefined given.". `float()` takes any
278
+ number, and `array()` a list or an object. [Configuration](configuration.md) covers your configuration files.
@@ -0,0 +1,118 @@
1
+ # Hashing
2
+
3
+ The `Hash` facade hashes your users' passwords with bcrypt or Argon2, and checks a password against its hash.
4
+
5
+ ## Introduction
6
+
7
+ A password is stored as a hash, which can't be turned back into the password: to sign a user in, Oriel hashes what they
8
+ typed and compares the two. Bcrypt, the default, is slow on purpose, and its work factor can rise as computers get
9
+ faster, so guessing passwords from a stolen hash stays costly. The starter kits hash with bcrypt when users register,
10
+ and check the hash when they sign in.
11
+
12
+ ## Configuration
13
+
14
+ `config/hashing.ts` names the driver, `bcrypt` unless `HASH_DRIVER` names another, and each driver's settings:
15
+
16
+ ```ts title="config/hashing.ts"
17
+ import { env } from '@orieljs/framework'
18
+
19
+ export default {
20
+ driver: env('HASH_DRIVER', 'bcrypt'),
21
+ bcrypt: { rounds: Number(env('BCRYPT_ROUNDS', 12)), verify: true },
22
+ argon: { memory: 65536, threads: 1, time: 4, verify: true },
23
+ rehash_on_login: true,
24
+ }
25
+ ```
26
+
27
+ The drivers are `bcrypt`, `argon` (Argon2i) and `argon2id`. Bcrypt's `rounds` is its work factor; Argon2's `memory`
28
+ (in KiB), `time` and `threads` are its. The settings may be numbers or the strings `env()` gives, such as `'12'`.
29
+
30
+ Your tests run with `BCRYPT_ROUNDS=4`, so the hashing in them is quick.
31
+
32
+ ## Basic usage
33
+
34
+ ### Hashing passwords
35
+
36
+ `Hash.make()` hashes a value with your driver. It returns a promise:
37
+
38
+ ```ts title="app/Http/Controllers/PasswordController.ts"
39
+ import { Hash, redirect, request } from '@orieljs/framework'
40
+ import type { User } from '../../Models/User'
41
+ import { Controller } from './Controller'
42
+
43
+ export class PasswordController extends Controller {
44
+ /** Update the password for the user. */
45
+ async update() {
46
+ // Validate the new password length...
47
+
48
+ const user = (await request().user<User>())!
49
+
50
+ await user.fill({ password: await Hash.make(request().string('newPassword').toString()) }).save()
51
+
52
+ return redirect('/profile')
53
+ }
54
+ }
55
+ ```
56
+
57
+ A model can do this for you: an attribute with the `hashed` cast is hashed when the model is saved, and a value that is
58
+ already a hash made with your current settings is kept. [Hashed casting](eloquent-mutators.md#hashed-casting) describes
59
+ it.
60
+
61
+ #### Adjusting the work factor
62
+
63
+ `Hash.make()` takes the value only. Set the work factor in `config/hashing.ts`, or, for bcrypt, pass it to the
64
+ `bcrypt()` helper, which hashes with bcrypt whatever your driver is:
65
+
66
+ ```ts
67
+ const hashed = await bcrypt('password', { rounds: 12 })
68
+ ```
69
+
70
+ > [!NOTE]
71
+ > Laravel's `Hash::make()` takes the work factor as a second argument. Oriel's `Hash.make()` reads it from your
72
+ > config, and `bcrypt()` takes `rounds`.
73
+
74
+ ### Verifying that a password matches a hash
75
+
76
+ `Hash.check()` hashes a password and compares it with a hash. It returns a promise too:
77
+
78
+ ```ts
79
+ declare const hashedPassword: string
80
+ // ---cut---
81
+ if (await Hash.check('plain-text', hashedPassword)) {
82
+ // The passwords match...
83
+ }
84
+ ```
85
+
86
+ An empty hash never matches.
87
+
88
+ ### Determining if a password needs to be rehashed
89
+
90
+ `Hash.needsRehash()` tells you whether a hash was made with other settings than your current ones, such as fewer
91
+ rounds or another algorithm. It is synchronous:
92
+
93
+ ```ts
94
+ declare let hashed: string
95
+ // ---cut---
96
+ if (Hash.needsRehash(hashed)) {
97
+ hashed = await Hash.make('plain-text')
98
+ }
99
+ ```
100
+
101
+ With `rehash_on_login` on, as in a new app, signing a user in with `Auth.attempt()` does this for you: when the
102
+ password matches and its hash needs rehashing, the user's password is hashed again and saved.
103
+
104
+ ### Other methods
105
+
106
+ - `Hash.info(hashed)` gives a hash's algorithm and options, such as `{ algorithm: 'bcrypt', options: { cost: 12 } }`.
107
+ - `Hash.isHashed(value)` tells you whether a value is a bcrypt or Argon2 hash.
108
+ - `Hash.driver('argon2id')` returns another driver, to hash or check with it.
109
+
110
+ Bcrypt hashes start with `$2y$`, as PHP's `password_hash()` writes them, so the hashes of a Laravel app's users check
111
+ in Oriel, and the other way round.
112
+
113
+ ## Hash algorithm verification
114
+
115
+ With `verify` on, as in a new app, `Hash.check()` refuses a hash made by another algorithm than the driver's: it
116
+ throws a `HashAlgorithmError`, "This password does not use the Bcrypt algorithm.", rather than return `false`. This
117
+ stops a hash of another kind from being checked in a way it wasn't meant for. When you move your users from one
118
+ algorithm to another, set `verify: false` in the driver's settings while their old hashes remain.