@trullock/firebase-common 1.1.1 → 1.2.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/package.json +2 -2
- package/src/automapper.js +22 -4
- package/src/utils.js +71 -0
- package/tests/automapper.test.js +130 -0
- package/tests/mixins.test.js +114 -0
- /package/tests/{getters.js → getters.test.js} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trullock/firebase-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Collection of common firebase helpers",
|
|
5
5
|
"homepage": "https://github.com/trullock/firebase#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"test": "node ./tests/
|
|
24
|
+
"test": "node ./tests/test.js"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
}
|
package/src/automapper.js
CHANGED
|
@@ -4,7 +4,7 @@ let mappings = [];
|
|
|
4
4
|
/**
|
|
5
5
|
*
|
|
6
6
|
* @param {function} toFirestore A function of the form (key: string, value: object/primitive) => object/primitive. Return a mapped value or undefined to fallback to default mapping
|
|
7
|
-
* @param {
|
|
7
|
+
* @param {function} fromFirestore A function of the form (key: string, value: object/primitive) => object/primitive. Return a mapped value or undefined to fallback to default mapping
|
|
8
8
|
*/
|
|
9
9
|
export function registerMapping(toFirestore, fromFirestore)
|
|
10
10
|
{
|
|
@@ -60,6 +60,23 @@ export function registerClassForPersistence(type, persistedGetters)
|
|
|
60
60
|
return doRegisterClassForPersistence(type, null, null, persistedGetters || [])
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function getAllStaticProps(type) {
|
|
64
|
+
const props = [];
|
|
65
|
+
let current = type;
|
|
66
|
+
while (current && current !== Function.prototype)
|
|
67
|
+
{
|
|
68
|
+
Object.getOwnPropertyNames(current)
|
|
69
|
+
.filter(p => !['length', 'name', 'prototype'].includes(p))
|
|
70
|
+
.forEach(p => props.push({
|
|
71
|
+
name: p,
|
|
72
|
+
value: Object.getOwnPropertyDescriptor(current, p).value
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
current = Object.getPrototypeOf(current);
|
|
76
|
+
}
|
|
77
|
+
return props;
|
|
78
|
+
}
|
|
79
|
+
|
|
63
80
|
function doRegisterClassForPersistence(type, name, parent, persistedGetters)
|
|
64
81
|
{
|
|
65
82
|
let key = name ? name : type.name;
|
|
@@ -72,10 +89,11 @@ function doRegisterClassForPersistence(type, name, parent, persistedGetters)
|
|
|
72
89
|
};
|
|
73
90
|
|
|
74
91
|
// static nested classes
|
|
75
|
-
|
|
92
|
+
let props = getAllStaticProps(type);
|
|
93
|
+
for(let prop of props)
|
|
76
94
|
{
|
|
77
|
-
if(value?.toString()?.substr(0, 5) == 'class')
|
|
78
|
-
doRegisterClassForPersistence(value,
|
|
95
|
+
if(prop.value?.toString()?.substr(0, 5) == 'class')
|
|
96
|
+
doRegisterClassForPersistence(prop.value, prop.name, key, persistedGetters)
|
|
79
97
|
}
|
|
80
98
|
}
|
|
81
99
|
|
package/src/utils.js
CHANGED
|
@@ -25,4 +25,75 @@ export function firestorePath(pathSegments)
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
return segemnts.join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function getAllProtoProps(obj) {
|
|
41
|
+
const props = [];
|
|
42
|
+
let proto = Object.getPrototypeOf(obj);
|
|
43
|
+
while (proto && proto !== Object.prototype)
|
|
44
|
+
{
|
|
45
|
+
Object.getOwnPropertyNames(proto)
|
|
46
|
+
.filter(p => p != 'constructor')
|
|
47
|
+
.forEach(p => props.push({
|
|
48
|
+
name: p,
|
|
49
|
+
descriptor: Object.getOwnPropertyDescriptor(proto, p)
|
|
50
|
+
}));
|
|
51
|
+
proto = Object.getPrototypeOf(proto);
|
|
52
|
+
}
|
|
53
|
+
return props
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getAllStaticProps(cls) {
|
|
57
|
+
const props = []
|
|
58
|
+
let current = cls;
|
|
59
|
+
while (current && current !== Function.prototype)
|
|
60
|
+
{
|
|
61
|
+
let properties = Object.getOwnPropertyNames(current)
|
|
62
|
+
properties = properties.filter(p => !['length', 'name', 'prototype'].includes(p))
|
|
63
|
+
properties.forEach(p => props.push({
|
|
64
|
+
name: p,
|
|
65
|
+
descriptor: Object.getOwnPropertyDescriptor(current, p)
|
|
66
|
+
}));
|
|
67
|
+
current = Object.getPrototypeOf(current);
|
|
68
|
+
}
|
|
69
|
+
return props;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getAllNonStaticProps(cls) {
|
|
73
|
+
// TODO: can we extend cls with a class with a construction that doesnt call super() to get instance properties without actually "instantiating" the class?
|
|
74
|
+
const instance = new cls();
|
|
75
|
+
// tempted not to do this, as it requires instantiating the class, but it is the only way I can think of to get instance properties that are not declared in the constructor
|
|
76
|
+
return Object.getOwnPropertyNames(instance).map(p => ({ name: p, descriptor: Object.getOwnPropertyDescriptor(instance, p) }))
|
|
77
|
+
.concat(getAllProtoProps(instance))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
export function Mixins(bases) {
|
|
82
|
+
class Bases {
|
|
83
|
+
constructor() {
|
|
84
|
+
bases.forEach(base => Object.assign(this, new base()));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
bases.forEach(base => {
|
|
88
|
+
let properties = getAllNonStaticProps(base);
|
|
89
|
+
properties.forEach(prop => {
|
|
90
|
+
Object.defineProperty(Bases.prototype, prop.name, prop.descriptor)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
properties = getAllStaticProps(base);
|
|
94
|
+
properties.forEach(prop => {
|
|
95
|
+
Object.defineProperty(Bases, prop.name, prop.descriptor)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
return Bases;
|
|
28
99
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { autoMapFromFirestore, autoMapToFirestore, registerClassForPersistence } from "../src/automapper.js";
|
|
4
|
+
import { Mixins } from "../src/utils.js";
|
|
5
|
+
|
|
6
|
+
class AggregateRoot
|
|
7
|
+
{
|
|
8
|
+
_uncomittedEvents = []
|
|
9
|
+
|
|
10
|
+
get isDirty() {
|
|
11
|
+
return this._uncomittedEvents.length > 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Applys a new domain event to this object
|
|
17
|
+
* @param {string} eventType The type of the event
|
|
18
|
+
* @param {object} data The event data
|
|
19
|
+
*/
|
|
20
|
+
apply(eventType, data)
|
|
21
|
+
{
|
|
22
|
+
data = data || {}
|
|
23
|
+
if(!data.aggregateRootId)
|
|
24
|
+
data.aggregateRootId = this.id;
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
this._uncomittedEvents.push({
|
|
28
|
+
type: eventType,
|
|
29
|
+
sequence: ++this._lastEventSequence,
|
|
30
|
+
data
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this._applyEvent(eventType, data);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_applyEvent(eventType, data)
|
|
37
|
+
{
|
|
38
|
+
let handlerName = 'on' + eventType.substr(0, 1).toUpperCase() + eventType.substr(1);
|
|
39
|
+
let fn = this[handlerName];
|
|
40
|
+
if(fn)
|
|
41
|
+
fn.call(this, data);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class Foo {
|
|
46
|
+
b = 2
|
|
47
|
+
bar = new Foo.Bar();
|
|
48
|
+
|
|
49
|
+
static Bar = class {
|
|
50
|
+
a = 1
|
|
51
|
+
|
|
52
|
+
barzy() {
|
|
53
|
+
return "barzy"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
foozzy() {
|
|
58
|
+
return "foozzy"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class Baz extends Foo {
|
|
63
|
+
static c = 3
|
|
64
|
+
d = 4
|
|
65
|
+
|
|
66
|
+
bazzy() {
|
|
67
|
+
return "bazzy"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class Academy extends Mixins([Baz, AggregateRoot])
|
|
72
|
+
{
|
|
73
|
+
changeName(name)
|
|
74
|
+
{
|
|
75
|
+
this.apply('academyNameChanged', {
|
|
76
|
+
name
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
onAcademyNameChanged(event)
|
|
81
|
+
{
|
|
82
|
+
this.name = event.name;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
constructor()
|
|
86
|
+
{
|
|
87
|
+
super();
|
|
88
|
+
this.bar = new Baz.Bar();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static Test = class {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
test('test', t => {
|
|
95
|
+
let a = new Academy();
|
|
96
|
+
|
|
97
|
+
assert.equal(a.isDirty, false)
|
|
98
|
+
|
|
99
|
+
a.changeName('name');
|
|
100
|
+
|
|
101
|
+
assert.equal(a.isDirty, true)
|
|
102
|
+
|
|
103
|
+
assert.equal(a.name, 'name')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
test('automapping', t => {
|
|
108
|
+
registerClassForPersistence(Academy);
|
|
109
|
+
|
|
110
|
+
let a = new Academy();
|
|
111
|
+
let mapped = autoMapToFirestore(a);
|
|
112
|
+
let unmapped = autoMapFromFirestore(mapped);
|
|
113
|
+
|
|
114
|
+
assert.equal(a.d, 4)
|
|
115
|
+
assert.equal(mapped.d, 4)
|
|
116
|
+
assert.equal(unmapped.d, 4)
|
|
117
|
+
|
|
118
|
+
assert.equal(a.bar.a, 1)
|
|
119
|
+
assert.equal(mapped.bar.a, 1)
|
|
120
|
+
assert.equal(unmapped.bar.a, 1)
|
|
121
|
+
|
|
122
|
+
assert.equal(a.bar.barzy(), "barzy")
|
|
123
|
+
assert.equal(unmapped.bar.barzy(), "barzy")
|
|
124
|
+
|
|
125
|
+
assert.equal(a.foozzy(), "foozzy")
|
|
126
|
+
assert.equal(unmapped.foozzy(), "foozzy")
|
|
127
|
+
|
|
128
|
+
assert.equal(a.bazzy(), "bazzy")
|
|
129
|
+
assert.equal(unmapped.bazzy(), "bazzy")
|
|
130
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import test, { beforeEach } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { Mixins } from "../src/utils.js";
|
|
4
|
+
|
|
5
|
+
class Sequencer
|
|
6
|
+
{
|
|
7
|
+
static sequence = [];
|
|
8
|
+
|
|
9
|
+
static reset()
|
|
10
|
+
{
|
|
11
|
+
this.sequence = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static next()
|
|
15
|
+
{
|
|
16
|
+
return this.sequence.shift();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Sequencer.reset();
|
|
21
|
+
|
|
22
|
+
class Foo {
|
|
23
|
+
a = 1
|
|
24
|
+
|
|
25
|
+
static staticA = 111
|
|
26
|
+
|
|
27
|
+
foo(){}
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
this.aa = Sequencer.next()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class Bar extends Foo {
|
|
35
|
+
b = 2
|
|
36
|
+
|
|
37
|
+
static staticB = 222
|
|
38
|
+
|
|
39
|
+
bar(){}
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
super()
|
|
43
|
+
this.bb = Sequencer.next()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class Qux {
|
|
48
|
+
qux(){}
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
this.dd = Sequencer.next()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class Baz extends Mixins([Bar, Qux]) {
|
|
56
|
+
c = 3
|
|
57
|
+
|
|
58
|
+
static staticC = 333
|
|
59
|
+
|
|
60
|
+
baz(){}
|
|
61
|
+
|
|
62
|
+
constructor() {
|
|
63
|
+
super()
|
|
64
|
+
this.cc = Sequencer.next()
|
|
65
|
+
|
|
66
|
+
// access own and inherited properties, plus own and inherited static properties
|
|
67
|
+
this.ee = this.c + this.b + this.a + Baz.staticC + Baz.staticB + Baz.staticA;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
beforeEach(()=> {
|
|
72
|
+
Sequencer.reset();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('instance properties', t => {
|
|
76
|
+
let baz = new Baz()
|
|
77
|
+
|
|
78
|
+
assert.equal(baz.a, 1)
|
|
79
|
+
assert.equal(baz.b, 2)
|
|
80
|
+
assert.equal(baz.c, 3)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('prototype methods', t => {
|
|
84
|
+
let baz = new Baz()
|
|
85
|
+
|
|
86
|
+
assert.equal(typeof baz.foo, 'function')
|
|
87
|
+
assert.equal(typeof baz.bar, 'function')
|
|
88
|
+
assert.equal(typeof baz.baz, 'function')
|
|
89
|
+
assert.equal(typeof baz.qux, 'function')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('constructors', t => {
|
|
93
|
+
let baz = new Baz()
|
|
94
|
+
|
|
95
|
+
assert.equal(baz.aa, 0)
|
|
96
|
+
assert.equal(baz.bb, 1)
|
|
97
|
+
assert.equal(baz.dd, 2)
|
|
98
|
+
assert.equal(baz.cc, 3)
|
|
99
|
+
|
|
100
|
+
let baz2 = new Baz()
|
|
101
|
+
|
|
102
|
+
assert.equal(baz2.aa, 4)
|
|
103
|
+
assert.equal(baz2.bb, 5)
|
|
104
|
+
assert.equal(baz2.dd, 6)
|
|
105
|
+
assert.equal(baz2.cc, 7)
|
|
106
|
+
|
|
107
|
+
assert.equal(baz.ee, 1 + 2 + 3 + 111 + 222 + 333)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('static properties', t => {
|
|
111
|
+
assert.equal(Baz.staticA, 111)
|
|
112
|
+
assert.equal(Baz.staticB, 222)
|
|
113
|
+
assert.equal(Baz.staticC, 333)
|
|
114
|
+
})
|
|
File without changes
|