@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trullock/firebase-common",
3
- "version": "1.1.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/getters.js"
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 {funciton} fromFirestore 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 {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
- for(let [k, value] of Object.entries(type))
92
+ let props = getAllStaticProps(type);
93
+ for(let prop of props)
76
94
  {
77
- if(value?.toString()?.substr(0, 5) == 'class')
78
- doRegisterClassForPersistence(value, k, key, persistedGetters)
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