skandha 0.10.1 → 0.11.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 CHANGED
@@ -1,8 +1,23 @@
1
1
  # SkandhaJS
2
2
 
3
- ## Rationale
3
+ ## Introduction
4
4
 
5
- The goal of SkandhaJS is to provide data containers that have reusable behaviors, such as selection, highlighting, filtering, drag-and-drop, etc. The only requirement for using these containers is that every item in the container has a unique id. SkandhaJS allows you to combine behaviors and make them interact properly. For example, when selecting an item you may want the item also to be highlighted. As we will see, these interactions are achieved by implementing callback functions.
5
+ SkandhaJS is a library that provides data containers with reusable behaviors, or "facets," that are implemented as classes. Examples of facets are selection, highlighting, filtering, drag-and-drop, etc. These facets provide interfaces for specific behaviors and rely on callback functions provided by the programmer to specify the details of those behaviors. The goal of SkandhaJS is to provide a way to create interactions between different behaviors in a consistent and modular way.
6
+
7
+ As a brief illustration, consider the example of selecting a todo in a list of todos:
8
+
9
+ 1. The application has a local state with a list of todos. The state contains a `todoSelection` facet so that clients can make a selection.
10
+ 2. When the user clicks on a todo, the todo-list component calls `todoSelection.selectItem(todo.id, shift, ctrl)`.
11
+ 3. The `todoSelection.selectItem` function runs the `select` function from its `callbackMap`.
12
+ 4. The `select` callback performs the selection and a side-effect: it highlights the item as well.
13
+
14
+ ## Benefits
15
+
16
+ There are three ways in which facets help you to write generic code:
17
+
18
+ 1. They can be reused in any container, regardless of what data-type is stored in the container. This means that selection, filtering and drag-and-drop work the same everywhere in the application.
19
+ 2. They allow you to capture the interaction between behaviours in reusable functions.
20
+ 3. They allow you to handle user-interaction in UI components in a uniform way. All UI components that receive a selection facet will use the same interface to make a selection (and in theory the component can remain agnostic of how selection works, or even of the type of items that are being selected).
6
21
 
7
22
  ## Links
8
23
 
@@ -15,64 +30,88 @@ The goal of SkandhaJS is to provide data containers that have reusable behaviors
15
30
  ## Glossary (conceptual)
16
31
 
17
32
  - callback function: a function that is called by an operation to help implement that operation
18
- - callbacks object: a collection of callback functions that is used by an operation.
19
- - container: a class that contains multiple facets. Typically, these facets are made to work together using callback functions.
33
+ - callbackMap object: a collection of callback functions that is used by an operation.
34
+ - container: an object that contains data and facets. Typically, these facets are made to work together using callback functions.
20
35
  - facet: a class that implements a reusable behaviour, such as selection.
21
36
  - operation: a member function of a facet that changes the data of that facet.
22
37
 
23
38
  ## Glossary (technical)
24
39
 
25
40
  - aspiration: a library that implements a form of Aspect Oriented Programming using callback functions.
26
- - mapDataToProps: a helper function that maps data from one object to another. It's also used to map data between facets.
41
+ - mapDataToProps: a helper function that maps data from one object to another. It's used to map data between facets.
27
42
  - registerCtr: the function that tells SkandhaJS which containers and facets exist.
28
- - setCallbackMap: the function that installs the callbacks objects that are used by a facet.
43
+ - setCallbackMap: the function that installs the callbackMap objects that are used by a facet.
29
44
 
30
- ## Example: a simplified Selection facet
45
+ ## Example: a Selection facet
31
46
 
32
- In Skandha, behaviours (such as selection, or filtering) are implemented in so-called facets that live in containers. For example, a todo container may have facets for selecting, highlighting and filtering todos. Because facets are reusable, they can also be used in other containers. And because facets are abstractions, you can use them to create UI components that handle selection in any container. In this example, we will show a simplified Selection facet class.
47
+ In Skandha, a behaviour such as selection is implemented in a "facet" class. The purpose of the selection facet is to store (and manipulate) the meta-data related to selection. In this example, we will show a simplified Selection facet class.
33
48
 
