skandha 0.9.0 → 0.10.1

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
@@ -2,10 +2,7 @@
2
2
 
3
3
  ## Rationale
4
4
 
5
- The goal of SkandhaJS is to provide data containers that have reusable behaviors, such as
6
- selection, highlighting, filtering, drag-and-drop, etc. The only requirement for using these containers is
7
- 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
8
- be highlighted. As we will see, these interactions are achieved by implementing callback functions.
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.
9
6
 
10
7
  ## Links
11
8
 
@@ -15,94 +12,105 @@ be highlighted. As we will see, these interactions are achieved by implementing
15
12
  - The [aspiration](http://github.com/mnieber/aspiration) library handles aspect oriented programming (AOP)
16
13
  - The [example code](http://github.com/mnieber/skandha-sample-app) shown in this README.
17
14
 
18
- ## Snippet
15
+ ## Glossary (conceptual)
16
+
17
+ - 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.
20
+ - facet: a class that implements a reusable behaviour, such as selection.
21
+ - operation: a member function of a facet that changes the data of that facet.
22
+
23
+ ## Glossary (technical)
24
+
25
+ - 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.
27
+ - 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.
29
+
30
+ ## Example: a simplified Selection facet
31
+
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.
19
33
 
20
34
  ```
21
35
  // file: Selection.ts
22
36
  import { input, output, operation } from 'skandha';
23
- import { host, stub } from 'aspiration';
24
37
 
25
38
  export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
26
39
 
27
40
  export class Selection<ValueT = any> {
28
41
  static className = () => 'Selection';
29
42
 
30
- @input selectableIds: Array<string> = stub();
43
+ @input selectableIds: Array<string> = [];
31
44
  @output ids: Array<string> = [];
32
45
  @output anchorId?: string;
33
- @output items: Array<ValueT> = stub();
46
+ @output items: Array<ValueT> = [];
34
47
 
35
- @operation @host(['selectionParams'], selectItemDefaultCbs) selectItem(
36
- selectionParams: SelectionParamsT
37
- ) {
38
- return (cbs: Selection_selectItem) => {
39
- cbs.select();
40
- };
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.
41
51
  }
42
52
  }
43
53
  ```
44
54
 
45
- ## Fact: the Selection facet has input, data and output members
55
+ ## Fact: facets have @input, @data and @output members
46
56
 
47
- In the snippet above we see a so-called Facet class. A facet class implements a reusable behaviour. The `Selection` facet stores a list of selectable ids and has a `selectItem` function that allows you to select from those ids. The result of the selection is stored in the `ids`, `anchorId` and `items` fields. Note that `selectItem` is an empty function. The exact method for doing the selection is determined by a callback function, as we shall see later.
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.
48
58
 
49
- ## Fact: the Selection facet has a static className function
59
+ ## Fact: operations are decorated with `@operation`.
50
60
 
51
- Various facets are usually combined in a single container. To allow you to look up a facet by it's name, each facet class implements the static `className` function.
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.
52
63
 
53
- ## Fact: the `selectItem` function uses a callback function
64
+ ## Fact: operations are logged
54
65
 
55
- We shall see later how callbacks are installed in a facet. For now, it's sufficient to understand that `selectItem` (the host function) executes the `cbs.selectItem()` callback function. This callback function will have access to all parameters of the host function (in this case: `selectionParams`).
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.
56
67
 
57
- ## Snippet
68
+ ## Example: a container with Selection, Highlight and Filtering
58
69
 
59
- ```
60
- // file: Selection.ts
61
- export class Selection_selectItem extends Cbs {
62
- selectionParams: SelectionParamsT = stub();
63
- select() {}
64
- }
65
- ```
70
+ 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).
66
72
 
67
73
  ```
68
74
  // file: createContainer.ts
69
- import { registerCtr } from 'skandha';
75
+ import { Selection_selectItem } from 'SelectionCbs';
70
76
 
71
77
  const createContainer() {
72
- const ctr = {
73
- selection: new Selection()
78
+ const todosCtr = {
79
+ selection: new Selection(),
80
+ highlight: new Highlight(),
81
+ filtering: new Filtering()
74
82
  };
75
83
 
76
- setCallbacks(ctr.selection, {
84
+ setCallbackMap(todosCtr.selection, {
77
85
  selectItem: {
78
86
  select(this: Selection_selectItem): {
79
- handleSelectItem(ctr.selection, this.selectionParams);
87
+ handleSelectItem(todosCtr.selection, this.selectionParams);
88
+ highlightFollowsSelection(todosCtr, this.selectionParams);
80
89
  },
81
90
  }
82
91
  });
83
92
 
84
- // If you omit `members` then all fields of `ctr` are included.
85
- registerCtr(ctr, { name: 'TodosCtr', members: ['selection'] });
86
- return ctr;
93
+ setCallbackMap(todosCtr.filtering, {
94
+ apply: {
95
+ exit() {
96
+ highlightIsCorrectedOnFilterChange(todosCtr);
97
+ },
98
+ },
99
+ });
100
+
101
+ registerCtr({ ctr: todosCtr, options: { name: 'Todos'} });
102
+ return todosCtr;
87
103
  }
88
104
  ```
89
105
 
90
- ## Fact: a Cbs class defines a set of callbacks
91
-
92
- In the above snippet we see that the `Selection_selectItem` class defines the callbacks that can be installed for the `selectItem` function of the `Selection` facet. This class also defines the parameters that are received by the host function and made available to each callback.
93
-
94
- ## Fact: the setCallbacks function installs the callbacks for the Selection facet
95
-
96
- In the same snippet we see that `setCallbacks` is called to install the `select` callback function. This callback function forwards to a helper function called `handleSelectItem`. Note that the `Selection_selectItem` instance is available through the `this` constant.
97
-
98
- ## Fact: the registerCtr function registers the container with SkandhaJS
99
-
100
- The purpose of calling registerCtr on the container is twofold:
101
-
102
- - it makes it possible to look up a facet by its name or class, e.g. `getf(ctr, Selection)` returns `ctr.selection`.
103
- - it tells SkandhaJS what facets should be included in log reports. When the client calls any facet function that is decorated with @operation then SkandhaJS will log the entire container before and after the operation was executed. Logging can be enabled using the `setOptions` function.
104
-
105
- ## Snippet: the handleSelectItem functon
106
+ ```
107
+ // file: highlightFollowsSelection.ts
108
+ const highlightFollowsSelection = (ctr: any, selectionParams: SelectionParamsT) => {
109
+ if (!selectionParams.isShift && !selectionParams.isCtrl) {
110
+ ctr.highlight.highlightItem(selectionParams.itemId);
111
+ }
112
+ }
113
+ ```
106
114
 
107
115
  ```
108
116
  // file: handleSelectItem.ts
@@ -133,163 +141,156 @@ export function handleSelectItem(
133
141
  }
134
142
  ```
135
143
 
136
- ## Fact: the handleSelectItem does the actual work of selecting items
144
+ ## Note: the todos container has selection, highlight and filtering
137
145
 
138
- For completeness, we show how `handleSelectItem` is implemented. Note that this function stores the results directly in the selection facet instance.
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.
139
147
 
140
- ## Snippet: the Highlight facet
148
+ ## Fact: callbacks are installed with `setCallbackMap()`
141
149
 
142
- ```
143
- // file: Highlight.ts
144
- export class Highlight_highlightItem extends Cbs {
145
- id: string = stub();
146
- scrollItemIntoView() {}
147
- }
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.
148
151
 
149
- export class Highlight<ValueT = any> {
150
- @data id?: string;
151
- @data item?: ValueT;
152
+ ## Fact: callbacks can take care of side effects
152
153
 
153
- @operation @host(["id"]) highlightItem(id: string) {
154
- return (cbs: Highlight_highlightItem) => {
155
- this.id = id;
156
- if (cbs.scrollItemIntoView) {
157
- cbs.scrollItemIntoView();
158
- }
159
- };
160
- }
161
- }
162
- ```
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.
163
155
 
164
- ## Fact: the `Highlight` facet implements the highlight behaviour
156
+ ## Fact: the callback function can access the callbacks object through `this`.
165
157
 
166
- The `Highlight` snippet has a similar structure as the `Selection` facet. It has a callback function for scrolling the highlighted item into view. Note that the `highlightItem` function does not set the `item` field, because the facet only deals with ids. We shall later see how the `item` field is set by looking up the item in an external collection.
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`.
167
159
 
168
- ## Snippet: a container with Selection and Highlight
160
+ ## Fact: the `exit()` callback function is called at the end of an operation
169
161
 
170
- ```
171
- // file: createContainer.ts
172
- const createContainer() {
173
- const ctr = {
174
- selection: new Selection(),
175
- highlight: new Highlight()
176
- };
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.
177
163
 
178
- setCallbacks(ctr.selection, {
179
- selectItem: {
180
- select(this: Selection_selectItem): {
181
- handleSelectItem(ctr.selection, this.selectionParams);
164
+ ## Fact: the `registerCtr()` allows SkandhaJS to introspect the container
182
165
 
183
- if (!this.selectionParams.isShift && !this.selectionParams.isCtrl) {
184
- ctr.highlight.highlightItem(this.selectionParams.itemId);
185
- }
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
186
167
 
187
- // The above 3 lines could be extracted in a reusable helper function, e.g.
188
- // highlightFollowsSelection(ctr, this.selectionParams);
189
- },
190
- }
191
- });
168
+ ## Example: a complete Selection facet
192
169
 
193
- return ctr;
194
- ```
170
+ It's time now to return to the Selection facet and show how callbacks really work.
195
171
 
196
- ## Fact: callbacks allow you to create co-ordinated behaviours
172
+ ```
173
+ // file: Selection.ts
174
+ import { input, output, operation } from 'skandha';
175
+ import { host, stub } from 'aspiration';
176
+ import { Selection_selectItem, SelectionParamsT } from 'SelectionCbs';
197
177
 
198
- Above we see for the first time how SkandhaJS helps you to create co-ordinated behaviours. When the client calls `ctr.selection.selectItem` and the `select` callback is executed, then the callback ensures that the selected item is also highlighted. As the comment in the snippet suggests, we can make the body of the `select` callback more readable by extracting some of the code in a `highlightFollowsSelection` helper function.
178
+ export class Selection<ValueT = any> {
179
+ static className = () => 'Selection';
199
180
 
200
- ## Snippet: a container with Selection, Highlight and Filtering
181
+ @input selectableIds: Array<string> = stub();
182
+ @output ids: Array<string> = [];
183
+ @output anchorId?: string;
184
+ @output items: Array<ValueT> = stub();
201
185
 
186
+ @operation @host(['selectionParams']) selectItem(
187
+ selectionParams: SelectionParamsT
188
+ ) {
189
+ return (cbs: Selection_selectItem) => {
190
+ cbs.select();
191
+ };
192
+ }
193
+ }
202
194
  ```
203
- // file: createContainer.ts
204
- const createContainer() {
205
- const ctr = {
206
- selection: new Selection(),
207
- highlight: new Highlight(),
208
- filtering: new Filtering()
209
- };
210
195
 
211
- setCallbacks(ctr.selection, {
212
- selectItem: {
213
- select(this: Selection_selectItem): {
214
- handleSelectItem(ctr.selection, this.selectionParams);
215
- highlightFollowsSelection(ctr, this.selectionParams);
216
- },
217
- }
218
- });
196
+ ```
197
+ // file: SelectionCbs.ts
198
+ import { stub } from 'aspiration';
219
199
 
220
- setCallbacks(filtering, {
221
- apply: {
222
- exit() {
223
- highlightIsCorrectedOnFilterChange(ctr);
224
- },
225
- },
226
- });
200
+ export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
227
201
 
228
- return ctr;
202
+ export class Selection_selectItem extends Cbs {
203
+ selectionParams: SelectionParamsT = stub();
204
+ select() {}
229
205
  }
230
206
  ```
231
207
 
232
- ## Fact: the `exit` callback function is called at the end of the host function
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
233
219
 
234
- In the snippet we add a filtering facet that has an `apply` host function. In this host function, we can install an `exit` callback that corrects the highlight when the filter changes (because the filter may hide the highlighted item). The `exit` callback is called at the end of the host function. Similarly, the `enter` callback is called at the beginning.
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`).
235
221
 
236
- ## Snippet: mapping data onto each facet
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.
237
229
 
238
230
  ```
239
231
  // file: createContainer.ts
240
232
  import * from 'ramda' as R;
233
+ import { mapDataToProps, pmap } from 'skandha';
241
234
 
242
- const connectContainer(ctr: any) {
243
- const inputs = { todos: loadTodos() };
244
- const outputs = { filteredTodoById: {} };
235
+ const createContainer = () => {
236
+ const todosCtr = {
237
+ data: {
238
+ todos: loadTodos(),
239
+ filteredTodoById: {},
240
+ },
241
+ selection: new Selection(),
242
+ highlight: new Highlight(),
243
+ filtering: new Filtering(),
244
+ }
245
245
 
246
- const con = createConnector({
247
- selection: () => ctr.selection,
248
- highlight: () => ctr.highlight,
249
- filtering: () => ctr.filtering`,
250
- inputs: () => inputs,
251
- outputs: () => outputs,
252
- });
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
+ ),
269
+ )
270
+ }
253
271
 
