skandha 0.9.0 → 0.9.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,103 @@ 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
+ - callbacks object: a collection of callback functions that is used by an operation.
18
+ - container: a class that contains multiple facets.
19
+ - facet: a class that implements a reusable behaviour, such as selection.
20
+ - operation: a member function of a facet that changes the data of that facet.
21
+
22
+ ## Glossary (technical)
23
+
24
+ - aspiration: a library that implements a form of Aspect Oriented Programming using callback functions.
25
+ - registerCtr: the function that tells SkandhaJS which containers and facets exist.
26
+ - setCallbacks: the function that installs the callbacks objects that are used by a facet.
27
+
28
+ ## Example: a simplified Selection facet
29
+
30
+ 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
31
 
20
32
  ```
21
33
  // file: Selection.ts
22
34
  import { input, output, operation } from 'skandha';
23
- import { host, stub } from 'aspiration';
24
35
 
25
36
  export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
26
37
 
27
38
  export class Selection<ValueT = any> {
28
39
  static className = () => 'Selection';
29
40
 
30
- @input selectableIds: Array<string> = stub();
41
+ @input selectableIds: Array<string> = [];
31
42
  @output ids: Array<string> = [];
32
43
  @output anchorId?: string;
33
- @output items: Array<ValueT> = stub();
44
+ @output items: Array<ValueT> = [];
34
45
 
35
- @operation @host(['selectionParams'], selectItemDefaultCbs) selectItem(
36
- selectionParams: SelectionParamsT
37
- ) {
38
- return (cbs: Selection_selectItem) => {
39
- cbs.select();
40
- };
46
+ @operation selectItem(selectionParams: SelectionParamsT) {
47
+ // This function calls a callback function to do the actual work.
48
+ // Details about how this works are omitted for now.
41
49
  }
42
50
  }
43
51
  ```
44
52
 
45
- ## Fact: the Selection facet has input, data and output members
53
+ ## Fact: facets have @input, @data and @output members
46
54
 
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.
55
+ 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()`.
48
56
 
49
- ## Fact: the Selection facet has a static className function
57
+ ## Fact: operations are decorated with `@operation`.
50
58
 
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.
59
+ Conceptually, an operation is a member function that is used to manipulate the facet. The `Selection` facet has a `selectItems` operation that allows you to select from the selectable ids. It stores its results in the `ids`, `anchorId` and `items` fields.
60
+ 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
61
 
53
- ## Fact: the `selectItem` function uses a callback function
62
+ ## Fact: operations are logged
54
63
 
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`).
64
+ 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. The log message contains all @input, @data and @output fields of all facets in the container.
56
65
 
57
- ## Snippet
66
+ ## Example: a container with Selection, Highlight and Filtering
58
67
 
59
- ```
60
- // file: Selection.ts
61
- export class Selection_selectItem extends Cbs {
62
- selectionParams: SelectionParamsT = stub();
63
- select() {}
64
- }
65
- ```
68
+ Now that we have some idea of what a facet is, we will discuss containers.
69
+ In this example we will take a look at a todos container. It uses the `Selection` facet that we saw earlier.
66
70
 
67
71
  ```
68
72
  // file: createContainer.ts
69
- import { registerCtr } from 'skandha';
73
+ import { Selection_selectItem } from 'SelectionCbs';
70
74
 
71
75
  const createContainer() {
72
76
  const ctr = {
73
- selection: new Selection()
77
+ selection: new Selection(),
78
+ highlight: new Highlight(),
79
+ filtering: new Filtering()
74
80
  };
75
81
 
76
82
  setCallbacks(ctr.selection, {
77
83
  selectItem: {
78
84
  select(this: Selection_selectItem): {
79
85
  handleSelectItem(ctr.selection, this.selectionParams);
86
+ highlightFollowsSelection(ctr, this.selectionParams);
80
87
  },
81
88
  }
82
89
  });
83
90
 
84
- // If you omit `members` then all fields of `ctr` are included.
85
- registerCtr(ctr, { name: 'TodosCtr', members: ['selection'] });
91
+ setCallbacks(ctr.filtering, {
92
+ apply: {
93
+ exit() {
94
+ highlightIsCorrectedOnFilterChange(ctr);
95
+ },
96
+ },
97
+ });
98
+
99
+ registerCtr({ ctr: ctr, options: { name: 'Todos'} });
86
100
  return ctr;
87
101
  }
88
102
  ```
89
103
 
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
104
+ ```
105
+ // file: highlightFollowsSelection.ts
106
+ const highlightFollowsSelection = (ctr: any, selectionParams: SelectionParamsT) => {
107
+ if (!selectionParams.isShift && !selectionParams.isCtrl) {
108
+ ctr.highlight.highlightItem(selectionParams.itemId);
109
+ }
110
+ }
111
+ ```
106
112
 