34
49
  ```
35
50
  // file: Selection.ts
36
51
  import { input, output, operation } from 'skandha';
37
-
38
- export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
52
+ import { DefineCbs, getCallbacks, withCbs, stub } from 'aspiration';
39
53
 
40
54
  export class Selection<ValueT = any> {
41
55
  static className = () => 'Selection';
42
56
 
43
- @input selectableIds: Array<string> = [];
57
+ @input selectableIds: Array<string> = stub();
44
58
  @output ids: Array<string> = [];
45
59
  @output anchorId?: string;
46
- @output items: Array<ValueT> = [];
60
+ @output items: Array<ValueT> = stub();
47
61
 
48
- @operation selectItem(selectionParams: SelectionParamsT) {
49
- // This function calls a callback function to do the actual work.
50
- // Details about how this works are omitted for now.
62
+ @operation @withCbs() selectItem(args: SelectionParamsT) {
63
+ const cbs = getCallbacks(this) as SelectionCbs['selectItem'];
64
+ cbs.select();
51
65
  }
52
66
  }
67
+
68
+ type Cbs<T> = {
69
+ selectItem: {
70
+ selectItem(): void;
71
+ };
72
+ };
73
+
74
+ export type SelectionCbs<T = any> = DefineCbs<Selection<T>, Cbs<T>>;
53
75
  ```
54
76
 
55
- ## Fact: facets have @input, @data and @output members
77
+ ## Facets have @input, @data and @output members
78
+
79
+ In `Selection.ts` we see the Selection facet class. It has a `selectableIds` field that stores the list of ids from which we can select. The `@input` decorator marks this field as an input. The `ids`, `anchorId` and `items` fields store the result of calling `selectItem()` and are marked as `@output` fields. Fields that are decorated with `@data` are used both as output and input. These decorators help with logging, and they also make it possible to bind the facets to state-management solutions such as MobX.
80
+
81
+ ## Operations are decorated with `@operation`.
82
+
83
+ The `Selection` facet has a `selectItem()` operation that allows you to select from the selectable ids. It stores its results in the `ids`, `anchorId` and `items` fields. As the comment mentions, `selectItem()` calls a callback function to do the actual work. This design leaves the selection method (e.g. what should happen if shift is pressed?) up to the client, which makes it possible to support different use-cases flexibly.
84
+
85
+ ## Operations are logged
56
86
 
57
- In `Selection.ts` we see the Selection facet class. It has a `selectableIds` field that stores the list of ids from which we can select. It is marked with the `@input` decorator because this field stores an input. The `ids`, `anchorId` and `items` fields are marked as `@output` fields. They are used to store the result of calling `selectItem()`. Fields that are decorated with `@data` are used both as output and input.
87
+ As a side effect of calling an operation, the entire container is logged to the console (if logging is enabled); this includes the Selection facet, and any other facets that the container may have (such as filtering, or highlight). The log message contains all @input, @data and @output fields of all facets in the container.
58
88
 
59
- ## Fact: operations are decorated with `@operation`.
89
+ ## The `@withCbs` decorator is used to enable callbacks in an operation.
60
90
 
61
- The `Selection` facet has a `selectItem()` operation that allows you to select from the selectable ids. It stores its results in the `ids`, `anchorId` and `items` fields.
62
- As the comment mentions, `selectItem()` calls a callback function to do the actual work. The reason is that the correct way to make a selection (e.g. what should happen if shift is pressed?) depends on the use-case. Therefore, we want to leave it up to the client to decide this.
91
+ The `@withCbs` decorator comes from the `Aspiration` library. When you call a function `f` that is decorated with `@withCbs` then the following happens:
63
92
 
64
- ## Fact: operations are logged
93
+ - Aspiration looks up the callbackMap object (that was installed with `setCallbackMap()`) for the given operation;
94
+ - it copies the operation arguments to the callbackMap object, so that callback functions can access these arguments;
95
+ - if calls `f(arguments)`, which internally calls the installed callback functions;
96
+ - it restores the callbackMap object to its previous state. This ensures that nested calls work correctly.
65
97
 
