@inglorious/store 12.2.0 → 12.3.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/README.md +46 -1
- package/package.json +3 -3
- package/src/store.js +16 -0
- package/types/api.d.ts +9 -3
- package/types/events.d.ts +149 -0
- package/types/index.d.ts +1 -0
- package/types/store.d.ts +78 -6
package/README.md
CHANGED
|
@@ -1659,7 +1659,52 @@ interface SalesEntity extends ChartEntity {
|
|
|
1659
1659
|
// types: { Router, Sales: Chart }
|
|
1660
1660
|
```
|
|
1661
1661
|
|
|
1662
|
-
**5.
|
|
1662
|
+
**5. Check the events you send**
|
|
1663
|
+
|
|
1664
|
+
The type declarations already list every event and its payload, so the api can check what `notify` sends. Read the events from them with `EventsOf`, and give them to the api as its third type argument:
|
|
1665
|
+
|
|
1666
|
+
```typescript
|
|
1667
|
+
// types/index.d.ts
|
|
1668
|
+
import type { EventsOf } from "@inglorious/store"
|
|
1669
|
+
|
|
1670
|
+
export type AppEvents = EventsOf<CounterType | LabelType>
|
|
1671
|
+
// { increment: void; add: number; copyLabel: void; ...the label's events }
|
|
1672
|
+
|
|
1673
|
+
export type AppApi = Api<AppEntity, AppState, AppEvents>
|
|
1674
|
+
```
|
|
1675
|
+
|
|
1676
|
+
Now every handler's `api.notify` checks the event's name, wherever it is sent (`"add"`, `"Counter:add"`, `` `#${entity.id}:add` ``), and its payload:
|
|
1677
|
+
|
|
1678
|
+
```typescript
|
|
1679
|
+
api.notify(`#${entity.id}:add`, 2) // fine
|
|
1680
|
+
api.notify(`#${entity.id}:ad`, 2) // error: no type answers `ad`
|
|
1681
|
+
api.notify(`#${entity.id}:add`, "2") // error: `add` takes a number
|
|
1682
|
+
api.notify(`#${entity.id}:increment`, 1) // error: `increment` takes no payload
|
|
1683
|
+
```
|
|
1684
|
+
|
|
1685
|
+
The store's own events (`add`, `remove`, `patch`, `replace`, `pause`, `resume`, `update`) are always there. Only the event's name is checked, not the target before it, because the target is usually built at runtime. Pass the events to `createStore<AppEntity, AppState, AppEvents>` too, so that `store.notify` checks them, and `mount` gives them to the root render. A typed api is still an `Api`: components that take any app's api accept it.
|
|
1686
|
+
|
|
1687
|
+
Without the third type argument, `notify` takes any name and any payload, as before.
|
|
1688
|
+
|
|
1689
|
+
**6. Give sub-renderers their props**
|
|
1690
|
+
|
|
1691
|
+
Every render method takes props first: `render(props, api)`, where a type's props are its entity, and sub-renderers that draw one item, `renderX(props, ownProps, api)`, which get the component's props, then the item's own (`renderLine(props, line, api)`). `EntityType` reads every member as a handler, whose first parameter is the entity, so it rejects sub-renderers whose props are not the entity. `defineType` checks each member by its name instead: `render` takes the entity as its props, a sub-renderer keeps the types it declares, and every other member is a handler, whose entity and api are inferred:
|
|
1692
|
+
|
|
1693
|
+
```typescript
|
|
1694
|
+
import { defineType } from "@inglorious/store"
|
|
1695
|
+
|
|
1696
|
+
export const Scoreboard = defineType<ScoreboardEntity, AppApi>()({
|
|
1697
|
+
...template, // renderScoreboard(props, api), renderContender(props, speaker, api), ...
|
|
1698
|
+
render(props, api) {
|
|
1699
|
+
const debate = api.getEntity("debate")
|
|
1700
|
+
return this.renderScoreboard({ ...props, memory: debate?.memory }, api)
|
|
1701
|
+
},
|
|
1702
|
+
})
|
|
1703
|
+
```
|
|
1704
|
+
|
|
1705
|
+
Inside its methods, `this` is the behavior, so `this.renderScoreboard` is checked too. It takes the entity and the api first, then the behavior, because TypeScript cannot infer one while being told the others. The result is an `EntityType`, so it goes in `types` or in a list of behaviors like any other: `[Chat, handlers, fsm(states), defineType<DebateEntity, AppApi>()({ ...template, render })]`.
|
|
1706
|
+
|
|
1707
|
+
**7. Test your handlers**
|
|
1663
1708
|
|
|
1664
1709
|
```typescript
|
|
1665
1710
|
import { trigger } from "@inglorious/store/test"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/store",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.3.0",
|
|
4
4
|
"description": "A state manager for real-time, collaborative apps, inspired by game development patterns and compatible with Redux.",
|
|
5
5
|
"author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"mutative": "^1.3.0"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@inglorious/utils": "^5.
|
|
81
|
+
"@inglorious/utils": "^5.2.0"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
84
|
"@inglorious/eslint-config": "1.2.0",
|
|
85
|
-
"@inglorious/utils": "5.
|
|
85
|
+
"@inglorious/utils": "5.2.0",
|
|
86
86
|
"@reduxjs/toolkit": "^2.12.0",
|
|
87
87
|
"prettier": "^3.9.6",
|
|
88
88
|
"typescript": "~7.0.2",
|
package/src/store.js
CHANGED
|
@@ -515,6 +515,22 @@ export function createStore({
|
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Defines a behavior for an entity. It does nothing at runtime: it hands back
|
|
520
|
+
* the behavior it is given, and JavaScript apps never need it.
|
|
521
|
+
*
|
|
522
|
+
* It is for TypeScript apps, like Vue's `defineComponent` or Vite's
|
|
523
|
+
* `defineConfig`: its declaration in `types/store.d.ts` checks each member of
|
|
524
|
+
* the behavior by its name, so that a template's sub-renderers can take props
|
|
525
|
+
* while the handlers still get the entity, which a plain annotation cannot
|
|
526
|
+
* express. See that declaration for why.
|
|
527
|
+
*
|
|
528
|
+
* @returns {(behavior: object) => object} A function that returns the behavior it is given.
|
|
529
|
+
*/
|
|
530
|
+
export function defineType() {
|
|
531
|
+
return (behavior) => behavior
|
|
532
|
+
}
|
|
533
|
+
|
|
518
534
|
/**
|
|
519
535
|
* A draft that writes straight into the entities it is given.
|
|
520
536
|
*
|
package/types/api.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
Store,
|
|
8
8
|
Stored,
|
|
9
9
|
} from "./store"
|
|
10
|
+
import type { AnyEvents, Notify } from "./events"
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* API object exposed to handlers.
|
|
@@ -14,10 +15,14 @@ import type {
|
|
|
14
15
|
* An app's own `Api<AppEntity, AppState>` is an `Api`: a component typed with
|
|
15
16
|
* the plain `Api` accepts it. So the members that read types give the plain
|
|
16
17
|
* `EntityType`, rather than one typed for the app's entities.
|
|
18
|
+
*
|
|
19
|
+
* Told the app's events, `Api<AppEntity, AppState, AppEvents>` checks what
|
|
20
|
+
* `notify` sends (see `EventsOf`), and is still an `Api`.
|
|
17
21
|
*/
|
|
18
22
|
export interface Api<
|
|
19
23
|
TEntity extends BaseEntity = BaseEntity,
|
|
20
24
|
TState extends EntitiesState<TEntity> = EntitiesState<TEntity>,
|
|
25
|
+
TEvents = AnyEvents,
|
|
21
26
|
> {
|
|
22
27
|
getTypes: () => Record<string, EntityType>
|
|
23
28
|
getType: (typeName: string) => EntityType
|
|
@@ -35,7 +40,7 @@ export interface Api<
|
|
|
35
40
|
}
|
|
36
41
|
select: <TResult>(selector: (state: TState) => TResult) => TResult
|
|
37
42
|
dispatch: (event: Event) => void
|
|
38
|
-
notify:
|
|
43
|
+
notify: Notify<TEvents, TEntity>
|
|
39
44
|
[key: string]: any
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -45,7 +50,8 @@ export interface Api<
|
|
|
45
50
|
export function createApi<
|
|
46
51
|
TEntity extends BaseEntity = BaseEntity,
|
|
47
52
|
TState extends EntitiesState<TEntity> = EntitiesState<TEntity>,
|
|
53
|
+
TEvents = AnyEvents,
|
|
48
54
|
>(
|
|
49
|
-
store: Store<TEntity, TState>,
|
|
55
|
+
store: Store<TEntity, TState, TEvents>,
|
|
50
56
|
extras?: Record<string, any>,
|
|
51
|
-
): Api<TEntity, TState>
|
|
57
|
+
): Api<TEntity, TState, TEvents>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// The events a store answers, so that `notify` can check them: the event's
|
|
2
|
+
// name, wherever it is sent, and its payload.
|
|
3
|
+
|
|
4
|
+
import type { BaseEntity, Stored } from "./store"
|
|
5
|
+
|
|
6
|
+
declare const anyEvents: unique symbol
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The events of an api or a store that has not been told its events: any
|
|
10
|
+
* name, any payload. It is the default, so an untyped app type-checks as
|
|
11
|
+
* before.
|
|
12
|
+
*/
|
|
13
|
+
export type AnyEvents = { readonly [anyEvents]: true }
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The events a store's types answer, by name, with their payloads, read from
|
|
17
|
+
* the types' own declarations: the second parameter of each handler is its
|
|
18
|
+
* payload, and a handler that takes none has no payload. `render` and the
|
|
19
|
+
* other `render…` members are renderers, not events, so they are left out.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* type CounterType = {
|
|
23
|
+
* add: (entity: CounterEntity, amount: number) => void
|
|
24
|
+
* reset: (entity: CounterEntity) => void
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* type AppEvents = EventsOf<CounterType | LabelType>
|
|
28
|
+
* // { add: number; reset: void; ...the label's events }
|
|
29
|
+
*
|
|
30
|
+
* type AppApi = Api<AppEntity, AppState, AppEvents>
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* An event two types answer takes either type's payload.
|
|
34
|
+
*/
|
|
35
|
+
export type EventsOf<TTypes> = {
|
|
36
|
+
[TEvent in HandlerName<TTypes>]: TTypes extends unknown
|
|
37
|
+
? TEvent extends keyof TTypes
|
|
38
|
+
? PayloadOf<TTypes[TEvent]>
|
|
39
|
+
: never
|
|
40
|
+
: never
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The events the store answers itself, whatever its types: an entity
|
|
45
|
+
* arriving, leaving or changing, and the world pausing and resuming.
|
|
46
|
+
*/
|
|
47
|
+
export type BuiltInEvents<TEntity extends BaseEntity = BaseEntity> = {
|
|
48
|
+
add: TEntity & { id: string }
|
|
49
|
+
remove: string
|
|
50
|
+
patch: Partial<TEntity> & { id: string }
|
|
51
|
+
replace: Stored<TEntity>
|
|
52
|
+
pause: void
|
|
53
|
+
resume: void
|
|
54
|
+
update: number | undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* How an event is sent: by its name alone, broadcast to whoever answers it,
|
|
59
|
+
* or after a target that narrows it down to a type (`Type:event`), an entity
|
|
60
|
+
* (`#id:event`) or both (`Type#id:event`). Only the event's name is checked:
|
|
61
|
+
* the target is usually built at runtime, such as `` `#${entity.id}:submit` ``.
|
|
62
|
+
*/
|
|
63
|
+
export type EventType<TEvents> =
|
|
64
|
+
EventName<TEvents> | `${string}:${EventName<TEvents>}`
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sends an event. Told the store's events, it checks the event's name and
|
|
68
|
+
* its payload: `notify("#debate:lineAdd", line)` must name an event the
|
|
69
|
+
* store's types answer, and `line` must be that event's payload. An event
|
|
70
|
+
* with no payload takes none. Otherwise any name and payload go.
|
|
71
|
+
*/
|
|
72
|
+
export type Notify<
|
|
73
|
+
TEvents = AnyEvents,
|
|
74
|
+
TEntity extends BaseEntity = BaseEntity,
|
|
75
|
+
> = AnyEvents extends TEvents
|
|
76
|
+
? (type: string, payload?: any) => void
|
|
77
|
+
: TypedNotify<WithBuiltIns<TEvents, BuiltInEvents<TEntity>>>
|
|
78
|
+
|
|
79
|
+
type EventName<TEvents> = keyof TEvents & string
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The app's events and the store's own, by name. An app event named like a
|
|
83
|
+
* built-in one, such as an `add` that takes an amount, takes either payload,
|
|
84
|
+
* because its handlers hear both: the store broadcasts entities as `add` too.
|
|
85
|
+
*/
|
|
86
|
+
type WithBuiltIns<TEvents, TBuiltIns> = {
|
|
87
|
+
[K in keyof TEvents | keyof TBuiltIns]:
|
|
88
|
+
| (K extends keyof TEvents ? TEvents[K] : never)
|
|
89
|
+
| (K extends keyof TBuiltIns ? TBuiltIns[K] : never)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The event a sent type names: what follows its target, if it has one. */
|
|
93
|
+
type EventOf<TType extends string> = TType extends `${string}:${infer TEvent}`
|
|
94
|
+
? TEvent
|
|
95
|
+
: TType
|
|
96
|
+
|
|
97
|
+
/** The payload as `notify`'s last parameters: none, optional or required. */
|
|
98
|
+
type PayloadArgs<TPayload> = [TPayload] extends [void]
|
|
99
|
+
? [payload?: undefined]
|
|
100
|
+
: undefined extends TPayload
|
|
101
|
+
? [payload?: TPayload]
|
|
102
|
+
: [payload: TPayload]
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A typed `notify`, declared as a method and taken out by name, with an
|
|
106
|
+
* overload that nothing can call: it only lets a typed api or store fit
|
|
107
|
+
* where a plain one is expected, such as a component that takes any app's
|
|
108
|
+
* api. It comes first, so that a call that fits neither is explained by the
|
|
109
|
+
* typed one.
|
|
110
|
+
*/
|
|
111
|
+
type TypedNotify<TEvents> = {
|
|
112
|
+
notify(type: never, payload: never): void
|
|
113
|
+
notify<TType extends EventType<TEvents>>(
|
|
114
|
+
type: TType,
|
|
115
|
+
...payload: PayloadArgs<TEvents[EventOf<TType> & keyof TEvents]>
|
|
116
|
+
): void
|
|
117
|
+
}["notify"]
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The handlers' names of each type: every function but the renderers. Only
|
|
121
|
+
* named members count: a type that also takes any handler, through an index
|
|
122
|
+
* signature such as `[handler: string]: unknown`, keeps its named events.
|
|
123
|
+
*/
|
|
124
|
+
type HandlerName<TTypes> = TTypes extends unknown
|
|
125
|
+
? keyof {
|
|
126
|
+
[
|
|
127
|
+
K in keyof TTypes as string extends K
|
|
128
|
+
? never
|
|
129
|
+
: number extends K
|
|
130
|
+
? never
|
|
131
|
+
: K extends `render${string}`
|
|
132
|
+
? never
|
|
133
|
+
: TTypes[K] extends (...args: never[]) => unknown
|
|
134
|
+
? K
|
|
135
|
+
: never
|
|
136
|
+
]: true
|
|
137
|
+
} &
|
|
138
|
+
string
|
|
139
|
+
: never
|
|
140
|
+
|
|
141
|
+
/** A handler's payload: its second parameter, or none if it takes only the entity. */
|
|
142
|
+
type PayloadOf<THandler> = THandler extends (
|
|
143
|
+
entity: never,
|
|
144
|
+
...rest: infer TRest
|
|
145
|
+
) => unknown
|
|
146
|
+
? TRest extends []
|
|
147
|
+
? void
|
|
148
|
+
: TRest[0]
|
|
149
|
+
: never
|
package/types/index.d.ts
CHANGED
package/types/store.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Api } from "./api"
|
|
2
|
+
import type { AnyEvents, Notify } from "./events"
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* What every entity has. Declare an entity's other fields on its own interface:
|
|
@@ -111,6 +112,68 @@ export type TypeDefinition<
|
|
|
111
112
|
*/
|
|
112
113
|
export type AnyTypeDefinition = TypeDefinition<never, never>
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* What each member of a type must be, told by its name: `render(props, api)`
|
|
117
|
+
* draws the type, and a type's props are its entity; any other `render…` is
|
|
118
|
+
* a sub-renderer, `renderX(props, ownProps, api)`, which keeps the types it
|
|
119
|
+
* declares; every other member handles an event.
|
|
120
|
+
*/
|
|
121
|
+
export type CheckedType<
|
|
122
|
+
TType,
|
|
123
|
+
TEntity extends BaseEntity = BaseEntity,
|
|
124
|
+
TApi extends Api = Api,
|
|
125
|
+
> = {
|
|
126
|
+
[K in keyof TType]: K extends "render"
|
|
127
|
+
? Renderer<TEntity, TApi>
|
|
128
|
+
: K extends `render${string}`
|
|
129
|
+
? TType[K]
|
|
130
|
+
: EventHandler<TEntity, TApi>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* For TypeScript only. At runtime it hands back the behavior it is given, and
|
|
135
|
+
* JavaScript apps never need it: it exists so that TypeScript can check a
|
|
136
|
+
* behavior that a plain annotation cannot describe, the same trick as Vue's
|
|
137
|
+
* `defineComponent` or Vite's `defineConfig`.
|
|
138
|
+
*
|
|
139
|
+
* Why it is needed: an annotation such as `EntityType<ScoreboardEntity, AppApi>`
|
|
140
|
+
* checks every member as an event handler, `(entity, payload, api)`. That is
|
|
141
|
+
* wrong for a template's sub-renderers, `renderX(props, ownProps, api)`,
|
|
142
|
+
* whose first parameter is the component's props rather than the entity, so
|
|
143
|
+
* the annotation rejects them. TypeScript cannot exempt members by their name
|
|
144
|
+
* in an annotation; it can only check them one by one after inferring the
|
|
145
|
+
* object, which takes a generic function. This is that function.
|
|
146
|
+
*
|
|
147
|
+
* What it checks, member by member: `render(props, api)` takes the entity as
|
|
148
|
+
* its props; any other `render…` keeps the types it declares; every other
|
|
149
|
+
* member is an event handler, whose entity and api are inferred. Inside its
|
|
150
|
+
* methods, `this` is the behavior itself.
|
|
151
|
+
*
|
|
152
|
+
* Use it only for a behavior whose sub-renderers take props; otherwise an
|
|
153
|
+
* annotation is enough. It takes the entity and the api first, then the
|
|
154
|
+
* behavior, because TypeScript cannot infer the behavior while being told
|
|
155
|
+
* the rest:
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* export const Scoreboard = defineType<ScoreboardEntity, AppApi>()({
|
|
159
|
+
* ...template, // renderScoreboard(props, api), renderContender(props, speaker, api), ...
|
|
160
|
+
* render(props, api) {
|
|
161
|
+
* return this.renderScoreboard({ ...props, memory: readMemory(api) }, api)
|
|
162
|
+
* },
|
|
163
|
+
* })
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* The result is an `EntityType` for the entity, so it goes wherever a
|
|
167
|
+
* behavior goes: in the store's `types`, or in a list of behaviors such as
|
|
168
|
+
* `[Chat, handlers, fsm(states), defineType<DebateEntity, AppApi>()({ ... })]`.
|
|
169
|
+
*/
|
|
170
|
+
export function defineType<
|
|
171
|
+
TEntity extends BaseEntity,
|
|
172
|
+
TApi extends Api = Api,
|
|
173
|
+
>(): <TType>(
|
|
174
|
+
behavior: CheckedType<TType, TEntity, TApi> & ThisType<TType>,
|
|
175
|
+
) => EntityType<TEntity, TApi>
|
|
176
|
+
|
|
114
177
|
/**
|
|
115
178
|
* System definition with event handlers
|
|
116
179
|
*/
|
|
@@ -134,12 +197,13 @@ export type EntitiesState<TEntity extends BaseEntity = BaseEntity> = {
|
|
|
134
197
|
export type TypesConfig<
|
|
135
198
|
TEntity extends BaseEntity = BaseEntity,
|
|
136
199
|
TState extends EntitiesState<TEntity> = EntitiesState<TEntity>,
|
|
200
|
+
TEvents = AnyEvents,
|
|
137
201
|
> = string extends TEntity["type"]
|
|
138
202
|
? { [typeName: string]: AnyTypeDefinition }
|
|
139
203
|
: {
|
|
140
204
|
[TName in TEntity["type"]]?: TypeDefinition<
|
|
141
205
|
Extract<TEntity, { type: TName }>,
|
|
142
|
-
Api<TEntity, TState>
|
|
206
|
+
Api<TEntity, TState, TEvents>
|
|
143
207
|
>
|
|
144
208
|
} & { [typeName: string]: unknown }
|
|
145
209
|
|
|
@@ -172,10 +236,11 @@ export type UpdateStrategy = "structural-sharing" | "copy-on-dispatch"
|
|
|
172
236
|
export interface StoreConfig<
|
|
173
237
|
TEntity extends BaseEntity = BaseEntity,
|
|
174
238
|
TState extends EntitiesState<TEntity> = EntitiesState<TEntity>,
|
|
239
|
+
TEvents = AnyEvents,
|
|
175
240
|
> {
|
|
176
241
|
// The entity type comes from the type arguments, `entities` or
|
|
177
242
|
// `middlewares`, never from `types`, which are checked against it.
|
|
178
|
-
types?: NoInfer<TypesConfig<TEntity, TState>>
|
|
243
|
+
types?: NoInfer<TypesConfig<TEntity, TState, TEvents>>
|
|
179
244
|
/** The initial entities. With `autoCreateEntities`, those the store creates itself can be left out. */
|
|
180
245
|
entities?: Partial<TState>
|
|
181
246
|
systems?: System<TState>[]
|
|
@@ -201,10 +266,12 @@ export type Unsubscribe = () => void
|
|
|
201
266
|
export interface Store<
|
|
202
267
|
TEntity extends BaseEntity = BaseEntity,
|
|
203
268
|
TState extends EntitiesState<TEntity> = EntitiesState<TEntity>,
|
|
269
|
+
TEvents = AnyEvents,
|
|
204
270
|
> {
|
|
205
271
|
subscribe: (listener: Listener) => Unsubscribe
|
|
206
272
|
update: () => Event[]
|
|
207
|
-
|
|
273
|
+
/** Sends an event; told the store's events, it checks them (see `Notify`). */
|
|
274
|
+
notify: Notify<TEvents, TEntity>
|
|
208
275
|
dispatch: (event: Event) => void
|
|
209
276
|
getTypes: () => Record<string, EntityType>
|
|
210
277
|
getType: (typeName: string) => EntityType
|
|
@@ -218,7 +285,7 @@ export interface Store<
|
|
|
218
285
|
}
|
|
219
286
|
setState: (nextState: TState) => void
|
|
220
287
|
reset: () => void
|
|
221
|
-
_api?: Api<TEntity, TState>
|
|
288
|
+
_api?: Api<TEntity, TState, TEvents>
|
|
222
289
|
extras?: Record<string, any>
|
|
223
290
|
}
|
|
224
291
|
|
|
@@ -248,9 +315,14 @@ export type AddEventPayload<TEntity extends BaseEntity = BaseEntity> =
|
|
|
248
315
|
export type RemoveEventPayload = string
|
|
249
316
|
|
|
250
317
|
/**
|
|
251
|
-
* Creates a store to manage state and events
|
|
318
|
+
* Creates a store to manage state and events. Told the app's events as a
|
|
319
|
+
* third type argument, its `notify` and its handlers' api check them:
|
|
320
|
+
* `createStore<AppEntity, AppState, AppEvents>({ ... })`.
|
|
252
321
|
*/
|
|
253
322
|
export function createStore<
|
|
254
323
|
TEntity extends BaseEntity = BaseEntity,
|
|
255
324
|
TState extends EntitiesState<TEntity> = EntitiesState<TEntity>,
|
|
256
|
-
|
|
325
|
+
TEvents = AnyEvents,
|
|
326
|
+
>(
|
|
327
|
+
config: StoreConfig<TEntity, TState, TEvents>,
|
|
328
|
+
): Store<TEntity, TState, TEvents>
|