254
- const lookUpTodo = (id: string) => outputs.filteredTodoById[id];
272
+ setOptions({logging: true});
273
+ ```
255
274
 
256
- con.filtering.inputItems = con.inputs.todos;
257
- con.outputs.filteredTodoById = con.filtering.filteredItems.tf(R.indexBy('id));
258
- con.selection.selectableIds = con.outputs.filteredTodoById.tf(R.keys);
259
- con.selection.item = con.selection.ids.tf(lookUpTodos);
260
- con.highlight.item = con.highlight.id.tf(lookUpTodo);
275
+ ## Fact: the `mapDataToProps()` function maps data between objects
261
276
 
262
- con.connect();
263
- ```
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.
264
278
 
265
- ## Fact: the Connector class maps data between objects
279
+ ## Fact: the `pmap()` function adds type-checking
266
280
 
267
- The Connector instance allows you to map data from one object onto another object. In our example, the `inputs.todos` field is mapped onto the `filtering.inputItems` field. What the `Connector` class does is turn `filtering.inputItems` into a `get` property that returns `filtering.inputItems`.
268
- The snippet contains three steps. In the first step we declare the objects, such as `selection`, `filtering`, etc. For each object, we specify the getter function that returns the object. In the second step, we declare relations between the different fields of these objects. In this step, we can also declare one or more transformation functions (such as `lookUpTodo`) that should be used in the mapping. In the final step, we call `con.connect` to create the `get` properties that perform the declared mappings.
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.
269
282
 