66
- As a side effect of calling an operation, the entire container is logged to the console; this includes the Selection facet, and any other facets that the container may have (such as filtering, or highlight). The log message contains all @input, @data and @output fields of all facets in the container.
98
+ If the original function `f` wants to access the callbackMap object then it uses the `getCallbacks` helper function. Note that the client code calls `f` in the usual way; it remains agnostic of the fact that callbacks are used to help implement `f`.
99
+
100
+ ## Facets have a static `className()` function
101
+
102
+ To allow SkandhaJS to log the container, you need to add the `className()` function to each facet class, so that Skandha knows the name of the facet.
103
+ The `className()` function is also used to look up facets in containers. Note that if you subclass a facet class then you should (in most cases)
104
+ not override the `className()`. By keeping the original `className()` function, you will be able to replace facets with their subclassed versions without affecting the facet lookup mechanism.
67
105
 
68
106
  ## Example: a container with Selection, Highlight and Filtering
69
107
 
70
108
  Now that we have some idea of what a facet is, we will discuss containers.
71
- In this example we will take a look at a todos container. It uses the `Selection` facet that we saw earlier (and some other facets that we haven't defined yet).
109
+ In this example we will take a look at a todos container. It uses the `Selection` facet that we saw earlier (and some other facets that we haven't defined yet). Note that in this example, we're not yet storing any data in the container (this will come later).
72
110
 
73
111
  ```
74
112
  // file: createContainer.ts
75
- import { Selection_selectItem } from 'SelectionCbs';
113
+ import { SelectionCbs } from 'SelectionCbs';
114
+ import * as R from 'ramda';
76
115
 
77
116
  const createContainer() {
78
117
  const todosCtr = {
@@ -83,13 +122,15 @@ const createContainer() {
83
122
 
84
123
  setCallbackMap(todosCtr.selection, {
85
124
  selectItem: {
86
- select(this: Selection_selectItem): {
87
- handleSelectItem(todosCtr.selection, this.selectionParams);
88
- highlightFollowsSelection(todosCtr, this.selectionParams);
125
+ select(this: SelectionCbs['selectItem']): {
126
+ handleSelectItem(todosCtr.selection, this.args);
127
+ highlightFollowsSelection(todosCtr, this.args);
89
128
  },
90
129
  }
91
130
  });
92
131
 
132
+ setCallbackMap(todosCtr.highlight, {});
133
+
93
134
  setCallbackMap(todosCtr.filtering, {
94
135
  apply: {
95
136
  exit() {
@@ -141,156 +182,89 @@ export function handleSelectItem(
141
182
  }
142
183
  ```
143
184
 
144
- ## Note: the todos container has selection, highlight and filtering
145
-
146
- In `createContainer.ts` we see how `createContainer()` creates a `todosCtr` object that has three facets: selection, highlight and filtering. After creating the container, the `installCallbacks()` function is called to determine how the facet operations are implemented. In this case, we only install the `select()` callback function.
185
+ ## Callbacks are installed with `setCallbackMap()`
147
186
 
148
- ## Fact: callbacks are installed with `setCallbackMap()`
187
+ We call `setCallbackMap()` to install a callbackMap object for each operation of the `Selection` facet. In our case we only have one operation (`selectItem()`) that has only one callback (`select()`). This callback function calls `handleSelectItem()` to take care of doing the selection. This is a typical pattern: you want the call to `setCallbackMap()` to be as short and readable as possible, so that it's easy to understand what happens in each operation of each facet.
149
188
 
150
- We call `setCallbackMap()` to install a callbacks object for each operation of the `Selection` facet. In our case we only have one operation (`selectItem()`) that has only one callback (`select()`). This callback function calls `handleSelectItem()` to take care of doing the selection. This is a typical pattern: you want the call to `setCallbackMap()` to be as short and readable as possible, so that it's easy to understand what happens in each operation of each facet.
189
+ ## The callback function can access the callbackMap object through `this`.
151
190
 
152
- ## Fact: callbacks can take care of side effects
191
+ When the clients calls `todosCtr.selection.selectItem()` and `select()` is called back, then all arguments of the operation are available through `this`. In our case, we see that the `select()` callback function is accessing `this.args`. See the `aspiration` library documentation for details.
153
192
 
154
- An important advantage of using callbacks is that they can also take care of side effects. In this case, it calls `highlightFollowsSelection()` to ensure that selected items are also highlighted. Note that the callback function is able to connect selection and highlight because it has access to the entire container.
193
+ ## Callbacks can take care of side effects
155
194
 
156
- ## Fact: the callback function can access the callbacks object through `this`.
195
+ An important advantage of using callbacks is that they can also take care of side effects. In this case, it calls `highlightFollowsSelection()` to ensure that selected items are also highlighted. Note that the callback function is able to connect selection and highlight because it has access to the entire container. Keeping the code of `setCallbackMap()` short also helps the reader to understand which interactions take place. This is vital, because we are purposely mixing concerns in this part of the code, which means that there is a higher risk of confusing the reader.
157
196
 
158
- When the clients calls `todosCtr.selection.selectItem()` and `select()` is called back, then all arguments of the operation are available through `this`. In our case, we see that the `select()` callback function is accessing `this.selectionParams`.
197
+ ## The `exit()` callback function is called at the end of an operation
159
198
 
160
- ## Fact: the `exit()` callback function is called at the end of an operation
199
+ In `createContainer.ts` we also have a `Filtering` facet that has an `apply(f)` operation (where `f` is the filter function). In this operation, we install the `exit()` callback that corrects the highlight when the filter function changes (because the new filter may hide the highlighted item). The `exit()` callback is called automatically at the end of each operation. Similarly, the `enter()` callback is called automatically at the beginning.
161
200
 
162
- In `createContainer.ts` we also have a `Filtering` facet that has an `apply(f)` operation (where `f` is the filter function). In this operation, we install the `exit()` callback that corrects the highlight when the filter function changes (because the new filter may hide the highlighted item). The `exit()` callback is called automatically at the end of each operation. Similarly, the `enter()` callback is called at the beginning.
201
+ ## The `registerCtr()` allows SkandhaJS to introspect the container
163
202
 
164
- ## Fact: the `registerCtr()` allows SkandhaJS to introspect the container
203
+ At the end of `createContainer.ts` we call `registerCtr()` so that SkandhaJS can introspect the container. This is necessary for two reasons. First, it makes it possible to look up a facet by its name or class, e.g. `getf(todosCtr, Selection)` returns `todosCtr.selection`. Second, it informs SkandhaJS how to log all facets of the container.
165
204
 
166
- At the end of `createContainer.ts` we call `registerCtr()` so that SkandhaJS can introspect the container. This is necessary for two reasons. First, it makes it possible to look up a facet by its name or class, e.g. `getf(todosCtr, Selection)` returns `todosCtr.selection`. Second, it informs SkandhaJS how to log all facets of the container
167
-
168
- ## Example: a complete Selection facet
205
+ ## Example: `mapDataToProps()` maps data onto each facet
169
206
 
170
- It's time now to return to the Selection facet and show how callbacks really work.
207
+ We've seen how facets are implemented, and how they can be added to a container in a way that allows us to create interactions between them. The next step is to map data between facets. Such mappings are necessary when the input data for one facet is based on the output data of another facet. For example, we should only allow selection of todos that have not been filtered out. In this example, we will see how we can create such a mapping. Moreover, we will add code to load the todos and set them as the inputs of the filtering facet.
171
208
 
172
209
  ```
173
- // file: Selection.ts
174
- import { input, output, operation } from 'skandha';
175
- import { host, stub } from 'aspiration';
176
- import { Selection_selectItem, SelectionParamsT } from 'SelectionCbs';
210
+ // file: createContainer.ts
211
+ import * from 'ramda' as R;
212
+ import { mapDataToProps } from 'skandha';
177
213
 
178
- export class Selection<ValueT = any> {
179
- static className = () => 'Selection';
214
+ class TodosData {
215
+ static className = () => 'Inputs';
180
216
 
181
- @input selectableIds: Array<string> = stub();
182
- @output ids: Array<string> = [];
183
- @output anchorId?: string;
184
- @output items: Array<ValueT> = stub();
217
+ @data todos: TodoT[] = [];
218
+ @data filteredTodos: TodoT[] = [];
185
219
 
186
- @operation @host(['selectionParams']) selectItem(
187
- selectionParams: SelectionParamsT
188
- ) {
189
- return (cbs: Selection_selectItem) => {
190
- cbs.select();
191
- };
220
+ @data get todoById() {
221
+ return R.indexBy(R.prop('id'), todosCtr);
192
222
  }
193
223
  }
194
- ```
195
-
196
- ```
197
- // file: SelectionCbs.ts
198
- import { stub } from 'aspiration';
199
-
200
- export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
201
-
202
- export class Selection_selectItem extends Cbs {
203
- selectionParams: SelectionParamsT = stub();
204
- select() {}
205
- }
206
- ```
207
-
208
- ## Fact: the `@host` decorator is used to enable callbacks in an operation.
209
-
210
- The `@host` decorator comes from the `Aspiration` library. When you call a function `f` that is decorated with `@host` then the following happens:
211
-
212
- - Aspiration looks up the callbacks object for the operation (that was installed with `setCallbackMap()`)
213
- - it copies the operation arguments to the callbacks object (so that callback functions can access these arguments)
214
- - if calls `f(arguments)(callbacksObject)`.
215
-
216
- Remember that `f` is the original function. We see from the example that `f` returns a closure. This closure receives the callbacks object, but (because it's a closure) it also has access to the arguments of f. This approach allows the client code to call `f` in the usual way; it can be agnostic of the fact that callbacks are used to help implement `f`.
217
-
218
- ## Fact: callback functions have access to all operation arguments
219
-
220
- As we mentioned earlier, each callback function of the callbacks object has access to all arguments of the operation. For this to work, it's required that you pass the list of argument names to the `@host` decorator (unfortunately, typescript does not have introspection capabilities that allow to automate this). These names need to match the corresponding fields of the callbacks object class (`Selection_selectItem`).
221
-
222
- ## Fact: facets have a static `className()` function
223
-
224
- To allow SkandhaJS to introspect the container, you need to add the `className()` function to each facet class, so that Skandha knows the name of the facet.
225
-
226
- ## Example: `mapDataToProps()` maps data onto each facet
227
-
228
- We've seen how facets are implemented, and how they can be added to a container in a way that allows us to create interactions between them. Since our container contains selection and filtering, we should only allow selection of todos that have not been filtered out. In other words, the list of selectable ids should be derived from the list of filtered todos. In this example, we will see how we can create such a mapping from the filtering facet to the selection facet. Moreover, we will add code to load the todos and set them as the inputs of the filtering facet.
229
-
230
- ```
231
- // file: createContainer.ts
232
- import * from 'ramda' as R;
233
- import { mapDataToProps, pmap } from 'skandha';
234
224
 
235
225
  const createContainer = () => {
236
226
  const todosCtr = {
237
- data: {
238
- todos: loadTodos(),
239
- filteredTodoById: {},
240
- },
227
+ data: new TodosData(),
241
228
  selection: new Selection(),
242
229
  highlight: new Highlight(),
243
230
  filtering: new Filtering(),
244
231
  }
232
+ todosCtr.data.todos = loadTodos();
233
+ const getTodoById = (id?: string) => id ? todosCtr.data.todoById[id] : undefined;
245
234
 
246
- const lookUpTodo = (id?: string) => id ? todosCtr.data.filteredTodoById[id] : undefined;
247
-
248
- mapDataToProps(
249
- pmap(
250
- [todosCtr.filtering, 'inputItems'],
251
- () => todosCtr.data.todos
252
- ),
253
- pmap(
254
- [todosCtr.data, 'filteredTodoById'],
255
- () => R.indexBy('id')(todosCtr.filtering.filteredItems)
256
- ),
257
- pmap(
258
- [todosCtr.selection, 'selectableIds'],
259
- () => R.keys(todosCtr.data.filteredTodoById)
260
- ),
261
- pmap(
262
- [todosCtr.selection, 'item'],
263
- () => R.map(lookUpTodo, todosCtr.selection.ids)
264
- ),
265
- pmap(
266
- [todosCtr.highlight, 'item'],
267
- () => lookUpTodo(todosCtr.highlight.id)
268
- ),
235
+ mapDataToProps(todosCtr,
236
+ filtering: {
237
+ inputItems: () => todosCtr.data.todos,
238
+ filteredItems: () => todosCtr.filtering.filteredItems,
239
+ },
240
+ selection: {
241
+ selectableIds: () => R.map(R.prop('id'), todosCtr.data.filteredTodos),
242
+ items: () => R.map(getTodoById, todosCtr.selection.ids),
243
+ },
244
+ highlight: {
245
+ item: () => getTodoById(todosCtr.highlight.id)
246
+ }
269
247
  )
270
248
  }
271
249
 
272
250
  setOptions({logging: true});
273
251
  ```
274
252
 
275
- ## Fact: the `mapDataToProps()` function maps data between objects
276
-
277
- The `mapDataToProp()` function takes a container, field name and function then it turns that field into a `get` property that executes the given function. The `mapDataToProps()` is a small convenience function that calls `mapDataToProp()` on each of its arguments. In our example, we see that the `todosCtr.data.todos` field is mapped onto the `todosCtr.filtering.inputItems` field.
278
-
279
- ## Fact: the `pmap()` function adds type-checking
253
+ ## The `mapDataToProps()` function maps data between objects
280
254
 
281
- The `pmap()` function takes two arguments. The first argument is an array that has a container and a container-member. The second argument is a function. The `pmap` function checks that the output of this function can be stored in the container-member (if not, then a compile-time error results). It returns an array that contains its two arguments (because that is the format that `mapDataToProps` expects). As you can see, `pmap` does not do any useful work at run-time, but it adds type-checking at compile-time.
255
+ The `mapDataToProp()` function takes a container, field name and function and installs a `get` property (on the given container, using the given field name) that executes the given function. In our example, we see that the `todosCtr.data.todos` field is mapped onto the `todosCtr.filtering.inputItems` field.
282
256
 
283
- ## Fact: logging can be enabled using `setOptions()`
257
+ ## Logging can be enabled using `setOptions()`
284
258
 
285
- By calling `setOptions({logging: true})`, the SkandhaJS library allows you to inspect each facet before and after calling an operation. All facet members that are decorated with `@data` (or `@input`, or `@output`) are logged in a way that looks similar to what you are used to from Redux:
259
+ If you call `setOptions({logging: true})` then the SkandhaJS library logs each facet before and after each operation. All facet members that are decorated with `@data` (or `@input`, or `@output`) are logged in a way similar to Redux:
286
260
 
287
261
  - before and after each operation, the entire container that holds the facet is logged
288
262
  - all data members of each facet in the container are included in the log
289
263
  - log entries are nested so that you can see how operation calls are nested
290
264
 
291
- ## Fact: the `registerCtr()` function from facets-mobx makes facets observable
265
+ ## The `registerCtr()` function from facets-mobx makes facets observable
292
266
 
293
- If you want to render the facets with React then we need to let React know when data changes. The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library was created for this purpose. It has a `registerCtr()` function that can be used instead of the default one from `skandha`. This replacement version of `registerCtr()` function turns each `@data` member into a Mobx property that is either observable or computed.
267
+ If you want to render the facets with React then we need to let React know when data changes. The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library was created for this purpose. It has a `registerCtr()` function that can be used instead of the default one from `skandha`. This drop-in replacement version of the normal `registerCtr()` function turns each `@data` member into a Mobx property that is either observable or computed.
294
268
 
295
269
  ## Conclusion
296
270
 
@@ -301,5 +275,4 @@ separating concerns in a clear and understandable way. We could add a `Editing`
301
275
  a policy that says that changing the highlight disables editing. By using facets, we will not be tempted to put
302
276
  editing-specific code directly in the highlight function (that would obviously be mixing concerns).
303
277
 
304
- In my experience, using SkandhaJS leads to code reuse and improved structure. On top of that, it offers a good
305
- debugging experience by logging all changes in the container. Finally, it works nicely with MobX to render the container contents.
278
+ In my experience, using SkandhaJS leads to code reuse and improved structure. On top of that, it offers a good debugging experience by logging all changes in the container. Finally, it works nicely with MobX to render the container contents.
package/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  export { getCtrState } from './internal/logging';
2
- export { getOptions, setOptions } from './internal/options';
3
- export { getc, getFacetMemberNames } from './lib/ctr';
2
+ export { decorateCb, getOptions, setOptions } from './internal/options';
3
+ export { getFacetMemberNames, getc } from './lib/ctr';
4
4
  export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
5
5
  export { getf } from './lib/facet';
6
6
  export { installPolicies } from './lib/install';
7
7
  export { getOperationMemberNames, operation } from './lib/operation';
8
- export { mapDataToProp, mapDataToProps, pmap } from './lib/patch';
8
+ export { mapDataToProp, mapDataToProps } from './lib/patch';
9
9
  export { registerCtr } from './lib/registerCtr';
10
10
  export type { OptionsT as RegisterCtrOptionsT } from './lib/registerCtr';
11
- export declare type ClassT = any;
11
+ export type ClassT = any;
package/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  export { getCtrState } from './internal/logging';
2
- export { getOptions, setOptions } from './internal/options';
3
- export { getc, getFacetMemberNames } from './lib/ctr';
2
+ export { decorateCb, getOptions, setOptions } from './internal/options';
3
+ export { getFacetMemberNames, getc } from './lib/ctr';
4
4
  export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
5
5
  export { getf } from './lib/facet';
6
6
  export { installPolicies } from './lib/install';
7
7
  export { getOperationMemberNames, operation } from './lib/operation';
8
- export { mapDataToProp, mapDataToProps, pmap } from './lib/patch';
8
+ export { mapDataToProp, mapDataToProps } from './lib/patch';
9
9
  export { registerCtr } from './lib/registerCtr';
@@ -1,9 +1,12 @@
1
1
  export declare const options: {
2
2
  logging: boolean;
3
3
  formatObject: (x: any) => any;
4
+ decorateCb: (x: any) => any;
4
5
  };
5
6
  export declare const getOptions: () => {
6
7
  logging: boolean;
7
8
  formatObject: (x: any) => any;
9
+ decorateCb: (x: any) => any;
8
10
  };
9
11
  export declare const setOptions: (moreOptions: any) => void;
12
+ export declare const decorateCb: (x: any) => any;
@@ -1,8 +1,10 @@
1
1
  export var options = {
2
2
  logging: false,
3
3
  formatObject: function (x) { return x; },
4
+ decorateCb: function (x) { return x; },
4
5
  };
5
6
  export var getOptions = function () { return options; };
6
7
  export var setOptions = function (moreOptions) {
7
8
  Object.assign(options, moreOptions);
8
9
  };
10
+ export var decorateCb = function (x) { return options.decorateCb(x); };
package/internal/utils.js CHANGED
@@ -1,7 +1,11 @@
1
- var __spreadArray = (this && this.__spreadArray) || function (to, from) {
2
- for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
3
- to[j] = from[i];
4
- return to;
1
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
2
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
3
+ if (ar || !(i in from)) {
4
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
5
+ ar[i] = from[i];
6
+ }
7
+ }
8
+ return to.concat(ar || Array.prototype.slice.call(from));
5
9
  };
6
10
  import { symbols } from './symbols';
7
11
  export function getOrCreate(obj, key, fn) {
@@ -16,7 +20,7 @@ export var zip = function (arr) {
16
20
  arrs[_i - 1] = arguments[_i];
17
21
  }
18
22
  return arr.map(function (val, i) {
19
- return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a), [arr[i]]); }, [val]);
23
+ return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a, true), [arr[i]], false); }, [val]);
20
24
  });
