@orieljs/framework 0.1.0-alpha.1 → 0.1.0-alpha.10

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-7ZQZYDOC.js +3253 -0
  5. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  6. package/dist/{chunk-K6MAA3RM.js → chunk-GVNLZZTN.js} +7 -1
  7. package/dist/chunk-GVNLZZTN.js.map +1 -0
  8. package/dist/chunk-OXAATQA6.js +8433 -0
  9. package/dist/chunk-OXAATQA6.js.map +1 -0
  10. package/dist/{chunk-WI7E7Q46.js → chunk-VOYW3R5J.js} +8 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +841 -168
  13. package/dist/contracts.js +7 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +2916 -5178
  16. package/dist/index.js +7357 -2609
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-B8J6xq-7.d.ts → jsx-runtime-6um4Mwfb.d.ts} +99 -149
  19. package/dist/jsx-runtime.d.ts +3 -1
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/testing-CKwfWJGn.d.ts +1586 -0
  22. package/dist/testing.d.ts +3 -1408
  23. package/dist/testing.js +29 -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 +652 -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 +35 -30
  82. package/dist/chunk-AI2RFMBT.js +0 -745
  83. package/dist/chunk-AI2RFMBT.js.map +0 -1
  84. package/dist/chunk-BZPCRKFQ.js +0 -2941
  85. package/dist/chunk-BZPCRKFQ.js.map +0 -1
  86. package/dist/chunk-K6MAA3RM.js.map +0 -1
  87. package/dist/chunk-WI7E7Q46.js.map +0 -1
