@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.
- package/NOTICE +4 -0
- package/README.md +10 -1
- package/TRADEMARKS.md +33 -0
- package/dist/{chunk-POEP4B2M.js → chunk-7ZQZYDOC.js} +745 -105
- package/dist/chunk-7ZQZYDOC.js.map +1 -0
- package/dist/{chunk-I63ZFMX6.js → chunk-GVNLZZTN.js} +1 -1
- package/dist/chunk-GVNLZZTN.js.map +1 -0
- package/dist/{chunk-ZOEPKBVF.js → chunk-SPQUX3C7.js} +1347 -428
- package/dist/chunk-SPQUX3C7.js.map +1 -0
- package/dist/{chunk-MFTZQMZM.js → chunk-VOYW3R5J.js} +4 -4
- package/dist/chunk-VOYW3R5J.js.map +1 -0
- package/dist/contracts.d.ts +515 -17
- package/dist/contracts.js +1 -1
- package/dist/index.d-WQpfdIbG.d.ts +630 -0
- package/dist/index.d.ts +964 -152
- package/dist/index.js +4279 -866
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-CBZQYYDS.d.ts → jsx-runtime-6um4Mwfb.d.ts} +96 -5
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing-BM481Hdz.d.ts → testing-CKwfWJGn.d.ts} +180 -15
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +3 -3
- package/docs/authentication.md +487 -0
- package/docs/authorization.md +435 -0
- package/docs/cache.md +647 -0
- package/docs/collections.md +2270 -0
- package/docs/configuration.md +334 -0
- package/docs/console.md +651 -0
- package/docs/container.md +430 -0
- package/docs/controllers.md +406 -0
- package/docs/csrf.md +126 -0
- package/docs/database-testing.md +144 -0
- package/docs/database.md +236 -0
- package/docs/deployment.md +276 -0
- package/docs/eloquent-collections.md +218 -0
- package/docs/eloquent-factories.md +255 -0
- package/docs/eloquent-mutators.md +254 -0
- package/docs/eloquent-observers.md +145 -0
- package/docs/eloquent-relationships.md +557 -0
- package/docs/eloquent.md +494 -0
- package/docs/encryption.md +104 -0
- package/docs/errors.md +318 -0
- package/docs/events.md +336 -0
- package/docs/facades.md +278 -0
- package/docs/hashing.md +118 -0
- package/docs/helpers.md +1281 -0
- package/docs/http-tests.md +386 -0
- package/docs/inertia.md +485 -0
- package/docs/installation.md +209 -0
- package/docs/lifecycle.md +234 -0
- package/docs/live-tests.md +226 -0
- package/docs/live.md +910 -0
- package/docs/logging.md +255 -0
- package/docs/mail.md +619 -0
- package/docs/middleware.md +384 -0
- package/docs/migrations.md +416 -0
- package/docs/mocking.md +264 -0
- package/docs/navigation.json +273 -0
- package/docs/notifications.md +225 -0
- package/docs/pagination.md +247 -0
- package/docs/passwords.md +207 -0
- package/docs/providers.md +187 -0
- package/docs/queries.md +600 -0
- package/docs/queues.md +330 -0
- package/docs/requests.md +533 -0
- package/docs/responses.md +444 -0
- package/docs/routing.md +738 -0
- package/docs/scheduling.md +277 -0
- package/docs/seeding.md +122 -0
- package/docs/session.md +409 -0
- package/docs/starter-kits.md +396 -0
- package/docs/storage.md +192 -0
- package/docs/structure.md +264 -0
- package/docs/testing.md +165 -0
- package/docs/time-travel.md +138 -0
- package/docs/validation.md +1792 -0
- package/docs/verification.md +152 -0
- package/docs/views.md +566 -0
- package/docs/vite.md +322 -0
- package/package.json +34 -32
- package/dist/chunk-I63ZFMX6.js.map +0 -1
- package/dist/chunk-MFTZQMZM.js.map +0 -1
- package/dist/chunk-POEP4B2M.js.map +0 -1
- package/dist/chunk-ZOEPKBVF.js.map +0 -1
- package/dist/index.d-BJyJx6U1.d.ts +0 -319
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Service container
|
|
2
|
+
|
|
3
|
+
The service container builds your app's services and hands them to the code that needs them, from the application or
|
|
4
|
+
from the current request's scope.
|
|
5
|
+
|
|
6
|
+
## Introduction
|
|
7
|
+
|
|
8
|
+
The container manages your classes' dependencies. You tell it once how to build a service, usually in a service
|
|
9
|
+
provider, and take the service wherever you need it. Here a controller needs a service that finds podcasts on Apple
|
|
10
|
+
Music:
|
|
11
|
+
|
|
12
|
+
```ts title="app/Http/Controllers/PodcastController.ts"
|
|
13
|
+
// @filename: app/Services/AppleMusic.ts
|
|
14
|
+
export declare class AppleMusic {
|
|
15
|
+
findPodcast(id: string): Promise<object>
|
|
16
|
+
}
|
|
17
|
+
// ---cut---
|
|
18
|
+
import { inject, view } from '@orieljs/framework'
|
|
19
|
+
import { AppleMusic } from '../../Services/AppleMusic'
|
|
20
|
+
import { Controller } from './Controller'
|
|
21
|
+
|
|
22
|
+
export class PodcastController extends Controller {
|
|
23
|
+
readonly #apple = inject(AppleMusic)
|
|
24
|
+
|
|
25
|
+
async show(id: string) {
|
|
26
|
+
return view('podcasts.show', { podcast: await this.#apple.findPodcast(id) })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The controller doesn't build its `AppleMusic`: it asks the container for one with `inject()`. In a test, you can give
|
|
32
|
+
the container a stand-in, and the controller uses it without a change.
|
|
33
|
+
|
|
34
|
+
> [!NOTE]
|
|
35
|
+
> Laravel reads the type hints of a class's constructor and passes it what they name: the container's autowiring.
|
|
36
|
+
> TypeScript types are gone when your app runs, so Oriel can't see what a constructor wants. It builds every class
|
|
37
|
+
> with no constructor arguments, and a class takes what it needs with `inject()`, in its fields. A constructor
|
|
38
|
+
> parameter receives `undefined`.
|
|
39
|
+
|
|
40
|
+
### Zero configuration resolution
|
|
41
|
+
|
|
42
|
+
A class whose constructor takes no arguments needs no binding: the container builds it with `new` when you ask for
|
|
43
|
+
it. A route in `routes/web.ts` can take a service you never registered:
|
|
44
|
+
|
|
45
|
+
```ts title="routes/web.ts"
|
|
46
|
+
import { inject, Route } from '@orieljs/framework'
|
|
47
|
+
|
|
48
|
+
class Service {
|
|
49
|
+
name = 'service'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Route.get('/', () => inject(Service).name)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
An unbound class is built afresh each time you ask for it. The controllers, the middleware and the event listeners
|
|
56
|
+
of your app are built by the container in the same way, so their fields can use `inject()`.
|
|
57
|
+
|
|
58
|
+
### When to use the container
|
|
59
|
+
|
|
60
|
+
With `inject()` and [facades](facades.md), you can write a whole app without binding anything yourself. You bind a
|
|
61
|
+
service when the container can't build it alone, or shouldn't build a new one each time:
|
|
62
|
+
|
|
63
|
+
- the code that uses it names an interface, which doesn't exist when your app runs: bind a token to an
|
|
64
|
+
implementation (see [Binding interfaces to implementations](#binding-interfaces-to-implementations));
|
|
65
|
+
- its constructor takes arguments, which a binding passes it;
|
|
66
|
+
- your app should share one instance, or one per request;
|
|
67
|
+
- you write a package that other apps install, and it binds its services for them.
|
|
68
|
+
|
|
69
|
+
## Binding
|
|
70
|
+
|
|
71
|
+
### Binding basics
|
|
72
|
+
|
|
73
|
+
#### Simple bindings
|
|
74
|
+
|
|
75
|
+
Your bindings go in a service provider's `register()`, where `this.app` is the application, which is the container.
|
|
76
|
+
`bind()` takes the class or token to bind and a factory that returns an instance. The factory receives the container,
|
|
77
|
+
which can build what the instance needs:
|
|
78
|
+
|
|
79
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
80
|
+
// @filename: app/Services/Transistor.ts
|
|
81
|
+
export declare class PodcastParser {}
|
|
82
|
+
export declare class Transistor {
|
|
83
|
+
constructor(parser: PodcastParser)
|
|
84
|
+
}
|
|
85
|
+
// ---cut---
|
|
86
|
+
import { ServiceProvider } from '@orieljs/framework'
|
|
87
|
+
import { PodcastParser, Transistor } from '../Services/Transistor'
|
|
88
|
+
|
|
89
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
90
|
+
override register(): void {
|
|
91
|
+
this.app.bind(Transistor, (app) => new Transistor(app.make(PodcastParser)))
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A `bind()` builds a new instance each time it is resolved, with the container that resolves it: inside a request,
|
|
97
|
+
the request's scope. The concrete can also be a class, which is built with `new`, and a class can be bound alone:
|
|
98
|
+
`this.app.bind(Transistor)`.
|
|
99
|
+
|
|
100
|
+
Outside a provider, `app()` returns the application: `app().bind(Transistor, …)`. Bindings belong to the
|
|
101
|
+
application: a request's scope refuses them (see [Request scopes](#request-scopes)).
|
|
102
|
+
|
|
103
|
+
#### Binding a singleton
|
|
104
|
+
|
|
105
|
+
`singleton()` binds a class or a token that is built once. Every later resolution, in the application and in every
|
|
106
|
+
request, returns the same instance:
|
|
107
|
+
|
|
108
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
109
|
+
// @filename: app/Services/Transistor.ts
|
|
110
|
+
export declare class PodcastParser {}
|
|
111
|
+
export declare class Transistor {
|
|
112
|
+
constructor(parser: PodcastParser)
|
|
113
|
+
}
|
|
114
|
+
// ---cut---
|
|
115
|
+
import { ServiceProvider } from '@orieljs/framework'
|
|
116
|
+
import { PodcastParser, Transistor } from '../Services/Transistor'
|
|
117
|
+
|
|
118
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
119
|
+
override register(): void {
|
|
120
|
+
this.app.singleton(Transistor, (app) => new Transistor(app.make(PodcastParser)))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A singleton is built by the application, even when a request resolves it first, so its factory receives the
|
|
126
|
+
application. It lasts as long as your app's process.
|
|
127
|
+
|
|
128
|
+
#### Binding scoped singletons
|
|
129
|
+
|
|
130
|
+
`scoped()` binds a class or a token that is built once per scope: once for each request, each job a queue worker
|
|
131
|
+
runs, and each command. Within the scope, every resolution returns the same instance; the next scope builds its own.
|
|
132
|
+
The framework binds the session and the cookie jar this way:
|
|
133
|
+
|
|
134
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
135
|
+
// @filename: app/Services/Transistor.ts
|
|
136
|
+
export declare class PodcastParser {}
|
|
137
|
+
export declare class Transistor {
|
|
138
|
+
constructor(parser: PodcastParser)
|
|
139
|
+
}
|
|
140
|
+
// ---cut---
|
|
141
|
+
import { ServiceProvider } from '@orieljs/framework'
|
|
142
|
+
import { PodcastParser, Transistor } from '../Services/Transistor'
|
|
143
|
+
|
|
144
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
145
|
+
override register(): void {
|
|
146
|
+
this.app.scoped(Transistor, (app) => new Transistor(app.make(PodcastParser)))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The factory receives the scope. A scoped service resolved outside any scope is an error, and so is one resolved for a
|
|
152
|
+
singleton: a singleton outlives every request, so it can't hold one request's service (see
|
|
153
|
+
[Resolution errors](#resolution-errors)).
|
|
154
|
+
|
|
155
|
+
#### Binding instances
|
|
156
|
+
|
|
157
|
+
`instance()` binds an object you have already made. Every resolution returns it, and `instance()` returns it too:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
declare class Transistor {
|
|
161
|
+
constructor(parser: PodcastParser)
|
|
162
|
+
}
|
|
163
|
+
declare class PodcastParser {}
|
|
164
|
+
// ---cut---
|
|
165
|
+
const service = app().instance(Transistor, new Transistor(new PodcastParser()))
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
On a request's scope, `instance()` binds the object for that request only. The framework binds each request this way.
|
|
169
|
+
|
|
170
|
+
### Binding interfaces to implementations
|
|
171
|
+
|
|
172
|
+
An interface is a type, and types are gone when your app runs, so the container can't take one as a key. Give the
|
|
173
|
+
interface a token instead: `token()` makes a key that carries the interface's type. Name the token after the
|
|
174
|
+
interface, as Oriel's contracts do:
|
|
175
|
+
|
|
176
|
+
```ts title="app/Contracts/EventPusher.ts"
|
|
177
|
+
import { token } from '@orieljs/framework/contracts'
|
|
178
|
+
|
|
179
|
+
export interface EventPusher {
|
|
180
|
+
push(event: string, payload: object): Promise<void>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const EventPusher = token<EventPusher>('App.EventPusher')
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Then bind the token to an implementation:
|
|
187
|
+
|
|
188
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
189
|
+
// @filename: app/Contracts/EventPusher.ts
|
|
190
|
+
export interface EventPusher {
|
|
191
|
+
push(event: string, payload: object): Promise<void>
|
|
192
|
+
}
|
|
193
|
+
export declare const EventPusher: import('@orieljs/framework/contracts').Token<EventPusher>
|
|
194
|
+
// @filename: app/Services/RedisEventPusher.ts
|
|
195
|
+
export declare class RedisEventPusher {
|
|
196
|
+
push(event: string, payload: object): Promise<void>
|
|
197
|
+
}
|
|
198
|
+
// ---cut---
|
|
199
|
+
import { ServiceProvider } from '@orieljs/framework'
|
|
200
|
+
import { EventPusher } from '../Contracts/EventPusher'
|
|
201
|
+
import { RedisEventPusher } from '../Services/RedisEventPusher'
|
|
202
|
+
|
|
203
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
204
|
+
override register(): void {
|
|
205
|
+
this.app.singleton(EventPusher, RedisEventPusher)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The code that needs an `EventPusher` takes it by the token, and TypeScript types it as the interface:
|
|
211
|
+
|
|
212
|
+
```ts title="app/Services/PodcastPublisher.ts"
|
|
213
|
+
// @filename: app/Contracts/EventPusher.ts
|
|
214
|
+
export interface EventPusher {
|
|
215
|
+
push(event: string, payload: object): Promise<void>
|
|
216
|
+
}
|
|
217
|
+
export declare const EventPusher: import('@orieljs/framework/contracts').Token<EventPusher>
|
|
218
|
+
// ---cut---
|
|
219
|
+
import { inject } from '@orieljs/framework'
|
|
220
|
+
import { EventPusher } from '../Contracts/EventPusher'
|
|
221
|
+
import type { Podcast } from '../Models/Podcast'
|
|
222
|
+
|
|
223
|
+
export class PodcastPublisher {
|
|
224
|
+
readonly #pusher = inject(EventPusher)
|
|
225
|
+
|
|
226
|
+
async publish(podcast: Podcast): Promise<void> {
|
|
227
|
+
await this.#pusher.push('podcast.published', { id: podcast.id })
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
A token needs a binding: the container can't build an interface. The token's description is its identity, so two
|
|
233
|
+
tokens made with the same description are the same key: give each of yours its own, such as `App.EventPusher`.
|
|
234
|
+
|
|
235
|
+
Your providers register after the framework's, so a binding of yours for one of the framework's tokens replaces the
|
|
236
|
+
framework's own.
|
|
237
|
+
|
|
238
|
+
> [!NOTE]
|
|
239
|
+
> Laravel binds an interface by its name, which PHP keeps when your code runs. A TypeScript interface leaves nothing
|
|
240
|
+
> behind, so Oriel binds a token in its place: an object that is a key when your app runs and carries the interface's
|
|
241
|
+
> type for TypeScript.
|
|
242
|
+
|
|
243
|
+
### Extending bindings
|
|
244
|
+
|
|
245
|
+
`extend()` changes a service each time it is built. The callback receives the instance and the container, and returns
|
|
246
|
+
the instance to use, the same one or another that wraps it:
|
|
247
|
+
|
|
248
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
249
|
+
// @filename: app/Services/Service.ts
|
|
250
|
+
export declare class Service {}
|
|
251
|
+
export declare class DecoratedService extends Service {
|
|
252
|
+
constructor(service: Service)
|
|
253
|
+
}
|
|
254
|
+
// ---cut---
|
|
255
|
+
import { ServiceProvider } from '@orieljs/framework'
|
|
256
|
+
import { DecoratedService, Service } from '../Services/Service'
|
|
257
|
+
|
|
258
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
259
|
+
override register(): void {
|
|
260
|
+
this.app.extend(Service, (service, app) => new DecoratedService(service))
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The extenders run in the order you add them. A singleton or an instance the application already holds is extended at
|
|
266
|
+
once. Like the bindings, extenders belong to the application, not to a request's scope.
|
|
267
|
+
|
|
268
|
+
> [!NOTE]
|
|
269
|
+
> Laravel's contextual binding (`when()->needs()->give()`), its binding of primitives and typed variadics, and its
|
|
270
|
+
> contextual attributes (`#[Config]`, `#[Auth]`…) work from the parameters of your constructors, which Oriel doesn't
|
|
271
|
+
> read, so none of them is in Oriel. Neither are the `#[Singleton]`, `#[Scoped]` and `#[Bind]` attributes of a class
|
|
272
|
+
> or an interface (bind in a service provider instead), tags, `bindIf()`, `makeWith()`, `call()`, `rebinding()` and
|
|
273
|
+
> PSR-11.
|
|
274
|
+
|
|
275
|
+
## Resolving
|
|
276
|
+
|
|
277
|
+
### The `make` method
|
|
278
|
+
|
|
279
|
+
`make()` resolves a class or a token. TypeScript types the result from what you pass:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
declare class Transistor {}
|
|
283
|
+
// ---cut---
|
|
284
|
+
const transistor = app().make(Transistor)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`bound()` tells you whether something is bound or held as an instance, and `resolved()` whether it has been resolved
|
|
288
|
+
already:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
declare class Transistor {}
|
|
292
|
+
// ---cut---
|
|
293
|
+
if (app().bound(Transistor)) {
|
|
294
|
+
// ...
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Outside a provider, the `app()` helper gives you the container. With an argument, it resolves it, as `inject()` does:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
declare class Transistor {}
|
|
302
|
+
// ---cut---
|
|
303
|
+
const transistor = app(Transistor)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Inside a request, `app().make()` resolves from the request's scope, so it reaches the request's own services. To have
|
|
307
|
+
the container itself, resolve `Container`: you get the current request's scope inside a request, and the application
|
|
308
|
+
outside one.
|
|
309
|
+
|
|
310
|
+
### Injecting with `inject()`
|
|
311
|
+
|
|
312
|
+
`inject()` resolves a class or a token from the current container. Call it in a field of a class the container
|
|
313
|
+
builds, as the `PodcastController` above does, or anywhere your code runs while the app is running, such as a route's
|
|
314
|
+
closure:
|
|
315
|
+
|
|
316
|
+
```ts title="routes/web.ts"
|
|
317
|
+
// @filename: app/Services/PodcastPublisher.ts
|
|
318
|
+
export declare class PodcastPublisher {
|
|
319
|
+
publish(podcast: import('../Models/Podcast').Podcast): Promise<void>
|
|
320
|
+
}
|
|
321
|
+
// ---cut---
|
|
322
|
+
import { inject, redirect, Route } from '@orieljs/framework'
|
|
323
|
+
import type { Podcast } from '../app/Models/Podcast'
|
|
324
|
+
import { PodcastPublisher } from '../app/Services/PodcastPublisher'
|
|
325
|
+
|
|
326
|
+
Route.post('/podcasts/{podcast}/publish', async (podcast: Podcast) => {
|
|
327
|
+
await inject(PodcastPublisher).publish(podcast)
|
|
328
|
+
|
|
329
|
+
return redirect().back()
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The current container is, in this order:
|
|
334
|
+
|
|
335
|
+
1. while the container builds an object, the container building it, so a field's `inject()` resolves from it;
|
|
336
|
+
2. inside a request, a queued job or a command, its scope;
|
|
337
|
+
3. otherwise, the application, the newest one your process created.
|
|
338
|
+
|
|
339
|
+
A controller and a middleware are built by the request's scope, so their fields reach the request's services. The
|
|
340
|
+
application builds a singleton and an event listener itself, so their fields can't: there, `inject()` a request's
|
|
341
|
+
service inside the method that needs it, which runs in the request.
|
|
342
|
+
|
|
343
|
+
`inject()` works only once an application exists. At a module's top level, it runs when the module is first imported,
|
|
344
|
+
which can be before your app is created: see [Facades](facades.md#facades-at-a-modules-top-level).
|
|
345
|
+
|
|
346
|
+
## Request scopes
|
|
347
|
+
|
|
348
|
+
A scope is a container for one unit of work. It shares the application's bindings and singletons, and keeps its own
|
|
349
|
+
scoped services and instances. Oriel opens one for:
|
|
350
|
+
|
|
351
|
+
- each HTTP request, including the requests your tests make;
|
|
352
|
+
- each job a queue worker runs;
|
|
353
|
+
- each command `node oriel` runs.
|
|
354
|
+
|
|
355
|
+
A scope ends with its request, once the response is sent and the request terminated, or with its job or its command.
|
|
356
|
+
Its scoped services and instances end with it, and the next request builds its own. Every `inject()`, helper and
|
|
357
|
+
facade called while it runs resolves from it.
|
|
358
|
+
|
|
359
|
+
These live in a request's scope:
|
|
360
|
+
|
|
361
|
+
| Service | How |
|
|
362
|
+
| --- | --- |
|
|
363
|
+
| The request, from `request()` | An instance the HTTP kernel binds on the scope |
|
|
364
|
+
| The current route | An instance the router binds on the scope |
|
|
365
|
+
| The session, from `session()` and the `Session` facade | Scoped |
|
|
366
|
+
| The cookie jar, from `cookie()` and the `Cookie` facade | Scoped |
|
|
367
|
+
| The Inertia response factory, from `inertia()` and the `Inertia` facade | Scoped |
|
|
368
|
+
| `vite()`, with its CSP nonce | Scoped |
|
|
369
|
+
|
|
370
|
+
A scope refuses bindings, extenders and `resolving()` callbacks, which belong to the application:
|
|
371
|
+
`instance()` is the way to put an object in it. To run code in a scope of your own, as a script might, create one and
|
|
372
|
+
run your code inside it:
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
declare const application: Application
|
|
376
|
+
// ---cut---
|
|
377
|
+
const scope = application.createScope()
|
|
378
|
+
|
|
379
|
+
await scope.run(async () => {
|
|
380
|
+
// inject(), the helpers and the facades resolve from the scope here...
|
|
381
|
+
})
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
> [!NOTE]
|
|
385
|
+
> Laravel's `scoped()` instances are flushed when a new request or job starts, as under Octane or a queue worker. Oriel
|
|
386
|
+
> gives each request, job and command a scope of its own, and they can run at the same time: two requests never see
|
|
387
|
+
> each other's scoped services.
|
|
388
|
+
|
|
389
|
+
## Container events
|
|
390
|
+
|
|
391
|
+
`resolving()` registers a callback that runs each time the container builds a class or a token, after the extenders.
|
|
392
|
+
It receives the instance and the container, and can set the instance up before it is handed out:
|
|
393
|
+
|
|
394
|
+
```ts title="app/Providers/AppServiceProvider.ts"
|
|
395
|
+
// @filename: app/Services/Transistor.ts
|
|
396
|
+
export declare class Transistor {
|
|
397
|
+
timeout: number
|
|
398
|
+
}
|
|
399
|
+
// ---cut---
|
|
400
|
+
import { ServiceProvider } from '@orieljs/framework'
|
|
401
|
+
import { Transistor } from '../Services/Transistor'
|
|
402
|
+
|
|
403
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
404
|
+
override register(): void {
|
|
405
|
+
this.app.resolving(Transistor, (transistor, app) => {
|
|
406
|
+
transistor.timeout = 30
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
A singleton is built once, so its callback runs once. An object you bind with `instance()` isn't built, and runs no
|
|
413
|
+
callback. Laravel's `resolving()` without a class, which runs for everything, isn't in Oriel.
|
|
414
|
+
|
|
415
|
+
## Resolution errors
|
|
416
|
+
|
|
417
|
+
When the container can't give you what you ask for, it throws. The messages name what was asked for, by its class
|
|
418
|
+
name or its token's description:
|
|
419
|
+
|
|
420
|
+
| Error | When | Message |
|
|
421
|
+
| --- | --- | --- |
|
|
422
|
+
| `BindingResolutionError` | A token has no binding | Target [App.EventPusher] is not bound. Is its service provider registered in bootstrap/providers.ts? |
|
|
423
|
+
| `BindingResolutionError` | A scoped service is resolved outside a scope, or for a singleton | Scoped binding [Session.Store] was resolved outside a scope. Resolve it inside app.createScope().run(...), and don't inject scoped services into singletons. |
|
|
424
|
+
| `ContainerNotAvailableError` | `inject()`, a helper or a facade runs before any application exists | Cannot resolve [App.EventPusher]: no container is available. inject(), helpers and facades only work while the application is running (inside a request, job, command, provider or app.run()). Don't call them at module top level. |
|
|
425
|
+
| `CircularDependencyError` | Two services need each other to be built | Circular dependency detected: App.A -> App.B -> App.A |
|
|
426
|
+
| `Error` | A binding is registered on a request's scope | Cannot register bindings on a request scope (bind). Register them on the application in a service provider; use instance() for per-request values. |
|
|
427
|
+
| `TypeError` | `bind()` gets a token with no concrete | bind(App.EventPusher) needs a concrete: tokens can't be constructed |
|
|
428
|
+
|
|
429
|
+
`BindingResolutionError`, `CircularDependencyError` and `ContainerNotAvailableError` are exported from
|
|
430
|
+
`@orieljs/framework`, so you can catch them by class.
|