21
25
  };
22
26
  export function getCtrAdmin(ctr) {
package/lib/facet.js CHANGED
@@ -11,7 +11,7 @@ export function getf(facetClass, ctr) {
11
11
  : (_a = getCtrAdmin(ctr).facetByFacetClassName) === null || _a === void 0 ? void 0 : _a[facetClassName(facetClass)];
12
12
  if (!facet) {
13
13
  var facetName = facetClass === 'string' ? facetClass : facetClassName(facetClass);
14
- console.error("No facet " + facetName + " in container " + getCtrAdmin(ctr).logName);
14
+ console.error("No facet ".concat(facetName, " in container ").concat(getCtrAdmin(ctr).logName));
15
15
  }
16
16
  return facet;
17
17
  }
package/lib/operation.js CHANGED
@@ -13,7 +13,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
13
13
  function verb(n) { return function (v) { return step([n, v]); }; }
14
14
  function step(op) {
15
15
  if (f) throw new TypeError("Generator is already executing.");
16
- while (_) try {
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
17
  if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
18
  if (y = 0, t) op = [op[0] & 2, t.value];
19
19
  switch (op[0]) {
package/lib/patch.d.ts CHANGED
@@ -1,3 +1,8 @@
1
1
  export declare function mapDataToProp<F, P extends keyof F>(facet: F, prop: P, getter: () => F[P]): void;
2
- export declare function mapDataToProps(...mappings: any[]): void;
3
- export declare function pmap<F, P extends keyof F>(member: [F, P], getter: () => F[P]): ([F, P] | (() => F[P]))[];
2
+ type Mappings<F> = {
3
+ [K in keyof F]?: {
4
+ [P in keyof F[K]]?: () => F[K][P];
5
+ };
6
+ };
7
+ export declare function mapDataToProps<T>(target: T, mappings: Mappings<T>): void;
8
+ export {};
package/lib/patch.js CHANGED
@@ -6,16 +6,21 @@ export function mapDataToProp(facet, prop, getter) {
6
6
  configurable: true,
7
7
  });
8
8
  }
9
- export function mapDataToProps() {
10
- var mappings = [];
11
- for (var _i = 0; _i < arguments.length; _i++) {
12
- mappings[_i] = arguments[_i];
9
+ export function mapDataToProps(target, mappings) {
10
+ for (var key in mappings) {
11
+ if (mappings.hasOwnProperty(key)) {
12
+ var facet = target[key];
13
+ var propMappings = mappings[key];
14
+ if (facet && propMappings) {
15
+ for (var prop in propMappings) {
16
+ if (propMappings.hasOwnProperty(prop)) {
17
+ var getter = propMappings[prop];
18
+ if (getter) {
19
+ mapDataToProp(facet, prop, getter);
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
13
25
  }
14
- for (var _a = 0, mappings_1 = mappings; _a < mappings_1.length; _a++) {
15
- var mapping = mappings_1[_a];
16
- mapDataToProp(mapping[0][0], mapping[0][1], mapping[1]);
17
- }
18
- }
19
- export function pmap(member, getter) {
20
- return [member, getter];
21
26
  }
@@ -1,4 +1,4 @@
1
- export declare type OptionsT = {
1
+ export type OptionsT = {
2
2
  name: string;
3
3
  facets?: string[];
4
4
  ctrState?: Function;
@@ -15,13 +15,13 @@ export function registerCtr(ctr, options) {
15
15
  setCtr(facet, ctr);
16
16
  ctrAdmin.facetMembers.push(facetName);
17
17
  if (!facet.constructor.className) {
18
- console.error("Facet " + facetName + " of container " + ctrAdmin.logName + " " +
18
+ console.error("Facet ".concat(facetName, " of container ").concat(ctrAdmin.logName, " ") +
19
19
  "does not have a static className function");
20
20
  }
21
21
  var className = facetClassName(facet.constructor);
22
- getFacetAdmin(facet).logName = ctrAdmin.logName + "/" + className;
22
+ getFacetAdmin(facet).logName = "".concat(ctrAdmin.logName, "/").concat(className);
23
23
  if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
24
- console.error("Two facets of same type " + className + " in container " + ctrAdmin.logName);
24
+ console.error("Two facets of same type ".concat(className, " in container ").concat(ctrAdmin.logName));
25
25
  }
26
26
  ctrAdmin.facetByFacetClassName[className] = facet;
27
27
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skandha",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "Create data containers with modular behaviours",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/mnieber/skandha.git",