@@ -0,0 +1,557 @@
1
+ # Eloquent: relationships
2
+
3
+ Tables relate to each other: a post has comments, a user has roles. Define each relation once, as a method of your
4
+ model, then query it, load it with its parent, and write through it.
5
+
6
+ ## Introduction
7
+
8
+ A relation is a method of your model that returns the relation, such as `this.hasMany(Comment)`. Calling it gives a
9
+ query of the related models, with every [query builder](queries.md) method, already limited to this model's:
10
+
11
+ ```ts
12
+ declare const post: Post
13
+ // ---cut---
14
+ const comments = await post.comments().where('votes', '>', 5).get()
15
+ ```
16
+
17
+ To have a relation's models with their parent, load it: `Post.with('comments')` (see [Eager loading](#eager-loading)).
18
+
19
+ ## Defining relationships
20
+
21
+ ### One to one / has one
22
+
23
+ A user has one phone. `hasOne()` takes the related model, and finds it by a `user_id` column on its table:
24
+
25
+ ```ts title="app/Models/User.ts"
26
+ import { Model } from '@orieljs/framework'
27
+ import { Phone } from './Phone'
28
+
29
+ export class User extends Model {
30
+ /** Get the phone associated with the user. */
31
+ phone() {
32
+ return this.hasOne(Phone)
33
+ }
34
+ }
35
+ ```
36
+
37
+ The foreign key is the model's name in snake case with `_id`. Give another as the second argument, and the column it
38
+ matches on this model, `id` unless you say, as the third: `this.hasOne(Phone, 'foreign_key', 'local_key')`.
39
+
40
+ #### Defining the inverse of the relationship
41
+
42
+ The phone belongs to its user. `belongsTo()` reads the phone's `user_id`, named after the related model:
43
+
44
+ ```ts title="app/Models/Phone.ts"
45
+ import { Model } from '@orieljs/framework'
46
+ import { User } from './User'
47
+
48
+ export class Phone extends Model {
49
+ /** Get the user that owns the phone. */
50
+ user() {
51
+ return this.belongsTo(User)
52
+ }
53
+ }
54
+ ```
55
+
56
+ `this.belongsTo(User, 'foreign_key', 'owner_key')` names the columns yourself.
57
+
58
+ > [!NOTE]
59
+ > Laravel names the foreign key after the method that calls `belongsTo()`, so an `author()` relation reads
60
+ > `author_id`. JavaScript can't see the calling method, so Oriel names it after the related model: an
61
+ > `author() { return this.belongsTo(User) }` reads `user_id`. Give the key: `this.belongsTo(User, 'author_id')`.
62
+
63
+ ### One to many / has many
64
+
65
+ A post has many comments. `hasMany()` finds them by their `post_id`:
66
+
67
+ ```ts title="app/Models/Post.ts"
68
+ import { Model } from '@orieljs/framework'
69
+ import { Comment } from './Comment'
70
+
71
+ export class Post extends Model {
72
+ /** Get the comments for the blog post. */
73
+ comments() {
74
+ return this.hasMany(Comment)
75
+ }
76
+ }
77
+ ```
78
+
79
+ ```ts
80
+ const post = await Post.findOrFail(1)
81
+
82
+ const comments = await post.comments().get()
83
+
84
+ const comment = await post.comments().where('body', 'like', '%Oriel%').first()
85
+ ```
86
+
87
+ #### One to many (inverse) / belongs to
88
+
89
+ ```ts title="app/Models/Comment.ts"
90
+ import { Model } from '@orieljs/framework'
91
+ import { Post } from './Post'
92
+
93
+ export class Comment extends Model {
94
+ /** Get the post that owns the comment. */
95
+ post() {
96
+ return this.belongsTo(Post)
97
+ }
98
+ }
99
+ ```
100
+
101
+ ```ts
102
+ const comment = await Comment.findOrFail(1)
103
+
104
+ const post = await comment.post().first()
105
+ ```
106
+
107
+ #### Automatically hydrating parent models on children
108
+
109
+ Loading a post's comments, then each comment's post, would query the post again for every comment. `chaperone()` on
110
+ the relation sets each loaded comment's `post` to the post it was loaded with, with no query:
111
+
112
+ ```ts title="app/Models/Post.ts"
113
+ import { Model } from '@orieljs/framework'
114
+ import { Comment } from './Comment'
115
+
116
+ export class Post extends Model {
117
+ comments() {
118
+ return this.hasMany(Comment).chaperone()
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### Has many through
124
+
125
+ A has-many-through relation reaches models through another one. A project has environments, and each environment has
126
+ deployments, so a project's deployments are reached through its environments:
127
+
128
+ ```ts title="app/Models/Project.ts"
129
+ // @filename: app/Models/Environment.ts
130
+ import { Model } from '@orieljs/framework'
131
+ export class Environment extends Model {}
132
+ // @filename: app/Models/Deployment.ts
133
+ import { Model } from '@orieljs/framework'
134
+ export class Deployment extends Model {}
135
+ // ---cut---
136
+ import { Model } from '@orieljs/framework'
137
+ import { Deployment } from './Deployment'
138
+ import { Environment } from './Environment'
139
+
140
+ export class Project extends Model {
141
+ /** Get all of the deployments for the project. */
142
+ deployments() {
143
+ return this.hasManyThrough(Deployment, Environment)
144
+ }
145
+ }
146
+ ```
147
+
148
+ The keys are `project_id` on `environments` and `environment_id` on `deployments`; the next arguments name others.
149
+
150
+ ## Many to many relationships
151
+
152
+ A user has many roles, and a role belongs to many users. The two tables meet in a third, the pivot table, named after
153
+ both models in alphabetical order: `role_user`, with `user_id` and `role_id`.
154
+
155
+ ```ts title="app/Models/User.ts"
156
+ import { Model } from '@orieljs/framework'
157
+ import { Role } from './Role'
158
+
159
+ export class User extends Model {
160
+ /** The roles that belong to the user. */
161
+ roles() {
162
+ return this.belongsToMany(Role)
163
+ }
164
+ }
165
+ ```
166
+
167
+ The role's side is the same: `this.belongsToMany(User)`. The next arguments name the pivot table and its keys:
168
+ `this.belongsToMany(Role, 'role_user', 'user_id', 'role_id')`.
169
+
170
+ ```ts
171
+ declare const user: User
172
+ // ---cut---
173
+ const roles = await user.roles().orderBy('name').get()
174
+ ```
175
+
176
+ ### Retrieving intermediate table columns
177
+
178
+ Each model a many-to-many query returns has a `pivot`, a model with its pivot row's keys. `withPivot()` adds other
179
+ columns of the pivot table, and `withTimestamps()` its `created_at` and `updated_at`, which `attach()` then sets:
180
+
181
+ ```ts title="app/Models/User.ts"
182
+ import { Model } from '@orieljs/framework'
183
+ import { Role } from './Role'
184
+
185
+ export class User extends Model {
186
+ roles() {
187
+ return this.belongsToMany(Role).withPivot('active', 'created_by').withTimestamps()
188
+ }
189
+ }
190
+ ```
191
+
192
+ ```ts
193
+ declare const user: User
194
+ // ---cut---
195
+ for (const role of await user.roles().get()) {
196
+ console.log(role.pivot?.getAttribute('created_at'))
197
+ }
198
+ ```
199
+
200
+ ### Filtering queries via intermediate table columns
201
+
202
+ `wherePivot()` and `wherePivotIn()` filter by the pivot table's columns:
203
+
204
+ ```ts
205
+ declare const user: User
206
+ // ---cut---
207
+ const active = await user.roles().wherePivot('active', 1).get()
208
+
209
+ const some = await user.roles().wherePivotIn('priority', [1, 2]).get()
210
+ ```
211
+
212
+ > [!NOTE]
213
+ > Laravel's custom pivot models (`using()`), `as()` and `wherePivotBetween()` and its kin aren't in Oriel.
214
+
215
+ ## Polymorphic relationships
216
+
217
+ A polymorphic relation lets a model belong to more than one kind of model. An image can belong to a user or to a
218
+ post: its `imageable_id` holds the owner's key, and `imageable_type` the owner's kind.
219
+
220
+ ### One to one (polymorphic)
221
+
222
+ ```ts title="app/Models/Image.ts"
223
+ import { Model } from '@orieljs/framework'
224
+
225
+ export class Image extends Model {
226
+ /** Get the parent imageable model (user or post). */
227
+ imageable() {
228
+ return this.morphTo('imageable')
229
+ }
230
+ }
231
+ ```
232
+
233
+ ```ts title="app/Models/Post.ts"
234
+ import { Model } from '@orieljs/framework'
235
+ import { Image } from './Image'
236
+
237
+ export class Post extends Model {
238
+ /** Get the post's image. */
239
+ image() {
240
+ return this.morphOne(Image, 'imageable')
241
+ }
242
+ }
243
+ ```
244
+
245
+ The table needs the two columns, which `table.morphs('imageable')` adds in a migration. `morphTo()` takes the relation's
246
+ name, from which the columns are named. The name is required: Laravel reads it from the method that calls `morphTo()`,
247
+ which JavaScript can't see.
248
+
249
+ ```ts
250
+ const post = await Post.findOrFail(1)
251
+
252
+ const image = await post.image().first()
253
+
254
+ const owner = await (await Image.findOrFail(1)).imageable().first()
255
+ ```
256
+
257
+ ### One to many (polymorphic)
258
+
259
+ `morphMany()` gives a model many of them: `this.morphMany(Comment, 'commentable')`.
260
+
261
+ ### Custom polymorphic types
262
+
263
+ The `_type` column holds the owner's class name, `Post`, unless you map names to models. A map keeps your data from
264
+ depending on your class names:
265
+
266
+ ```ts title="app/Providers/AppServiceProvider.ts"
267
+ import { Relation, ServiceProvider } from '@orieljs/framework'
268
+ import { Post } from '../Models/Post'
269
+ import { User } from '../Models/User'
270
+
271
+ export class AppServiceProvider extends ServiceProvider {
272
+ override boot(): void {
273
+ Relation.morphMap({ post: Post, user: User })
274
+ }
275
+ }
276
+ ```
277
+
278
+ > [!NOTE]
279
+ > Laravel's many-to-many polymorphic relations (`morphToMany()`, `morphedByMany()`), `hasOneThrough()`, the
280
+ > "one of many" relations (`latestOfMany()`) and default models (`withDefault()`) aren't in Oriel.
281
+
282
+ ## Querying relations
283
+
284
+ ### Relationship methods vs. reading a relation
285
+
286
+ In Laravel, `$post->comments` reads a relation as a property. In Oriel, `post.comments` is the relation method
287
+ itself, as it is on any class, so reading through it, `post.author.name`, gives you a property of the function, with
288
+ no error. Read a relation one of two ways:
289
+
290
+ - **Query it:** call the method, and run the query: `await post.author().first()`, or
291
+ `await post.comments().get()`.
292
+ - **Load it first, then read it:** `Post.with('author')` or `post.load('author')`, then `post.getRelation('author')`,
293
+ typed from the method: a `User` or `null` for a belongs-to, a collection for a has-many.
294
+
295
+ ```ts
296
+ const post = await Post.with('author').findOrFail(1)
297
+
298
+ const author = post.getRelation('author') // User | null
299
+
300
+ const comments = await post.comments().get()
301
+ ```
302
+
303
+ `getRelation()` on a relation that isn't loaded throws a `RelationNotLoadedError`, which says how to load it.
304
+
305
+ > [!WARNING]
306
+ > Outside production, reading a property of a relation method, `post.author.name`, prints a warning once, with how
307
+ > to read the relation. `Model.warnOnRelationPropertyAccess(false)`, in a service provider's `boot()`, turns the
308
+ > warning off.
309
+
310
+ ### Querying relationship existence
311
+
312
+ `has()` limits a query to the models that have related models, at least one or as many as you say. A dotted name
313
+ reaches through relations:
314
+
315
+ ```ts
316
+ const posts = await Post.has('comments').get()
317
+
318
+ const popular = await Post.has('comments', '>=', 3).get()
319
+
320
+ const writers = await User.has('posts.comments').get()
321
+ ```
322
+
323
+ `whereHas()` adds conditions on the related models:
324
+
325
+ ```ts
326
+ const posts = await Post.whereHas('comments', (query) => {
327
+ query.where('body', 'like', 'code%')
328
+ }).get()
329
+ ```
330
+
331
+ `orHas()` and `orWhereHas()` join with `or`.
332
+
333
+ ### Querying relationship absence
334
+
335
+ `doesntHave()` and `whereDoesntHave()` are their opposites:
336
+
337
+ ```ts
338
+ const quiet = await Post.doesntHave('comments').get()
339
+
340
+ const clean = await Post.whereDoesntHave('comments', (query) => {
341
+ query.where('body', 'like', 'spam%')
342
+ }).get()
343
+ ```
344
+
345
+ `has()` and its kin don't work on a `morphTo()` relation, whose related table isn't known in advance.
346
+
347
+ ## Aggregating related models
348
+
349
+ ### Counting related models
350
+
351
+ `withCount()` adds a `<relation>_count` attribute with the number of related models, without loading them:
352
+
353
+ ```ts
354
+ const posts = await Post.withCount('comments').get()
355
+
356
+ for (const post of posts) {
357
+ console.log(post.getAttribute('comments_count'))
358
+ }
359
+ ```
360
+
361
+ `as` names the attribute, and an object gives each count its conditions:
362
+
363
+ ```ts
364
+ const posts = await Post.withCount('comments as comment_total')
365
+ .withCount({ 'comments as popular_count': (query) => query.where('votes', '>', 5) })
366
+ .get()
367
+ ```
368
+
369
+ #### Deferred count loading
370
+
371
+ `loadCount()` counts on a model you already have:
372
+
373
+ ```ts
374
+ const post = await Post.findOrFail(1)
375
+
376
+ await post.loadCount('comments')
377
+ ```
378
+
379
+ ### Other aggregate functions
380
+
381
+ `withSum()`, `withAvg()`, `withMin()` and `withMax()` add `<relation>_<function>_<column>`, and `withExists()` adds
382
+ `<relation>_exists`:
383
+
384
+ ```ts
385
+ const posts = await Post.withSum('comments', 'votes').withExists('comments').get()
386
+
387
+ for (const post of posts) {
388
+ console.log(post.getAttribute('comments_sum_votes'), post.getAttribute('comments_exists'))
389
+ }
390
+ ```
391
+
392
+ `loadSum()`, `loadAvg()`, `loadMin()`, `loadMax()` and `loadExists()` add them to a model you have.
393
+
394
+ ## Eager loading
395
+
396
+ Reading the author of each of 25 posts, one at a time, runs 26 queries: one for the posts, then one per author.
397
+ `with()` loads the authors of all of them in one more query:
398
+
399
+ ```ts
400
+ const posts = await Post.with('author').get()
401
+
402
+ for (const post of posts) {
403
+ console.log(post.getRelation('author')?.name)
404
+ }
405
+ ```
406
+
407
+ #### Eager loading multiple relationships
408
+
409
+ ```ts
410
+ const posts = await Post.with('author', 'comments').get()
411
+ ```
412
+
413
+ #### Nested eager loading
414
+
415
+ A dotted name loads a relation of a relation:
416
+
417
+ ```ts
418
+ const posts = await Post.with('comments.post').get()
419
+ ```
420
+
421
+ #### Eager loading specific columns
422
+
423
+ ```ts
424
+ const posts = await Post.with('author:id,name').get()
425
+ ```
426
+
427
+ List the key the relation matches on, here `id`, or the models can't be matched to their parents.
428
+
429
+ #### Eager loading by default
430
+
431
+ `with` on the model loads its relations for every query, and `withCount` counts them:
432
+
433
+ ```ts
434
+ export class Post extends Model {
435
+ protected override with = ['author']
436
+ }
437
+ ```
438
+
439
+ `without()` leaves a default relation out of one query.
440
+
441
+ ### Constraining eager loads
442
+
443
+ Give `with()` an object to add conditions to a relation's query:
444
+
445
+ ```ts
446
+ const posts = await Post.with({ comments: (query) => query.where('body', 'like', '%code%') }).get()
447
+ ```
448
+
449
+ ### Lazy eager loading
450
+
451
+ `load()` loads relations on a model you already have, and `loadMissing()` loads only those that aren't loaded yet:
452
+
453
+ ```ts
454
+ const post = await Post.findOrFail(1)
455
+
456
+ if (post.getAttribute('author_id') !== null) {
457
+ await post.load('author', 'comments')
458
+ }
459
+
460
+ await post.loadMissing('comments')
461
+ ```
462
+
463
+ > [!NOTE]
464
+ > Laravel's `preventLazyLoading()` isn't in Oriel: since a relation is read only once loaded, or by running its query,
465
+ > nothing loads behind your back.
466
+
467
+ ## Inserting and updating related models
468
+
469
+ ### The `save` method
470
+
471
+ `save()` on a has-many relation sets the model's foreign key and saves it, and `saveMany()` does it for several:
472
+
473
+ ```ts
474
+ const comment = Comment.make({ body: 'A new comment.' })
475
+
476
+ const post = await Post.findOrFail(1)
477
+
478
+ await post.comments().save(comment)
479
+
480
+ await post.comments().saveMany([Comment.make({ body: 'Another new comment.' }), Comment.make({ body: 'Another.' })])
481
+ ```
482
+
483
+ ### The `create` method
484
+
485
+ `create()` makes the related model with the foreign key set and saves it, and `createMany()` makes several:
486
+
487
+ ```ts
488
+ const post = await Post.findOrFail(1)
489
+
490
+ const comment = await post.comments().create({ body: 'A new comment.' })
491
+
492
+ await post.comments().createMany([{ body: 'A new comment.' }, { body: 'Another new comment.' }])
493
+ ```
494
+
495
+ ### Belongs to relationships
496
+
497
+ `associate()` sets a model's foreign key to its new parent, and `dissociate()` clears it. Save the model after:
498
+
499
+ ```ts
500
+ const user = await User.findOrFail(10)
501
+ const phone = await Phone.findOrFail(1)
502
+
503
+ phone.user().associate(user)
504
+ await phone.save()
505
+
506
+ phone.user().dissociate()
507
+ await phone.save()
508
+ ```
509
+
510
+ ### Many to many relationships
511
+
512
+ #### Attaching / detaching
513
+
514
+ `attach()` adds a row to the pivot table, with any extra pivot columns, and `detach()` removes one, several, or every
515
+ row of this model:
516
+
517
+ ```ts
518
+ const user = await User.findOrFail(1)
519
+
520
+ await user.roles().attach(1, { expires: new Date('2026-12-31') })
521
+
522
+ await user.roles().detach(1)
523
+
524
+ await user.roles().detach()
525
+ ```
526
+
527
+ #### Syncing associations
528
+
529
+ `sync()` leaves exactly the ids you give attached, adding and removing rows, and returns what it changed.
530
+ `syncWithoutDetaching()` only adds, and `toggle()` attaches the ids that aren't attached and detaches those that are:
531
+
532
+ ```ts
533
+ declare const user: User
534
+ // ---cut---
535
+ const changes = await user.roles().sync([1, 2, 3]) // { attached: [...], detached: [...], updated: [...] }
536
+
537
+ await user.roles().syncWithoutDetaching([1, 2, 3])
538
+
539
+ await user.roles().toggle([1, 2, 3])
540
+ ```
541
+
542
+ #### Updating a record on the intermediate table
543
+
544
+ ```ts
545
+ declare const user: User
546
+ // ---cut---
547
+ await user.roles().updateExistingPivot(1, { active: false })
548
+ ```
549
+
550
+ > [!NOTE]
551
+ > Laravel's `touches`, which updates a parent's `updated_at` when a child changes, isn't in Oriel: `touch()` the parent
552
+ > yourself.
553
+
554
+ ## Serializing relations
555
+
556
+ A model's `toArray()` and its JSON include the relations it has loaded, under their names in snake case
557
+ (`blogPosts` as `blog_posts`), as their models' own arrays. A relation that isn't loaded isn't there.