gemi 0.56.0 → 0.58.0
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/dist/app/index.js +1 -1
- package/dist/broadcasting/index.js +1 -1
- package/dist/bun/plugin.js +1 -1
- package/dist/bun/preload.js +1 -1
- package/dist/{chunk-jdj7k3r9.js → chunk-0a2xgcj3.js} +2 -2
- package/dist/{chunk-jdj7k3r9.js.map → chunk-0a2xgcj3.js.map} +1 -1
- package/dist/{chunk-3e88tyee.js → chunk-3337e5g0.js} +2 -2
- package/dist/{chunk-3e88tyee.js.map → chunk-3337e5g0.js.map} +1 -1
- package/dist/chunk-3aa287k7.js +6 -0
- package/dist/{chunk-cv9w5cmb.js.map → chunk-3aa287k7.js.map} +2 -2
- package/dist/{chunk-01am9k5v.js → chunk-3g5bjvdf.js} +2 -2
- package/dist/{chunk-01am9k5v.js.map → chunk-3g5bjvdf.js.map} +1 -1
- package/dist/{chunk-w9k9s4wh.js → chunk-3xadx444.js} +2 -2
- package/dist/{chunk-w9k9s4wh.js.map → chunk-3xadx444.js.map} +1 -1
- package/dist/{chunk-npg72mez.js → chunk-3zxwscmf.js} +2 -2
- package/dist/{chunk-kgmr9qxx.js.map → chunk-3zxwscmf.js.map} +1 -1
- package/dist/{chunk-64s1pzz1.js → chunk-437085pe.js} +2 -2
- package/dist/{chunk-64s1pzz1.js.map → chunk-437085pe.js.map} +1 -1
- package/dist/{chunk-eqrd31ye.js → chunk-4yt5x8s2.js} +2 -2
- package/dist/{chunk-eqrd31ye.js.map → chunk-4yt5x8s2.js.map} +1 -1
- package/dist/{chunk-9r0sb4zn.js → chunk-5athahgr.js} +2 -2
- package/dist/{chunk-9r0sb4zn.js.map → chunk-5athahgr.js.map} +1 -1
- package/dist/{chunk-g30q4n5y.js → chunk-5n2rvfh3.js} +2 -2
- package/dist/{chunk-g30q4n5y.js.map → chunk-5n2rvfh3.js.map} +1 -1
- package/dist/{chunk-1zfsgffv.js → chunk-6235kb30.js} +3 -3
- package/dist/{chunk-1zfsgffv.js.map → chunk-6235kb30.js.map} +1 -1
- package/dist/{chunk-javjeayw.js → chunk-7b0x860b.js} +2 -2
- package/dist/{chunk-javjeayw.js.map → chunk-7b0x860b.js.map} +1 -1
- package/dist/{chunk-4qwwy968.js → chunk-7ef5n8k2.js} +2 -2
- package/dist/{chunk-4qwwy968.js.map → chunk-7ef5n8k2.js.map} +1 -1
- package/dist/{chunk-q0waxxz5.js → chunk-7j6wbv12.js} +2 -2
- package/dist/{chunk-q0waxxz5.js.map → chunk-7j6wbv12.js.map} +1 -1
- package/dist/chunk-86jebsm4.js +9 -0
- package/dist/{chunk-kry5vwam.js.map → chunk-86jebsm4.js.map} +3 -3
- package/dist/chunk-87qab82w.js +5 -0
- package/dist/chunk-87qab82w.js.map +37 -0
- package/dist/{chunk-36pg61vt.js → chunk-8gew8b9a.js} +2 -2
- package/dist/{chunk-36pg61vt.js.map → chunk-8gew8b9a.js.map} +1 -1
- package/dist/{chunk-v6v6sem5.js → chunk-9m2tbf3n.js} +2 -2
- package/dist/{chunk-v6v6sem5.js.map → chunk-9m2tbf3n.js.map} +1 -1
- package/dist/{chunk-gzdf2025.js → chunk-a2sgjpvq.js} +2 -2
- package/dist/{chunk-gzdf2025.js.map → chunk-a2sgjpvq.js.map} +1 -1
- package/dist/chunk-b35e128b.js +5 -0
- package/dist/{chunk-b50zmz3t.js.map → chunk-b35e128b.js.map} +1 -1
- package/dist/{chunk-ct274qts.js → chunk-cyaz97p5.js} +2 -2
- package/dist/{chunk-ct274qts.js.map → chunk-cyaz97p5.js.map} +1 -1
- package/dist/{chunk-v06qcyj5.js → chunk-d125j8t0.js} +3 -3
- package/dist/{chunk-v06qcyj5.js.map → chunk-d125j8t0.js.map} +1 -1
- package/dist/{chunk-hs5v3eqj.js → chunk-dgsgjg53.js} +2 -2
- package/dist/{chunk-hs5v3eqj.js.map → chunk-dgsgjg53.js.map} +1 -1
- package/dist/{chunk-xjy5apyr.js → chunk-eejmhtnc.js} +2 -2
- package/dist/{chunk-xjy5apyr.js.map → chunk-eejmhtnc.js.map} +1 -1
- package/dist/{chunk-3y75q5a2.js → chunk-fjm4y8bn.js} +2 -2
- package/dist/{chunk-3y75q5a2.js.map → chunk-fjm4y8bn.js.map} +1 -1
- package/dist/{chunk-9c89q2mz.js → chunk-grdahng8.js} +2 -2
- package/dist/{chunk-9c89q2mz.js.map → chunk-grdahng8.js.map} +1 -1
- package/dist/{chunk-699z6d8y.js → chunk-gw6agevz.js} +3 -3
- package/dist/{chunk-699z6d8y.js.map → chunk-gw6agevz.js.map} +1 -1
- package/dist/{chunk-62ke19q4.js → chunk-hppagzz4.js} +4 -4
- package/dist/{chunk-62ke19q4.js.map → chunk-hppagzz4.js.map} +1 -1
- package/dist/chunk-hwa5sqw5.js +19 -0
- package/dist/{chunk-tmnhkphv.js.map → chunk-hwa5sqw5.js.map} +12 -6
- package/dist/chunk-hxf1re93.js +4 -0
- package/dist/{chunk-vkngcrzq.js.map → chunk-hxf1re93.js.map} +6 -5
- package/dist/{chunk-d36dfqxw.js → chunk-j0c6ytkj.js} +3 -3
- package/dist/{chunk-d36dfqxw.js.map → chunk-j0c6ytkj.js.map} +1 -1
- package/dist/{chunk-kgmr9qxx.js → chunk-jhkjz9jr.js} +2 -2
- package/dist/{chunk-npg72mez.js.map → chunk-jhkjz9jr.js.map} +1 -1
- package/dist/{chunk-62723jyy.js → chunk-k0fvsyeh.js} +1 -1
- package/dist/{chunk-31kcf7dq.js → chunk-keehyx51.js} +2 -2
- package/dist/{chunk-31kcf7dq.js.map → chunk-keehyx51.js.map} +1 -1
- package/dist/{chunk-enhkf60v.js → chunk-m3xy5xyf.js} +2 -2
- package/dist/{chunk-enhkf60v.js.map → chunk-m3xy5xyf.js.map} +1 -1
- package/dist/{chunk-c75mymmq.js → chunk-mkfpnymy.js} +1 -1
- package/dist/{chunk-rgb69nh1.js → chunk-mwpdp09e.js} +2 -2
- package/dist/{chunk-rgb69nh1.js.map → chunk-mwpdp09e.js.map} +1 -1
- package/dist/chunk-pmhd6zfc.js +37 -0
- package/dist/chunk-pmhd6zfc.js.map +20 -0
- package/dist/chunk-qb5mv6pj.js +5 -0
- package/dist/chunk-qb5mv6pj.js.map +12 -0
- package/dist/{chunk-1pwwrpa3.js → chunk-qgxr0g36.js} +2 -2
- package/dist/{chunk-1pwwrpa3.js.map → chunk-qgxr0g36.js.map} +1 -1
- package/dist/{chunk-4yafsffx.js → chunk-sy7jbdeb.js} +2 -2
- package/dist/{chunk-4yafsffx.js.map → chunk-sy7jbdeb.js.map} +1 -1
- package/dist/{chunk-gasdfwva.js → chunk-szss069z.js} +2 -2
- package/dist/{chunk-gasdfwva.js.map → chunk-szss069z.js.map} +1 -1
- package/dist/{chunk-m0ggfy1m.js → chunk-tss5svjr.js} +2 -2
- package/dist/{chunk-m0ggfy1m.js.map → chunk-tss5svjr.js.map} +1 -1
- package/dist/{chunk-dzzmqv0j.js → chunk-vr90r27j.js} +2 -2
- package/dist/{chunk-dzzmqv0j.js.map → chunk-vr90r27j.js.map} +1 -1
- package/dist/{chunk-pvdbrt4z.js → chunk-w62m5f0n.js} +3 -3
- package/dist/{chunk-pvdbrt4z.js.map → chunk-w62m5f0n.js.map} +1 -1
- package/dist/{chunk-cn2r5jfj.js → chunk-w7rf99w6.js} +2 -2
- package/dist/{chunk-cn2r5jfj.js.map → chunk-w7rf99w6.js.map} +1 -1
- package/dist/{chunk-tja0c815.js → chunk-wbrj0gya.js} +2 -2
- package/dist/{chunk-tja0c815.js.map → chunk-wbrj0gya.js.map} +1 -1
- package/dist/{chunk-rsdg619q.js → chunk-xdv1b8mr.js} +2 -2
- package/dist/{chunk-rsdg619q.js.map → chunk-xdv1b8mr.js.map} +1 -1
- package/dist/{chunk-h3mwgbg7.js → chunk-xey9cbap.js} +2 -2
- package/dist/{chunk-h3mwgbg7.js.map → chunk-xey9cbap.js.map} +1 -1
- package/dist/{chunk-wgpa04jb.js → chunk-xzk827r3.js} +2 -2
- package/dist/{chunk-wgpa04jb.js.map → chunk-xzk827r3.js.map} +1 -1
- package/dist/{chunk-x14sk95v.js → chunk-y6a8r2bn.js} +3 -3
- package/dist/{chunk-x14sk95v.js.map → chunk-y6a8r2bn.js.map} +1 -1
- package/dist/{chunk-33wjsw4r.js → chunk-yed5whgs.js} +3 -3
- package/dist/{chunk-33wjsw4r.js.map → chunk-yed5whgs.js.map} +1 -1
- package/dist/{chunk-02gdzs5t.js → chunk-yf7vz71n.js} +1 -1
- package/dist/{chunk-y9fp58bg.js → chunk-yjzs247s.js} +2 -2
- package/dist/{chunk-y9fp58bg.js.map → chunk-yjzs247s.js.map} +1 -1
- package/dist/{chunk-kgg1eqne.js → chunk-yy0eb9wn.js} +2 -2
- package/dist/{chunk-kgg1eqne.js.map → chunk-yy0eb9wn.js.map} +1 -1
- package/dist/{chunk-4xx78ba9.js → chunk-zbxgbr12.js} +2 -2
- package/dist/{chunk-4xx78ba9.js.map → chunk-zbxgbr12.js.map} +1 -1
- package/dist/{chunk-8k1zqrvh.js → chunk-zh2egcyb.js} +2 -2
- package/dist/{chunk-8k1zqrvh.js.map → chunk-zh2egcyb.js.map} +1 -1
- package/dist/chunks/ThemeProvider-li1J_igh.js.map +1 -1
- package/dist/client/ClientRouter.d.ts.map +1 -1
- package/dist/client/ProgressManager.d.ts +1 -1
- package/dist/client/RouteStateContext.d.ts +9 -0
- package/dist/client/RouteStateContext.d.ts.map +1 -1
- package/dist/client/ServerDataProvider.d.ts +7 -0
- package/dist/client/ServerDataProvider.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +68 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/rpc.d.ts +42 -0
- package/dist/client/rpc.d.ts.map +1 -1
- package/dist/client/useFeature.d.ts +40 -0
- package/dist/client/useFeature.d.ts.map +1 -0
- package/dist/config/index.js +1 -1
- package/dist/console/run.js +2 -2
- package/dist/console/run.js.map +1 -1
- package/dist/container/index.js +2 -2
- package/dist/container/index.js.map +1 -1
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/index.js +2 -2
- package/dist/email/index.js.map +1 -1
- package/dist/facades/Features.d.ts +57 -0
- package/dist/facades/Features.d.ts.map +1 -0
- package/dist/facades/index.d.ts +1 -0
- package/dist/facades/index.d.ts.map +1 -1
- package/dist/facades/index.js +2 -2
- package/dist/facades/index.js.map +1 -1
- package/dist/foundation/index.js +2 -2
- package/dist/foundation/index.js.map +1 -1
- package/dist/gemi.d.ts +13 -5
- package/dist/http/ApiRouter.d.ts +3 -2
- package/dist/http/ApiRouter.d.ts.map +1 -1
- package/dist/http/HttpRequest.d.ts +4 -0
- package/dist/http/HttpRequest.d.ts.map +1 -1
- package/dist/http/ViewRouter.d.ts +127 -4
- package/dist/http/ViewRouter.d.ts.map +1 -1
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +2 -2
- package/dist/http/index.js.map +1 -1
- package/dist/http/middlewareList.d.ts +25 -0
- package/dist/http/middlewareList.d.ts.map +1 -0
- package/dist/http/requestContext.d.ts +57 -0
- package/dist/http/requestContext.d.ts.map +1 -1
- package/dist/i18n/dictionaryRuntime.js +2 -2
- package/dist/i18n/dictionaryRuntime.js.map +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/index.js.map +1 -1
- package/dist/ide/typescript-plugin/index.js +1206 -0
- package/dist/ide/typescript-plugin/index.js.map +17 -0
- package/dist/kernel/index.js +3 -3
- package/dist/kernel/index.js.map +3 -3
- package/dist/kernel/providers.d.ts +16 -4
- package/dist/kernel/providers.d.ts.map +1 -1
- package/dist/orm/context.d.ts +65 -0
- package/dist/orm/context.d.ts.map +1 -1
- package/dist/orm/index.js +2 -2
- package/dist/orm/index.js.map +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/services/discovery.d.ts +27 -0
- package/dist/services/discovery.d.ts.map +1 -1
- package/dist/services/events/Event.d.ts +208 -0
- package/dist/services/events/Event.d.ts.map +1 -0
- package/dist/services/events/EventManager.d.ts +285 -0
- package/dist/services/events/EventManager.d.ts.map +1 -0
- package/dist/services/events/EventServiceProvider.d.ts +46 -0
- package/dist/services/events/EventServiceProvider.d.ts.map +1 -0
- package/dist/services/events/FakeEventManager.d.ts +155 -0
- package/dist/services/events/FakeEventManager.d.ts.map +1 -0
- package/dist/services/events/FakeEventManager.test-d.d.ts +2 -0
- package/dist/services/events/FakeEventManager.test-d.d.ts.map +1 -0
- package/dist/services/events/Listener.d.ts +189 -0
- package/dist/services/events/Listener.d.ts.map +1 -0
- package/dist/services/events/Listener.test-d.d.ts +2 -0
- package/dist/services/events/Listener.test-d.d.ts.map +1 -0
- package/dist/services/events/config.d.ts +55 -0
- package/dist/services/events/config.d.ts.map +1 -0
- package/dist/services/events/listenerJob.d.ts +43 -0
- package/dist/services/events/listenerJob.d.ts.map +1 -0
- package/dist/services/features/FeatureFlagStore.d.ts +60 -0
- package/dist/services/features/FeatureFlagStore.d.ts.map +1 -0
- package/dist/services/features/FeatureManager.d.ts +82 -0
- package/dist/services/features/FeatureManager.d.ts.map +1 -0
- package/dist/services/features/FeaturesServiceProvider.d.ts +6 -0
- package/dist/services/features/FeaturesServiceProvider.d.ts.map +1 -0
- package/dist/services/features/bucket.d.ts +57 -0
- package/dist/services/features/bucket.d.ts.map +1 -0
- package/dist/services/features/config.d.ts +57 -0
- package/dist/services/features/config.d.ts.map +1 -0
- package/dist/services/features/context.d.ts +31 -0
- package/dist/services/features/context.d.ts.map +1 -0
- package/dist/services/features/defineFeature.d.ts +145 -0
- package/dist/services/features/defineFeature.d.ts.map +1 -0
- package/dist/services/features/evaluate.d.ts +74 -0
- package/dist/services/features/evaluate.d.ts.map +1 -0
- package/dist/services/features/sources/DatabaseFeatureFlagSource.d.ts +19 -0
- package/dist/services/features/sources/DatabaseFeatureFlagSource.d.ts.map +1 -0
- package/dist/services/features/sources/FeatureFlagSource.d.ts +30 -0
- package/dist/services/features/sources/FeatureFlagSource.d.ts.map +1 -0
- package/dist/services/features/sources/StaticFeatureFlagSource.d.ts +24 -0
- package/dist/services/features/sources/StaticFeatureFlagSource.d.ts.map +1 -0
- package/dist/services/features/types.d.ts +63 -0
- package/dist/services/features/types.d.ts.map +1 -0
- package/dist/services/index.d.ts +18 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +8 -8
- package/dist/services/index.js.map +8 -4
- package/dist/services/queue/QueueManager.d.ts +31 -0
- package/dist/services/queue/QueueManager.d.ts.map +1 -1
- package/dist/services/router/ViewRouteDispatcher.d.ts +8 -0
- package/dist/services/router/ViewRouteDispatcher.d.ts.map +1 -1
- package/dist/services/router/createFlatViewRoutes.d.ts +14 -0
- package/dist/services/router/createFlatViewRoutes.d.ts.map +1 -1
- package/dist/services/router/streamQueryInjection.d.ts.map +1 -1
- package/dist/support/index.js +2 -2
- package/dist/support/index.js.map +1 -1
- package/dist/testing/Page.d.ts +12 -0
- package/dist/testing/Page.d.ts.map +1 -1
- package/dist/testing/index.d.ts +24 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +7 -3
- package/dist/testing/index.js.map +1 -1
- package/ide/typescript-plugin/package.json +5 -0
- package/package.json +4 -2
- package/dist/chunk-4t80js0n.js +0 -33
- package/dist/chunk-4t80js0n.js.map +0 -18
- package/dist/chunk-98a576s9.js +0 -5
- package/dist/chunk-98a576s9.js.map +0 -30
- package/dist/chunk-b50zmz3t.js +0 -5
- package/dist/chunk-cv9w5cmb.js +0 -6
- package/dist/chunk-f9mfw82d.js +0 -5
- package/dist/chunk-f9mfw82d.js.map +0 -12
- package/dist/chunk-kry5vwam.js +0 -9
- package/dist/chunk-tmnhkphv.js +0 -19
- package/dist/chunk-vkngcrzq.js +0 -4
- /package/dist/{chunk-62723jyy.js.map → chunk-k0fvsyeh.js.map} +0 -0
- /package/dist/{chunk-c75mymmq.js.map → chunk-mkfpnymy.js.map} +0 -0
- /package/dist/{chunk-02gdzs5t.js.map → chunk-yf7vz71n.js.map} +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { Job } from "../queue/Job";
|
|
2
|
+
import { type EventConfig } from "./config";
|
|
3
|
+
import type { Event } from "./Event";
|
|
4
|
+
import type { ListenerClass } from "./Listener";
|
|
5
|
+
/**
|
|
6
|
+
* The registry a dispatch fans out through: event name -> the listeners bound
|
|
7
|
+
* to it.
|
|
8
|
+
*
|
|
9
|
+
* Shaped after `QueueManager` deliberately — constructed in `register()` from
|
|
10
|
+
* whatever the config slice declared, handed the discovered set in `boot()`,
|
|
11
|
+
* and reporting through a `registered*` getter what it was *given* rather than
|
|
12
|
+
* what it accepted, so a test can see a collision instead of having it tidied
|
|
13
|
+
* away.
|
|
14
|
+
*
|
|
15
|
+
* Three things are not `QueueManager`'s, and each is a decision rather than a
|
|
16
|
+
* detail:
|
|
17
|
+
*
|
|
18
|
+
* **Many listeners per key.** Two listeners for one event is the normal case
|
|
19
|
+
* here, where two jobs for one name is the pathological one. So the map holds
|
|
20
|
+
* arrays and `useListeners` appends. The collision it does refuse is two
|
|
21
|
+
* *listener classes* claiming one `static name`.
|
|
22
|
+
*
|
|
23
|
+
* **Registration order is the walk's order, and means nothing.** It is stable —
|
|
24
|
+
* `discoverClasses` sorts per directory and the module namespace sorts by name
|
|
25
|
+
* — so it is reproducible, but no listener may depend on running before
|
|
26
|
+
* another, because nothing about a filesystem layout was chosen to express
|
|
27
|
+
* that. What makes it safe to say is the rule below: nothing a listener does
|
|
28
|
+
* can stop the next one.
|
|
29
|
+
*
|
|
30
|
+
* **A dispatch nobody handles warns.** That is the entire early-warning system
|
|
31
|
+
* for the subsystem, and it is why it is not optional; see `dispatchAndWait`.
|
|
32
|
+
*
|
|
33
|
+
* A `queued` listener is not run here at all. It is registered with the
|
|
34
|
+
* `QueueManager` as a synthetic job and pushed to it on dispatch, so retries,
|
|
35
|
+
* `maxAttempts`, dead-lettering and worker threads are the queue's rather than
|
|
36
|
+
* a second implementation of the queue's — and nothing the request had (the
|
|
37
|
+
* current actor, the ambient transaction) is still promised by the time it
|
|
38
|
+
* runs. See `Listener.queued`.
|
|
39
|
+
*
|
|
40
|
+
* **A dispatch does not always fan out at the dispatch.** An event declaring
|
|
41
|
+
* `static afterCommit` has the whole of it — queued listeners included — held
|
|
42
|
+
* on the ORM's transaction scope and drained when that transaction commits, or
|
|
43
|
+
* dropped when it rolls back. That is the only thing in this file that reads
|
|
44
|
+
* ambient state, and `deferToCommit` is where it happens.
|
|
45
|
+
*/
|
|
46
|
+
export declare class EventManager {
|
|
47
|
+
static token: string;
|
|
48
|
+
/** Event name -> every listener bound to it, in registration order. */
|
|
49
|
+
private listeners;
|
|
50
|
+
/**
|
|
51
|
+
* Event name -> the class to rebuild it from, for a listener coming back off
|
|
52
|
+
* the queue.
|
|
53
|
+
*
|
|
54
|
+
* Read from each listener's `static event` at registration, which is the one
|
|
55
|
+
* place in the subsystem an event class is dereferenced. An event nobody
|
|
56
|
+
* listens for is never in here, and that is exactly right: nothing can have
|
|
57
|
+
* been queued for it.
|
|
58
|
+
*/
|
|
59
|
+
private events;
|
|
60
|
+
/**
|
|
61
|
+
* Event names already warned about. Per-manager, and the manager is per
|
|
62
|
+
* application, so a test that builds a fresh kernel gets a fresh set.
|
|
63
|
+
*/
|
|
64
|
+
private warnedFor;
|
|
65
|
+
/**
|
|
66
|
+
* Event names already warned about for the `dispatchAndWait` + `afterCommit`
|
|
67
|
+
* pairing. A second set rather than a second entry in the one above: the two
|
|
68
|
+
* warnings are about different mistakes, and sharing the set would mean a
|
|
69
|
+
* dispatch that legitimately has no listeners silences the one that says a
|
|
70
|
+
* caller is awaiting nothing.
|
|
71
|
+
*/
|
|
72
|
+
private warnedAboutWaiting;
|
|
73
|
+
readonly config: Required<EventConfig>;
|
|
74
|
+
constructor(config?: EventConfig);
|
|
75
|
+
/**
|
|
76
|
+
* Replaces the registered set, for the provider to hand over what it found
|
|
77
|
+
* under `app/listeners`.
|
|
78
|
+
*
|
|
79
|
+
* Two phases, for the reason `QueueManager.useJobs` documents: the manager is
|
|
80
|
+
* constructed in `register()`, which is synchronous and must resolve nothing,
|
|
81
|
+
* while reading a directory and importing what is in it is neither. So the
|
|
82
|
+
* manager is built from whatever the config slice declared — nothing, when
|
|
83
|
+
* the app left `listeners` out — and the discovered set arrives in `boot()`.
|
|
84
|
+
*
|
|
85
|
+
* The consequence worth knowing: anything that constructs an application and
|
|
86
|
+
* skips phase two dispatches into an empty registry, and an empty registry is
|
|
87
|
+
* a legal steady state rather than an error. The development-only warning
|
|
88
|
+
* below is the only thing that says so.
|
|
89
|
+
*
|
|
90
|
+
* Every listener is **constructed once here**, to read the fields that decide
|
|
91
|
+
* where it runs — `queued`, and through it `maxAttempts` and `worker`. A
|
|
92
|
+
* listener's constructor therefore runs at boot as well as on every dispatch,
|
|
93
|
+
* so it wants to assign fields and nothing else. A queued one also has its
|
|
94
|
+
* synthetic job built here; `EventServiceProvider` hands those to the queue
|
|
95
|
+
* once boot reaches it, so calling this again *after* boot would leave the
|
|
96
|
+
* queue holding jobs for listeners this registry no longer has.
|
|
97
|
+
*/
|
|
98
|
+
useListeners(listeners: ListenerClass[]): void;
|
|
99
|
+
/**
|
|
100
|
+
* The synthetic jobs the queue has to be told about — one per queued
|
|
101
|
+
* listener, named `listener:<name>`.
|
|
102
|
+
*
|
|
103
|
+
* `EventServiceProvider.boot()` is the only caller. It reads this rather than
|
|
104
|
+
* having the manager reach for the `QueueManager` itself, because
|
|
105
|
+
* registration happens in `register()`, where resolving another service is
|
|
106
|
+
* exactly what a provider must not do. A copy, so the caller cannot edit what
|
|
107
|
+
* a dispatch pushes.
|
|
108
|
+
*/
|
|
109
|
+
get queuedListenerJobs(): ReadonlyArray<new () => Job>;
|
|
110
|
+
/**
|
|
111
|
+
* What the manager was handed, discovered or declared.
|
|
112
|
+
*
|
|
113
|
+
* "Is this listener registered?" is a question with a silent wrong answer —
|
|
114
|
+
* an unregistered listener is a side effect that does not happen, and nothing
|
|
115
|
+
* is waiting on it to notice. This is where a test asks it out loud.
|
|
116
|
+
*
|
|
117
|
+
* It reports what came in, not what the registry accepted, so a name claimed
|
|
118
|
+
* twice appears twice here — deliberately, the same way `QueueManager` and
|
|
119
|
+
* `Scheduler` do. A copy, so a walk over it cannot edit what dispatch reads.
|
|
120
|
+
*/
|
|
121
|
+
get registeredListeners(): ReadonlyArray<ListenerClass>;
|
|
122
|
+
/**
|
|
123
|
+
* Fires the event and returns. The sync listeners run to completion after the
|
|
124
|
+
* caller has moved on.
|
|
125
|
+
*
|
|
126
|
+
* Nothing is awaited and nothing can reject: `runListeners` catches every
|
|
127
|
+
* listener's failure itself, so the floating promise here has no rejection to
|
|
128
|
+
* float.
|
|
129
|
+
*
|
|
130
|
+
* `args` is the event's constructor arguments; see `dispatchAndWait`.
|
|
131
|
+
*
|
|
132
|
+
* On an `afterCommit` event inside a transaction, "after the caller has moved
|
|
133
|
+
* on" becomes "after that transaction commits" — see `deferToCommit`.
|
|
134
|
+
*/
|
|
135
|
+
dispatch(event: Event, args?: readonly unknown[]): void;
|
|
136
|
+
/**
|
|
137
|
+
* Fires the event and resolves once every **sync** listener has settled.
|
|
138
|
+
*
|
|
139
|
+
* On an `afterCommit` event inside an open transaction it resolves
|
|
140
|
+
* **immediately, having run nothing** — there is no listener to wait for yet,
|
|
141
|
+
* and the transaction it would be waiting on is the caller's own. That is the
|
|
142
|
+
* sharp edge `Event.afterCommit` documents and the reason the flag ships
|
|
143
|
+
* opt-in; it warns once per event name in development, because nothing else
|
|
144
|
+
* about the call site can show it.
|
|
145
|
+
*/
|
|
146
|
+
dispatchAndWait(event: Event, args?: readonly unknown[]): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Queues the fan-out on the open transaction when the event asked for that,
|
|
149
|
+
* and reports whether it did — `false` means the caller runs it now.
|
|
150
|
+
*
|
|
151
|
+
* Three cases, and only the third defers: an event without
|
|
152
|
+
* `static afterCommit` behaves exactly as it did before this existed; one
|
|
153
|
+
* with it, dispatched outside a transaction, has nothing to wait for; one
|
|
154
|
+
* with it, inside a transaction, runs when that transaction commits and not
|
|
155
|
+
* at all if it rolls back.
|
|
156
|
+
*
|
|
157
|
+
* `currentTransaction()` is not consulted directly, and the difference
|
|
158
|
+
* matters: `deferUntilCommit` also answers `false` for a scope carrying a
|
|
159
|
+
* handle that `withTransaction` did not open, which has no commit hook and
|
|
160
|
+
* therefore no list that will ever be drained. Reading the handle alone would
|
|
161
|
+
* queue the dispatch onto nothing and lose it silently.
|
|
162
|
+
*
|
|
163
|
+
* The whole fan-out is deferred, sync listeners and queued ones alike. A
|
|
164
|
+
* queued listener is *pushed* at commit rather than at dispatch, which is the
|
|
165
|
+
* only correct reading of `afterCommit`: the queue drains in-process and
|
|
166
|
+
* often synchronously from `push`, so pushing early is running early.
|
|
167
|
+
*/
|
|
168
|
+
private deferToCommit;
|
|
169
|
+
/**
|
|
170
|
+
* Runs every **sync** listener bound to this event, in registration order,
|
|
171
|
+
* and resolves when the last of them has settled. Every queued one is handed
|
|
172
|
+
* to the `QueueManager` on the way past and is not waited for.
|
|
173
|
+
*
|
|
174
|
+
* The dispatch itself is `dispatch` and `dispatchAndWait` above; this is what
|
|
175
|
+
* they run, and what an `afterCommit` event's transaction runs later. Split
|
|
176
|
+
* out so that "when does the fan-out happen" is decided in exactly one place
|
|
177
|
+
* rather than in each entry point.
|
|
178
|
+
*
|
|
179
|
+
* ### Sync and queued in one pass
|
|
180
|
+
*
|
|
181
|
+
* A queued listener is pushed at the point in the order it occupies, so its
|
|
182
|
+
* *dispatch* is ordered with the rest and its *execution* is not: the queue
|
|
183
|
+
* decides when. What that costs is worth knowing — `dispatchAndWait`
|
|
184
|
+
* resolving says every sync listener has finished and says nothing at all
|
|
185
|
+
* about a queued one, which may not have started or may already have failed
|
|
186
|
+
* twice. Whether a listener is queued is therefore a decision about what the
|
|
187
|
+
* caller can rely on having happened, not only about latency.
|
|
188
|
+
*
|
|
189
|
+
* @param args the arguments the event's constructor was called with, which
|
|
190
|
+
* `Event.dispatch` forwards. Only a queued listener needs them: the event
|
|
191
|
+
* instance itself never crosses the queue, and what is rebuilt on the other
|
|
192
|
+
* side is `new EventClass(...args)`. Dispatching a hand-built instance
|
|
193
|
+
* straight at the manager therefore cannot queue, and says so on stderr
|
|
194
|
+
* rather than pushing a payload that would rehydrate into an event with
|
|
195
|
+
* `undefined` fields.
|
|
196
|
+
*
|
|
197
|
+
* ### One at a time, deliberately
|
|
198
|
+
*
|
|
199
|
+
* Each listener is awaited before the next one starts, rather than started
|
|
200
|
+
* together under a `Promise.allSettled`. Concurrency would make "registration
|
|
201
|
+
* order" a claim about start order only, and the order it is a claim about is
|
|
202
|
+
* the one `registeredListeners` reports and the discovery tests assert on.
|
|
203
|
+
*
|
|
204
|
+
* What it costs is worth knowing, because it is the one way listener
|
|
205
|
+
* independence is not total: a listener that *hangs* — an un-timed-out
|
|
206
|
+
* `fetch` to a host that never answers — delays every listener after it for
|
|
207
|
+
* as long as it hangs, and under `dispatch` the caller has already moved on,
|
|
208
|
+
* so an audit row a later listener writes simply is not there yet. A throw is
|
|
209
|
+
* not this: that is caught below and the next listener runs immediately. A
|
|
210
|
+
* listener that can block indefinitely is a listener that wants its own
|
|
211
|
+
* timeout, or a `Job`.
|
|
212
|
+
*
|
|
213
|
+
* ### Errors
|
|
214
|
+
*
|
|
215
|
+
* Each listener runs inside its own `try`. A throw is logged with the event
|
|
216
|
+
* name, the listener name, and the error, and the loop continues — listeners
|
|
217
|
+
* are independent side effects, and letting listener 2 cancel 3 through 5
|
|
218
|
+
* would make a filesystem walk's order load-bearing. Neither this nor
|
|
219
|
+
* `dispatch` ever rejects, for the same reason from the caller's side: a
|
|
220
|
+
* rejection would make listener 1's failure the dispatcher's problem while
|
|
221
|
+
* listener 5's silently is not.
|
|
222
|
+
*
|
|
223
|
+
* ### Why zero listeners is worth a line on stderr
|
|
224
|
+
*
|
|
225
|
+
* A registry keyed by the wrong string, a `static name` that does not survive
|
|
226
|
+
* a production build, a typo, and a listener directory that was never walked
|
|
227
|
+
* all produce the same single symptom: nobody handled it. Zero listeners is
|
|
228
|
+
* also a perfectly legal steady state, so the warning is development-only and
|
|
229
|
+
* fires once per event name — a dispatch in a loop would otherwise bury the
|
|
230
|
+
* terminal.
|
|
231
|
+
*/
|
|
232
|
+
protected runListeners(event: Event, args?: readonly unknown[]): Promise<void>;
|
|
233
|
+
/**
|
|
234
|
+
* Rebuilds an event from what crossed the queue.
|
|
235
|
+
*
|
|
236
|
+
* The queue carries a name and an argument array, because that is all JSON
|
|
237
|
+
* can carry: **the constructor arguments are what is serialized, not the
|
|
238
|
+
* instance** — the same bargain `Job.dispatch` makes. So an event whose
|
|
239
|
+
* constructor does work beyond assigning fields does that work a second time
|
|
240
|
+
* here, in the worker, with none of the dispatching request around it.
|
|
241
|
+
*
|
|
242
|
+
* A name that resolves to nothing throws, and is the one place in this file
|
|
243
|
+
* that does. The payload is already off the queue by the time this runs and
|
|
244
|
+
* the listener is one line away from being handed `undefined`, so the
|
|
245
|
+
* alternative is a `handle` reading `event.email` off nothing, several frames
|
|
246
|
+
* further on, with the name that would have explained it no longer in scope.
|
|
247
|
+
* A throw here is caught by `QueueManager.run` and takes the queue's ordinary
|
|
248
|
+
* retry-then-dead-letter path.
|
|
249
|
+
*/
|
|
250
|
+
rehydrate(eventName: string, args?: readonly unknown[]): Event;
|
|
251
|
+
/**
|
|
252
|
+
* Hands one queued listener to the queue, as `[eventName, args]`.
|
|
253
|
+
*
|
|
254
|
+
* Nothing is awaited: `push` returns as soon as the entry is on the queue,
|
|
255
|
+
* which is the whole of what `queued = true` buys. The event instance the
|
|
256
|
+
* sync listeners share does not go with it — only the name and the arguments
|
|
257
|
+
* do, so nothing that was resolved from the request's context can be smuggled
|
|
258
|
+
* across into an application that does not have it.
|
|
259
|
+
*
|
|
260
|
+
* Failing to queue is caught for the same reason a listener's throw is: it is
|
|
261
|
+
* one listener's problem, and the listeners after it in the walk did not do
|
|
262
|
+
* anything to deserve it. Two things reach that catch — an argument JSON
|
|
263
|
+
* cannot serialise, and an application with no queue bound — and both would
|
|
264
|
+
* otherwise reject `dispatchAndWait`, which is documented never to.
|
|
265
|
+
*/
|
|
266
|
+
private enqueue;
|
|
267
|
+
/**
|
|
268
|
+
* The one warning for the pairing that reads as doing something and does not.
|
|
269
|
+
*
|
|
270
|
+
* `await UserRegistered.dispatchAndWait(...)` inside a transaction, on an
|
|
271
|
+
* event that defers to commit, resolves with nothing having run — the
|
|
272
|
+
* listeners are queued on a transaction the caller has not finished. There is
|
|
273
|
+
* no other symptom: the promise resolves, the listeners do run later, and the
|
|
274
|
+
* only thing that is wrong is that the caller's `await` bought it nothing.
|
|
275
|
+
* Code that reads what a listener wrote on the next line finds it missing,
|
|
276
|
+
* and nothing connects the two.
|
|
277
|
+
*
|
|
278
|
+
* Development only and once per event name, on the same terms as the
|
|
279
|
+
* zero-listener warning: both are legal steady states that are usually a
|
|
280
|
+
* mistake, and a dispatch in a loop must not bury the terminal.
|
|
281
|
+
*/
|
|
282
|
+
private warnNothingIsWaitedFor;
|
|
283
|
+
private warnNothingIsListening;
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=EventManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventManager.d.ts","sourceRoot":"","sources":["../../../services/events/EventManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,KAAK,EAAE,KAAK,EAAc,MAAM,SAAS,CAAC;AAEjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAkBhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,qBAAa,YAAY;IACvB,MAAM,CAAC,KAAK,SAAY;IAExB,uEAAuE;IACvE,OAAO,CAAC,SAAS,CAAsC;IAEvD;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,CAAkC;IAEhD;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAqB;IAEtC;;;;;;OAMG;IACH,OAAO,CAAC,kBAAkB,CAAqB;IAE/C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;gBAE3B,MAAM,GAAE,WAAgB;IAKpC;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,YAAY,CAAC,SAAS,EAAE,aAAa,EAAE;IA0DvC;;;;;;;;;OASG;IACH,IAAI,kBAAkB,IAAI,aAAa,CAAC,UAAU,GAAG,CAAC,CAIrD;IAED;;;;;;;;;;OAUG;IACH,IAAI,mBAAmB,IAAI,aAAa,CAAC,aAAa,CAAC,CAEtD;IAED;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAKvD;;;;;;;;;OASG;IACG,eAAe,CACnB,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC;IAKhB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,aAAa;IAoBrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8DG;cACa,YAAY,CAC1B,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC;IA2BhB;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,SAAS,OAAO,EAAO,GAAG,KAAK;IAiBlE;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,OAAO;IA6Bf;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,sBAAsB;CAY/B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ServiceProvider } from "../../support/ServiceProvider";
|
|
2
|
+
export declare class EventServiceProvider extends ServiceProvider {
|
|
3
|
+
register(): void;
|
|
4
|
+
/**
|
|
5
|
+
* Fills in the listener registry when the app did not declare one.
|
|
6
|
+
*
|
|
7
|
+
* ### Why the raw slice and not the manager's config
|
|
8
|
+
*
|
|
9
|
+
* The decision here is whether the app said anything, and by the time the
|
|
10
|
+
* manager holds a config it can no longer tell: `withDefaults` treats an
|
|
11
|
+
* absent key and an `undefined` one alike and substitutes the default `[]`,
|
|
12
|
+
* which is the same value an app writes when it means "no listeners, and I
|
|
13
|
+
* mean it". Reading the slice before defaults are applied is the only place
|
|
14
|
+
* the difference still exists.
|
|
15
|
+
*
|
|
16
|
+
* So: `listeners` present, including `listeners: []`, is used verbatim and no
|
|
17
|
+
* directory is read. Absent or `undefined`, the classes under `listenersDir`
|
|
18
|
+
* are.
|
|
19
|
+
*
|
|
20
|
+
* ### Why phase two
|
|
21
|
+
*
|
|
22
|
+
* Discovery imports every file it walks, which is asynchronous, and
|
|
23
|
+
* `register()` is not. It also has to finish before the first dispatch rather
|
|
24
|
+
* than before the first request — a dispatch against an empty registry is not
|
|
25
|
+
* an error, it is a side effect that quietly does not happen — so the
|
|
26
|
+
* registry has to be complete by the end of boot.
|
|
27
|
+
*
|
|
28
|
+
* ### Why the queue is handed the listener jobs from here
|
|
29
|
+
*
|
|
30
|
+
* A queued listener is registered with the `QueueManager` as a synthetic job,
|
|
31
|
+
* and that registration cannot happen in `register()`: resolving another
|
|
32
|
+
* service is the one thing a `register()` must not do, and the jobs are not
|
|
33
|
+
* known until discovery has run anyway. It also cannot happen before
|
|
34
|
+
* `QueueServiceProvider.boot()` fills the queue's registry, which is why this
|
|
35
|
+
* provider sits after it in `frameworkProviders` — `useJobs` replaces that
|
|
36
|
+
* registry wholesale, so the queue has to be finished with it before
|
|
37
|
+
* `registerJob` starts adding to it.
|
|
38
|
+
*
|
|
39
|
+
* The declared-listeners path falls through to it rather than returning
|
|
40
|
+
* early: an app that lists its listeners in `app/config/events.ts` still has
|
|
41
|
+
* queued ones, and skipping the queue for them would make `queued = true` a
|
|
42
|
+
* field that silently does nothing depending on how the app registers.
|
|
43
|
+
*/
|
|
44
|
+
boot(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=EventServiceProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventServiceProvider.d.ts","sourceRoot":"","sources":["../../../services/events/EventServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAOhE,qBAAa,oBAAqB,SAAQ,eAAe;IACvD,QAAQ;IAOR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACG,IAAI;CAoBX"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { Application } from "../../foundation/Application";
|
|
2
|
+
import type { Event, EventClass } from "./Event";
|
|
3
|
+
import { EventManager } from "./EventManager";
|
|
4
|
+
/**
|
|
5
|
+
* One dispatch, as a fake saw it.
|
|
6
|
+
*
|
|
7
|
+
* Both halves are kept because neither is derivable from the other. The
|
|
8
|
+
* instance is what a predicate reads — `(e) => e.email === "…"` — and the
|
|
9
|
+
* arguments are what the failure message can print, because an instance cannot
|
|
10
|
+
* be asked what it was constructed with. A dispatch made straight at the
|
|
11
|
+
* manager carries no arguments at all, which is why they are optional here in
|
|
12
|
+
* exactly the way they are on `dispatch`.
|
|
13
|
+
*/
|
|
14
|
+
export interface DispatchedEvent {
|
|
15
|
+
event: Event;
|
|
16
|
+
args?: readonly unknown[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* An `EventManager` that records dispatches and runs nothing.
|
|
20
|
+
*
|
|
21
|
+
* Installed by `Event.fake()`, which is the only thing that constructs one —
|
|
22
|
+
* see there for what a fake is for and why `restore()` is not optional.
|
|
23
|
+
*
|
|
24
|
+
* It extends the real manager rather than reimplementing its shape, so a test
|
|
25
|
+
* holding one can still ask `registeredListeners` and get the honest answer
|
|
26
|
+
* (empty: a fake registers none). What it overrides is the two dispatch entry
|
|
27
|
+
* points, and everything downstream of them is unreachable as a result — no
|
|
28
|
+
* listener is constructed, no `handle` runs, nothing is pushed to the
|
|
29
|
+
* `QueueManager`, and no `afterCommit` event is queued on the open transaction.
|
|
30
|
+
* That last one is worth stating: a fake short-circuits *before* the
|
|
31
|
+
* transaction check, so a faked dispatch is recorded at the moment it is made
|
|
32
|
+
* whether or not the event defers, and a test does not have to commit a
|
|
33
|
+
* transaction to see it.
|
|
34
|
+
*
|
|
35
|
+
* The assertions throw plain `Error`s rather than going through a matcher, so
|
|
36
|
+
* this file depends on no test runner. Their messages name **what was
|
|
37
|
+
* dispatched**, because the common failure is not "nothing fired" but "that
|
|
38
|
+
* fired with a different payload", and printing the recorded dispatches ends
|
|
39
|
+
* that investigation where it starts.
|
|
40
|
+
*/
|
|
41
|
+
export declare class FakeEventManager extends EventManager {
|
|
42
|
+
private readonly application;
|
|
43
|
+
private readonly previous;
|
|
44
|
+
/**
|
|
45
|
+
* Every dispatch since the fake was installed, in order. Public and readable
|
|
46
|
+
* for the case the assertions do not cover — the last resort, rather than the
|
|
47
|
+
* intended surface.
|
|
48
|
+
*/
|
|
49
|
+
readonly dispatched: DispatchedEvent[];
|
|
50
|
+
/**
|
|
51
|
+
* Installs a fake into `application`'s container, or returns the one already
|
|
52
|
+
* there.
|
|
53
|
+
*
|
|
54
|
+
* A second call returning the same recorder is the deliberate half: a fake
|
|
55
|
+
* set up by a shared test helper and a `Event.fake()` in the body of the test
|
|
56
|
+
* would otherwise be two recorders, and the one being asserted on would be
|
|
57
|
+
* the one that saw nothing. It is also why this is a static rather than a
|
|
58
|
+
* constructor — "make me one" and "make sure there is one" are different
|
|
59
|
+
* requests, and only the second is ever wanted.
|
|
60
|
+
*
|
|
61
|
+
* `resolved` rather than `make`: asking the container for the real manager
|
|
62
|
+
* merely to find out whether it exists would *build* it, and building it is
|
|
63
|
+
* what `restore()` then has to put back. The lazily-bound singleton stays
|
|
64
|
+
* lazy, and an application that never resolved an `EventManager` is left with
|
|
65
|
+
* one it never resolved.
|
|
66
|
+
*
|
|
67
|
+
* `instanceof` is safe here and nowhere else in this subsystem: the only
|
|
68
|
+
* thing that puts a `FakeEventManager` in a container is this method, so both
|
|
69
|
+
* sides of the comparison come from one module graph. The registry keys next
|
|
70
|
+
* door cannot make that assumption, because the dispatching code may have
|
|
71
|
+
* been bundled and minified separately from the listeners.
|
|
72
|
+
*/
|
|
73
|
+
static install(application: Application): FakeEventManager;
|
|
74
|
+
private constructor();
|
|
75
|
+
/** Records the dispatch. No listener runs, and nothing reaches the queue. */
|
|
76
|
+
dispatch(event: Event, args?: readonly unknown[]): void;
|
|
77
|
+
/**
|
|
78
|
+
* Records the dispatch and resolves. Resolved rather than rejected-on-nothing
|
|
79
|
+
* for the same reason the real one never rejects: a caller awaiting a
|
|
80
|
+
* dispatch is awaiting side effects it does not name, and under a fake there
|
|
81
|
+
* are none to fail.
|
|
82
|
+
*/
|
|
83
|
+
dispatchAndWait(event: Event, args?: readonly unknown[]): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Puts the container back the way it was found.
|
|
86
|
+
*
|
|
87
|
+
* Restoring the *previous* instance rather than forgetting unconditionally:
|
|
88
|
+
* an application that had already resolved its `EventManager` — one that
|
|
89
|
+
* booted, discovered listeners and registered synthetic jobs with the queue —
|
|
90
|
+
* must get that same object back, not a second one built from the config
|
|
91
|
+
* slice with an empty registry and no jobs behind it.
|
|
92
|
+
*
|
|
93
|
+
* When there was none, the binding is forgotten instead, so the container's
|
|
94
|
+
* lazy singleton factory builds the real manager on next use.
|
|
95
|
+
*
|
|
96
|
+
* Calling it twice is harmless. Not calling it is the failure this method
|
|
97
|
+
* exists for: the fake stays in the container for every test after this one,
|
|
98
|
+
* and every listener in them silently does not run.
|
|
99
|
+
*/
|
|
100
|
+
restore(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Fails unless the event was dispatched at least once, optionally matching a
|
|
103
|
+
* predicate.
|
|
104
|
+
*
|
|
105
|
+
* The predicate takes the event instance and is typed off the class passed
|
|
106
|
+
* in, so `(e) => e.email` is checked against `UserRegistered` without a cast.
|
|
107
|
+
*
|
|
108
|
+
* Matched **by declared name**, never by class identity — the rule the whole
|
|
109
|
+
* subsystem is keyed on. A test importing its event from the same file as the
|
|
110
|
+
* code under test would not notice the difference; one asserting against a
|
|
111
|
+
* production build's bundled class would find `instanceof` false and this
|
|
112
|
+
* true, and this is the one that is right.
|
|
113
|
+
*/
|
|
114
|
+
assertDispatched<T extends EventClass>(event: T, predicate?: (event: InstanceType<T>) => boolean): void;
|
|
115
|
+
/**
|
|
116
|
+
* Fails if the event was dispatched at all, or — with a predicate — if any
|
|
117
|
+
* dispatch of it matched.
|
|
118
|
+
*
|
|
119
|
+
* The predicate form is the useful one: "a welcome email is not sent to an
|
|
120
|
+
* invited user" is an assertion about one dispatch among several, and
|
|
121
|
+
* asserting the event never fired at all would be a stronger claim than the
|
|
122
|
+
* test means.
|
|
123
|
+
*/
|
|
124
|
+
assertNotDispatched<T extends EventClass>(event: T, predicate?: (event: InstanceType<T>) => boolean): void;
|
|
125
|
+
/**
|
|
126
|
+
* Fails unless the event was dispatched exactly `times` times.
|
|
127
|
+
*
|
|
128
|
+
* Worth having beside `assertDispatched` because the failure it catches is
|
|
129
|
+
* the one that assertion cannot see: a dispatch that moved inside a loop, or
|
|
130
|
+
* a controller that fires the same event on both branches of a retry. "At
|
|
131
|
+
* least once" passes for all of those.
|
|
132
|
+
*/
|
|
133
|
+
assertDispatchedTimes<T extends EventClass>(event: T, times: number, predicate?: (event: InstanceType<T>) => boolean): void;
|
|
134
|
+
/**
|
|
135
|
+
* Fails if anything at all was dispatched.
|
|
136
|
+
*
|
|
137
|
+
* The assertion for a path that should be inert — a request rejected by
|
|
138
|
+
* validation, an idempotent write that found nothing to do — where naming the
|
|
139
|
+
* events not to expect would mean listing every event the app has.
|
|
140
|
+
*/
|
|
141
|
+
assertNothingDispatched(): void;
|
|
142
|
+
/** Recorded dispatches of `name`, narrowed by `predicate` when there is one. */
|
|
143
|
+
private matching;
|
|
144
|
+
/**
|
|
145
|
+
* The tail of a failure message: what *was* dispatched.
|
|
146
|
+
*
|
|
147
|
+
* Three cases, because they send the reader to three different places. An
|
|
148
|
+
* empty recorder means the code under test dispatched nothing — look at the
|
|
149
|
+
* controller. Dispatches of the same name that failed a predicate means the
|
|
150
|
+
* payload is not what the test expected, and printing them is usually the
|
|
151
|
+
* whole answer. Anything else means the wrong event, or none.
|
|
152
|
+
*/
|
|
153
|
+
private summary;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=FakeEventManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FakeEventManager.d.ts","sourceRoot":"","sources":["../../../services/events/FakeEventManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;;;;;;;GASG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC3B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IA4C9C,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IA5C3B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,eAAe,EAAE,CAAM;IAE5C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,GAAG,gBAAgB;IAY1D,OAAO;IAYP,6EAA6E;IACpE,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAIhE;;;;;OAKG;IACY,eAAe,CAC5B,KAAK,EAAE,KAAK,EACZ,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC;IAIhB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,IAAI,IAAI;IAQf;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,CAAC,SAAS,UAAU,EACnC,KAAK,EAAE,CAAC,EACR,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,OAAO,GAC9C,IAAI;IAUP;;;;;;;;OAQG;IACH,mBAAmB,CAAC,CAAC,SAAS,UAAU,EACtC,KAAK,EAAE,CAAC,EACR,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,OAAO,GAC9C,IAAI;IAYP;;;;;;;OAOG;IACH,qBAAqB,CAAC,CAAC,SAAS,UAAU,EACxC,KAAK,EAAE,CAAC,EACR,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,OAAO,GAC9C,IAAI;IAYP;;;;;;OAMG;IACH,uBAAuB,IAAI,IAAI;IAY/B,gFAAgF;IAChF,OAAO,CAAC,QAAQ;IAWhB;;;;;;;;OAQG;IACH,OAAO,CAAC,OAAO;CAUhB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FakeEventManager.test-d.d.ts","sourceRoot":"","sources":["../../../services/events/FakeEventManager.test-d.ts"],"names":[],"mappings":""}
|