107
113
  ```
108
114
  // file: handleSelectItem.ts
@@ -133,145 +139,125 @@ export function handleSelectItem(
133
139
  }
134
140
  ```
135
141
 
136
- ## Fact: the handleSelectItem does the actual work of selecting items
142
+ ## Note: the todos container has selection, highlight and filtering
137
143
 
138
- For completeness, we show how `handleSelectItem` is implemented. Note that this function stores the results directly in the selection facet instance.
144
+ In `createContainer.ts` we see how `createContainer()` creates a `ctr` 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. Finally, `registerCtr()` is called to allow SkandhaJS to 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(ctr, Selection)` returns `ctr.selection`. Second, it informs SkandhaJS how to log all facets of the container
139
145
 
140
- ## Snippet: the Highlight facet
141
-
142
- ```
143
- // file: Highlight.ts
144
- export class Highlight_highlightItem extends Cbs {
145
- id: string = stub();
146
- scrollItemIntoView() {}
147
- }
146
+ ## Fact: callbacks are installed with `setCallbacks`
148
147
 
149
- export class Highlight<ValueT = any> {
150
- @data id?: string;
151
- @data item?: ValueT;
148
+ We call `setCallbacks` to install a callbacks object for each operation of the `Selection` facet. The callbacks object contains all callbacks for an operation. 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 `setCallbacks` to be as short and readable as possible, so that it's easy to understand what happens in each operation of each facet.
152
149
 
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
- ```
150
+ ## Fact: callbacks can take care of side effects
163
151
 
164
- ## Fact: the `Highlight` facet implements the highlight behaviour
152
+ 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.
165
153
 
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.
154
+ ## Fact: the callback function can access the callbacks object through `this`.
167
155
 
168
- ## Snippet: a container with Selection and Highlight
156
+ When the clients calls `ctr.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`.
169
157
 
170
- ```
171
- // file: createContainer.ts
172
- const createContainer() {
173
- const ctr = {
174
- selection: new Selection(),
175
- highlight: new Highlight()
176
- };
158
+ ## Fact: the `exit` callback function is called at the end of an operation
177
159
 
178
- setCallbacks(ctr.selection, {
179
- selectItem: {
180
- select(this: Selection_selectItem): {
181
- handleSelectItem(ctr.selection, this.selectionParams);
160
+ 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.
182
161
 
183
- if (!this.selectionParams.isShift && !this.selectionParams.isCtrl) {
184
- ctr.highlight.highlightItem(this.selectionParams.itemId);
185
- }
162
+ ## Example: a complete Selection facet
186
163
 
187
- // The above 3 lines could be extracted in a reusable helper function, e.g.
188
- // highlightFollowsSelection(ctr, this.selectionParams);
189
- },
190
- }
191
- });
164
+ It's time now to return to the Selection facet and show how callbacks really work.
192
165
 
193
- return ctr;
194
166
  ```
167
+ // file: Selection.ts
168
+ import { input, output, operation } from 'skandha';
169
+ import { host, stub } from 'aspiration';
170
+ import { Selection_selectItem, SelectionParamsT } from 'SelectionCbs';
195
171
 
196
- ## Fact: callbacks allow you to create co-ordinated behaviours
197
-
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.
172
+ export class Selection<ValueT = any> {
173
+ static className = () => 'Selection';
199
174
 
200
- ## Snippet: a container with Selection, Highlight and Filtering
175
+ @input selectableIds: Array<string> = stub();
176
+ @output ids: Array<string> = [];
177
+ @output anchorId?: string;
178
+ @output items: Array<ValueT> = stub();
201
179
 
180
+ @operation @host(['selectionParams']) selectItem(
181
+ selectionParams: SelectionParamsT
182
+ ) {
183
+ return (cbs: Selection_selectItem) => {
184
+ cbs.select();
185
+ };
186
+ }
187
+ }
202
188
  ```
203
- // file: createContainer.ts
204
- const createContainer() {
205
- const ctr = {
206
- selection: new Selection(),
207
- highlight: new Highlight(),
208
- filtering: new Filtering()
209
- };
210
189
 
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
- });
190
+ ```
191
+ // file: SelectionCbs.ts
192
+ import { stub } from 'aspiration';
219
193
 
220
- setCallbacks(filtering, {
221
- apply: {
222
- exit() {
223
- highlightIsCorrectedOnFilterChange(ctr);
224
- },
225
- },
226
- });
194
+ export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
227
195
 
228
- return ctr;
196
+ export class Selection_selectItem extends Cbs {
197
+ selectionParams: SelectionParamsT = stub();
198
+ select() {}
229
199
  }
230
200
  ```
231
201
 
232
- ## Fact: the `exit` callback function is called at the end of the host function
202
+ ## Fact: the `@host` decorator is used to enable callbacks in an operation.
203
+
204
+ The `@host` decorator comes from the `Aspiration` library. When you call a function `f` that is decorated with `@host` then the following happens:
205
+
206
+ - Aspiration looks up the callbacks object for the operation (that was installed with `setCallbacks`)
207
+ - it copies the operation arguments to the callbacks object (so that callback functions can access these arguments)
208
+ - if calls `f(arguments)(callbacksObject)`.
209
+
210
+ 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`.
211
+
212
+ ## Fact: callback functions have access to all operation arguments
213
+
214
+ 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`).
215
+
216
+ ## Fact: facets have a static `className()` function
233
217
 
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.
218
+ 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.
235
219
 
