skandha 0.5.0 → 0.9.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
@@ -2,16 +2,10 @@
2
2
 
3
3
  ## Rationale
4
4
 
5
- Despite having a lot of good libraries, programmers still find themselves reimplementing
6
- the same solutions over and over.
7
- The reason is that not every solution can be captured in a library. When there are cross-cutting concerns (aka
8
- aspects, as in aspect oriented programming), then out-of-the-box solutions often do not fit well enough. For this
9
- reason, programmers are often reluctant to store their data in framework-specific data-structures, even though such frameworks could potentially save time.
10
-
11
- Skandha proposes an alternative solution (somewhat inspired by Erlang) that aims to let the programmer re-use existing logic,
12
- while allowing them to use their own data-structures. This is achieved by mapping the user's data-structures onto a set of
13
- minimal interfaces called facets. These facets perform tasks by interacting via so-called policies. By keeping facets
14
- minimal, they can be generic, which leads to better code reuse.
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.
15
9
 
16
10
  ## Links
17
11
 
@@ -19,322 +13,265 @@ minimal, they can be generic, which leads to better code reuse.
19
13
  - The [skandha-facets](http://github.com/mnieber/skandha-facets) library contains useful facets such as selection, highlight, filtering, addition, etc.
20
14
  - The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library contains bindings to MobX
21
15
  - The [aspiration](http://github.com/mnieber/aspiration) library handles aspect oriented programming (AOP)
16
+ - The [example code](http://github.com/mnieber/skandha-sample-app) shown in this README.
22
17
 
23
- ## Example code
18
+ ## Snippet
24
19
 
25
- The full example code for this README can be found [here](http://github.com/mnieber/skandha-sample-app). Note that this repository
26
- contains two version of the example code. The frst version creates all the behaviours from scratch. Although it gives a good insight into
27
- the Skandha principles, the code will look bulky. The second version is reflective of a real use-case as it uses the reusable behaviours
28
- from [skandha-facets](http://github.com/mnieber/skandha-facets).
20
+ ```
21
+ // file: Selection.ts
22
+ import { input, output, operation } from 'skandha';
23
+ import { host, stub } from 'aspiration';
29
24
 
30
- ## Explanation
25
+ export type SelectionParamsT = { itemId?: string; isShift?: boolean; isCtrl?: boolean; };
31
26
 
32
- The Skandha library will be explained using an example that progressively introduces various concepts
33
- and functions. The example shows how to add reusable selection, highlight and filtering behaviour to a container
34
- with Todo items.
27
+ export class Selection<ValueT = any> {
28
+ static className = () => 'Selection';
35
29
 
36
- ### Containers and Facets
30
+ @input selectableIds: Array<string> = stub();
31
+ @output ids: Array<string> = [];
32
+ @output anchorId?: string;
33
+ @output items: Array<ValueT> = stub();
37
34
 
38
- A container is a set of related data, e.g. todo items together with data about selection, highlight, etc. A facet is a
39
- member of a container that represents a single aspect such as `Selection`. In the example below we see a `TodosCtr`
40
- with five facets: `Selection`, `Highlight`, `Filtering`, `Inputs` and `Outputs`. As we shall see, `Selection`,
41
- `Highlight` and `Filtering` are generic (not tied to the concept of a Todo) and therefore reusable.
35
+ @operation @host(['selectionParams'], selectItemDefaultCbs) selectItem(
36
+ selectionParams: SelectionParamsT
37
+ ) {
38
+ return (cbs: Selection_selectItem) => {
39
+ cbs.select();
40
+ };
41
+ }
42
+ }
43
+ ```
42
44
 
43
- The `Inputs` facet contains the data (i.e. the todos) that is selected, highlighted and filtered, whereas the `Outputs` facet
44
- contains some of the results that are produced by the container (e.g. the filtered list of todos). It's not strictly
45
- necessary to use `Inputs` and `Outputs` as you could directly connect `Selection`, `Highlight`, and `Filtering` to
46
- data-sources that are outside of the container, but it's easier to reason about the container if they exist.
45
+ ## Fact: the Selection facet has input, data and output members
47
46
 
48
- Our initial code is:
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.
49
48
 
50
- ```
51
- class Selection<TodoT> {}
49
+ ## Fact: the Selection facet has a static className function
52
50
 
53
- class Highlight<TodoT> {}
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.
54
52
 
55
- class Filtering<TodoT> {}
53
+ ## Fact: the `selectItem` function uses a callback function
56
54
 
57
- class Inputs {
58
- @data todoById: TodoByIdT = {};
59
- }
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`).
60
56
 
61
- class Outputs {
62
- @data filteredTodoById: TodoByIdT = {};
63
- }
57
+ ## Snippet
64
58
 
65
- class TodosCtr {
66
- @facet inputs: Inputs = new Inputs();
67
- @facet outputs: Outputs = new Outputs();
68
- @facet selection: Selection<TodoT> = new Selection<TodoT>();
69
- @facet highlight: Highlight<TodoT> = new Highlight<TodoT>();
70
- @facet filtering: Filtering<TodoT> = new Filtering<TodoT>();
59
+ ```
60
+ // file: Selection.ts
61
+ export class Selection_selectItem extends Cbs {
62
+ selectionParams: SelectionParamsT = stub();
63
+ select() {}
64
+ }
65
+ ```
66
+
67
+ ```
68
+ // file: createContainer.ts
69
+ import { registerCtr } from 'skandha';
70
+
71
+ const createContainer() {
72
+ const ctr = {
73
+ selection: new Selection()
74
+ };
75
+
76
+ setCallbacks(ctr.selection, {
77
+ selectItem: {
78
+ select(this: Selection_selectItem): {
79
+ handleSelectItem(ctr.selection, this.selectionParams);
80
+ },
71
81
  }
82
+ });
83
+
84
+ // If you omit `members` then all fields of `ctr` are included.
85
+ registerCtr(ctr, { name: 'TodosCtr', members: ['selection'] });
86
+ return ctr;
87
+ }
72
88
  ```
73
89
 
74
- Notes:
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
75
95
 
76
- - The `@data` and `@operation` decorators mark the fields in the facet that make up its abstract interface.
77
- Clients should use only these fields.
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.
78
97
 
79
- ### Implementing the Selection facet
98
+ ## Fact: the registerCtr function registers the container with SkandhaJS
80
99
 
81
- We will first implement the Selection facet. The key requirements are that:
100
+ The purpose of calling registerCtr on the container is twofold:
82
101
 
83
- - this facet is only concerned with ids. To make a selection in any data-structure, it suffices to map this data-structure
84
- to a list of ids. And to obtain the list of selected items, it suffices to map in the opposite direction: from ids to items.
85
- - the facet has to be generic, which means that the client code has control over how selection is actually
86
- performed, and over possible side-effects. This means the client can determine
87
- the effect of the shift key, or decide that selecting an item should also highlight it.
88
- - preferably the facet has to perform some useful work, and not just be an empty abstraction.
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.
89
104
 
90
- This is our proposal for the Selection facet:
105
+ ## Snippet: the handleSelectItem functon
91
106
 
92
107
  ```
93
- export class Selection_select extends Cbs {
94
- selectionParams: SelectionParamsT = stub();
95
- selectItem() {}
96
- }
108
+ // file: handleSelectItem.ts
109
+ export function handleSelectItem(
110
+ facet: Selection,
111
+ { itemId, isShift, isCtrl }: SelectionParamsT
112
+ ) {
113
+ if (itemId === undefined) {
114
+ facet.ids = [];
115
+ facet.anchorId = undefined;
116
+ return;
117
+ }
97
118
 
98
- class Selection<TodoT> {
99
- @data selectableIds: Array<string> = stub();
100
- @data ids: Array<string> = [];
101
- @data anchorId?: string;
102
- @data items?: Array<ValueT>;
119
+ if (isShift) {
120
+ // Omitted for brevity
121
+ } else if (isCtrl) {
122
+ facet.ids = facet.ids.includes(itemId)
123
+ ? facet.ids.filter((x) => x !== itemId)
124
+ : [...facet.ids, itemId];
125
+ } else {
126
+ facet.ids = [itemId];
127
+ }
103
128
 
104
- @operation @host selectItem(selectionParams: SelectionParamsT) {
105
- return (cbs: Selection_select) => {
106
- if (!this.selectableIds.includes(selectionParams.itemId)) {
107
- throw Error(`Invalid id: ${selectionParams.itemId}`);
108
- }
109
- cbs.selectItem();
110
- }
129
+ // Move the anchor
130
+ if (!facet.anchorId || !(isCtrl || isShift)) {
131
+ facet.anchorId = itemId;
111
132
  }
112
133
  }
113
134
  ```
114
135
 
115
- Notes:
116
-
117
- - The `@host` decorator comes from the [Aspiration](http://github.com/mnieber/aspiration) library. It makes
118
- the function take a callback object (i.e. a set of callbacks) that is installed by the client. This allows us to
119
- write a `Selection` facet that is generic (our 2nd requirement) but also does useful work (3rd requirement).
120
- We shall discuss below how the `cbs.selectItem` callback is implemented.
121
- - The `stub` function is used in callback objects to indicate that although the field is initialized with `undefined`
122
- it will receive a value later.
136
+ ## Fact: the handleSelectItem does the actual work of selecting items
123
137
 
124
- ### Implementing the Highlight and Filtering facets
138
+ For completeness, we show how `handleSelectItem` is implemented. Note that this function stores the results directly in the selection facet instance.
125
139
 
126
- Since `Highlight` and `Filtering` follow the same structure as the `Selection` facet, we will only
127
- show the code. For brevity, we will leave out the code for the callback objects such as
128
- `Highlight_highlightItem` (they are straightforward and not that interesting):
140
+ ## Snippet: the Highlight facet
129
141
 
130
142
  ```
143
+ // file: Highlight.ts
144
+ export class Highlight_highlightItem extends Cbs {
145
+ id: string = stub();
146
+ scrollItemIntoView() {}
147
+ }
148
+
131
149
  export class Highlight<ValueT = any> {
132
- @data id: string | undefined;
150
+ @data id?: string;
133
151
  @data item?: ValueT;
134
152
 
135
- @operation @host highlightItem(id: string) {
153
+ @operation @host(["id"]) highlightItem(id: string) {
136
154
  return (cbs: Highlight_highlightItem) => {
137
155
  this.id = id;
138
- };
139
- }
140
- }
141
-
142
- export class Filtering<ValueT = any> {
143
- @data isEnabled: boolean = false;
144
- @data filter: FilterT = () => [];
145
-
146
- @data inputItems?: Array<ValueT>;
147
- @data get filteredItems() {
148
- return this.isEnabled ? this.filter(this.inputItems) : this.inputItems;
149
- }
150
-
151
- @operation @host apply(filter: FilterT) {
152
- return (cbs: Filtering_apply) => {
153
- this.filter = filter;
154
- this.isEnabled = true;
155
- };
156
- }
157
-
158
- @operation @host setEnabled(flag: boolean) {
159
- return (cbs: Filtering_setEnabled) => {
160
- this.isEnabled = flag;
156
+ if (cbs.scrollItemIntoView) {
157
+ cbs.scrollItemIntoView();
158
+ }
161
159
  };
162
160
  }
163
161
  }
164
162
  ```
165
163
 
166
- ### Implementing the callback objects
164
+ ## Fact: the `Highlight` facet implements the highlight behaviour
167
165
 
168
- We've set up the basic structure of the facets, but the callbacks still have to be implemented.
169
- This step happens in the container class. We will use the following requirements:
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.
170
167
 
171
- - the `cbs.selectItem` callback should make a selection that takes the shift and control keys into account.
172
- We will not show the code (you can see it
173
- [here](https://github.com/mnieber/skandha-facets/blob/main/Selection.ts)) but assume we have a library
174
- function `handleSelectItem` for this.
175
- - items that are selected should become highlighted. We will introduce a reusable policy
176
- function for this called `highlightFollowsSelection`.
177
- - if applying the filter hides the highlighted item then we want to correct the highlight.
178
- We will use a another reusable policy called `highlightIsCorrectedOnFilterChange` (shown
179
- [here](https://github.com/mnieber/skandha-facets/blob/main/policies/highlightIsCorrectedOnFilterChange.ts))
168
+ ## Snippet: a container with Selection and Highlight
180
169
 
181
170
  ```
182
- class TodosCtr {
183
- // ...
184
-
185
- constructor() {
186
- registerFacets(this);
187
- this._setCallbacks();
188
- }
189
-
190
- _setCallbacks() {
191
- const ctr = this;
192
-
193
- setCallbacks(this.selection, {
194
- select: {
195
- selectItem(this: Selection_select): {
196
- handleSelectItem(ctr.selection, this.selectionParams);
197
- highlightFollowsSelection(ctr.selection, this.selectionParams); // See below
198
- },
199
- }
200
- });
201
-
202
- setCallbacks(this.filtering, {
203
- apply: {
204
- exit() {
205
- FacetPolicies.highlightIsCorrectedOnFilterChange(ctr.filtering);
206
- },
171
+ // file: createContainer.ts
172
+ const createContainer() {
173
+ const ctr = {
174
+ selection: new Selection(),
175
+ highlight: new Highlight()
176
+ };
177
+
178
+ setCallbacks(ctr.selection, {
179
+ selectItem: {
180
+ select(this: Selection_selectItem): {
181
+ handleSelectItem(ctr.selection, this.selectionParams);
182
+
183
+ if (!this.selectionParams.isShift && !this.selectionParams.isCtrl) {
184
+ ctr.highlight.highlightItem(this.selectionParams.itemId);
185
+ }
186
+
187
+ // The above 3 lines could be extracted in a reusable helper function, e.g.
188
+ // highlightFollowsSelection(ctr, this.selectionParams);
207
189
  },
208
- });
209
- }
210
- }
190
+ }
191
+ });
211
192
 
212
- export function highlightFollowsSelection(
213
- selection: Selection,
214
- selectionParams: SelectionParamsT
215
- ) {
216
- const {isCtrl, isShift, itemId} = selectionParams;
217
- const ctr = getc(selection);
218
- if (!isCtrl && !isShift) {
219
- getf(Highlight, ctr).highlightItem(itemId);
220
- }
221
- }
193
+ return ctr;
222
194
  ```
223
195
 
224
- Notes:
196
+ ## Fact: callbacks allow you to create co-ordinated behaviours
225
197
 
226
- - The call to `registerFacets` is mandatory. It creates a back-reference to the container in each facet
227
- instance.
228
- - The `getc` gets the container for a given facet, whereas `getf` gets a facet for a given container.
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.
229
199
 
230
- ### Mapping data onto the container, and between facets
200
+ ## Snippet: a container with Selection, Highlight and Filtering
231
201
 
232
- At this point we have a container with facets that perform useful operations, and that interact
233
- to achieve interesting behaviours. However, the container will not yet work because `Selection.selectableIds`
234
- and `Filtering.inputItems` are not connected to any data sources. Note that the client of the container
235
- is responsible for setting `Inputs.todoById` to the collection of todos (at some time after constructing the
236
- container).
202
+ ```
203
+ // file: createContainer.ts
204
+ const createContainer() {
205
+ const ctr = {
206
+ selection: new Selection(),
207
+ highlight: new Highlight(),
208
+ filtering: new Filtering()
209
+ };
210
+
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
+ });
237
219
 
238
- As a first step, we need to connect `Filtering.inputItems` to `Inputs.todoById` and `Outputs.filteredTodoById`.
239
- We can think of this as setting up a pipeline, where one of more source facet members are mapped onto a destination
240
- facet member:
220
+ setCallbacks(filtering, {
221
+ apply: {
222
+ exit() {
223
+ highlightIsCorrectedOnFilterChange(ctr);
224
+ },
225
+ },
226
+ });
241
227
 
242
- ```
243
- _applyPolicies() {
244
- mapDataToFacet(
245
- [Filtering, 'inputItems'],
246
- getm([Inputs, 'todoById']),
247
- (x: TodoByIdT) => Object.values(x)
248
- )(this);
249
-
250
- const listToItemById = items => items.reduce((acc: any, x: TodoT) => ({ ...acc, [x.id]: x }), {})
251
-
252
- mapDataToFacet(
253
- [Outputs, 'filteredTodoById'],
254
- getm([Filtering, 'filteredItems']),
255
- listToItemById,
256
- )(this);
257
- }
228
+ return ctr;
229
+ }
258
230
  ```
259
231
 
260
- Notes:
232
+ ## Fact: the `exit` callback function is called at the end of the host function
261
233
 
262
- - the first argument to `mapDataToFacet` is a getter function that takes the container and returns some data.
263
- In this case, it uses the `getm` helper function to return the `todoById` member of the `Inputs` facet.
264
- - the second argument to `mapDataToFacet` is the facet member that receives the data. SkandhaJS will patch the
265
- facet, replacing the `inputItems` member with a `get inputItems()` property that runs the
266
- getter function.
267
- - the third argument is a transformation that is applied to the result of the getter function. In this case,
268
- it means that `get inputItems()` returns `Object.values(getm(Inputs, 'todoById')(ctr))`,
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.
269
235
 
270
- As the next step, we will connect the list of filtered items to `Selection.selectableIds` (so that selection
271
- happens in the filtered list). In addition, we will add datamappings for `Selection.items`
272
- and `Selection.item`:
236
+ ## Snippet: mapping data onto each facet
273
237
 
274
238
  ```
275
- _applyPolicies() {
276
- // ...
277
-
278
- mapDataToFacet(
279
- [Selection, 'selectableIds'],
280
- getm([Outputs, 'filteredTodoById']),
281
- (x: TodoByIdT) => Object.keys(x)
282
- )(this);
283
-
284
- mapDatasToFacet(
285
- [Selection, 'items'],
286
- [
287
- getm([Selection, 'ids']),
288
- getm([Inputs, 'todoById']),
289
- ],
290
- (ids: string[], todoById: TodoByIdT) => ids.map((id) => todoById[id])
291
- )(this);
292
-
293
- mapDatasToFacet(
294
- [Highlight, 'item'],
295
- [
296
- getm([Highlight, 'id']),
297
- getm([Inputs, 'todoById']),
298
- ],
299
- (id: string, todoById: TodoByIdT) => todoById[id]
300
- )(this);
301
- }
239
+ // file: createContainer.ts
240
+ import * from 'ramda' as R;
241
+
242
+ 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
+ });
253
+
254
+ const lookUpTodo = (id: string) => outputs.filteredTodoById[id];
255
+
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);
261
+
262
+ con.connect();
302
263
  ```
303
264
 
304
- We are done: the facets in the container are connected. Note that if we put these mapping policies in a reusable
305
- library then we can shorten `_applyPolicies` to:
265
+ ## Fact: the Connector class maps data between objects
306
266
 
307
- ```
308
- _applyPolicies() {
309
- // ...
310
-
311
- const policies = [
312
- // selection
313
- SelectionUsesSelectableIds([Outputs, 'filteredTodoById'], Object.keys),
314
- SelectionUsesItemLookUpTable([Inputs, 'todoById']),
315
-
316
- // highlighting
317
- HighlightUsesItemLookUpTable([Inputs, 'todoById']),
318
-
319
- // filtering
320
- FilteringUsesInputItems([Inputs, 'todoById'], Object.values),
321
- mapDataToFacet(
322
- [Outputs, 'filteredTodoById'],
323
- getm([Filtering, 'filteredItems']),
324
- listToItemById,
325
- ),
326
- ]
327
-
328
- installPolicies<TodosCtr>(policies, this);
329
- }
330
- ```
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.
331
269
 
332
270
  ### Logging
333
271
 
334
- By calling `setOptions({logging: true})`, the Skandha library allows you to inspect each facet before and
335
- after calling an operation (remember that an operation is any facet member function decorated with @operation).
336
- This will log Facet members that are decorated with `@data` in a way that looks similar to what you are
337
- used to from Redux:
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:
338
275
 
339
276
  - before and after each operation, the entire container that holds the facet is logged
340
277
  - all data members of each facet in the container are included in the log
@@ -344,23 +281,14 @@ used to from Redux:
344
281
 
345
282
  If you want to render the facets with React then we need to let React know when data changes.
346
283
  The [skandha-mobx](http://github.com/mnieber/skandha-mobx) library was created for this purpose.
347
- It turns each `@data` member into a Mobx property that is either observable or computed. By decorating
348
- your render function with `observer` you will get a re-render for any changes (in observable facet members)
349
- in the container:
284
+ It has a `registerCtr` function that turns each `@data` member into a Mobx property that is
285
+ either observable or computed:
350
286
 
351
287
  ```
352
- class TodosCtr {
353
- // ...
354
-
355
- constructor() {
356
- registerFacets(this);
357
- this._setCallbacks();
358
- this._applyPolicies();
359
-
360
- // Turn all facets in the container into observable objects.
361
- // Members annotated with @data will become either @observable or @computed.
362
- makeCtrObservable(this);
363
- }
288
+ registerCtr({
289
+ ctr: ctr,
290
+ options: { name: 'TodosCtr'},
291
+ });
364
292
  ```
365
293
 
366
294
  ## Conclusion
@@ -373,5 +301,4 @@ a policy that says that changing the highlight disables editing. By using facets
373
301
  editing-specific code directly in the highlight function (that would obviously be mixing concerns).
374
302
 
375
303
  In my experience, using SkandhaJS leads to code reuse and improved structure. On top of that, it offers a good
376
- debugging experience (yes, you will still have to debug) by logging all changes in the container. Finally,
377
- it works nicely with MobX to render the container contents.
304
+ 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,12 +1,12 @@
1
- export { ctrState } from "./internal/logging";
2
- export { getc, getFacetMemberNames, addCleanUpFunctionToCtr, cleanUpCtr, } from "./lib/ctr";
3
- export { input, output, data, log, getLoggedMemberNames, getDataMemberNames, } from "./lib/data";
4
- export { mapDataToFacet, mapDatasToFacet, patchFacet, mapDataToProp, } from "./lib/patch";
5
- export { getOptions, setOptions } from "./internal/options";
6
- export { operation, getOperationMemberNames } from "./lib/operation";
7
- export { facet, registerFacets, getm, getf } from "./lib/facet";
8
- export { installPolicies } from "./lib/install";
1
+ export { getCtrState } from './internal/logging';
2
+ export { getOptions, setOptions } from './internal/options';
3
+ export { createConnector } from './lib/Connector';
4
+ export { getc, getFacetMemberNames } from './lib/ctr';
5
+ export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
6
+ export { getf } from './lib/facet';
7
+ export { installPolicies } from './lib/install';
8
+ export { getOperationMemberNames, operation } from './lib/operation';
9
+ export { mapDataToProp } from './lib/patch';
10
+ export { registerCtr } from './lib/registerCtr';
11
+ export type { OptionsT as RegisterCtrOptionsT } from './lib/registerCtr';
9
12
  export declare type ClassT = any;
10
- export declare type MemberNameT = string;
11
- export declare type ClassMemberT = [ClassT, MemberNameT, any?];
12
- export declare type GetterT<T = any> = (ctr: any) => T;
package/index.js CHANGED
@@ -1,8 +1,10 @@
1
- export { ctrState } from "./internal/logging";
2
- export { getc, getFacetMemberNames, addCleanUpFunctionToCtr, cleanUpCtr, } from "./lib/ctr";
3
- export { input, output, data, log, getLoggedMemberNames, getDataMemberNames, } from "./lib/data";
4
- export { mapDataToFacet, mapDatasToFacet, patchFacet, mapDataToProp, } from "./lib/patch";
5
- export { getOptions, setOptions } from "./internal/options";
6
- export { operation, getOperationMemberNames } from "./lib/operation";
7
- export { facet, registerFacets, getm, getf } from "./lib/facet";
8
- export { installPolicies } from "./lib/install";
1
+ export { getCtrState } from './internal/logging';
2
+ export { getOptions, setOptions } from './internal/options';
3
+ export { createConnector } from './lib/Connector';
4
+ export { getc, getFacetMemberNames } from './lib/ctr';
5
+ export { data, getDataMemberNames, getLoggedMemberNames, input, log, output, } from './lib/data';
6
+ export { getf } from './lib/facet';
7
+ export { installPolicies } from './lib/install';
8
+ export { getOperationMemberNames, operation } from './lib/operation';
9
+ export { mapDataToProp } from './lib/patch';
10
+ export { registerCtr } from './lib/registerCtr';
@@ -2,4 +2,5 @@ export declare function _defaultFacetLogName(facet: any): string;
2
2
  export declare function facetLogName(facet: any): any;
3
3
  export declare function log(facet: any, operationMember: any, args: any, start: any): void;
4
4
  export declare function facetState(facet: any): {};
5
- export declare function ctrState(ctr: any): any;
5
+ export declare function defaultGetCtrState(ctr: any): any;
6
+ export declare function getCtrState(ctr: any): any;
@@ -9,13 +9,13 @@ var __assign = (this && this.__assign) || function () {
9
9
  };
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
- import { getLoggedMemberNames } from "../lib/data";
13
- import { getCtrAdmin, getFacetAdmin } from "../internal/utils";
14
- import { getc, getFacetMemberNames } from "../lib/ctr";
15
- import { options } from "./options";
12
+ import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
13
+ import { getc, getFacetMemberNames } from '../lib/ctr';
14
+ import { getLoggedMemberNames } from '../lib/data';
15
+ import { options } from './options';
16
16
  export function _defaultFacetLogName(facet) {
17
17
  var ctr = getc(facet);
18
- var prefix = ctr ? ctr.constructor.name + "/" : "";
18
+ var prefix = ctr ? ctr.constructor.name + '/' : '';
19
19
  return prefix + facet.constructor.name;
20
20
  }
21
21
  export function facetLogName(facet) {
@@ -25,26 +25,23 @@ export function facetLogName(facet) {
25
25
  function camelToSnake(string) {
26
26
  return string
27
27
  .replace(/[\w]([A-Z])/g, function (m) {
28
- return m[0] + "_" + m[1];
28
+ return m[0] + '_' + m[1];
29
29
  })
30
30
  .toLowerCase();
31
31
  }
32
32
  var opName = function (operationMember) { return camelToSnake(operationMember).toUpperCase(); };
33
33
  export function log(facet, operationMember, args, start) {
34
- var _a;
35
34
  var ctr = getc(facet);
36
- var ctrAdmin = ctr ? getCtrAdmin(ctr) : undefined;
37
- var getCtrState = (_a = ctrAdmin === null || ctrAdmin === void 0 ? void 0 : ctrAdmin.ctrStateOverride) !== null && _a !== void 0 ? _a : ctrState;
38
35
  var getState = ctr ? function () { return getCtrState(ctr); } : function () { return facetState(facet); };
39
36
  var operationName = opName(operationMember);
40
- var label = facetLogName(facet) + "." + operationName;
37
+ var label = facetLogName(facet) + '.' + operationName;
41
38
  if (start) {
42
39
  console.group(label);
43
- console.log("%c args: ", "color: gray", args);
44
- console.log("%c state", "color: gray", getState());
40
+ console.log('%c args: ', 'color: gray', args);
41
+ console.log('%c state', 'color: gray', getState());
45
42
  }
46
43
  else {
47
- console.log("%c next", "color: gray", getState());
44
+ console.log('%c next', 'color: gray', getState());
48
45
  // @ts-ignore
49
46
  console.groupEnd(label);
50
47
  }
@@ -61,7 +58,7 @@ export function facetState(facet) {
61
58
  }
62
59
  }, {});
63
60
  }
64
- export function ctrState(ctr) {
61
+ export function defaultGetCtrState(ctr) {
65
62
  if (ctr) {
66
63
  return getFacetMemberNames(ctr).reduce(function (acc, facetMemberName) {
67
64
  var _a;
@@ -70,3 +67,7 @@ export function ctrState(ctr) {
70
67
  }, {});
71
68
  }
72
69
  }
70
+ export function getCtrState(ctr) {
71
+ var _a, _b;
72
+ return ((_b = (_a = getCtrAdmin(ctr)) === null || _a === void 0 ? void 0 : _a.ctrStateOverride) !== null && _b !== void 0 ? _b : defaultGetCtrState)(ctr);
73
+ }
@@ -1,3 +1,3 @@
1
1
  export var symbols = {
2
- admin: Symbol("skandha administration"),
2
+ admin: Symbol('skandha administration'),
3
3
  };
@@ -1,6 +1,5 @@
1
1
  export declare function getOrCreate(obj: any, key: any, fn: any): any;
2
2
  export declare const zip: (arr: any, ...arrs: any) => any;
3
- export declare function getCtrClassAdmin(ctrClass: any): any;
4
3
  export declare function getCtrAdmin(ctr: any): any;
5
4
  export declare function getFacetClassAdmin(facetClass: any): any;
6
5
  export declare function getFacetAdmin(facet: any): any;
package/internal/utils.js CHANGED
@@ -1,9 +1,13 @@
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
- import { symbols } from "./symbols";
10
+ import { symbols } from './symbols';
7
11
  export function getOrCreate(obj, key, fn) {
8
12
  if (!obj[key]) {
9
13
  obj[key] = fn();
@@ -16,12 +20,9 @@ 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
- export function getCtrClassAdmin(ctrClass) {
23
- return getOrCreate(ctrClass, symbols.admin, function () { return ({}); });
24
- }
25
26
  export function getCtrAdmin(ctr) {
26
27
  return getOrCreate(ctr, symbols.admin, function () { return ({}); });
27
28
  }
@@ -0,0 +1,5 @@
1
+ declare type FunctionByNameT = {
2
+ [name: string]: Function;
3
+ };
4
+ export declare const createConnector: (entities: FunctionByNameT) => any;
5
+ export {};
@@ -0,0 +1,98 @@
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
+ }());
package/lib/ctr.d.ts CHANGED
@@ -1,5 +1,3 @@
1
1
  export declare function getc<CtrT = any>(facet: any): CtrT;
2
2
  export declare function setCtr(facet: any, ctr: any): void;
3
3
  export declare function getFacetMemberNames(ctr: any): any;
4
- export declare function addCleanUpFunctionToCtr(ctr: any, f: any): void;
5
- export declare function cleanUpCtr(ctr: any): void;
package/lib/ctr.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getCtrAdmin, getFacetAdmin } from "../internal/utils";
1
+ import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
2
2
  export function getc(facet) {
3
3
  return getFacetAdmin(facet).parentContainer;
4
4
  }
@@ -9,15 +9,3 @@ export function getFacetMemberNames(ctr) {
9
9
  var _a;
10
10
  return (_a = getCtrAdmin(ctr).facetMembers) !== null && _a !== void 0 ? _a : [];
11
11
  }
12
- export function addCleanUpFunctionToCtr(ctr, f) {
13
- var _a;
14
- var ctrAdmin = getCtrAdmin(ctr);
15
- ctrAdmin.cleanUpFunctions = (_a = ctrAdmin.cleanUpFunctions) !== null && _a !== void 0 ? _a : [];
16
- ctrAdmin.cleanUpFunctions.push(f);
17
- }
18
- export function cleanUpCtr(ctr) {
19
- var _a;
20
- var ctrAdmin = getCtrAdmin(ctr);
21
- ctrAdmin.cleanUpFunctions = (_a = ctrAdmin.cleanUpFunctions) !== null && _a !== void 0 ? _a : [];
22
- ctrAdmin.cleanUpFunctions.forEach(function (x) { return x(); });
23
- }
package/lib/data.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getFacetClassAdmin } from "../internal/utils";
1
+ import { getFacetClassAdmin } from '../internal/utils';
2
2
  export function log(dataHost, dataMember, descriptor) {
3
3
  var _a;
4
4
  if (descriptor === void 0) { descriptor = undefined; }
package/lib/facet.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- import { ClassT, GetterT, ClassMemberT } from "..";
2
- export declare function facet(facetHost: any, facetMember: any, descriptor?: undefined): undefined;
3
- export declare function registerFacets(ctr: any, options: {
4
- name?: string;
5
- members?: string[];
6
- ctrState?: Function;
7
- }): void;
8
- export declare function getf(facetClass: ClassT, ctr?: any): any;
9
- export declare function getm<T = any>(classMember: ClassMemberT): GetterT<T>;
1
+ import { ClassT } from '..';
2
+ export declare function facetClassName(facetClass: ClassT): any;
3
+ export declare function getf(facetClass: ClassT | string, ctr?: any): any;
package/lib/facet.js CHANGED
@@ -1,47 +1,17 @@
1
- import { setCtr } from "./ctr";
2
- import { getCtrClassAdmin, getCtrAdmin, getFacetAdmin, } from "../internal/utils";
3
- export function facet(facetHost, facetMember, descriptor) {
4
- var _a;
5
- if (descriptor === void 0) { descriptor = undefined; }
6
- var ctrClassAdmin = getCtrClassAdmin(facetHost.constructor);
7
- ctrClassAdmin.facetMembers = (_a = ctrClassAdmin.facetMembers) !== null && _a !== void 0 ? _a : [];
8
- ctrClassAdmin.facetMembers.push(facetMember);
9
- return descriptor;
10
- }
11
- export function registerFacets(ctr, options) {
12
- var _a, _b, _c, _d, _e;
13
- var ctrName = (_a = options.name) !== null && _a !== void 0 ? _a : ctr.constructor.name;
14
- var ctrAdmin = getCtrAdmin(ctr);
15
- ctrAdmin.facetMembers = (_b = ctrAdmin.facetMembers) !== null && _b !== void 0 ? _b : [];
16
- ctrAdmin.facetByFacetClassName = (_c = ctrAdmin.facetByFacetClassName) !== null && _c !== void 0 ? _c : {};
17
- ctrAdmin.ctrStateOverride = options.ctrState;
18
- ((_e = (_d = options.members) !== null && _d !== void 0 ? _d : getCtrClassAdmin(ctr.constructor).facetMembers) !== null && _e !== void 0 ? _e : Object.getOwnPropertyNames(ctr)).forEach(function (member) {
19
- ctrAdmin.facetMembers.push(member);
20
- var facet = ctr[member];
21
- setCtr(facet, ctr);
22
- var facetAdmin = getFacetAdmin(facet);
23
- facetAdmin.logName = ctrName + "/" + facet.constructor.name;
24
- var className = facet.constructor.name;
25
- if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
26
- console.error("Two facets of same type " + className + " in container " + options.name);
27
- }
28
- ctrAdmin.facetByFacetClassName[className] = facet;
29
- });
1
+ import { getCtrAdmin } from '../internal/utils';
2
+ export function facetClassName(facetClass) {
3
+ return facetClass.className ? facetClass.className() : facetClass.name;
30
4
  }
31
5
  export function getf(facetClass, ctr) {
32
6
  var _a;
33
7
  if (!ctr)
34
8
  return function (ctr) { return getf(facetClass, ctr); };
35
- var facet = (_a = getCtrAdmin(ctr).facetByFacetClassName) === null || _a === void 0 ? void 0 : _a[facetClass.name];
9
+ var facet = typeof facetClass === 'string'
10
+ ? ctr[facetClass]
11
+ : (_a = getCtrAdmin(ctr).facetByFacetClassName) === null || _a === void 0 ? void 0 : _a[facetClassName(facetClass)];
36
12
  if (!facet) {
37
- console.error("No facet " + facetClass.name + " in container " + ctr.constructor.name);
13
+ var facetName = facetClass === 'string' ? facetClass : facetClassName(facetClass);
14
+ console.error("No facet ".concat(facetName, " in container ").concat(getCtrAdmin(ctr).logName));
38
15
  }
39
16
  return facet;
40
17
  }
41
- export function getm(classMember) {
42
- var f = function (ctr) { var _a; return getf(classMember[0], (_a = classMember[2]) !== null && _a !== void 0 ? _a : ctr)[classMember[1]]; };
43
- // Set some properties on f to easy debugging later
44
- f.facetClassName = classMember[0].name;
45
- f.facetMemberName = classMember[1];
46
- return f;
47
- }
package/lib/operation.js CHANGED
@@ -34,9 +34,9 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
34
34
  if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
35
  }
36
36
  };
37
- import { options } from "../internal/options";
38
- import { log } from "../internal/logging";
39
- import { getFacetClassAdmin } from "../internal/utils";
37
+ import { log } from '../internal/logging';
38
+ import { options } from '../internal/options';
39
+ import { getFacetClassAdmin } from '../internal/utils';
40
40
  function _operation(target, propertyName, descriptor, punctualOptions) {
41
41
  var _a;
42
42
  var f = descriptor.value;
@@ -44,7 +44,7 @@ function _operation(target, propertyName, descriptor, punctualOptions) {
44
44
  facetClassAdmin.operationMemberNames =
45
45
  (_a = facetClassAdmin.operationMemberNames) !== null && _a !== void 0 ? _a : [];
46
46
  facetClassAdmin.operationMemberNames.push(propertyName);
47
- if (typeof descriptor.value === "function") {
47
+ if (typeof descriptor.value === 'function') {
48
48
  descriptor.value = !!punctualOptions.async
49
49
  ? function () {
50
50
  var _a;
package/lib/patch.d.ts CHANGED
@@ -1,5 +1 @@
1
- import { GetterT, ClassMemberT } from "..";
2
1
  export declare function mapDataToProp(facet: any, prop: string, getter: () => any): void;
3
- export declare function patchFacet(facet: any, members: any, options?: any): void;
4
- export declare const mapDataToFacet: ([toFacetClass, toMember, toCtr]: ClassMemberT, getter: GetterT, transform?: Function | undefined) => (ctr: any) => void;
5
- export declare const mapDatasToFacet: ([toFacetClass, toMember, toCtr]: ClassMemberT, getters: Array<GetterT>, transform: Function) => (ctr: any) => void;
package/lib/patch.js CHANGED
@@ -1,17 +1,3 @@
1
- var __assign = (this && this.__assign) || function () {
2
- __assign = Object.assign || function(t) {
3
- for (var s, i = 1, n = arguments.length; i < n; i++) {
4
- s = arguments[i];
5
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
- t[p] = s[p];
7
- }
8
- return t;
9
- };
10
- return __assign.apply(this, arguments);
11
- };
12
- import { getf } from "./facet";
13
- import { facetLogName } from "../internal/logging";
14
- import { getDataMemberNames } from "./data";
15
1
  export function mapDataToProp(facet, prop, getter) {
16
2
  delete facet[prop];
17
3
  Object.defineProperty(facet, prop, {
@@ -20,57 +6,3 @@ export function mapDataToProp(facet, prop, getter) {
20
6
  configurable: true,
21
7
  });
22
8
  }
23
- export function patchFacet(facet, members, options) {
24
- var _a;
25
- var dataMemberNames = getDataMemberNames(facet);
26
- for (var prop in members) {
27
- if ((_a = options === null || options === void 0 ? void 0 : options.warnIfNotADataMember) !== null && _a !== void 0 ? _a : true) {
28
- if (!dataMemberNames.includes(prop)) {
29
- console.error("Patching a property " + prop + " that wasn't decorated with " +
30
- ("@data, @input or @output in " + facetLogName(facet)));
31
- }
32
- }
33
- delete facet[prop];
34
- Object.defineProperty(facet, prop, __assign(__assign({}, members[prop]), { enumerable: true, configurable: true }));
35
- }
36
- }
37
- export var mapDataToFacet = function (_a, getter, transform) {
38
- var toFacetClass = _a[0], toMember = _a[1], toCtr = _a[2];
39
- return function (ctr) {
40
- var _a;
41
- var patch = (_a = {},
42
- _a[toMember] = {
43
- get: function () {
44
- var context = {
45
- ctr: toCtr !== null && toCtr !== void 0 ? toCtr : ctr,
46
- getter: getter,
47
- toClassMember: [toFacetClass, toMember],
48
- data: getter(ctr),
49
- };
50
- return transform ? transform(context.data) : context.data;
51
- },
52
- },
53
- _a);
54
- patchFacet(getf(toFacetClass, toCtr !== null && toCtr !== void 0 ? toCtr : ctr), patch);
55
- };
56
- };
57
- export var mapDatasToFacet = function (_a, getters, transform) {
58
- var toFacetClass = _a[0], toMember = _a[1], toCtr = _a[2];
59
- return function (ctr) {
60
- var _a;
61
- var patch = (_a = {},
62
- _a[toMember] = {
63
- get: function () {
64
- var context = {
65
- ctr: toCtr !== null && toCtr !== void 0 ? toCtr : ctr,
66
- getters: getters,
67
- toClassMember: [toFacetClass, toMember],
68
- datas: getters.map(function (getter) { return getter(ctr); }),
69
- };
70
- return transform.apply(void 0, context.datas);
71
- },
72
- },
73
- _a);
74
- patchFacet(getf(toFacetClass, toCtr !== null && toCtr !== void 0 ? toCtr : ctr), patch);
75
- };
76
- };
@@ -0,0 +1,6 @@
1
+ export declare type OptionsT = {
2
+ name: string;
3
+ members?: string[];
4
+ ctrState?: Function;
5
+ };
6
+ export declare function registerCtr(ctr: any, options: OptionsT): void;
@@ -0,0 +1,31 @@
1
+ import { getCtrState } from '../internal/logging';
2
+ import { options as skandhaOptions } from '../internal/options';
3
+ import { getCtrAdmin, getFacetAdmin } from '../internal/utils';
4
+ import { setCtr } from './ctr';
5
+ import { facetClassName } from './facet';
6
+ export function registerCtr(ctr, options) {
7
+ var _a, _b, _c;
8
+ var ctrAdmin = getCtrAdmin(ctr);
9
+ ctrAdmin.logName = options.name;
10
+ ctrAdmin.facetMembers = (_a = ctrAdmin.facetMembers) !== null && _a !== void 0 ? _a : [];
11
+ ctrAdmin.facetByFacetClassName = (_b = ctrAdmin.facetByFacetClassName) !== null && _b !== void 0 ? _b : {};
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];
15
+ setCtr(facet, ctr);
16
+ ctrAdmin.facetMembers.push(member);
17
+ if (!facet.constructor.className) {
18
+ console.error("Facet ".concat(member, " of container ").concat(ctrAdmin.logName, " ") +
19
+ "does not have a static className function");
20
+ }
21
+ var className = facetClassName(facet.constructor);
22
+ getFacetAdmin(facet).logName = "".concat(ctrAdmin.logName, "/").concat(className);
23
+ if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
24
+ console.error("Two facets of same type ".concat(className, " in container ").concat(ctrAdmin.logName));
25
+ }
26
+ ctrAdmin.facetByFacetClassName[className] = facet;
27
+ });
28
+ if (skandhaOptions.logging) {
29
+ console.log('%c %s ctr initialized', 'color: gray', ctrAdmin.logName, getCtrState(ctr));
30
+ }
31
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skandha",
3
- "version": "0.5.0",
3
+ "version": "0.9.0",
4
4
  "description": "Create data containers with modular behaviours",
5
5
  "main": "index.js",
6
6
  "repository": "https://github.com/mnieber/skandha.git",