@inglorious/store 12.1.1 → 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/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
- notify: (type: string, payload?: any) => void
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
- >(config: StoreConfig<TEntity, TState>): Store<TEntity, TState>
325
+ TEvents = AnyEvents,
326
+ >(
327
+ config: StoreConfig<TEntity, TState, TEvents>,
328
+ ): Store<TEntity, TState, TEvents>