270
- ### Logging
283
+ ## Fact: logging can be enabled using `setOptions()`
271
284
 
272
- By calling `setOptions({logging: true})`, the SkandhaJS library allows you to inspect each facet before and
273
- after calling an operation. An operation is any facet member function decorated with @operation.
274
- This will log Facet members that are decorated with `@data` (or `@input`, or `@output`) in a way that looks similar to what you are used to from Redux:
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:
275
286
 
276
287
  - before and after each operation, the entire container that holds the facet is logged
277
288
  - all data members of each facet in the container are included in the log
278
289
  - log entries are nested so that you can see how operation calls are nested
279
290
 
280
- ### Making facets observable with Mobx
291
+ ## Fact: the `registerCtr()` function from facets-mobx makes facets observable
281
292
 
282
- If you want to render the facets with React then we need to let React know when data changes.
283
- The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library was created for this purpose.
284
- It has a `registerCtr` function that turns each `@data` member into a Mobx property that is
285
- either observable or computed:
286
-
287
- ```
288
- registerCtr({
289
- ctr: ctr,
290
- options: { name: 'TodosCtr'},
291
- });
292
- ```
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.
293
294
 
294
295
  ## Conclusion
295
296
 
package/index.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  export { getCtrState } from './internal/logging';
2
2
  export { getOptions, setOptions } from './internal/options';
