@genesislcap/foundation-store 14.408.0 → 14.409.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.
Files changed (66) hide show
  1. package/README.md +6 -654
  2. package/package.json +13 -13
  3. package/docs/api/foundation-store.abstractstore._constructor_.md +0 -50
  4. package/docs/api/foundation-store.abstractstore.addstorefragments.md +0 -54
  5. package/docs/api/foundation-store.abstractstore.binding.md +0 -104
  6. package/docs/api/foundation-store.abstractstore.bindingasrx.md +0 -16
  7. package/docs/api/foundation-store.abstractstore.bindingasrx_1.md +0 -51
  8. package/docs/api/foundation-store.abstractstore.bindingasrx_2.md +0 -51
  9. package/docs/api/foundation-store.abstractstore.broadcastchannelname.md +0 -13
  10. package/docs/api/foundation-store.abstractstore.commit.md +0 -28
  11. package/docs/api/foundation-store.abstractstore.commitvalue.md +0 -73
  12. package/docs/api/foundation-store.abstractstore.connect.md +0 -54
  13. package/docs/api/foundation-store.abstractstore.createasynclistener.md +0 -52
  14. package/docs/api/foundation-store.abstractstore.createerrorlistener.md +0 -18
  15. package/docs/api/foundation-store.abstractstore.createlistener.md +0 -18
  16. package/docs/api/foundation-store.abstractstore.disconnect.md +0 -54
  17. package/docs/api/foundation-store.abstractstore.emit.md +0 -56
  18. package/docs/api/foundation-store.abstractstore.errors.md +0 -13
  19. package/docs/api/foundation-store.abstractstore.invokeasyncapi.md +0 -106
  20. package/docs/api/foundation-store.abstractstore.md +0 -419
  21. package/docs/api/foundation-store.abstractstore.name.md +0 -13
  22. package/docs/api/foundation-store.abstractstore.removestorefragments.md +0 -54
  23. package/docs/api/foundation-store.abstractstore.root.md +0 -13
  24. package/docs/api/foundation-store.abstractstoreroot.element.md +0 -13
  25. package/docs/api/foundation-store.abstractstoreroot.md +0 -151
  26. package/docs/api/foundation-store.abstractstoreroot.onconnected.md +0 -11
  27. package/docs/api/foundation-store.abstractstoreroot.ondisconnected.md +0 -11
  28. package/docs/api/foundation-store.abstractstoreroot.onready.md +0 -11
  29. package/docs/api/foundation-store.abstractstoreroot.ready.md +0 -13
  30. package/docs/api/foundation-store.abstractstoreroot.root.md +0 -12
  31. package/docs/api/foundation-store.createerrormap.md +0 -61
  32. package/docs/api/foundation-store.errordetailmap.md +0 -16
  33. package/docs/api/foundation-store.errormap.md +0 -90
  34. package/docs/api/foundation-store.errormap.messages.md +0 -13
  35. package/docs/api/foundation-store.errormap.set.md +0 -70
  36. package/docs/api/foundation-store.errormaplogger.md +0 -16
  37. package/docs/api/foundation-store.logger.md +0 -13
  38. package/docs/api/foundation-store.md +0 -243
  39. package/docs/api/foundation-store.registerstore.md +0 -79
  40. package/docs/api/foundation-store.store.addstorefragments.md +0 -54
  41. package/docs/api/foundation-store.store.binding.md +0 -150
  42. package/docs/api/foundation-store.store.bindingasrx.md +0 -26
  43. package/docs/api/foundation-store.store.bindingasrx_1.md +0 -61
  44. package/docs/api/foundation-store.store.bindingasrx_2.md +0 -61
  45. package/docs/api/foundation-store.store.bindingasrx_3.md +0 -54
  46. package/docs/api/foundation-store.store.errors.md +0 -30
  47. package/docs/api/foundation-store.store.md +0 -171
  48. package/docs/api/foundation-store.store.name.md +0 -14
  49. package/docs/api/foundation-store.store.removestorefragments.md +0 -54
  50. package/docs/api/foundation-store.storebinding.md +0 -13
  51. package/docs/api/foundation-store.storeconnectable.connect.md +0 -54
  52. package/docs/api/foundation-store.storeconnectable.disconnect.md +0 -54
  53. package/docs/api/foundation-store.storeconnectable.md +0 -51
  54. package/docs/api/foundation-store.storeroot.element.md +0 -13
  55. package/docs/api/foundation-store.storeroot.md +0 -128
  56. package/docs/api/foundation-store.storeroot.onconnected.md +0 -50
  57. package/docs/api/foundation-store.storeroot.ondisconnected.md +0 -50
  58. package/docs/api/foundation-store.storeroot.onready.md +0 -50
  59. package/docs/api/foundation-store.storeroot.ready.md +0 -13
  60. package/docs/api/foundation-store.storerooteventdetailmap.md +0 -17
  61. package/docs/api/foundation-store.storesubscriber.handlechange.md +0 -11
  62. package/docs/api/foundation-store.storesubscriber.md +0 -54
  63. package/docs/api/foundation-store.subscriberchangecallback.md +0 -11
  64. package/docs/api/foundation-store.subscriberchangehandler.md +0 -13
  65. package/docs/api/index.md +0 -30
  66. package/docs/api-report.md.api.md +0 -189
