@trullock/firebase-common 1.1.1 → 1.3.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 +23 -5
- package/src/utils.js +71 -0
- package/tests/automapper.test.js +150 -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.3.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, dest: object) => 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
|
|
|
@@ -219,7 +237,7 @@ export function autoMapFromFirestore(obj)
|
|
|
219
237
|
let handled = false;
|
|
220
238
|
for(let mapping of mappings)
|
|
221
239
|
{
|
|
222
|
-
let result = mapping.fromFirestore(key, value)
|
|
240
|
+
let result = mapping.fromFirestore(key, value, dest)
|
|
223
241
|
if(result !== undefined)
|
|
224
242
|
{
|
|
225
243
|
dest[key] = result;
|
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,150 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { autoMapFromFirestore, autoMapToFirestore, registerClassForPersistence, registerMapping } 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
|
+
static Test = class {}
|
|
74
|
+
|
|
75
|
+
uniqueList = new Set();
|
|
76
|
+
|
|
77
|
+
changeName(name)
|
|
78
|
+
{
|
|
79
|
+
this.apply('academyNameChanged', {
|
|
80
|
+
name
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
onAcademyNameChanged(event)
|
|
85
|
+
{
|
|
86
|
+
this.name = event.name;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
constructor()
|
|
90
|
+
{
|
|
91
|
+
super();
|
|
92
|
+
this.bar = new Baz.Bar();
|
|
93
|
+
this.uniqueList.add('a')
|
|
94
|
+
this.uniqueList.add('b')
|
|
95
|
+
this.uniqueList.add('b')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
test('test', t => {
|
|
101
|
+
let a = new Academy();
|
|
102
|
+
|
|
103
|
+
assert.equal(a.isDirty, false)
|
|
104
|
+
|
|
105
|
+
a.changeName('name');
|
|
106
|
+
|
|
107
|
+
assert.equal(a.isDirty, true)
|
|
108
|
+
|
|
109
|
+
assert.equal(a.name, 'name')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
test('automapping', t => {
|
|
114
|
+
registerClassForPersistence(Academy);
|
|
115
|
+
|
|
116
|
+
registerMapping((key, value) => {
|
|
117
|
+
if(value instanceof Set)
|
|
118
|
+
return Array.from(value)
|
|
119
|
+
return undefined;
|
|
120
|
+
}, (key, value, dest) => {
|
|
121
|
+
if(dest[key] instanceof Set)
|
|
122
|
+
return new Set(value);
|
|
123
|
+
return undefined;
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
let a = new Academy();
|
|
128
|
+
let mapped = autoMapToFirestore(a);
|
|
129
|
+
let unmapped = autoMapFromFirestore(mapped);
|
|
130
|
+
|
|
131
|
+
assert.equal(a.d, 4)
|
|
132
|
+
assert.equal(mapped.d, 4)
|
|
133
|
+
assert.equal(unmapped.d, 4)
|
|
134
|
+
|
|
135
|
+
assert.equal(a.bar.a, 1)
|
|
136
|
+
assert.equal(mapped.bar.a, 1)
|
|
137
|
+
assert.equal(unmapped.bar.a, 1)
|
|
138
|
+
|
|
139
|
+
assert.equal(a.bar.barzy(), "barzy")
|
|
140
|
+
assert.equal(unmapped.bar.barzy(), "barzy");
|
|
141
|
+
|
|
142
|
+
assert.equal(a.foozzy(), "foozzy")
|
|
143
|
+
assert.equal(unmapped.foozzy(), "foozzy")
|
|
144
|
+
|
|
145
|
+
assert.equal(a.bazzy(), "bazzy")
|
|
146
|
+
assert.equal(unmapped.bazzy(), "bazzy")
|
|
147
|
+
|
|
148
|
+
assert(a.uniqueList instanceof Set)
|
|
149
|
+
assert.equal(unmapped.uniqueList.size, 2)
|
|
150
|
+
})
|
|
@@ -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
|