3
- export { createConnector } from './lib/Connector';
4
3
  export { getc, getFacetMemberNames } from './lib/ctr';
5
4
  export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
6
5
  export { getf } from './lib/facet';
7
6
  export { installPolicies } from './lib/install';
8
7
  export { getOperationMemberNames, operation } from './lib/operation';
9
- export { mapDataToProp } from './lib/patch';
8
+ export { mapDataToProp, mapDataToProps, pmap } from './lib/patch';
10
9
  export { registerCtr } from './lib/registerCtr';
11
10
  export type { OptionsT as RegisterCtrOptionsT } from './lib/registerCtr';
12
11
  export declare type ClassT = any;
package/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  export { getCtrState } from './internal/logging';
2
2
  export { getOptions, setOptions } from './internal/options';
3
- export { createConnector } from './lib/Connector';
4
3
  export { getc, getFacetMemberNames } from './lib/ctr';
5
4
  export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
6
5
  export { getf } from './lib/facet';
7
6
  export { installPolicies } from './lib/install';
8
7
  export { getOperationMemberNames, operation } from './lib/operation';
9
- export { mapDataToProp } from './lib/patch';
8
+ export { mapDataToProp, mapDataToProps, pmap } from './lib/patch';
10
9
  export { registerCtr } from './lib/registerCtr';
