@orieljs/framework 0.1.0-alpha.6 → 0.1.0-alpha.8

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 (86) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-POEP4B2M.js → chunk-7ZQZYDOC.js} +745 -105
  5. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  6. package/dist/{chunk-I63ZFMX6.js → chunk-GVNLZZTN.js} +1 -1
  7. package/dist/chunk-GVNLZZTN.js.map +1 -0
  8. package/dist/{chunk-ZOEPKBVF.js → chunk-SPQUX3C7.js} +1347 -428
  9. package/dist/chunk-SPQUX3C7.js.map +1 -0
  10. package/dist/{chunk-MFTZQMZM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +515 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +964 -152
  16. package/dist/index.js +4279 -866
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-CBZQYYDS.d.ts → jsx-runtime-6um4Mwfb.d.ts} +96 -5
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BM481Hdz.d.ts → testing-CKwfWJGn.d.ts} +180 -15
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +3 -3
  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-I63ZFMX6.js.map +0 -1
  83. package/dist/chunk-MFTZQMZM.js.map +0 -1
  84. package/dist/chunk-POEP4B2M.js.map +0 -1
  85. package/dist/chunk-ZOEPKBVF.js.map +0 -1
  86. package/dist/index.d-BJyJx6U1.d.ts +0 -319
@@ -0,0 +1,218 @@
1
+ # Eloquent: collections
2
+
3
+ Every query of models returns an `EloquentCollection`: a collection with Laravel's collection methods, and a few more
4
+ that know about models and their keys.
5
+
6
+ ## Introduction
7
+
8
+ `get()`, `all()`, a has-many relation and an eager load all return an `EloquentCollection`. It has every method of
9
+ [Collections](collections.md), and it is iterable:
10
+
11
+ ```ts
12
+ const users = await User.whereNotNull('email_verified_at').get()
13
+
14
+ for (const user of users) {
15
+ console.log(user.name)
16
+ }
17
+ ```
18
+
19
+ Methods that keep the same models, such as `filter()`, `sortBy()` and `where()`, return an `EloquentCollection`.
20
+ Methods that make other values, such as `map()` and `pluck()`, return a plain `Collection`:
21
+
22
+ ```ts
23
+ const names = (await User.all()).filter((user) => user.email_verified_at !== null).map((user) => user.name)
24
+ ```
25
+
26
+ `toBase()` returns the models in a plain collection.
27
+
28
+ > [!WARNING]
29
+ > A collection's `sum()`, `avg()`, `min()`, `max()` and `median()` count numbers only. A column read as a string, such
30
+ > as a `decimal:2` cast or a large integer on PostgreSQL, is left out: convert it first, with
31
+ > `sum((order) => Number(order.price))`.
32
+
33
+ ## Available methods
34
+
35
+ #### `append(attributes)`
36
+
37
+ `append()` adds attributes to every model's `toArray()` and JSON, as a model's `appends` does (see
38
+ [Appending values to JSON](eloquent-mutators.md#appending-values-to-json)).
39
+
40
+ #### `contains(key)`
41
+
42
+ `contains()` tells you whether the collection holds a model, given the model or its key:
43
+
44
+ ```ts
45
+ declare const user: User
46
+ // ---cut---
47
+ const users = await User.all()
48
+
49
+ users.contains(1)
50
+
51
+ users.contains(user)
52
+ ```
53
+
54
+ With a key and a value, or a function, it is the collection's `contains()`. `containsStrict()` matches a model only by
55
+ identity, not by key.
56
+
57
+ #### `diff(items)`
58
+
59
+ `diff()` returns the models whose keys aren't among the models you give:
60
+
61
+ ```ts
62
+ const users = (await User.all()).diff(await User.whereIn('id', [1, 2, 3]).get())
63
+ ```
64
+
65
+ #### `except(...keys)`
66
+
67
+ `except()` returns the models without these keys:
68
+
69
+ ```ts
70
+ const users = (await User.all()).except(1, 2, 3)
71
+ ```
72
+
73
+ #### `find(key)`
74
+
75
+ `find()` returns the model with that key, or `undefined`. Given a list of keys, it returns a collection of those
76
+ models. `findOrFail()` throws a `ModelNotFoundError` instead of returning `undefined`:
77
+
78
+ ```ts
79
+ const users = await User.all()
80
+
81
+ const user = users.find(1)
82
+
83
+ const some = users.find([1, 2])
84
+ ```
85
+
86
+ #### `fresh(...relations)`
87
+
88
+ `fresh()` reads every model again from the database, with the relations you name loaded, in one query:
89
+
90
+ ```ts
91
+ const users = await User.all()
92
+
93
+ const fresh = await users.fresh('posts')
94
+ ```
95
+
96
+ #### `intersect(items)`
97
+
98
+ `intersect()` returns the models whose keys are also among the models you give:
99
+
100
+ ```ts
101
+ const users = (await User.all()).intersect(await User.whereIn('id', [1, 2, 3]).get())
102
+ ```
103
+
104
+ #### `load(...relations)`
105
+
106
+ `load()` loads relations on every model, one query per relation, and takes the forms `with()` takes:
107
+
108
+ ```ts
109
+ const users = await User.all()
110
+
111
+ await users.load('posts', 'roles')
112
+
113
+ await users.load({ posts: (query) => query.where('title', 'like', '%Oriel%') })
114
+ ```
115
+
116
+ `loadCount()` adds the counts, and `loadSum()`, `loadAvg()`, `loadMin()`, `loadMax()` and `loadExists()` the other
117
+ aggregates, on every model.
118
+
119
+ #### `loadMissing(...relations)`
120
+
121
+ `loadMissing()` loads the relations that aren't loaded yet on each model:
122
+
123
+ ```ts
124
+ const users = await User.all()
125
+
126
+ await users.loadMissing('posts', 'roles')
127
+ ```
128
+
129
+ #### `modelKeys()`
130
+
131
+ `modelKeys()` returns the models' primary keys:
132
+
133
+ ```ts
134
+ const keys = (await User.all()).modelKeys() // [1, 2, 3, 4, 5]
135
+ ```
136
+
137
+ #### `makeVisible(attributes)` / `makeHidden(attributes)`
138
+
139
+ `makeVisible()` shows attributes that the model hides in its `toArray()` and JSON, and `makeHidden()` hides more, on
140
+ every model. `setVisible()` and `setHidden()` replace the lists:
141
+
142
+ ```ts
143
+ const users = (await User.all()).makeVisible(['password', 'remember_token'])
144
+
145
+ const safe = (await User.all()).makeHidden(['email'])
146
+ ```
147
+
148
+ #### `only(...keys)`
149
+
150
+ `only()` returns the models with these keys:
151
+
152
+ ```ts
153
+ const users = (await User.all()).only(1, 2, 3)
154
+ ```
155
+
156
+ #### `toQuery()`
157
+
158
+ `toQuery()` returns a query that matches the collection's models, by their keys, so you can update them all at once:
159
+
160
+ ```ts
161
+ const users = await User.whereNull('email_verified_at').get()
162
+
163
+ await users.toQuery().update({ email_verified_at: new Date() })
164
+ ```
165
+
166
+ The models must all be of one class, and the collection mustn't be empty.
167
+
168
+ #### `unique(key)`
169
+
170
+ `unique()` returns one model per key. Given an attribute or a function, it is the collection's `unique()`:
171
+
172
+ ```ts
173
+ const users = (await User.all()).unique()
174
+ ```
175
+
176
+ #### `merge(items)`
177
+
178
+ `merge()` adds the models you give, replacing those with the same key.
179
+
180
+ ## Custom collections
181
+
182
+ A model returns its collections from `newCollection()`. Override it to return a subclass of `EloquentCollection` with
183
+ your own methods:
184
+
185
+ ```ts title="app/Models/Team.ts"
186
+ import { EloquentCollection, Model } from '@orieljs/framework'
187
+
188
+ export class TeamCollection<T extends Model> extends EloquentCollection<T> {
189
+ names(): string[] {
190
+ return this.map((team) => String(team.getAttribute('name'))).all()
191
+ }
192
+ }
193
+
194
+ export class Team extends Model {
195
+ override newCollection<T extends Model>(this: T, models: T[] = []): TeamCollection<T> {
196
+ return new TeamCollection(models)
197
+ }
198
+ }
199
+ ```
200
+
201
+ Every query, relation and eager load of `Team` then returns a `TeamCollection`. Their type is still
202
+ `EloquentCollection`, so narrow it to call your methods:
203
+
204
+ ```ts
205
+ declare class Team extends Model {}
206
+ declare class TeamCollection<T extends Model> extends EloquentCollection<T> {
207
+ names(): string[]
208
+ }
209
+ const teams = await Team.all()
210
+ // ---cut---
211
+ if (teams instanceof TeamCollection) {
212
+ console.log(teams.names())
213
+ }
214
+ ```
215
+
216
+ > [!NOTE]
217
+ > Laravel names the collection class with a `#[CollectedBy]` attribute or a `newCollection()` method. Oriel uses the
218
+ > method.
@@ -0,0 +1,255 @@
1
+ # Eloquent: factories
2
+
3
+ A factory makes models with realistic attribute values, for your tests and your seeders: one model, or a hundred, with
4
+ their related models.
5
+
6
+ ## Introduction
7
+
8
+ Each factory is a class in `database/factories` that describes one model's default attributes. A new app has
9
+ `UserFactory`:
10
+
11
+ ```ts title="database/factories/UserFactory.ts"
12
+ import { Factory, Hash, fake } from '@orieljs/framework'
13
+ import { User } from '../../app/Models/User'
14
+
15
+ type Attributes = Record<string, unknown>
16
+
17
+ /** The password every factory-made user has, hashed once. */
18
+ let password: string | undefined
19
+
20
+ export default class UserFactory extends Factory<User> {
21
+ model = User
22
+
23
+ definition() {
24
+ return {
25
+ name: fake().name(),
26
+ email: fake().unique().safeEmail(),
27
+ email_verified_at: new Date(),
28
+ password: 'password',
29
+ remember_token: fake().lexify('??????????'),
30
+ }
31
+ }
32
+
33
+ /** Indicate that the model's email address should be unverified. */
34
+ unverified() {
35
+ return this.state({ email_verified_at: null })
36
+ }
37
+
38
+ /** Hashes the default password (the model has no hashed cast). */
39
+ protected override async attributesFor(overrides: Attributes, index: number): Promise<Attributes> {
40
+ const attributes = await super.attributesFor(overrides, index)
41
+ if (attributes.password === 'password') attributes.password = password ??= await Hash.make('password')
42
+ return attributes
43
+ }
44
+ }
45
+ ```
46
+
47
+ `model` names the model, and `definition()` returns the attributes every model gets, unless you give others. `fake()`
48
+ makes the values: names, email addresses, sentences, numbers and dates.
49
+
50
+ Factories set every attribute they are given, whatever the model's `fillable` says.
51
+
52
+ ## Defining model factories
53
+
54
+ ### Generating factories
55
+
56
+ `make:factory` writes a factory in `database/factories`, named after its model:
57
+
58
+ ```sh
59
+ node oriel make:factory PostFactory
60
+ ```
61
+
62
+ Oriel finds the factories in `database/factories` by itself when your app runs through `oriel`, as a command or under
63
+ `oriel serve`, and in your tests: every factory class a file there exports. An app started another way, such as with
64
+ `public/index.ts`, registers its factories with `Factory.register()`, or `User.factory()` throws "No factory registered
65
+ for [User].".
66
+
67
+ A model's `factory()` returns its factory. To have its own methods, such as `unverified()`, typed, declare it, as a new
68
+ app's `User` does:
69
+
70
+ ```ts title="app/Models/User.ts"
71
+ // @filename: database/factories/UserFactory.ts
72
+ import { Factory } from '@orieljs/framework'
73
+ import { User } from '../../app/Models/User'
74
+ export default class UserFactory extends Factory<User> {
75
+ model = User
76
+ definition() {
77
+ return {}
78
+ }
79
+ }
80
+ // ---cut---
81
+ import { Model, type HasFactory } from '@orieljs/framework'
82
+ import type UserFactory from '../../database/factories/UserFactory'
83
+
84
+ export class User extends Model {
85
+ declare static factory: HasFactory<UserFactory>
86
+ }
87
+ ```
88
+
89
+ ### Factory states
90
+
91
+ A state changes some attributes. Write it as a method that calls `state()`:
92
+
93
+ ```ts
94
+ class PostFactory extends Factory<Post> {
95
+ model = Post
96
+
97
+ definition() {
98
+ return { title: fake().sentence(), slug: fake().slug(), body: fake().paragraph() }
99
+ }
100
+
101
+ /** Indicate that the post is published. */
102
+ published() {
103
+ return this.state({ published_at: new Date() })
104
+ }
105
+ }
106
+ ```
107
+
108
+ `state()` also takes a function, which receives the attributes so far and the model's place in the batch:
109
+
110
+ ```ts
111
+ const users = await User.factory(3)
112
+ .state((attributes, { index }) => ({ name: `User ${index + 1}` }))
113
+ .make()
114
+ ```
115
+
116
+ #### "Trashed" state
117
+
118
+ `trashed()` makes soft-deleted models, with a `deleted_at` date:
119
+
120
+ ```ts
121
+ const post = await Post.factory().trashed().create()
122
+ ```
123
+
124
+ ### Factory callbacks
125
+
126
+ `afterMaking()` and `afterCreating()` run a function with each model made or created. Register them in the factory's
127
+ `configure()`:
128
+
129
+ ```ts
130
+ class UserFactory extends Factory<User> {
131
+ model = User
132
+
133
+ definition() {
134
+ return { name: fake().name(), email: fake().unique().safeEmail() }
135
+ }
136
+
137
+ override configure() {
138
+ return this.afterMaking((user) => {
139
+ // ...
140
+ }).afterCreating((user) => {
141
+ // ...
142
+ })
143
+ }
144
+ }
145
+ ```
146
+
147
+ ## Creating models using factories
148
+
149
+ ### Instantiating models
150
+
151
+ `make()` makes models without saving them. With `count()`, or a number given to `factory()`, it makes a collection: a
152
+ plain `Collection`, where Laravel's is an Eloquent collection, so `modelKeys()` and `load()` aren't on it:
153
+
154
+ ```ts
155
+ const user = await User.factory().make()
156
+
157
+ const users = await User.factory().count(3).make()
158
+
159
+ const others = await User.factory(3).make()
160
+ ```
161
+
162
+ #### Applying states
163
+
164
+ ```ts
165
+ const users = await User.factory().count(5).unverified().make()
166
+ ```
167
+
168
+ #### Overriding attributes
169
+
170
+ Give `make()` or `create()` the attributes to set:
171
+
172
+ ```ts
173
+ const user = await User.factory().make({ name: 'Abigail Otwell' })
174
+ ```
175
+
176
+ ### Persisting models
177
+
178
+ `create()` makes the models and saves them:
179
+
180
+ ```ts
181
+ const user = await User.factory().create()
182
+
183
+ const users = await User.factory().count(3).create()
184
+ ```
185
+
186
+ `makeOne()` and `createOne()` always return one model, typed as one. `createMany()` takes a number, or a list of
187
+ attributes, one model each. `raw()` returns the attributes without making a model.
188
+
189
+ ### Sequences
190
+
191
+ `sequence()` gives the models of a batch attributes in turn, starting again after the last:
192
+
193
+ ```ts
194
+ const users = await User.factory().count(10).sequence({ name: 'Taylor' }, { name: 'Abigail' }).create()
195
+ ```
196
+
197
+ Five users are named Taylor, and five Abigail.
198
+
199
+ ## Factory relationships
200
+
201
+ ### Has many relationships
202
+
203
+ `has()` creates related models with each model, through a relation of the model, named after the related model in the
204
+ plural unless you name it:
205
+
206
+ ```ts
207
+ const user = await User.factory().has(Post.factory().count(3)).create()
208
+
209
+ const writer = await User.factory().has(Post.factory().count(3), 'posts').create()
210
+ ```
211
+
212
+ ### Belongs to relationships
213
+
214
+ `for()` sets the parent of the models it makes, a model you have or one its factory creates once for all of them:
215
+
216
+ ```ts
217
+ const user = await User.factory().createOne()
218
+
219
+ const posts = await Post.factory().count(3).for(user, 'author').create()
220
+ ```
221
+
222
+ An attribute of a factory can also be another factory: the factory creates that model, and the attribute takes its
223
+ key, as `author_id: UserFactory.new()` does in a post's definition.
224
+
225
+ ### Many to many relationships
226
+
227
+ `has()` works on a many-to-many relation too, and its third argument gives the pivot table's extra columns:
228
+
229
+ ```ts
230
+ const user = await User.factory().has(Role.factory().count(3), 'roles', { active: 1 }).create()
231
+ ```
232
+
233
+ ### Polymorphic relationships
234
+
235
+ `has()` creates the models of a `morphMany()` relation, and `for()` sets the owner of a `morphTo()` one.
236
+
237
+ > [!NOTE]
238
+ > Laravel's "magic" relationship methods (`hasPosts()`, `forUser()`), `hasAttached()`, `recycle()` and the `Sequence`
239
+ > class aren't in Oriel. Use `has()` with its pivot argument, `for()`, and `sequence()`.
240
+
241
+ ## The `fake()` helper
242
+
243
+ `fake()` returns the generator factories use: a seedable subset of Faker's methods. `unique()` gives a value that the
244
+ generator hasn't given before, and `seed()` makes the values repeat from one run to the next:
245
+
246
+ | Kind | Methods |
247
+ | --- | --- |
248
+ | People and companies | `firstName()`, `lastName()`, `name()`, `userName()`, `email()`, `safeEmail()`, `company()`, `phoneNumber()` |
249
+ | Text | `word()`, `words()`, `sentence()`, `sentences()`, `paragraph()`, `paragraphs()`, `text()`, `title()`, `slug()`, `url()`, `city()`, `country()` |
250
+ | Numbers | `numberBetween()`, `randomNumber()`, `randomFloat()`, `randomDigit()`, `random()` (a number from 0 to 1), `boolean()`, `uuid()` |
251
+ | Patterns and dates | `numerify('###')`, `lexify('???')`, `bothify('##??')`, `hexColor()`, `dateTime()`, `dateTimeBetween('-1 year', 'now')`, `date()` |
252
+
253
+ > [!NOTE]
254
+ > Laravel's factories use the full Faker library, with its locales. Oriel's `fake()` has the methods above, in
255
+ > English. To pick from a list, use `fake().numberBetween()` as an index.