@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,273 @@
1
+ {
2
+ "sections": [
3
+ {
4
+ "title": "Getting started",
5
+ "pages": [
6
+ {
7
+ "slug": "installation",
8
+ "title": "Installation"
9
+ },
10
+ {
11
+ "slug": "configuration",
12
+ "title": "Configuration"
13
+ },
14
+ {
15
+ "slug": "structure",
16
+ "title": "Directory structure"
17
+ },
18
+ {
19
+ "slug": "starter-kits",
20
+ "title": "Starter kits"
21
+ },
22
+ {
23
+ "slug": "deployment",
24
+ "title": "Deployment"
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "title": "Architecture",
30
+ "pages": [
31
+ {
32
+ "slug": "lifecycle",
33
+ "title": "Request lifecycle"
34
+ },
35
+ {
36
+ "slug": "container",
37
+ "title": "Service container"
38
+ },
39
+ {
40
+ "slug": "providers",
41
+ "title": "Service providers"
42
+ },
43
+ {
44
+ "slug": "facades",
45
+ "title": "Facades"
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "title": "The basics",
51
+ "pages": [
52
+ {
53
+ "slug": "routing",
54
+ "title": "Routing"
55
+ },
56
+ {
57
+ "slug": "middleware",
58
+ "title": "Middleware"
59
+ },
60
+ {
61
+ "slug": "csrf",
62
+ "title": "CSRF protection"
63
+ },
64
+ {
65
+ "slug": "controllers",
66
+ "title": "Controllers"
67
+ },
68
+ {
69
+ "slug": "requests",
70
+ "title": "Requests"
71
+ },
72
+ {
73
+ "slug": "session",
74
+ "title": "Session"
75
+ },
76
+ {
77
+ "slug": "responses",
78
+ "title": "Responses"
79
+ },
80
+ {
81
+ "slug": "views",
82
+ "title": "Views"
83
+ },
84
+ {
85
+ "slug": "validation",
86
+ "title": "Validation"
87
+ },
88
+ {
89
+ "slug": "errors",
90
+ "title": "Error handling"
91
+ },
92
+ {
93
+ "slug": "logging",
94
+ "title": "Logging"
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ "title": "Digging deeper",
100
+ "pages": [
101
+ {
102
+ "slug": "console",
103
+ "title": "Console"
104
+ },
105
+ {
106
+ "slug": "cache",
107
+ "title": "Cache"
108
+ },
109
+ {
110
+ "slug": "collections",
111
+ "title": "Collections"
112
+ },
113
+ {
114
+ "slug": "events",
115
+ "title": "Events"
116
+ },
117
+ {
118
+ "slug": "storage",
119
+ "title": "File storage"
120
+ },
121
+ {
122
+ "slug": "helpers",
123
+ "title": "Helpers"
124
+ },
125
+ {
126
+ "slug": "mail",
127
+ "title": "Mail"
128
+ },
129
+ {
130
+ "slug": "notifications",
131
+ "title": "Notifications"
132
+ },
133
+ {
134
+ "slug": "queues",
135
+ "title": "Queues"
136
+ },
137
+ {
138
+ "slug": "scheduling",
139
+ "title": "Task scheduling"
140
+ }
141
+ ]
142
+ },
143
+ {
144
+ "title": "Security",
145
+ "pages": [
146
+ {
147
+ "slug": "authentication",
148
+ "title": "Authentication"
149
+ },
150
+ {
151
+ "slug": "authorization",
152
+ "title": "Authorization"
153
+ },
154
+ {
155
+ "slug": "verification",
156
+ "title": "Email verification"
157
+ },
158
+ {
159
+ "slug": "encryption",
160
+ "title": "Encryption"
161
+ },
162
+ {
163
+ "slug": "hashing",
164
+ "title": "Hashing"
165
+ },
166
+ {
167
+ "slug": "passwords",
168
+ "title": "Password reset"
169
+ }
170
+ ]
171
+ },
172
+ {
173
+ "title": "Database",
174
+ "pages": [
175
+ {
176
+ "slug": "database",
177
+ "title": "Getting started"
178
+ },
179
+ {
180
+ "slug": "queries",
181
+ "title": "Query builder"
182
+ },
183
+ {
184
+ "slug": "pagination",
185
+ "title": "Pagination"
186
+ },
187
+ {
188
+ "slug": "migrations",
189
+ "title": "Migrations"
190
+ },
191
+ {
192
+ "slug": "seeding",
193
+ "title": "Seeding"
194
+ }
195
+ ]
196
+ },
197
+ {
198
+ "title": "Models",
199
+ "pages": [
200
+ {
201
+ "slug": "eloquent",
202
+ "title": "Getting started"
203
+ },
204
+ {
205
+ "slug": "eloquent-relationships",
206
+ "title": "Relationships"
207
+ },
208
+ {
209
+ "slug": "eloquent-collections",
210
+ "title": "Collections"
211
+ },
212
+ {
213
+ "slug": "eloquent-mutators",
214
+ "title": "Accessors and casts"
215
+ },
216
+ {
217
+ "slug": "eloquent-factories",
218
+ "title": "Factories"
219
+ },
220
+ {
221
+ "slug": "eloquent-observers",
222
+ "title": "Observers"
223
+ }
224
+ ]
225
+ },
226
+ {
227
+ "title": "Frontend",
228
+ "pages": [
229
+ {
230
+ "slug": "inertia",
231
+ "title": "Inertia"
232
+ },
233
+ {
234
+ "slug": "live",
235
+ "title": "Live"
236
+ },
237
+ {
238
+ "slug": "vite",
239
+ "title": "Vite"
240
+ }
241
+ ]
242
+ },
243
+ {
244
+ "title": "Testing",
245
+ "pages": [
246
+ {
247
+ "slug": "testing",
248
+ "title": "Getting started"
249
+ },
250
+ {
251
+ "slug": "http-tests",
252
+ "title": "HTTP tests"
253
+ },
254
+ {
255
+ "slug": "live-tests",
256
+ "title": "Live tests"
257
+ },
258
+ {
259
+ "slug": "mocking",
260
+ "title": "Fakes"
261
+ },
262
+ {
263
+ "slug": "database-testing",
264
+ "title": "Database"
265
+ },
266
+ {
267
+ "slug": "time-travel",
268
+ "title": "Time travel"
269
+ }
270
+ ]
271
+ }
272
+ ]
273
+ }
@@ -0,0 +1,225 @@
1
+ # Notifications
2
+
3
+ A notification is a short message to one of your users, such as "your order has shipped", sent over one or more
4
+ channels: as an email, as a row in your database for your app to show, or both.
5
+
6
+ ## Introduction
7
+
8
+ Each notification is a class with a `via()` method, which names its channels, and a `toXxx()` method for each of
9
+ them. Oriel has two channels: `mail`, which sends an email, and `database`, which stores the notification in a
10
+ `notifications` table. You can add channels of your own with `Notification.extend()`.
11
+
12
+ > [!NOTE]
13
+ > Laravel also has broadcast, SMS (Vonage) and Slack channels. Oriel has `mail` and `database`.
14
+
15
+ ## Generating notifications
16
+
17
+ `make:notification` writes a class to `app/Notifications`:
18
+
19
+ ```sh
20
+ node oriel make:notification InvoicePaid
21
+ ```
22
+
23
+ The class it writes is queued (it has `readonly shouldQueue = true`), so run `node oriel queue:work` while you work
24
+ on it, or remove the field to send it right away.
25
+
26
+ ## Sending notifications
27
+
28
+ ### Using the Notifiable mixin
29
+
30
+ A model sends and stores notifications through the `Notifiable` mixin, which the `User` model of a new app already
31
+ has:
32
+
33
+ ```ts title="app/Models/User.ts"
34
+ import { AuthenticatableModel, Authorizable, HasApiTokens, Model, Notifiable } from '@orieljs/framework'
35
+
36
+ export class User extends HasApiTokens(Notifiable(Authorizable(AuthenticatableModel(Model)))) {}
37
+ ```
38
+
39
+ It gives the model `notify()`, `notifyNow()` and `routeNotificationFor()`, and the `notifications()`,
40
+ `readNotifications()` and `unreadNotifications()` relations of the [database channel](#database-notifications):
41
+
42
+ ```ts
43
+ const user = await User.findOrFail(1)
44
+
45
+ await user.notify(new InvoicePaid('$20.00'))
46
+ ```
47
+
48
+ ### Using the Notification facade
49
+
50
+ To notify several users at once, pass them (an array or any iterable) to `Notification.send()`:
51
+
52
+ ```ts
53
+ import { Notification } from '@orieljs/framework'
54
+
55
+ const users = await User.all()
56
+
57
+ await Notification.send(users, new InvoicePaid('$20.00'))
58
+ ```
59
+
60
+ `Notification.sendNow()` sends right away, even a queued notification, and takes the channels to use as a third
61
+ argument.
62
+
63
+ ### Specifying delivery channels
64
+
65
+ `via()` gets the notifiable, so a notification can choose its channels for each user:
66
+
67
+ ```ts
68
+ class InvoicePaid extends Notification {
69
+ via(notifiable: object): string[] {
70
+ return notifiable instanceof User ? ['mail', 'database'] : ['mail']
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Queueing notifications
76
+
77
+ A notification with `readonly shouldQueue = true` is queued: `notify()` and `Notification.send()` push one job per
78
+ notifiable and per channel, and a [queue worker](queues.md#running-the-queue-worker) sends them. The fields
79
+ `connection`, `queue` and `delay` choose where and when, and any of them also queues the notification, as a `uniqueId()`
80
+ or `retryUntil()` method does. `notifyNow()` and `Notification.sendNow()` send a queued notification right away.
81
+
82
+ ### Unique notifications
83
+
84
+ A queued notification with a `uniqueId()` method isn't queued again while its job is waiting or running, as a
85
+ [unique job](queues.md#unique-jobs). The lock is taken per notifiable and per channel, so the same notification to two
86
+ users, or by mail and by database, sends each of them.
87
+
88
+ ### On-demand notifications
89
+
90
+ To notify someone who isn't a user of your app, give the route yourself:
91
+
92
+ ```ts
93
+ await Notification.route('mail', 'taylor@example.com').notify(new InvoicePaid('$20.00'))
94
+ ```
95
+
96
+ The database channel is skipped for an on-demand notification, as it needs a model to store the row against.
97
+
98
+ > [!NOTE]
99
+ > Laravel chains `route()` for several channels (`->route('mail', ...)->route('vonage', ...)`). The value
100
+ > `Notification.route()` returns is typed with `notify()` only, so an on-demand notification has one channel.
101
+
102
+ ## Mail notifications
103
+
104
+ ### Formatting mail messages
105
+
106
+ `toMail()` returns a `MailMessage`, which Oriel renders in its own styled template, with a plain-text part beside the
107
+ HTML:
108
+
109
+ ```ts title="app/Notifications/InvoicePaid.ts"
110
+ import { MailMessage, Notification } from '@orieljs/framework'
111
+
112
+ export class InvoicePaid extends Notification {
113
+ constructor(private readonly amount: string) {
114
+ super()
115
+ }
116
+
117
+ via(): string[] {
118
+ return ['mail']
119
+ }
120
+
121
+ toMail(): MailMessage {
122
+ return new MailMessage()
123
+ .subject('Invoice paid')
124
+ .greeting('Hello!')
125
+ .line(`We received your payment of ${this.amount}.`)
126
+ .action('View invoice', 'https://example.com/invoices/1')
127
+ .line('Thank you for using our application!')
128
+ }
129
+ }
130
+ ```
131
+
132
+ - `greeting()` is "Hello!" when you don't set one ("Whoops!" for an error message), and `salutation()` is "Regards,"
133
+ and your app's name.
134
+ - `line()`, `lines()`, `lineIf()` and `linesIf()` add paragraphs; `action()` adds a button.
135
+ - `from()`, `to()`, `cc()`, `bcc()` and `replyTo()` set the addresses, `attach(path, { as, mime })` attaches a file and
136
+ `attachData(data, name, { mime })` attaches content you have in memory.
137
+
138
+ Each line is plain text: Oriel escapes it, and keeps it as you wrote it.
139
+
140
+ > [!NOTE]
141
+ > Laravel reads each line as Markdown, trims it and joins its lines into one. Oriel's lines are plain text, escaped,
142
+ > neither trimmed nor joined. A `MailMessage` has no `view()`, `markdown()`, `mailer()` or theme: for a mail of your
143
+ > own design, return a [mailable](mail.md) from `toMail()`. A mailable is sent to its own recipients, not to the
144
+ > notifiable's address.
145
+
146
+ ### Error messages
147
+
148
+ `error()` gives the message the error level, which colours its button red; `success()` colours it green, and
149
+ `level('info' | 'success' | 'error')` sets it directly.
150
+
151
+ ### Customizing the subject
152
+
153
+ When you don't call `subject()`, the subject is the class name in title case: `InvoicePaid` is sent as "Invoice Paid".
154
+ A notification written as an object literal has the name `Object`, so its subject is "Object": give it a `subject()`.
155
+
156
+ ### Customizing the recipient
157
+
158
+ The mail channel sends to the notifiable's `email` attribute. To send elsewhere, add a `routeNotificationForMail()`
159
+ method to the model:
160
+
161
+ ```ts
162
+ class Customer extends Notifiable(Model) {
163
+ declare billing_email: string
164
+
165
+ routeNotificationForMail(): string {
166
+ return this.billing_email
167
+ }
168
+ }
169
+ ```
170
+
171
+ When there is no route, as for a notifiable with no email address, nothing is sent.
172
+
173
+ ## Database notifications
174
+
175
+ ### Prerequisites
176
+
177
+ The database channel stores each notification in the `notifications` table, which a new app's migrations create.
178
+ `toDatabase()` (or `toArray()`) returns the data to store, and the notifiable must be a model:
179
+
180
+ ```ts
181
+ class InvoicePaid extends Notification {
182
+ constructor(readonly amount: string) {
183
+ super()
184
+ }
185
+
186
+ via(): string[] {
187
+ return ['database']
188
+ }
189
+
190
+ toDatabase(): Record<string, unknown> {
191
+ return { amount: this.amount }
192
+ }
193
+ }
194
+ ```
195
+
196
+ The row's `type` is the notification's class name, and its `data` is what `toDatabase()` returned.
197
+
198
+ ### Accessing the notifications
199
+
200
+ `notifications()` is a relation, newest first; `unreadNotifications()` and `readNotifications()` filter it:
201
+
202
+ ```ts
203
+ const user = await User.findOrFail(1)
204
+
205
+ for (const notification of await user.unreadNotifications().get()) {
206
+ console.log(notification.data)
207
+ }
208
+ ```
209
+
210
+ ### Marking notifications as read
211
+
212
+ ```ts
213
+ const user = await User.findOrFail(1)
214
+ const notification = await user.notifications().firstOrFail()
215
+
216
+ await notification.markAsRead()
217
+ await notification.markAsUnread()
218
+ ```
219
+
220
+ `read()` and `unread()` tell which it is, and a collection of notifications has its own `markAsRead()`.
221
+
222
+ ## Custom channels
223
+
224
+ `Notification.extend(name, factory)` adds a channel: an object with `send(notifiable, notification)`. List its name in
225
+ `via()`.