package/internal/utils.js CHANGED
@@ -1,11 +1,7 @@
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));
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;
9
5
  };
10
6
  import { symbols } from './symbols';
11
7
  export function getOrCreate(obj, key, fn) {
@@ -20,7 +16,7 @@ export var zip = function (arr) {
20
16
  arrs[_i - 1] = arguments[_i];
21
17
  }
22
18
  return arr.map(function (val, i) {
23
- return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a, true), [arr[i]], false); }, [val]);
19
+ return arrs.reduce(function (a, arr) { return __spreadArray(__spreadArray([], a), [arr[i]]); }, [val]);
24
20
  });
25
21
  };
26
22
  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 ".concat(facetName, " in container ").concat(getCtrAdmin(ctr).logName));
14
+ console.error("No facet " + facetName + " in container " + getCtrAdmin(ctr).logName);
15
15
  }
16
16
  return facet;
17
17
  }
package/lib/patch.d.ts CHANGED
@@ -1 +1,3 @@
1
- export declare function mapDataToProp(facet: any, prop: string, getter: () => any): void;
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]))[];
package/lib/patch.js CHANGED
@@ -6,3 +6,16 @@ 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];
13
+ }
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
+ }
@@ -1,6 +1,6 @@
1
1
  export declare type OptionsT = {
2
2
  name: string;
3
- members?: string[];
3
+ facets?: string[];
4
4
  ctrState?: Function;
5
5
  };
6
6
  export declare function registerCtr(ctr: any, options: OptionsT): void;
