skandha 0.9.1 → 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 +71 -54
- package/index.d.ts +1 -2
- package/index.js +1 -2
- package/internal/utils.js +5 -9
- package/lib/facet.js +1 -1
- package/lib/patch.d.ts +3 -1
- package/lib/patch.js +13 -0
- package/lib/registerCtr.js +3 -3
- package/package.json +1 -1
- package/lib/Connector.d.ts +0 -5
- package/lib/Connector.js +0 -107
package/README.md
CHANGED
|
@@ -14,16 +14,18 @@ The goal of SkandhaJS is to provide data containers that have reusable behaviors
|
|
|
14
14
|
|
|
15
15
|
## Glossary (conceptual)
|
|
16
16
|
|
|
17
|
+
- callback function: a function that is called by an operation to help implement that operation
|
|
17
18
|
- callbacks object: a collection of callback functions that is used by an operation.
|
|
18
|
-
- container: a class that contains multiple facets.
|
|
19
|
+
- container: a class that contains multiple facets. Typically, these facets are made to work together using callback functions.
|
|
19
20
|
- facet: a class that implements a reusable behaviour, such as selection.
|
|
20
21
|
- operation: a member function of a facet that changes the data of that facet.
|
|
21
22
|
|
|
22
23
|
## Glossary (technical)
|
|
23
24
|
|
|
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.
|
|
25
27
|
- registerCtr: the function that tells SkandhaJS which containers and facets exist.
|
|
26
|
-
-
|
|
28
|
+
- setCallbackMap: the function that installs the callbacks objects that are used by a facet.
|
|
27
29
|
|
|
28
30
|
## Example: a simplified Selection facet
|
|
29
31
|
|
|
@@ -52,52 +54,52 @@ export class Selection<ValueT = any> {
|
|
|
52
54
|
|
|
53
55
|
## Fact: facets have @input, @data and @output members
|
|
54
56
|
|
|
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()`.
|
|
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.
|
|
56
58
|
|
|
57
59
|
## Fact: operations are decorated with `@operation`.
|
|
58
60
|
|
|
59
|
-
|
|
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.
|
|
60
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.
|
|
61
63
|
|
|
62
64
|
## Fact: operations are logged
|
|
63
65
|
|
|
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.
|
|
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.
|
|
65
67
|
|
|
66
68
|
## Example: a container with Selection, Highlight and Filtering
|
|
67
69
|
|
|
68
70
|
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.
|
|
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).
|
|
70
72
|
|
|
71
73
|
```
|
|
72
74
|
// file: createContainer.ts
|
|
73
75
|
import { Selection_selectItem } from 'SelectionCbs';
|
|
74
76
|
|
|
75
77
|
const createContainer() {
|
|
76
|
-
const
|
|
78
|
+
const todosCtr = {
|
|
77
79
|
selection: new Selection(),
|
|
78
80
|
highlight: new Highlight(),
|
|
79
81
|
filtering: new Filtering()
|
|
80
82
|
};
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
setCallbackMap(todosCtr.selection, {
|
|
83
85
|
selectItem: {
|
|
84
86
|
select(this: Selection_selectItem): {
|
|
85
|
-
handleSelectItem(
|
|
86
|
-
highlightFollowsSelection(
|
|
87
|
+
handleSelectItem(todosCtr.selection, this.selectionParams);
|
|
88
|
+
highlightFollowsSelection(todosCtr, this.selectionParams);
|
|
87
89
|
},
|
|
88
90
|
}
|
|
89
91
|
});
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
setCallbackMap(todosCtr.filtering, {
|
|
92
94
|
apply: {
|
|
93
95
|
exit() {
|
|
94
|
-
highlightIsCorrectedOnFilterChange(
|
|
96
|
+
highlightIsCorrectedOnFilterChange(todosCtr);
|
|
95
97
|
},
|
|
96
98
|
},
|
|
97
99
|
});
|
|
98
100
|
|
|
99
|
-
registerCtr({ ctr:
|
|
100
|
-
return
|
|
101
|
+
registerCtr({ ctr: todosCtr, options: { name: 'Todos'} });
|
|
102
|
+
return todosCtr;
|
|
101
103
|
}
|
|
102
104
|
```
|
|
103
105
|
|
|
@@ -141,11 +143,11 @@ export function handleSelectItem(
|
|
|
141
143
|
|
|
142
144
|
## Note: the todos container has selection, highlight and filtering
|
|
143
145
|
|
|
144
|
-
In `createContainer.ts` we see how `createContainer()` creates a `
|
|
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.
|
|
145
147
|
|
|
146
|
-
## Fact: callbacks are installed with `
|
|
148
|
+
## Fact: callbacks are installed with `setCallbackMap()`
|
|
147
149
|
|
|
148
|
-
We call `
|
|
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.
|
|
149
151
|
|
|
150
152
|
## Fact: callbacks can take care of side effects
|
|
151
153
|
|
|
@@ -153,12 +155,16 @@ An important advantage of using callbacks is that they can also take care of sid
|
|
|
153
155
|
|
|
154
156
|
## Fact: the callback function can access the callbacks object through `this`.
|
|
155
157
|
|
|
156
|
-
When the clients calls `
|
|
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`.
|
|
157
159
|
|
|
158
|
-
## Fact: the `exit` callback function is called at the end of an operation
|
|
160
|
+
## Fact: the `exit()` callback function is called at the end of an operation
|
|
159
161
|
|
|
160
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.
|
|
161
163
|
|
|
164
|
+
## Fact: the `registerCtr()` allows SkandhaJS to introspect the container
|
|
165
|
+
|
|
166
|
+
At the end of `createContainer.ts` we call `registerCtr()` so that SkandhaJS can introspect the container. This is necessary for two reasons. First, it makes it possible to look up a facet by its name or class, e.g. `getf(todosCtr, Selection)` returns `todosCtr.selection`. Second, it informs SkandhaJS how to log all facets of the container
|
|
167
|
+
|
|
162
168
|
## Example: a complete Selection facet
|
|
163
169
|
|
|
164
170
|
It's time now to return to the Selection facet and show how callbacks really work.
|
|
@@ -203,7 +209,7 @@ export class Selection_selectItem extends Cbs {
|
|
|
203
209
|
|
|
204
210
|
The `@host` decorator comes from the `Aspiration` library. When you call a function `f` that is decorated with `@host` then the following happens:
|
|
205
211
|
|
|
206
|
-
- Aspiration looks up the callbacks object for the operation (that was installed with `
|
|
212
|
+
- Aspiration looks up the callbacks object for the operation (that was installed with `setCallbackMap()`)
|
|
207
213
|
- it copies the operation arguments to the callbacks object (so that callback functions can access these arguments)
|
|
208
214
|
- if calls `f(arguments)(callbacksObject)`.
|
|
209
215
|
|
|
@@ -217,63 +223,74 @@ As we mentioned earlier, each callback function of the callbacks object has acce
|
|
|
217
223
|
|
|
218
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.
|
|
219
225
|
|
|
220
|
-
## Example:
|
|
226
|
+
## Example: `mapDataToProps()` maps data onto each facet
|
|
221
227
|
|
|
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.
|
|
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.
|
|
223
229
|
|
|
224
230
|
```
|
|
225
231
|
// file: createContainer.ts
|
|
226
232
|
import * from 'ramda' as R;
|
|
233
|
+
import { mapDataToProps, pmap } from 'skandha';
|
|
227
234
|
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const todos = {
|
|
235
|
+
const createContainer = () => {
|
|
236
|
+
const todosCtr = {
|
|
237
|
+
data: {
|
|
238
|
+
todos: loadTodos(),
|
|
239
|
+
filteredTodoById: {},
|
|
240
|
+
},
|
|
235
241
|
selection: new Selection(),
|
|
236
242
|
highlight: new Highlight(),
|
|
237
243
|
filtering: new Filtering(),
|
|
238
244
|
}
|
|
239
245
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
+
}
|
|
248
271
|
|
|
249
|
-
|
|
272
|
+
setOptions({logging: true});
|
|
250
273
|
```
|
|
251
274
|
|
|
252
|
-
## Fact: the
|
|
275
|
+
## Fact: the `mapDataToProps()` function maps data between objects
|
|
276
|
+
|
|
277
|
+
The `mapDataToProp()` function takes a container, field name and function then it turns that field into a `get` property that executes the given function. The `mapDataToProps()` is a small convenience function that calls `mapDataToProp()` on each of its arguments. In our example, we see that the `todosCtr.data.todos` field is mapped onto the `todosCtr.filtering.inputItems` field.
|
|
278
|
+
|
|
279
|
+
## Fact: the `pmap()` function adds type-checking
|
|
253
280
|
|
|
254
|
-
The
|
|
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`.
|
|
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.
|
|
256
282
|
|
|
257
|
-
|
|
283
|
+
## Fact: logging can be enabled using `setOptions()`
|
|
258
284
|
|
|
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:
|
|
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:
|
|
261
286
|
|
|
262
287
|
- before and after each operation, the entire container that holds the facet is logged
|
|
263
288
|
- all data members of each facet in the container are included in the log
|
|
264
289
|
- log entries are nested so that you can see how operation calls are nested
|
|
265
290
|
|
|
266
|
-
|
|
291
|
+
## Fact: the `registerCtr()` function from facets-mobx makes facets observable
|
|
267
292
|
|
|
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
|
|
269
|
-
either observable or computed:
|
|
270
|
-
|
|
271
|
-
```
|
|
272
|
-
registerCtr({
|
|
273
|
-
ctr: ctr,
|
|
274
|
-
options: { name: 'Todos'},
|
|
275
|
-
});
|
|
276
|
-
```
|
|
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.
|
|
277
294
|
|
|
278
295
|
## Conclusion
|
|
279
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
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 "
|
|
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:
|
|
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
|
+
}
|
package/lib/registerCtr.js
CHANGED
|
@@ -15,13 +15,13 @@ export function registerCtr(ctr, options) {
|
|
|
15
15
|
setCtr(facet, ctr);
|
|
16
16
|
ctrAdmin.facetMembers.push(facetName);
|
|
17
17
|
if (!facet.constructor.className) {
|
|
18
|
-
console.error("Facet "
|
|
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 =
|
|
22
|
+
getFacetAdmin(facet).logName = ctrAdmin.logName + "/" + className;
|
|
23
23
|
if (ctrAdmin.facetByFacetClassName[className] !== undefined) {
|
|
24
|
-
console.error("Two facets of same type "
|
|
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
package/lib/Connector.d.ts
DELETED
package/lib/Connector.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
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
|
-
}
|
|
12
|
-
var Connector = /** @class */ (function () {
|
|
13
|
-
function Connector(obj, options) {
|
|
14
|
-
var _this = this;
|
|
15
|
-
var _a;
|
|
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
|
-
};
|
|
33
|
-
this.connect = function () {
|
|
34
|
-
for (var _i = 0, _a = Object.values(_this.entityByKey); _i < _a.length; _i++) {
|
|
35
|
-
var entity = _a[_i];
|
|
36
|
-
_this._connectEntity(entity);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
this._connectEntity = function (entity) {
|
|
40
|
-
var _loop_1 = function (entry) {
|
|
41
|
-
var name_1 = entry[0], field = entry[1];
|
|
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, ".") +
|
|
44
|
-
'Field not found. Disable strict mode to ignore this error.');
|
|
45
|
-
}
|
|
46
|
-
mapDataToProp(entity.obj, name_1, function () {
|
|
47
|
-
return field.getSource();
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
for (var _i = 0, _a = Object.entries(entity.fieldByName); _i < _a.length; _i++) {
|
|
51
|
-
var entry = _a[_i];
|
|
52
|
-
_loop_1(entry);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
this.options = {
|
|
56
|
-
strict: (_a = options === null || options === void 0 ? void 0 : options.strict) !== null && _a !== void 0 ? _a : true,
|
|
57
|
-
};
|
|
58
|
-
this.obj = obj;
|
|
59
|
-
}
|
|
60
|
-
return Connector;
|
|
61
|
-
}());
|
|
62
|
-
export var createConnector = function (obj, options) {
|
|
63
|
-
var con = new Connector(obj, options);
|
|
64
|
-
return new Proxy(con, {
|
|
65
|
-
get: function (target, key) {
|
|
66
|
-
var _a;
|
|
67
|
-
return (_a = target[key]) !== null && _a !== void 0 ? _a : target.getOrCreateEntity(key);
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
var Entity = /** @class */ (function () {
|
|
72
|
-
function Entity(obj) {
|
|
73
|
-
var _this = this;
|
|
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;
|
|
81
|
-
}
|
|
82
|
-
return Entity;
|
|
83
|
-
}());
|
|
84
|
-
var Field = /** @class */ (function () {
|
|
85
|
-
function Field(obj, name) {
|
|
86
|
-
var _this = this;
|
|
87
|
-
this.transforms = [];
|
|
88
|
-
this.getSource = function () {
|
|
89
|
-
if (!(_this.name in _this.obj)) {
|
|
90
|
-
throw new Error("Field ".concat(_this.name, " does not exist on object ").concat(_this.obj));
|
|
91
|
-
}
|
|
92
|
-
var result = _this.obj[_this.name];
|
|
93
|
-
for (var _i = 0, _a = _this.transforms; _i < _a.length; _i++) {
|
|
94
|
-
var tf = _a[_i];
|
|
95
|
-
result = tf(result);
|
|
96
|
-
}
|
|
97
|
-
return result;
|
|
98
|
-
};
|
|
99
|
-
this.obj = obj;
|
|
100
|
-
this.name = name;
|
|
101
|
-
}
|
|
102
|
-
Field.prototype.tf = function (transform) {
|
|
103
|
-
this.transforms.push(transform);
|
|
104
|
-
return this;
|
|
105
|
-
};
|
|
106
|
-
return Field;
|
|
107
|
-
}());
|