@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,277 @@
1
+ # Task scheduling
2
+
3
+ The scheduler runs your app's recurring tasks: you define them in your code, and one cron entry on your server runs
4
+ them all.
5
+
6
+ ## Introduction
7
+
8
+ Apps once needed a cron entry for every task. Oriel's scheduler, as Laravel's, keeps the whole schedule in your code, in
9
+ `routes/console.ts`, so it is in your repository with the rest of your app. Your server needs one cron entry, which runs
10
+ `schedule:run` every minute (see [Running the scheduler](#running-the-scheduler)).
11
+
12
+ ## Defining schedules
13
+
14
+ Define your tasks with the `Schedule` facade, in `routes/console.ts`. This one empties a table every day at midnight:
15
+
16
+ ```ts title="routes/console.ts"
17
+ import { DB, Schedule } from '@orieljs/framework'
18
+
19
+ Schedule.call(async () => {
20
+ await DB.table('recent_users').delete()
21
+ }).daily()
22
+ ```
23
+
24
+ A task is a console command, a closure or a job. `node oriel schedule:list` lists the tasks you have defined, each
25
+ with its cron expression.
26
+
27
+ ### Scheduling commands
28
+
29
+ `Schedule.command()` runs a console command by its name, with its arguments and options in an array, or written into
30
+ the name:
31
+
32
+ ```ts
33
+ Schedule.command('emails:send', ['taylor', '--force']).daily()
34
+
35
+ Schedule.command('emails:send taylor --force').daily()
36
+ ```
37
+
38
+ A command fails when it ends with an exit code other than 0.
39
+
40
+ > [!NOTE]
41
+ > Laravel runs each scheduled Artisan command in a PHP process of its own. Oriel runs it in the `schedule:run`
42
+ > process, through your app's console kernel, as `Artisan.call()` does: there is no process to start, and your
43
+ > commands share that process with the other tasks.
44
+
45
+ ### Scheduling closures
46
+
47
+ `Schedule.call()` runs a function, which may be async, as in the [example above](#defining-schedules). It fails when
48
+ it throws, or returns `false`.
49
+
50
+ ### Scheduling queued jobs
51
+
52
+ `Schedule.job()` takes a job, and the queue and the connection to push it onto:
53
+
54
+ ```ts
55
+ // @filename: app/Jobs/Heartbeat.ts
56
+ import { Queueable } from '@orieljs/framework'
57
+ export class Heartbeat extends Queueable {
58
+ async handle(): Promise<void> {}
59
+ }
60
+ // ---cut---
61
+ import { Heartbeat } from './app/Jobs/Heartbeat'
62
+
63
+ Schedule.job(new Heartbeat(), 'heartbeats', 'redis').everyFiveMinutes()
64
+ ```
65
+
66
+ The job is made when `routes/console.ts` loads, which is on every `node oriel` command, so don't read the database to
67
+ make it: a job that needs a model finds it in its `handle()`.
68
+
69
+ A queued job, such as one that extends `Queueable`, is pushed onto its queue each time the task runs, and a worker runs
70
+ it. Any other job runs at once, in the scheduler's process.
71
+
72
+ > [!NOTE]
73
+ > A job that runs in the scheduler's process fails, as a closure does, when its `handle()` returns `false`. Laravel's
74
+ > `Schedule::job()` doesn't look at what the job returns.
75
+
76
+ ### Schedule frequency options
77
+
78
+ A task's frequency is a method on it:
79
+
80
+ | Method | Runs the task |
81
+ | --- | --- |
82
+ | `cron('* * * * *')` | On a cron expression of your own |
83
+ | `everySecond()`, `everyTwoSeconds()`, `everyFiveSeconds()`, `everyTenSeconds()`, `everyFifteenSeconds()`, `everyTwentySeconds()`, `everyThirtySeconds()` | Every second, or every 2, 5, 10, 15, 20 or 30 seconds (see [Sub-minute tasks](#sub-minute-tasks)) |
84
+ | `everyMinute()` | Every minute |
85
+ | `everyTwoMinutes()`, `everyThreeMinutes()`, `everyFourMinutes()`, `everyFiveMinutes()`, `everyTenMinutes()`, `everyFifteenMinutes()`, `everyThirtyMinutes()` | Every 2, 3, 4, 5, 10, 15 or 30 minutes |
86
+ | `hourly()` | Every hour, on the hour |
87
+ | `hourlyAt(17)` | Every hour at 17 minutes past (or a list of minutes: `hourlyAt([0, 30])`) |
88
+ | `everyOddHour(minute)`, `everyTwoHours(minute)`, `everyThreeHours(minute)`, `everyFourHours(minute)`, `everySixHours(minute)` | Every odd hour, or every 2, 3, 4 or 6 hours, at `minute` past (0 when left out) |
89
+ | `daily()` | Every day at midnight |
90
+ | `dailyAt('13:00')`, `at('13:00')` | Every day at 13:00 |
91
+ | `twiceDaily(1, 13)` | Every day at 1:00 and 13:00 |
92
+ | `twiceDailyAt(1, 13, 15)` | Every day at 1:15 and 13:15 |
93
+ | `weekly()` | Every Sunday at 00:00 |
94
+ | `weeklyOn(1, '8:00')` | Every week on Monday at 8:00 (or a list of days) |
95
+ | `monthly()` | On the first day of every month at 00:00 |
96
+ | `monthlyOn(4, '15:00')` | Every month on the 4th at 15:00 |
97
+ | `twiceMonthly(1, 16, '13:00')` | Every month on the 1st and the 16th at 13:00 |
98
+ | `lastDayOfMonth('15:00')` | On the last day of every month at 15:00 |
99
+ | `quarterly()` | On the first day of every quarter at 00:00 |
100
+ | `quarterlyOn(4, '14:00')` | Every quarter on the 4th at 14:00 |
101
+ | `yearly()` | On the first day of every year at 00:00 |
102
+ | `yearlyOn(6, 1, '17:00')` | Every year on June 1st at 17:00 |
103
+
104
+ Combine a frequency with the constraints below to narrow it, such as a task that runs every hour on weekdays:
105
+
106
+ ```ts
107
+ Schedule.command('reports:send').weekdays().hourly()
108
+ ```
109
+
110
+ #### Day constraints
111
+
112
+ `weekdays()`, `weekends()`, `mondays()`, `tuesdays()`, `wednesdays()`, `thursdays()`, `fridays()`, `saturdays()` and
113
+ `sundays()` limit a task to those days. `days()` takes the days as numbers, with 0 for Sunday:
114
+
115
+ ```ts
116
+ Schedule.command('emails:send').hourly().days(0, 3)
117
+ ```
118
+
119
+ #### Between time constraints
120
+
121
+ `between()` runs a task only between two times of day, and `unlessBetween()` only outside them. A window may cross
122
+ midnight:
123
+
124
+ ```ts
125
+ Schedule.command('emails:send').hourly().between('7:00', '22:00')
126
+
127
+ Schedule.command('emails:send').hourly().unlessBetween('23:00', '4:00')
128
+ ```
129
+
130
+ #### Truth test constraints
131
+
132
+ `when()` runs a task only when its function returns `true`, and `skip()` skips it when its function returns `true`.
133
+ The function may be async:
134
+
135
+ ```ts
136
+ Schedule.command('emails:send').daily().when(() => true)
137
+
138
+ Schedule.command('emails:send').daily().skip(async () => (await DB.table('jobs').count()) > 1000)
139
+ ```
140
+
141
+ #### Environment constraints
142
+
143
+ Laravel's `environments()` isn't in Oriel. Give `when()` the check instead:
144
+
145
+ ```ts
146
+ Schedule.command('emails:send').daily().when(() => app().environment('staging', 'production'))
147
+ ```
148
+
149
+ ### Timezones
150
+
151
+ A task's times are read in your app's timezone, `timezone` in `config/app.ts`, which is UTC in a new app.
152
+ `timezone()` sets another for one task:
153
+
154
+ ```ts
155
+ Schedule.command('report:generate').timezone('America/New_York').at('2:00')
156
+ ```
157
+
158
+ ### Preventing task overlaps
159
+
160
+ A task runs again when it is due, even if its last run hasn't finished. `withoutOverlapping()` skips a run while the
161
+ last one is still going:
162
+
163
+ ```ts
164
+ Schedule.command('emails:send').everyMinute().withoutOverlapping()
165
+ ```
166
+
167
+ The task holds a cache lock while it runs. In case a run never ends, the lock expires after 24 hours, or after the
168
+ number of minutes you give: `withoutOverlapping(10)`. `node oriel schedule:clear-cache` deletes the locks of the tasks
169
+ that use it, so a task stuck behind a run that was killed runs again.
170
+
171
+ A closure has no command to tell it apart from the others, so it needs a name first, or `withoutOverlapping()` throws
172
+ "A scheduled event name is required to prevent overlapping. Use the 'name' method before 'withoutOverlapping'.":
173
+
174
+ ```ts
175
+ Schedule.call(async () => {
176
+ await DB.table('recent_users').delete()
177
+ })
178
+ .daily()
179
+ .name('prune-recent-users')
180
+ .withoutOverlapping()
181
+ ```
182
+
183
+ A job is named after its class, so it needs no `name()`.
184
+
185
+ ### Running tasks on one server
186
+
187
+ When your app runs the scheduler on several servers, `onOneServer()` runs a task on only one of them each time it is
188
+ due: the first server to take its lock runs it. The lock is in your default cache store, so every server must share
189
+ that store, as the `database` store does with a shared database, or the `redis` store with a shared Redis. A closure
190
+ needs a `name()` here too.
191
+
192
+ ```ts
193
+ Schedule.command('report:generate').fridays().at('17:00').onOneServer()
194
+ ```
195
+
196
+ ### Background tasks
197
+
198
+ The tasks due at the same time run one after the other. `runInBackground()` starts a task and goes on with the next
199
+ one without waiting for it, so a long task doesn't hold the others back. `schedule:run` still waits for it before it
200
+ ends:
201
+
202
+ ```ts
203
+ Schedule.command('analytics:report').daily().runInBackground()
204
+ ```
205
+
206
+ > [!NOTE]
207
+ > Laravel's scheduler can also run shell commands (`exec()`), group tasks, keep running in maintenance mode, write or
208
+ > mail a task's output, ping URLs and dispatch scheduling events. Oriel's has none of these: run a shell command from
209
+ > a closure with `node:child_process`, and write what you need to the log.
210
+
211
+ ## Running the scheduler
212
+
213
+ Add one cron entry to your server, which runs `schedule:run` every minute:
214
+
215
+ ```sh
216
+ * * * * * cd /path-to-your-project && node oriel schedule:run >> /dev/null 2>&1
217
+ ```
218
+
219
+ `schedule:run` runs each due task once, prints its name, and goes on to the next one when a task fails, after
220
+ printing the error. With nothing due, it prints "No scheduled commands are ready to run.".
221
+
222
+ ### Sub-minute tasks
223
+
224
+ A task that runs every few seconds, such as `everyTenSeconds()`, is due every minute. Its `schedule:run` runs it
225
+ again each time those seconds have passed, until the minute ends, then the next minute's `schedule:run` takes over:
226
+
227
+ ```ts
228
+ Schedule.call(async () => {
229
+ await DB.table('recent_users').delete()
230
+ }).everyTenSeconds()
231
+ ```
232
+
233
+ Since that `schedule:run` keeps going for the whole minute, a deploy would wait for it. `schedule:interrupt` tells it
234
+ to stop repeating its sub-minute tasks for the rest of the minute; run it as part of your deploy:
235
+
236
+ ```sh
237
+ node oriel schedule:interrupt
238
+ ```
239
+
240
+ The signal is kept in your default cache store until the minute ends.
241
+
242
+ ### Running the scheduler locally
243
+
244
+ On your own machine, run `schedule:work` instead of adding a cron entry. It runs the scheduler every minute in the
245
+ foreground, until you stop it:
246
+
247
+ ```sh
248
+ node oriel schedule:work
249
+ ```
250
+
251
+ When one minute's run fails, as when the cache can't be read, it reports the error and goes on with the next minute.
252
+ [Deployment](deployment.md#task-scheduling) shows both ways in production.
253
+
254
+ ## Task hooks
255
+
256
+ `before()` runs a function before the task, and `after()` after it, whether it succeeded or not. `onSuccess()` runs
257
+ one when it succeeded, and `onFailure()` when it failed. Each may be async:
258
+
259
+ ```ts
260
+ Schedule.command('emails:send')
261
+ .daily()
262
+ .before(() => {
263
+ // The task is about to run...
264
+ })
265
+ .after(() => {
266
+ // The task has run...
267
+ })
268
+ .onSuccess(() => {
269
+ // The task succeeded...
270
+ })
271
+ .onFailure(() => {
272
+ // The task failed...
273
+ })
274
+ ```
275
+
276
+ `then()` is another name for `after()`. A task skipped by a constraint, an overlap or another server runs none of
277
+ its hooks.
@@ -0,0 +1,122 @@
1
+ # Database: seeding
2
+
3
+ Seeders fill your database with data, such as a first user for your own machine or the rows your tests and demos
4
+ need.
5
+
6
+ ## Introduction
7
+
8
+ A seeder is a class in `database/seeders` with a `run()` method. A new app has one, `DatabaseSeeder`, which `db:seed`
9
+ runs unless you name another. It creates a test user; add a `this.call()` for each seeder you write.
10
+
11
+ Seeders run with mass assignment protection off, so `create()` takes every attribute you give it.
12
+
13
+ ## Writing seeders
14
+
15
+ `make:seeder` writes a seeder in `database/seeders`:
16
+
17
+ ```sh
18
+ node oriel make:seeder UserSeeder
19
+ ```
20
+
21
+ A seeder's `run()` can insert rows with the [query builder](queries.md), or create models with their
22
+ [factories](#using-model-factories). It is async, so `await` each insert:
23
+
24
+ ```ts title="database/seeders/UserSeeder.ts"
25
+ import { DB, Hash, Seeder, Str } from '@orieljs/framework'
26
+
27
+ export default class UserSeeder extends Seeder {
28
+ /** Run the database seeders. */
29
+ async run(): Promise<void> {
30
+ await DB.table('users').insert({
31
+ name: Str.random(10),
32
+ email: `${Str.random(10)}@example.com`,
33
+ password: await Hash.make('password'),
34
+ })
35
+ }
36
+ }
37
+ ```
38
+
39
+ The file's default export is the seeder, or its export named after the file.
40
+
41
+ ### Using model factories
42
+
43
+ Factories make many rows at once, with realistic values. A new app's `DatabaseSeeder` uses the `User` factory:
44
+
45
+ ```ts title="database/seeders/DatabaseSeeder.ts"
46
+ import { Seeder } from '@orieljs/framework'
47
+ import { User } from '../../app/Models/User'
48
+
49
+ export default class DatabaseSeeder extends Seeder {
50
+ async run(): Promise<void> {
51
+ await User.factory().create({
52
+ name: 'Test User',
53
+ email: 'test@example.com',
54
+ })
55
+ }
56
+ }
57
+ ```
58
+
59
+ `User.factory(50).create()` makes 50 users. [Factories](eloquent-factories.md) shows how to write factories and give
60
+ them related models.
61
+
62
+ ### Calling additional seeders
63
+
64
+ `call()` runs other seeders, in order, and prints each one's name as it runs:
65
+
66
+ ```ts title="database/seeders/DatabaseSeeder.ts"
67
+ // @filename: database/seeders/UserSeeder.ts
68
+ import { Seeder } from '@orieljs/framework'
69
+ export default class UserSeeder extends Seeder {
70
+ async run(): Promise<void> {}
71
+ }
72
+ // @filename: database/seeders/PostSeeder.ts
73
+ import { Seeder } from '@orieljs/framework'
74
+ export default class PostSeeder extends Seeder {
75
+ async run(): Promise<void> {}
76
+ }
77
+ // ---cut---
78
+ import { Seeder } from '@orieljs/framework'
79
+ import PostSeeder from './PostSeeder'
80
+ import UserSeeder from './UserSeeder'
81
+
82
+ export default class DatabaseSeeder extends Seeder {
83
+ async run(): Promise<void> {
84
+ await this.call(UserSeeder, PostSeeder)
85
+ }
86
+ }
87
+ ```
88
+
89
+ `callSilent()` runs them without printing.
90
+
91
+ > [!NOTE]
92
+ > Laravel's `WithoutModelEvents` trait, which mutes model events while a seeder runs, and `callWith()` aren't in
93
+ > Oriel. Wrap the code in `Model.withoutEvents()` to mute the events.
94
+
95
+ ## Running seeders
96
+
97
+ `db:seed` runs `DatabaseSeeder`. Name another seeder to run it alone:
98
+
99
+ ```sh
100
+ node oriel db:seed
101
+
102
+ node oriel db:seed UserSeeder
103
+ ```
104
+
105
+ `--database` runs the seeder on another connection, which becomes the default while it runs.
106
+
107
+ `--seed` runs `DatabaseSeeder` after `migrate`, `migrate:fresh` or `migrate:refresh`, and `--seeder` another one:
108
+
109
+ ```sh
110
+ node oriel migrate:fresh --seed
111
+
112
+ node oriel migrate:fresh --seed --seeder=UserSeeder
113
+ ```
114
+
115
+ #### Forcing seeders to run in production
116
+
117
+ In production, `db:seed` stops unless you pass `--force`, so you can't fill your live database with test data by
118
+ mistake:
119
+
120
+ ```sh
121
+ node oriel db:seed --force
122
+ ```