236
- ## Snippet: mapping data onto each facet
220
+ ## Example: a Connector maps data onto each facet
221
+
222
+ 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. Moreover, we need to load the todos and set them as the inputs of the filtering facet. In this example, we show two containers (data and todos) and how to map data between them.
237
223
 
238
224
  ```
239
225
  // file: createContainer.ts
240
226
  import * from 'ramda' as R;
241
227
 
242
228
  const connectContainer(ctr: any) {
243
- const inputs = { todos: loadTodos() };
244
- const outputs = { filteredTodoById: {} };
245
-
246
- const con = createConnector({
247
- selection: () => ctr.selection,
248
- highlight: () => ctr.highlight,
249
- filtering: () => ctr.filtering`,
250
- inputs: () => inputs,
251
- outputs: () => outputs,
252
- });
229
+ const data = {
230
+ inputs: { todos: loadTodos() },
231
+ outputs: { filteredTodoById: {} },
232
+ };
233
+
234
+ const todos = {
235
+ selection: new Selection(),
236
+ highlight: new Highlight(),
237
+ filtering: new Filtering(),
238
+ }
253
239
 
254
- const lookUpTodo = (id: string) => outputs.filteredTodoById[id];
240
+ const con = createConnector({ data: data, todos: todos });
241
+ const lookUpTodo = (id: string) => outputsCtr.filteredTodoById[id];
255
242
 
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);
243
+ con['todos.filtering'].inputItems = con['inputs'].todos;
244
+ con['outputs'].filteredTodoById = con['todos.filtering'].filteredItems.tf(R.indexBy('id));
245
+ con['todos.selection'].selectableIds = con['outputs'].filteredTodoById.tf(R.keys);
246
+ con['todos.selection'].item = con['todos.selection'].ids.tf(lookUpTodos);
247
+ con['todos.highlight'].item = con['todos.highlight'].id.tf(lookUpTodo);
261
248
 
262
249
  con.connect();
263
250
  ```
264
251
 
265
252
  ## Fact: the Connector class maps data between objects
266
253
 
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.
254
+ The Connector instance allows you to map data from one object onto another object.
255
+ In our example, the `data.inputs.todos` field is mapped onto the `todos.filtering.inputItems` field. What this means is that `Connector` turns `todos.filtering.inputItems` into a `get` property that returns `data.inputs.todos`.
269
256
 
270
257
  ### Logging
271
258
 
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:
259
+ By calling `setOptions({logging: true})`, the SkandhaJS library allows you to inspect each facet before and after calling an operation.
260
+ 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
261
 
276
262
  - before and after each operation, the entire container that holds the facet is logged
277
263
  - all data members of each facet in the container are included in the log
@@ -279,15 +265,13 @@ This will log Facet members that are decorated with `@data` (or `@input`, or `@o
279
265
 
280
266
  ### Making facets observable with Mobx
281
267
 
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
268
+ 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 turns each `@data` member into a Mobx property that is
285
269
  either observable or computed:
286
270
 
287
271
  ```
288
272
  registerCtr({
289
273
  ctr: ctr,
290
- options: { name: 'TodosCtr'},
274
+ options: { name: 'Todos'},
291
275
  });
292
276
  ```
293
277
 
@@ -1,5 +1,5 @@
1
- declare type FunctionByNameT = {
2
- [name: string]: Function;
1
+ declare type OptionsT = {
2
+ strict: boolean;
3
3
  };
4
- export declare const createConnector: (entities: FunctionByNameT) => any;
4
+ export declare const createConnector: (obj: any, options?: OptionsT) => any;
5
5
  export {};
package/lib/Connector.js CHANGED
@@ -1,11 +1,37 @@
1
1
  import { mapDataToProp } from './patch';
2
+ function _path(path, obj) {
3
+ var result = obj;
4
+ for (var _i = 0, _a = path.split('.'); _i < _a.length; _i++) {
5
+ var part = _a[_i];
6
+ result = result === null || result === void 0 ? void 0 : result[part];
7
+ if (result === undefined)
8
+ return result;
9
+ }
10
+ return result;
11
+ }
2
12
  var Connector = /** @class */ (function () {
3
- function Connector(entities, options) {
13
+ function Connector(obj, options) {
4
14
  var _this = this;
5
15
  var _a;
6
- this.entityByName = {};
16
+ this.entityByKey = {};
17
+ this.getOrCreateEntity = function (key) {
18
+ if (_this.entityByKey[key])
19
+ return _this.entityByKey[key];
20
+ var obj = _path(key, _this.obj);
21
+ var entity = new Proxy(new Entity(obj), {
22
+ get: function (target, key) {
23
+ return target[key] || target.getOrCreateField(key);
24
+ },
25
+ set: function (target, key, value) {
26
+ target.fieldByName[key] = value;
27
+ return true;
28
+ },
29
+ });
30
+ _this.entityByKey[key] = entity;
31
+ return entity;
32
+ };
7
33
  this.connect = function () {
8
- for (var _i = 0, _a = Object.values(_this.entityByName); _i < _a.length; _i++) {
34
+ for (var _i = 0, _a = Object.values(_this.entityByKey); _i < _a.length; _i++) {
9
35
  var entity = _a[_i];
10
36
  _this._connectEntity(entity);
11
37
  }
@@ -13,12 +39,11 @@ var Connector = /** @class */ (function () {
13
39
  this._connectEntity = function (entity) {
14
40
  var _loop_1 = function (entry) {
15
41
  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, ".") +
42
+ if (_this.options.strict && !(name_1 in entity.obj)) {
43
+ throw new Error("Cannot override the field ".concat(name_1, " in ").concat(entity.obj, ".") +
19
44
  'Field not found. Disable strict mode to ignore this error.');
20
45
  }
21
- mapDataToProp(obj, name_1, function () {
46
+ mapDataToProp(entity.obj, name_1, function () {
22
47
  return field.getSource();
23
48
  });
24
49
  };
@@ -30,64 +55,48 @@ var Connector = /** @class */ (function () {
30
55
  this.options = {
31
56
  strict: (_a = options === null || options === void 0 ? void 0 : options.strict) !== null && _a !== void 0 ? _a : true,
32
57
  };
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
- }
58
+ this.obj = obj;
38
59
  }
39
60
  return Connector;
40
61
  }());
41
- export var createConnector = function (entities) {
42
- var con = new Connector(entities);
62
+ export var createConnector = function (obj, options) {
63
+ var con = new Connector(obj, options);
43
64
  return new Proxy(con, {
44
65
  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];
66
+ var _a;
67
+ return (_a = target[key]) !== null && _a !== void 0 ? _a : target.getOrCreateEntity(key);
58
68
  },
59
69
  });
60
70
  };
61
71
  var Entity = /** @class */ (function () {
62
- function Entity(getObj) {
72
+ function Entity(obj) {
63
73
  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
- });
74
+ this.fieldByName = {};
75
+ this.getOrCreateField = function (name) {
76
+ var _a;
77
+ // If we create a new field then we should *not* store it in fieldByName.
78
+ return (_a = _this.fieldByName[name]) !== null && _a !== void 0 ? _a : new Field(_this.obj, name);
79
+ };
80
+ this.obj = obj;
71
81
  }
72
82
  return Entity;
73
83
  }());
74
84
  var Field = /** @class */ (function () {
75
- function Field(entity, name) {
85
+ function Field(obj, name) {
76
86
  var _this = this;
77
87
  this.transforms = [];
78
88
  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));
89
+ if (!(_this.name in _this.obj)) {
90
+ throw new Error("Field ".concat(_this.name, " does not exist on object ").concat(_this.obj));
82
91
  }
83
- var result = obj[_this.name];
92
+ var result = _this.obj[_this.name];
84
93
  for (var _i = 0, _a = _this.transforms; _i < _a.length; _i++) {
85
94
  var tf = _a[_i];
86
95
  result = tf(result);
87
96
  }
88
97
  return result;
89
98
  };
90
- this.entity = entity;
99
+ this.obj = obj;
91
100
  this.name = name;
92
101
  }
93
102
  Field.prototype.tf = function (transform) {
@@ -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,12 +10,12 @@ 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 ".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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skandha",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Create data containers with modular behaviours",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/mnieber/skandha.git",