@@ -10,18 +10,18 @@ export function registerCtr(ctr, options) {
10
10
  ctrAdmin.facetMembers = (_a = ctrAdmin.facetMembers) !== null && _a !== void 0 ? _a : [];
11
11
  ctrAdmin.facetByFacetClassName = (_b = ctrAdmin.facetByFacetClassName) !== null && _b !== void 0 ? _b : {};
12
12
  ctrAdmin.ctrStateOverride = options.ctrState;
13
- ((_c = options.members) !== null && _c !== void 0 ? _c : Object.getOwnPropertyNames(ctr)).forEach(function (member) {
14
- var facet = ctr[member];
13
+ ((_c = options.facets) !== null && _c !== void 0 ? _c : Object.getOwnPropertyNames(ctr)).forEach(function (facetName) {
14
+ var facet = ctr[facetName];
15
15
  setCtr(facet, ctr);
16
- ctrAdmin.facetMembers.push(member);
16
+ ctrAdmin.facetMembers.push(facetName);
17
17
  if (!facet.constructor.className) {
18
- console.error("Facet ".concat(member, " of container ").concat(ctrAdmin.logName, " ") +
18
+ console.error("Facet " + facetName + " of container " + ctrAdmin.logName + " " +
19
19
  "does not have a static className function");
20
20
  }
21
21
  var className = facetClassName(facet.constructor);
22
- getFacetAdmin(facet).logName = "".concat(ctrAdmin.logName, "/").concat(className);
22
+ getFacetAdmin(facet).logName = ctrAdmin.logName + "/" + className;
23
23
  if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
24
- console.error("Two facets of same type ".concat(className, " in container ").concat(ctrAdmin.logName));
24
+ console.error("Two facets of same type " + className + " in container " + 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.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Create data containers with modular behaviours",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/mnieber/skandha.git",
@@ -1,5 +0,0 @@
1
- declare type FunctionByNameT = {
2
- [name: string]: Function;
3
- };
4
- export declare const createConnector: (entities: FunctionByNameT) => any;
5
- export {};
package/lib/Connector.js DELETED
@@ -1,98 +0,0 @@
1
- import { mapDataToProp } from './patch';
2
- var Connector = /** @class */ (function () {
3
- function Connector(entities, options) {
4
- var _this = this;
5
- var _a;
6
- this.entityByName = {};
7
- this.connect = function () {
8
- for (var _i = 0, _a = Object.values(_this.entityByName); _i < _a.length; _i++) {
9
- var entity = _a[_i];
10
- _this._connectEntity(entity);
11
- }
12
- };
13
- this._connectEntity = function (entity) {
14
- var _loop_1 = function (entry) {
15
- var name_1 = entry[0], field = entry[1];
16
- var obj = entity.getObj();
17
- if (_this.options.strict && !(name_1 in obj)) {
18
- throw new Error("Cannot override the field ".concat(name_1, " in ").concat(obj, ".") +
19
- 'Field not found. Disable strict mode to ignore this error.');
20
- }
21
- mapDataToProp(obj, name_1, function () {
22
- return field.getSource();
23
- });
24
- };
25
- for (var _i = 0, _a = Object.entries(entity.fieldByName); _i < _a.length; _i++) {
26
- var entry = _a[_i];
27
- _loop_1(entry);
28
- }
29
- };
30
- this.options = {
31
- strict: (_a = options === null || options === void 0 ? void 0 : options.strict) !== null && _a !== void 0 ? _a : true,
32
- };
33
- for (var _i = 0, _b = Object.entries(entities); _i < _b.length; _i++) {
34
- var entry = _b[_i];
35
- var name_2 = entry[0], getObj = entry[1];
36
- this.entityByName[name_2] = new Entity(getObj);
37
- }
38
- }
39
- return Connector;
40
- }());
41
- export var createConnector = function (entities) {
42
- var con = new Connector(entities);
43
- return new Proxy(con, {
44
- get: function (target, key) {
45
- // Code example:
46
- //
47
- // const con = createConnector({ foo: () => myFoo });
48
- //
49
- // When you call `con.foo` then you
50
- // receive `con.entityByName.foo.fieldByName`.
51
- //
52
- // con.foo.bar = 5; // Equivalent to: con.entityByName.foo.fieldByName.bar = 5
53
- //
54
- // When you call `con.connect` then a normal lookup of the `connect`
55
- // member in `con` is performed.
56
- var _a, _b;
57
- return (_b = (_a = target.entityByName[key]) === null || _a === void 0 ? void 0 : _a.fieldByName) !== null && _b !== void 0 ? _b : target[key];
58
- },
59
- });
60
- };
61
- var Entity = /** @class */ (function () {
62
- function Entity(getObj) {
63
- var _this = this;
64
- this.getObj = getObj;
65
- this.fieldByName = new Proxy({}, {
66
- get: function (target, key) {
67
- var _a;
68
- return (_a = target[key]) !== null && _a !== void 0 ? _a : new Field(_this, key);
69
- },
70
- });
71
- }
72
- return Entity;
73
- }());
74
- var Field = /** @class */ (function () {
75
- function Field(entity, name) {
76
- var _this = this;
77
- this.transforms = [];
78
- this.getSource = function () {
79
- var obj = _this.entity.getObj();
80
- if (!(_this.name in obj)) {
81
- throw new Error("Field ".concat(_this.name, " does not exist on object ").concat(obj));
82
- }
83
- var result = obj[_this.name];
84
- for (var _i = 0, _a = _this.transforms; _i < _a.length; _i++) {
85
- var tf = _a[_i];
86
- result = tf(result);
87
- }
88
- return result;
89
- };
90
- this.entity = entity;
91
- this.name = name;
92
- }
93
- Field.prototype.tf = function (transform) {
94
- this.transforms.push(transform);
95
- return this;
96
- };
97
- return Field;
98
- }());