package/README.md CHANGED
@@ -1,673 +1,25 @@
1
- # Genesis Foundation Store
1
+ # @genesislcap/foundation-store
2
2
 
3
- [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)
4
- [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](https://www.typescriptlang.org/)
5
-
6
- `foundation-store` leverages the DI container to provide a preformat, decoupled and testable way to manage application
7
- state that adheres to our best practices. Using `foundation-store` is completely optional, as you may decide that your
8
- application doesn't warrant a store, or that you would prefer to use a store you are more familiar with. The system is
9
- flexible, so you can use whatever you like to handle application level state management, but you should be mindful of
10
- degrading performance.
11
-
12
- ## Background
13
-
14
- Client apps today manage application state in different ways. These apps might leverage common third party stores likes
15
- Redux, or use none at all, peppering @attr and @observable properties in different classes, and at various levels of DOM
16
- hierarchy. With the latter business logic might start creeping into our components, every component becomes smart
17
- instead of being dumb, providing shared access to data becomes difficult, tests require lots of mocks, things get hard
18
- to refactor etc. We should aim to lift state where possible, and it makes sense to do so.
19
-
20
- _We need to remember that although our components will be used in Genesis applications, they may also be used in
21
- isolation in third party client applications._
22
-
23
- ## Migration
24
-
25
- Hopefully you're keen to try `foundation-store`, and have used solutions like Redux in the past. If so you may find the
26
- following "Solution X" to `foundation-store` terminology migration guide beneficial.
27
-
28
- ### Redux
29
-
30
- - **Action**. Translates to a standard [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).
31
- These event types are defined in a store's EventDetailMap. Here's the [StoreRootEventDetailMap](./docs/api/foundation-store.storerooteventdetailmap.md) for example.
32
- - **Dispatch**. Use the component's [$emit](https://github.com/microsoft/fast/blob/master/packages/web-components/fast-element/docs/guide/working-with-shadow-dom.md#custom-events)
33
- method in conjunction with the [EventEmitter](../foundation-events/src/eventEmitter/README.md) mixin to strongly type it with store event maps.
34
- - **Action Creator**. Create the CustomEvent.[detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail)
35
- however and whenever you like. When you're ready, emit the event and detail pairing as per an EventDetailMap via the component's
36
- [$emit](https://github.com/microsoft/fast/blob/master/packages/web-components/fast-element/docs/guide/working-with-shadow-dom.md#custom-events) api, which in turn creates and sends the CustomEvent.
37
- - **Reducer**. Use the store's [createListener](./docs/api/foundation-store.abstractstore.createlistener.md) method to
38
- create a synchronous event listener which you can [commit values to the store](./docs/api/foundation-store.abstractstore.commit.md) from.
39
- These listeners only receive events, so new values may come from `CustomEvent.detail` payloads, and / or reading from the store itself which these handlers are members of.
40
- - **Effect**. Use the store's [createAsyncListener](./docs/api/foundation-store.abstractstore.createasynclistener.md) method to create an async event listener which can run Side Effects.
41
- Similar to the Reducer context above, however you should _NOT_ commit values to the store in these, but instead
42
- [emit](./docs/api/foundation-store.abstractstore.emit.md) outcome events, ie. success / failure, which can be handled by synchronous listeners.
43
- - **Slice**. A store fragment. A part of the store with a specific purpose, domain model.
44
- - **Selector**. A simple getter on a store fragment.
45
-
46
- ## Setup
47
-
48
- Create a root store.ts file somewhere, for example `./store/store.ts`. This will be the root store for the application,
49
- which may consist for other store fragments. Each fragment could be considered as a domain, with a single purpose. This
50
- setup allows us to isolate data and provide the component trees access to only the data they really need to function.
51
-
52
- Example root store: (insert see examples link)
53
-
54
- ```typescript
55
- import {CustomEventMap, EventListenerMap, registerEmitter} from '@genesislcap/foundation-events';
56
- import {
57
- AbstractStoreRoot,
58
- StoreRoot,
59
- StoreRootEventDetailMap,
60
- registerStore,
61
- } from '@genesislcap/foundation-store';
62
- import {observable, volatile} from '@microsoft/fast-element';
63
- import {DesignSystem} from './designSystem';
64
- import {Position} from './position';
65
- import {Trades} from './trades';
66
-
67
- /**
68
- * 1: Define any store custom event details for more complex payloads.
69
- * See https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail
70
- */
71
- export interface StoreZooEventDetail {
72
- zoo: Animal[];
73
- location: string;
74
- }
75
-
76
- /**
77
- * 2: Define store event to event detail map.
78
- * For the root store this should be a union of StoreRootEventDetailMap.
79
- */
80
- export type StoreEventDetailMap = StoreRootEventDetailMap & {
81
- 'store-foo': void;
82
- 'store-bar': boolean;
83
- 'store-zoo': StoreZooEventDetail; // < details with more than one property
84
- 'store-zoo-animals': Animal[];
85
- }
86
-
87
- /**
88
- * 3: Extend built in event maps so that addEventListener/removeEventListener are aware of our events for code completion
89
- */
90
- declare global {
91
- interface HTMLElementEventMap extends CustomEventMap<StoreEventDetailMap> {}
92
- }
93
-
94
- /**
95
- * 4: Define internal event to event detail map.
96
- */
97
- type InternalEventDetailMap = {
98
- 'store-zoo-success': SomeResponse;
99
- 'store-zoo-error': Error;
100
- }
101
-
102
- /**
103
- * 5: Define entire readonly store made up of store fragments and or additional properties.
104
- */
105
- export interface Store extends StoreRoot {
106
- /**
107
- * Store properties
108
- */
109
- readonly prop1: number;
110
- readonly prop2: number;
111
- readonly someToggle: boolean;
112
- readonly derivedData: number;
113
- readonly volatileDerivedData: number;
114
- /**
115
- * Store fragments
116
- */
117
- readonly designSystem: DesignSystem;
118
- readonly notifications: Notifications;
119
- readonly positions: Positions;
120
- readonly trades: Trades;
121
- /**
122
- * Store event handlers
123
- */
124
- onFooEvent(event: CustomEvent<void>): void;
125
- onBarEvent(event: CustomEvent<boolean>): void;
126
- onZooEvent(event: CustomEvent<StoreZooEventDetail>): void;
127
- }
128
-
129
- /**
130
- * 6: Define the default implementation
131
- */
132
- class DefaultStore extends AbstractStoreRoot<Store, StoreEventDetailMap, InternalEventDetailMap> implements Store {
133
- /**
134
- * Store properties
135
- */
136
- @observable prop1: number = 10;
137
- @observable prop2: number = 20;
138
- @observable someToggle: boolean = true;
139
-
140
- constructor(
141
- /**
142
- * 7: Inject any store fragments
143
- */
144
- @DesignSystem readonly designSystem: DesignSystem,
145
- @Notifications readonly notifications: Notifications,
146
- @Positions readonly positions: Positions,
147
- @Trades readonly trades: Trades,
148
- ) {
149
- super(...arguments);
150
-
151
- /**
152
- * 8: Listeners not on the public interface can be created anonymously if you prefer
153
- */
154
- this.createListener<SomeResponse>('store-zoo-succes', (detail) => {
155
- const {prop1, prop2, ...rest} = detail;
156
- this.commit.prop1 = prop1;
157
- this.commit.prop2 = prop2;
158
- });
159
- this.createErrorListener('store-zoo');
160
- }
161
-
162
- /**
163
- * 8: Define your event listeners as per the interface. Please ensure you do so using arrow functions to aid binding.
164
- * These handlers can be async if you would like to do some async work in them. We suggest you don't commit store
165
- * mutations in async functions, instead raise an event which you can handle synchronously and commit from there so
166
- * things are tracked correctly.
167
- */
168
- onFooEvent = this.createListener('store-foo', detail => {...});
169
- onBarEvent = this.createListener<boolean>('store-bar', detail => this.commit.someToggle = detail); // < commit values to the store synchronously
170
- onZooEvent = this.createAsyncListener<StoreZooEventDetail>('store-zoo', async (detail) =>
171
- this.invokeAsyncAPI(
172
- async () => this.someAsyncTask(detail), // < likely an injected service,
173
- 'store-zoo-error',
174
- 'store-zoo-success'
175
- )
176
- );
177
-
178
- /**
179
- * 9: Create getters for common derived data needs, similar to selectors in the Redux sense. These however do not
180
- * need any special code as they are computing based on properties that are already observable. Derivied data with
181
- * branching code paths needs to be marked as volatile.
182
- */
183
-
184
- get derivedData(): number {
185
- return this.prop1 * this.prop2;
186
- }
187
-
188
- @volatile
189
- get volatileDerivedData() {
190
- return this.someToggle ? this.prop1 * this.prop2 : this.prop1 + this.prop2;
191
- }
192
- }
193
-
194
- /**
195
- * 10: Register the store which defines the DI key using the interface
196
- */
197
- export const Store = registerStore<Store>(DefaultStore, 'RootStore');
198
- ```
199
-
200
- Your root store is now ready to be injected into your application. Hopefully the above gives you a good idea of general
201
- store setup. The example might look a bit verbose, but in reality you can write a small store fragment in 20+ lines of
202
- code. For example:
203
-
204
- ```typescript
205
- import {CustomEventMap} from '@genesislcap/foundation-events';
206
- import {AbstractStore, Store, registerStore} from '@genesislcap/foundation-store';
207
- import {observable} from '@microsoft/fast-element';
208
-
209
- export type TradeEntryUIEventDetailMap = { 'trade-entry-ui-open': boolean }
210
-
211
- declare global {
212
- interface HTMLElementEventMap extends CustomEventMap<TradeEntryUIEventDetailMap> {}
213
- }
214
-
215
- export interface TradeEntryUI extends Store {
216
- readonly isOpen: boolean;
217
- onOpen(event: CustomEvent<boolean>): void;
218
- }
219
-
220
- class DefaultTradeEntryUI extends AbstractStore<TradeEntryUI, TradeEntryUIEventDetailMap> implements TradeEntryUI {
221
- @observable isOpen: boolean;
222
- onOpen = this.createListener<boolean>('trade-entry-ui-open', detail => this.commit.isOpen = detail);
223
- }
224
-
225
- export const TradeEntryUI = registerStore<TradeEntryUI>(DefaultTradeEntryUI, 'TradeEntryUI');
226
- ```
227
-
228
- ## Store events
229
-
230
- ### `'store-connected'`
231
-
232
- In your main application class, likely `./src/main/main.ts`, you need to fire a `'store-connected'` event in-order to
233
- fully initialise the store.
234
-
235
- ```typescript
236
- // ./main/main.ts
237
-
238
- type EventMap = StoreEventDetailMap & {...}; // < whatever other events you allow your custom element to emit
239
-
240
- /**
241
- * MainApplication
242
- *
243
- * @fires store-connected - Fires a custom 'store-connected' event after connection to connect the store.
244
- * @fires store-ready - Fires a custom 'store-ready' event after connection to ready the store.
245
- * @fires store-disconnected - Fires a custom 'store-disconnected' event after disconnection to unbind the store.
246
- *
247
- * @public
248
- */
249
- @customElement({ name, template, styles })
250
- export class MainApplication extends EventEmitter<EventMap>(FASTElement) {
251
- /**
252
- * @public
253
- */
254
- @Store store: Store; // < injected root store
255
-
256
- /**
257
- * @public
258
- */
259
- connectedCallback() {
260
- super.connectedCallback();
261
- this.addEventListeners();
262
- this.readyStore();
263
- }
264
-
265
- /**
266
- * @public
267
- */
268
- disconnectedCallback() {
269
- super.disconnectedCallback();
270
- this.removeEventListeners();
271
- this.disconnectStore();
272
- }
273
-
274
- /**
275
- * @internal
276
- */
277
- protected addEventListeners() {
278
- this.addEventListener('store-connected', this.store.onConnected);
279
- }
280
-
281
- /**
282
- * @internal
283
- */
284
- protected removeEventListeners() {
285
- this.removeEventListener('store-connected', this.store.onConnected);
286
- }
287
-
288
- /**
289
- * @internal
290
- */
291
- protected readyStore() {
292
- this.$emit('store-connected', this);
293
- /**
294
- * Do some other work if needed.
295
- */
296
- this.$emit('store-ready', true);
297
- }
298
-
299
- /**
300
- * @internal
301
- */
302
- protected disconnectStore() {
303
- this.$emit('store-disconnected');
304
- }
305
- }
306
- ```
307
-
308
- The `'store-connected'` event handler needs to be explicitly bound. When the root store handles `'store-connected'`,
309
- it auto binds all the store event listeners to the rootElement.
310
-
311
- At this point you can start emitting strongly typed store events, and they will be handled by their corresponding store.
312
- See [EventEmitter](src/.foundation/events/eventEmitter/README.md) for more information.
313
-
314
- ### `'store-ready'`
315
-
316
- We've created an explicit store ready event to indicate when the store is ready to use, as you may wish to do some
317
- additional work upfront. It's not a hard requirement to emit this, but is considered best practice. If you've no work to
318
- do, you can just emit this right after `'store-connected'`.
319
-
320
- ```typescript
321
- // ./main/main.ts
322
-
323
- this.$emit('store-connected', this);
324
- /**
325
- * Do some other work if needed.
326
- */
327
- this.$emit('store-ready', true);
328
- ```
329
-
330
- ### `'store-disconnected'`
331
-
332
- Emitting `'store-disconnected'` will remove all the previously bound event listeners.
333
-
334
- ```typescript
335
- // ./main/main.ts
336
-
337
- disconnectedCallback() {
338
- super.disconnectedCallback();
339
- this.$emit('store-disconnected');
340
- }
341
- ```
342
-
343
- ## Using store fragments
344
-
345
- To use a store fragment in your custom element simply inject it using its `interface` as the DI key.
346
-
347
- ```typescript
348
- export type EventMap = TradeEntryEventDetailMap;
349
-
350
- export class TradeEntryForm extends EventEmitter<EventMap>(FASTElement) {
351
- @TradeEntry tradeEntry: TradeEntry;
352
- }
353
- ```
354
-
355
- Now in your template you can use the values from the store fragment and raise typed events.
356
-
357
- ````typescript jsx
358
- import type {TradeEntryForm, EventMap} from './home';
359
-
360
- const inputEmit = createInputEmitter<EventMap>();
361
-
362
- <zero-text-field
363
- type="number"
364
- :value=${x => x.tradeEntry.price}
365
- @change="${inputEmit('trade-entry-price-changed')}"
366
- >
367
- ````
368
-
369
- `createEmitChangeAs` is a utility from `@genesislcap/foundation-events` to allow components with string change inputs
370
- to emit their string values as typed event detail payloads. It will warn you if you try to use it with a component that
371
- doesn't have a string target.value type. It's a convenience method only and is the same as writing:
372
-
373
- ```typescript jsx
374
- <zero-text-field
375
- type="number"
376
- :value=${x => x.tradeEntry.price}
377
- @change="${(x, c) => x.$emit('trade-entry-price-changed', targetValue(c))}"
378
- >
379
- ````
380
-
381
- We will be adding a number of these for Selects and other primitives. You can of course call to a class method and have
382
- that emit the typed event to the store, but the idea with the helpers are to remove some boilerplate and misdirection.
383
-
384
- ```typescript jsx
385
- <zero-select @change=${((x, c) => x.instrumentIdChange(c.event.target as Select))}>
386
- ```
387
-
388
- ```typescript
389
- instrumentIdChange(target: Select) {
390
- this.$emit('trade-entry-instrumentID-changed', target.selectedOptions[0]?.value);
391
- }
392
- ```
393
-
394
- If the component in question is perhaps a library asset or simply needs to remain unaware of the DI, you can map store
395
- properties to the custom element via its attributes.
396
-
397
- ```typescript jsx
398
- // ./some-parent/some-parent.template.ts
399
-
400
- <trade-entry-form price="${x => x.tradeEntry.price}" />
401
- ```
402
-
403
- If the components are specific to your application and won't be shared, and you've split up your store into appropriate
404
- domain specific fragments, injecting the store fragment directly into where it's needed will greatly reduce mapping
405
- boilerplate. Remember components should only have access to the data they need. If the injected store fragment provides
406
- them more than that, consider splitting that store fragment up further, or reworking your store structure.
407
-
408
- ### Accessing values from one store fragment to another
409
-
410
- If you need to read values from another part of the store in your store you may be able to simple inject it, but you
411
- won't need to pass it to the super class for processing if that's done elsewhere. If you would rather not inject it, you
412
- can use `this.root.fragmentX.fragmentY.value`. You should provide root store type information when creating your store
413
- fragment that will be reading root, for example:
414
- ```typescript
415
- import type {Store as StoreRoot} from '../store';
416
- ...
417
- class DefaultPositions extends AbstractStore<Positions, PositionsEventDetailMap, InternalEventDetailMap, StoreRoot> implements Positions {
418
- onPositionSelected = this.createListener<PositionEntity>('position-selected', (position) => {
419
- const lastTradeId = this.root.trades.selectedTrade.id;
420
- ...
421
- });
422
- }
423
- ```
424
-
425
- ### Testing with the injected dependency in place
426
-
427
- Using a DI based store is very powerful as it allows us to swap out our store fragments when needed, ie. a unit test:
428
-
429
- ```typescript jsx
430
- const mock = new TradeEntryMock();
431
- const Suite = createComponentSuite<TradeEntryForm>('TradeEntryForm Test', () => tradeEntryForm(), null, [
432
- Registration.instance(TradeEntry, mock),
433
- ]);
434
- ```
435
-
436
- The code in your component remains as is.
437
-
438
- ## Committing value mutations
439
-
440
- Stores are read only, so if you try to set a property directly TS will flag this. To commit a value to the store you
441
- must emit a known store event. In these event handlers, you call `this.commit` prepended with the value you want to
442
- mutate. For example:
443
-
444
- ```typescript
445
- // some trades store fragment
446
-
447
- onTradeSelected = this.createListener<TradeEntity>('trade-selected', trade => this.commit.selectedTrade = trade);
448
- ```
449
-
450
- The `this.commit` interface is typed the same as the store fragment itself for full code completion, and simply acts as
451
- a proxy to the underlying store. Currently, an alternative api also exists called commitValue if you prefer.
452
-
453
- ```typescript
454
- onTradeSelected = this.createListener<TradeEntity>('trade-selected', trade => this.commitValue('selectedTrade', trade));
455
- ```
456
-
457
- The store won't complain if you forget or just don't want to use the commit api, it just means value changes won't be
458
- tracked overtime.
459
-
460
- ```typescript
461
- onTradeSelected = this.createListener<TradeEntity>('trade-selected', trade => this.selectedTrade = trade);
462
- ```
463
-
464
- ## Side effects
465
-
466
- When you need to reach out to another part of the system or generally do some async work, you should ensure the initial
467
- event handler is async. We recommend that you don't commit values in this handler, as it may become difficult to track
468
- mutations overtime if other events are occurring, but it's really up to you if you want to just await and commit.
469
- Ideally we want store interactions to be standardised, predictable and traceable.
470
-
471
- ```typescript
472
- constructor(
473
- @TradeEntry readonly tradeEntry: TradeEntry,
474
- @TradesService readonly service: TradesService,
475
- ) {
476
- super(tradeEntry); // < only pass the super child sub store fragments
477
-
478
- /**
479
- * Listeners not on the public interface can be created anonymously if you prefer
480
- */
481
- this.createListener<TradeEntity[]>('trades-load-success', trades => this.commit.trades = trades);
482
- this.createErrorListener('trades-load-error', () => this.commit.trades = undefined);
483
- }
484
-
485
- onTradesLoad = this.createAsyncListener<string>('trades-load', async (positionId) =>
486
- this.invokeAsyncAPI(
487
- async () => this.service.getTrades(positionId),
488
- 'trades-load-error',
489
- 'trades-load-success'
490
- )
491
- );
492
- ```
493
-
494
- ### Errors
495
-
496
- When you use `this.createErrorListener`, stores will automatically keep track of any errors that may occur in a
497
- `store.errors` map that is keyed by the event type. This means your store can hold multiple errors at a time. To output
498
- all the messages at once you can bind to `store.errors.messages`, for example:
499
-
500
- ```typescript jsx
501
- <pre>${x => x.trades.errors.messages}</pre>
502
- ```
503
-
504
- You can check for specific errors too:
505
-
506
- ```typescript jsx
507
- ${when(x => x.trades.errors.has('trade-insert-error'), html<TradeInsertErrorFeedback>`...`)}
508
- ```
509
-
510
- This granularity can be useful if say you wanted to raise error notifications and allow users to clear errors one-by-one.
511
-
512
- ```typescript
513
- this.trades.errors.delete('trade-insert-error');
514
-
515
- // or clear them all
516
- this.trades.errors.clear();
517
- ```
518
-
519
- Please note that `store.errors` is not a true `Map` so doesn't have all the native `Map` apis, however it should have
520
- most of what you need. You also operate on the errors map directly without raising events. This is like this for
521
- simplicity, and because errors are transient in nature.
522
-
523
- ## Store bindings
524
-
525
- You can easily bind to the properties and getters of a store inside the internal template engine, or outside of the
526
- template engine via the store's `binding()` api.
527
-
528
- ### Template store bindings
529
-
530
- For example, you may wish to monitor the value of `this.store.ready` via template bindings to conditional render a
531
- loading phase in your template.
532
-
533
- ```typescript jsx
534
- // ./main/main.template.ts
535
-
536
- ${when(x => !x.store.ready, html<Loading>`Loading...`)}
537
- ```
538
-
539
- ...or monitor the value indirectly, to swap out the entire template.
540
-
541
- ```typescript
542
- // ./main/main.ts
543
-
544
- selectTemplate() {
545
- return this.store.ready ? MainTemplate : LoadingTemplate;
546
- }
547
-
548
- // ./main/main.template.ts
549
-
550
- <template ...>
551
- ${x => x.selectTemplate()}
552
- </template>
553
- ```
554
-
555
- ### Direct store bindings
556
-
557
- To bind to the store outside the template engine, you use the store's `binding()` api. The store's `binding()` api is
558
- strongly typed, so you will be unable to bind to a non-existent property or getter. You also don't need to think about
559
- if the data point is a property or a getter, perhaps returning derived data, as the api works the same for both cases.
560
-
561
- Some examples:
562
-
563
- ```typescript
564
- // TS knows the returned type of value
565
- this.store.binding(x => x.ready, value => {...});
566
- this.store.binding(x => x.someNumericProp, value => {...});
567
-
568
- // TS needs some help to type the returned value, so you need to provide that (we hope to fix this).
569
- this.store.binding<boolean>('ready', value => {...});
570
- this.store.binding<number>('someNumericProp', value => {...});
571
-
572
- // You can even create your own derived binding that are not already defined as getters in the store
573
- this.store.binding(x => x.prop1 * x.prop2, value => {...});
574
-
575
- // These can even be volatile providing you pass true for isVolatileBinding
576
- this.store.binding(x => x.someToggle ? x.prop1 : x.prop2, value => {...}, true);
577
- ```
578
-
579
- Here is an example of using the underlying
580
- [bindingObserver](https://www.fast.design/docs/fast-element/observables-and-state#bindings) as per docs.
581
-
582
- ```typescript
583
- this.store.binding(x => x.prop1).subscribe({
584
- handleChange(source) { // < note that the source is the bindingObserver itself, x => x.prop1, and not the value of the change
585
- ...
586
- }
587
- })
588
- ```
589
-
590
- Updates are batch processed, so if a bunch of properties are updated in the store that any of the stores getters or
591
- derived bindings you've passed in via the `binding()` api use, they will by design tick only once to avoid
592
- any unnecessary updates and potential re-renders.
593
- See [Reactivity](https://www.fast.design/docs/fast-element/observables-and-state#reactivity) for more information.
594
-
595
- ### Direct store bindings as RxJS
596
-
597
- Stores also offer a `bindingAsRx()` api which returns a Rxjs Observable to allow you to observe a value using Rxjs which
598
- may be useful depending on the needs of your application.
599
-
600
- ```typescript
601
- const entireStore$ = this.store.bindingAsRx().subscribe(value => {...});
602
-
603
- const ready$ = this.store.bindingAsRx('ready').subscribe(value => {...});
604
-
605
- const prop1$ = this.store.bindingAsRx(x => x.prop1).subscribe(value => {...});
606
- ```
607
-
608
- **_We may need to do some additional work here to finish this feature._**
609
-
610
- ## UI state in stores
611
-
612
- You may want to keep your UI state in your stores alongside the application data. If so consider splitting these up into
613
- separate fragments, for example:
614
-
615
- - TradeEntry: Trade entry field values (Data).
616
- - TradeEntryUI: Trade entry presentation status, isOpen, isLoading etc. (UI State).
617
-
618
- UI state can of course be kept in the component itself, however there may be reasons to keep it in the store. Consider
619
- if you needed to know if the trade entry was being presented on-screen in a different part of the application. You could
620
- try to listen for the `'trade-entry-ui-open'` event, but until the event hits the target store and is committed, the
621
- true state isn't guaranteed. The store should be the single source of truth.
622
-
623
- With that in mind, we could inject that UI state store fragment and bind to the value of interest, for example:
624
-
625
- ```typescript
626
- // some other part of the UI
627
-
628
- this.tradeEntryUI.binding(x => x.isOpen, value => value ? this.stop() : this.start());
629
- ```
630
-
631
- Keeping UI state in a store also might make it easier for you to save a snapshot of the UI state for rehydration, ie.
632
- what windows did the user have open etc. You may wish to add some middleware to the commit proxy (base store concept)
633
- for converting state transitions into browser history entries, which might allow you to deep link and press browser back
634
- button to say close the modal, with each back button moving backwards though the UI state. Be careful not to blur the
635
- lines between the data and the UI state in our store fragments.
636
-
637
- For example a UI conditional could map to a UI sub fragment, whereas a datapoint could map to the parent fragment. The
638
- component would only need tradeEntry injected to have access to both, depending on how we structure fragments.
639
- ```typescript jsx
640
- ${when(x => x.tradeEntry.ui.isOpen, html<TradeEntryForm>`
641
- <zero-card>
642
- <zero-text-field :value=${x => x.tradeEntry.quantity}>
643
- </zero-card>
644
- `}
645
- ```
3
+ Documentation for this package is published on the Genesis docs site:
646
4
 
5
+ **Docs: [Foundation Store](https://docs.genesis.global/docs/develop/client-capabilities/state-management/foundation-store/)**
647
6
 
648
7
  ## Installation
649
8
 
650
- To enable this module in your application, follow the steps below.
651
-
652
- 1. Add `@genesislcap/foundation-store` as a dependency in your `package.json` file. Whenever you change the dependencies of your project, ensure you run the `$ npm run bootstrap` command again. You can find more information in the [package.json basics](https://learn.genesis.global/secure/web/basics/package-json-basics/) page.
9
+ Add the package to your `package.json` dependencies. After changing dependencies, run `npm run bootstrap` (or your project's equivalent). See [package.json basics](https://learn.genesis.global/secure/web/basics/package-json-basics/) for more information.
653
10
 
654
11
  ```json
655
12
  {
656
- ...
657
13
  "dependencies": {
658
- ...
659
14
  "@genesislcap/foundation-store": "latest"
660
- ...
661
- },
662
- ...
15
+ }
663
16
  }
664
17
  ```
665
18
 
666
- ## [API Docs](./docs/api/index.md)
667
-
668
19
  ## License
669
20
 
670
21
  Note: this project provides front-end dependencies and uses licensed components listed in the next section; thus, licenses for those components are required during development. Contact [Genesis Global](https://genesis.global/contact-us/) for more details.
671
22
 
672
23
  ### Licensed components
24
+
673
25
  Genesis low-code platform