@iebh/tera-fy 1.15.9 → 2.0.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/CHANGELOG.md +51 -0
- package/api.md +620 -494
- package/dist/plugin.vue2.es2019.js +2294 -1
- package/dist/terafy.es2019.js +2 -2
- package/dist/terafy.js +2 -2
- package/documentation.yml +9 -1
- package/lib/syncro.js +592 -0
- package/lib/terafy.client.js +153 -24
- package/lib/terafy.server.js +61 -21
- package/package.json +5 -1
- package/plugins/firebase.js +122 -0
- package/plugins/vue2.js +83 -94
- package/plugins/vue3.js +38 -142
package/lib/terafy.client.js
CHANGED
|
@@ -105,6 +105,7 @@ export default class TeraFy {
|
|
|
105
105
|
// Session
|
|
106
106
|
'getUser',
|
|
107
107
|
'requireUser',
|
|
108
|
+
'getCredentials',
|
|
108
109
|
|
|
109
110
|
// Projects
|
|
110
111
|
'bindProject',
|
|
@@ -114,11 +115,17 @@ export default class TeraFy {
|
|
|
114
115
|
'requireProject',
|
|
115
116
|
'selectProject',
|
|
116
117
|
|
|
118
|
+
// Project namespaces
|
|
119
|
+
// 'mountNamespace', // Handled by this library
|
|
120
|
+
// 'unmountNamespace', // Handled by this library
|
|
121
|
+
'getNamespace',
|
|
122
|
+
'setNamespace',
|
|
123
|
+
'listNamespaces',
|
|
124
|
+
|
|
117
125
|
// Project State
|
|
118
126
|
'getProjectState',
|
|
119
127
|
'setProjectState',
|
|
120
128
|
'setProjectStateDefaults',
|
|
121
|
-
'setProjectStateFlush',
|
|
122
129
|
'setProjectStateRefresh',
|
|
123
130
|
'saveProjectState',
|
|
124
131
|
'replaceProjectState',
|
|
@@ -164,6 +171,16 @@ export default class TeraFy {
|
|
|
164
171
|
plugins = [];
|
|
165
172
|
|
|
166
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Active namespaces we are subscribed to
|
|
176
|
+
* Each key is the namespace name with the value as the local reactive \ observer \ object equivelent
|
|
177
|
+
* The key string is always of the form `${ENTITY}::${ID}` e.g. `projects:1234`
|
|
178
|
+
*
|
|
179
|
+
* @type {Object<Object>}
|
|
180
|
+
*/
|
|
181
|
+
namespaces = {};
|
|
182
|
+
|
|
183
|
+
|
|
167
184
|
// Messages - send(), sendRaw(), rpc(), acceptMessage() {{{
|
|
168
185
|
|
|
169
186
|
/**
|
|
@@ -303,6 +320,69 @@ export default class TeraFy {
|
|
|
303
320
|
|
|
304
321
|
// }}}
|
|
305
322
|
|
|
323
|
+
// Project namespace - mountNamespace(), unmountNamespace() {{{
|
|
324
|
+
/**
|
|
325
|
+
* Make a namespace available locally
|
|
326
|
+
* This generally creates whatever framework flavoured reactive/observer/object is supported locally - generally with writes automatically synced with the master state
|
|
327
|
+
*
|
|
328
|
+
* @function mountNamespace
|
|
329
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
330
|
+
*
|
|
331
|
+
* @returns {Promise<Reactive>} A promise which resolves to the reactive object
|
|
332
|
+
*/
|
|
333
|
+
mountNamespace(name) {
|
|
334
|
+
if (!/^[\w-]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
|
|
335
|
+
if (this.namespaces[name]) return Promise.resolve(this.namespaces[name]); // Already mounted
|
|
336
|
+
|
|
337
|
+
return Promise.resolve()
|
|
338
|
+
.then(()=> this._mountNamespace(name))
|
|
339
|
+
.then(()=> this.namespaces[name] || Promise.reject(`teraFy.mountNamespace('${name}') resolved but no namespace has been mounted`))
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @interface
|
|
345
|
+
* Actual namespace mounting function designed to be overriden by plugins
|
|
346
|
+
*
|
|
347
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
348
|
+
*
|
|
349
|
+
* @returns {Promise} A promise which resolves when the mount operation has completed
|
|
350
|
+
*/
|
|
351
|
+
_mountNamespace(name) {
|
|
352
|
+
console.warn('teraFy._mountNamespace() has not been overriden by a TERA-fy plugin, load one to add this functionality for your preferred framework');
|
|
353
|
+
throw new Error('teraFy._mountNamespace() is not supported');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Release a locally mounted namespace
|
|
359
|
+
* This function will remove the namespace from `namespaces`, cleaning up any memory / subscription hooks
|
|
360
|
+
*
|
|
361
|
+
* @function unmountNamespace
|
|
362
|
+
*
|
|
363
|
+
* @param {String} name The name of the namespace to unmount
|
|
364
|
+
*
|
|
365
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
366
|
+
*/
|
|
367
|
+
unmountNamespace(name) {
|
|
368
|
+
if (!this.namespaces[name]) return Promise.resolve(); // Already unmounted
|
|
369
|
+
return this._unmountNamespace(name);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @interface
|
|
375
|
+
* Actual namespace unmounting function designed to be overriden by plugins
|
|
376
|
+
*
|
|
377
|
+
* @param {String} name The name of the namespace to unmount
|
|
378
|
+
*
|
|
379
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
380
|
+
*/
|
|
381
|
+
_unmountNamespace(name) {
|
|
382
|
+
console.warn('teraFy.unbindNamespace() has not been overriden by a TERA-fy plugin, load one to add this functionality for your preferred framework');
|
|
383
|
+
}
|
|
384
|
+
// }}}
|
|
385
|
+
|
|
306
386
|
// Project state - createProjectStatePatch(), applyProjectStatePatchLocal() {{{
|
|
307
387
|
/**
|
|
308
388
|
* Create + transmit a new project state patch base on the current and previous states
|
|
@@ -838,19 +918,35 @@ export default class TeraFy {
|
|
|
838
918
|
* @param {Object} source Initalized source object to extend from
|
|
839
919
|
*/
|
|
840
920
|
mixin(target, source) {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
921
|
+
// Iterate through the source object upwards extracting each prototype
|
|
922
|
+
let prototypeStack = [];
|
|
923
|
+
let node = source;
|
|
924
|
+
do {
|
|
925
|
+
prototypeStack.unshift(node);
|
|
926
|
+
} while (node = Object.getPrototypeOf(node)); // Walk upwards until we hit null (no more inherited classes)
|
|
927
|
+
|
|
928
|
+
// Iterate through stacks inheriting each prop into the target
|
|
929
|
+
prototypeStack.forEach(stack =>
|
|
930
|
+
Object.getOwnPropertyNames(stack)
|
|
931
|
+
.filter(prop =>
|
|
932
|
+
!['constructor', 'init', 'prototype', 'name'].includes(prop) // Ignore forbidden properties
|
|
933
|
+
&& !prop.startsWith('__') // Ignore double underscore meta properties
|
|
934
|
+
)
|
|
935
|
+
.forEach(prop => {
|
|
936
|
+
if (typeof source[prop] == 'function') { // Inheriting function - glue onto object as non-editable, non-enumerable property
|
|
937
|
+
Object.defineProperty(
|
|
938
|
+
target,
|
|
939
|
+
prop,
|
|
940
|
+
{
|
|
941
|
+
enumerable: false,
|
|
942
|
+
value: source[prop].bind(target), // Rebind functions
|
|
943
|
+
},
|
|
944
|
+
);
|
|
945
|
+
} else { // Everything else, just glue onto the object
|
|
946
|
+
target[prop] = source[prop];
|
|
947
|
+
}
|
|
948
|
+
})
|
|
949
|
+
)
|
|
854
950
|
}
|
|
855
951
|
|
|
856
952
|
|
|
@@ -955,6 +1051,14 @@ export default class TeraFy {
|
|
|
955
1051
|
*/
|
|
956
1052
|
|
|
957
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Provide an object of credentials for 3rd party services like Firebase/Supabase
|
|
1056
|
+
*
|
|
1057
|
+
* @function getCredentials
|
|
1058
|
+
* @returns {Object} An object containing 3rd party service credentials
|
|
1059
|
+
*/
|
|
1060
|
+
|
|
1061
|
+
|
|
958
1062
|
/**
|
|
959
1063
|
* Require a user login to TERA
|
|
960
1064
|
* If there is no user OR they are not logged in a prompt is shown to go and do so
|
|
@@ -1038,6 +1142,40 @@ export default class TeraFy {
|
|
|
1038
1142
|
*/
|
|
1039
1143
|
|
|
1040
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* Get a one-off snapshot of a namespace without mounting it
|
|
1147
|
+
* This can be used for simpler apps which don't have their own reactive / observer equivelent
|
|
1148
|
+
*
|
|
1149
|
+
* @function getNamespace
|
|
1150
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
1151
|
+
*
|
|
1152
|
+
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
1153
|
+
*/
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Set (or merge by default) a one-off snapshot over an existing namespace
|
|
1158
|
+
* This can be used for simpler apps which don't have their own reactive / observer equivelent and just want to quickly set something
|
|
1159
|
+
*
|
|
1160
|
+
* @function setNamespace
|
|
1161
|
+
* @param {String} name The name of the namespace
|
|
1162
|
+
* @param {Object} state The state to merge
|
|
1163
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
1164
|
+
* @param {'merge'|'set'} [options.method='merge'] How to handle the state. 'merge' (merge a partial state over the existing namespace state), 'set' (completely overwrite the existing namespace)
|
|
1165
|
+
*
|
|
1166
|
+
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
1167
|
+
*/
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Return a list of namespaces available to the current project
|
|
1172
|
+
*
|
|
1173
|
+
* @function listNamespaces
|
|
1174
|
+
* @returns {Promise<Array<Object>>} Collection of available namespaces for the current project
|
|
1175
|
+
* @property {String} name The name of the namespace
|
|
1176
|
+
*/
|
|
1177
|
+
|
|
1178
|
+
|
|
1041
1179
|
/**
|
|
1042
1180
|
* Return the current, full snapshot state of the active project
|
|
1043
1181
|
*
|
|
@@ -1081,15 +1219,6 @@ export default class TeraFy {
|
|
|
1081
1219
|
*/
|
|
1082
1220
|
|
|
1083
1221
|
|
|
1084
|
-
/**
|
|
1085
|
-
* Force copying local changes to the server
|
|
1086
|
-
* This is only ever needed when saving large quantities of data that need to be immediately available
|
|
1087
|
-
*
|
|
1088
|
-
* @function setProjectStateFlush
|
|
1089
|
-
* @returns {Promise} A promise which resolves when the operation has completed
|
|
1090
|
-
*/
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
1222
|
/**
|
|
1094
1223
|
* Force refetching the remote project state into local
|
|
1095
1224
|
* This is only ever needed when saving large quantities of data that need to be immediately available
|
|
@@ -1428,9 +1557,9 @@ export default class TeraFy {
|
|
|
1428
1557
|
*
|
|
1429
1558
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
1430
1559
|
* @param {String} [options.body] Optional additional body text
|
|
1560
|
+
* @param {Boolean} [options.isHtml=false] If truthy, treat the body as HTML
|
|
1431
1561
|
* @param {String} [options.value] Current or default value to display pre-filled
|
|
1432
1562
|
* @param {String} [options.title='Input required'] The dialog title to display
|
|
1433
|
-
* @param {Boolean} [options.bodyHtml=false] If truthy, treat the body as HTML
|
|
1434
1563
|
* @param {String} [options.placeholder] Optional placeholder text
|
|
1435
1564
|
* @param {Boolean} [options.required=true] Treat nullish or empty inputs as a cancel operation
|
|
1436
1565
|
*
|
package/lib/terafy.server.js
CHANGED
|
@@ -469,6 +469,16 @@ export default class TeraFyServer {
|
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Provide an object of credentials for 3rd party services like Firebase/Supabase
|
|
474
|
+
*
|
|
475
|
+
* @returns {Object} An object containing 3rd party service credentials
|
|
476
|
+
*/
|
|
477
|
+
getCredentials() {
|
|
478
|
+
return app.service('$auth').credentials;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
|
|
472
482
|
/**
|
|
473
483
|
* In embed mode only - create a popup window and try to auth via that
|
|
474
484
|
*
|
|
@@ -736,6 +746,54 @@ export default class TeraFyServer {
|
|
|
736
746
|
|
|
737
747
|
// }}}
|
|
738
748
|
|
|
749
|
+
// Project namespaces - getNamespace(), setNamespace(), listNamespaces() {{{
|
|
750
|
+
/**
|
|
751
|
+
* Get a one-off snapshot of a namespace without mounting it
|
|
752
|
+
* This can be used for simpler apps which don't have their own reactive / observer equivelent
|
|
753
|
+
*
|
|
754
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
755
|
+
*
|
|
756
|
+
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
757
|
+
*/
|
|
758
|
+
getNamespace(name) {
|
|
759
|
+
if (!/^[\w-]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
|
|
760
|
+
|
|
761
|
+
return this.$syncro.getSnapshot(`project_namespaces::${this.$projects.active.id}::${name}`);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Set (or merge by default) a one-off snapshot over an existing namespace
|
|
767
|
+
* This can be used for simpler apps which don't have their own reactive / observer equivelent and just want to quickly set something
|
|
768
|
+
*
|
|
769
|
+
* @param {String} name The name of the namespace
|
|
770
|
+
* @param {Object} state The state to merge
|
|
771
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
772
|
+
* @param {'merge'|'set'} [options.method='merge'] How to handle the state. 'merge' (merge a partial state over the existing namespace state), 'set' (completely overwrite the existing namespace)
|
|
773
|
+
*
|
|
774
|
+
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
775
|
+
*/
|
|
776
|
+
setNamespace(name, state, options) {
|
|
777
|
+
if (!/^[\w--]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
|
|
778
|
+
if (typeof state != 'object') throw new Error('State must be an object');
|
|
779
|
+
|
|
780
|
+
return this.$syncro.setSnapshot(`project_namespaces::${this.$projects.active.id}}::${name}`, state, {
|
|
781
|
+
method: options.method,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Return a list of namespaces available to the current project
|
|
788
|
+
*
|
|
789
|
+
* @returns {Promise<Array<Object>>} Collection of available namespaces for the current project
|
|
790
|
+
* @property {String} name The name of the namespace
|
|
791
|
+
*/
|
|
792
|
+
listNamespaces() {
|
|
793
|
+
return app.service('$projects').listNamespaces();
|
|
794
|
+
}
|
|
795
|
+
// }}}
|
|
796
|
+
|
|
739
797
|
// Project State - getProjectState(), setProjectState(), setProjectStateDefaults(), replaceProjectState(), applyProjectStatePatch() {{{
|
|
740
798
|
|
|
741
799
|
/**
|
|
@@ -819,13 +877,11 @@ export default class TeraFyServer {
|
|
|
819
877
|
* @param {String|Array<String>} [path] The sub-path within the project state to set, if unspecifed the entire target is used as a target and a save operation is forced
|
|
820
878
|
* @param {*} value The value to set as the default structure
|
|
821
879
|
* @param {Object} [options] Additional options to mutate behaviour, see setProjectState() for the full list of supported options
|
|
822
|
-
* @param {Boolean} [options.flush=true] Force a re-read from the server after setting, prevents race conditions with large objects. Calls `setProjectStateFlush()` after applying defaults
|
|
823
880
|
*
|
|
824
881
|
* @returns {Promise<*>} A promise which resolves to the eventual input value after defaults have been applied
|
|
825
882
|
*/
|
|
826
883
|
setProjectStateDefaults(path, value, options) {
|
|
827
884
|
let settings = {
|
|
828
|
-
flush: true,
|
|
829
885
|
...options,
|
|
830
886
|
};
|
|
831
887
|
if (!app.service('$projects').active) throw new Error('No active project');
|
|
@@ -841,7 +897,6 @@ export default class TeraFyServer {
|
|
|
841
897
|
...settings,
|
|
842
898
|
},
|
|
843
899
|
)
|
|
844
|
-
.then(()=> settings.flush && this.setProjectStateFlush())
|
|
845
900
|
.then(()=> pathTools.get(target, path));
|
|
846
901
|
} else { // Called as (value) - Populate entire project layout
|
|
847
902
|
pathTools.defaults(target, path);
|
|
@@ -850,26 +905,11 @@ export default class TeraFyServer {
|
|
|
850
905
|
newState: cloneDeep(target),
|
|
851
906
|
});
|
|
852
907
|
return this.saveProjectState()
|
|
853
|
-
.then(()=> settings.flush && this.setProjectStateFlush())
|
|
854
908
|
.then(()=> target);
|
|
855
909
|
}
|
|
856
910
|
}
|
|
857
911
|
|
|
858
912
|
|
|
859
|
-
/**
|
|
860
|
-
* Force copying local changes to the server
|
|
861
|
-
* This is only ever needed when saving large quantities of data that need to be immediately available
|
|
862
|
-
*
|
|
863
|
-
* @returns {Promise} A promise which resolves when the operation has completed
|
|
864
|
-
*/
|
|
865
|
-
setProjectStateFlush() {
|
|
866
|
-
this.debug('INFO', 1, 'Force project state flush!');
|
|
867
|
-
if (!app.service('$projects').active) throw new Error('No active project');
|
|
868
|
-
return app.service('$projects').active.$touchLocal()
|
|
869
|
-
.then(()=> null)
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
|
|
873
913
|
/**
|
|
874
914
|
* Force refetching the remote project state into local
|
|
875
915
|
*
|
|
@@ -1644,9 +1684,9 @@ export default class TeraFyServer {
|
|
|
1644
1684
|
*
|
|
1645
1685
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
1646
1686
|
* @param {String} [options.body] Optional additional body text
|
|
1687
|
+
* @param {Boolean} [options.isHtml=false] If truthy, treat the body as HTML
|
|
1647
1688
|
* @param {String} [options.value] Current or default value to display pre-filled
|
|
1648
1689
|
* @param {String} [options.title='Input required'] The dialog title to display
|
|
1649
|
-
* @param {Boolean} [options.bodyHtml=false] If truthy, treat the body as HTML
|
|
1650
1690
|
* @param {String} [options.placeholder] Optional placeholder text
|
|
1651
1691
|
* @param {Boolean} [options.required=true] Treat nullish or empty inputs as a cancel operation
|
|
1652
1692
|
*
|
|
@@ -1655,7 +1695,7 @@ export default class TeraFyServer {
|
|
|
1655
1695
|
uiPrompt(text, options) {
|
|
1656
1696
|
let settings = {
|
|
1657
1697
|
body: '',
|
|
1658
|
-
|
|
1698
|
+
isHtml: false,
|
|
1659
1699
|
title: 'Input required',
|
|
1660
1700
|
value: '',
|
|
1661
1701
|
placeholder: '',
|
|
@@ -1674,7 +1714,7 @@ export default class TeraFyServer {
|
|
|
1674
1714
|
component: 'UiPrompt',
|
|
1675
1715
|
componentProps: {
|
|
1676
1716
|
body: settings.body,
|
|
1677
|
-
|
|
1717
|
+
isHtml: settings.isHtml,
|
|
1678
1718
|
placeholder: settings.placeholder,
|
|
1679
1719
|
value: settings.value,
|
|
1680
1720
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/tera-fy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "TERA website worker",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "esbuild --platform=browser --format=esm --bundle lib/terafy.client.js --outfile=dist/terafy.js --minify --serve --servedir=.",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"./projectFile": "./lib/projectFile.js",
|
|
34
34
|
"./proxy": "./lib/terafy.proxy.js",
|
|
35
35
|
"./server": "./lib/terafy.server.js",
|
|
36
|
+
"./syncro": "./lib/syncro.js",
|
|
36
37
|
"./plugins/*": "./plugins/*.js",
|
|
37
38
|
"./widgets/*": "./widgets/*"
|
|
38
39
|
},
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"node": ">=18"
|
|
73
74
|
},
|
|
74
75
|
"dependencies": {
|
|
76
|
+
"@momsfriendlydevco/marshal": "^2.1.2",
|
|
75
77
|
"detect-port": "^1.6.1",
|
|
76
78
|
"filesize": "^10.1.4",
|
|
77
79
|
"http-proxy": "^1.18.1",
|
|
@@ -91,6 +93,8 @@
|
|
|
91
93
|
"nodemon": "^3.1.7"
|
|
92
94
|
},
|
|
93
95
|
"peerDependencies": {
|
|
96
|
+
"@supabase/supabase-js": "^2.48.1",
|
|
97
|
+
"firebase": "^11.3.1",
|
|
94
98
|
"vue": "^3.0.0"
|
|
95
99
|
},
|
|
96
100
|
"optionalDependencies": {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {initializeApp as Firebase} from 'firebase/app';
|
|
2
|
+
import {getFirestore as Firestore} from 'firebase/firestore';
|
|
3
|
+
import {createClient as Supabase} from '@supabase/supabase-js'
|
|
4
|
+
import Syncro from '../lib/syncro.js';
|
|
5
|
+
import TeraFyPluginBase from './base.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Plugin which adds Firebase / Firestore support for namespace mounts
|
|
9
|
+
*
|
|
10
|
+
* @class TeraFyPluginFirebase
|
|
11
|
+
*/
|
|
12
|
+
export default class TeraFyPluginFirebase extends TeraFyPluginBase {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lookup object of mounted Syncro objects by path
|
|
16
|
+
*
|
|
17
|
+
* @type {Object<Syncro>}
|
|
18
|
+
*/
|
|
19
|
+
syncros = {};
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @interface
|
|
24
|
+
* The Syncro#reactive option to use when creating new Syncro instances
|
|
25
|
+
* This is expected to be overriden by other plugins
|
|
26
|
+
* If falsy the Syncro module will fall back to its internal (POJO only) getReactive() function
|
|
27
|
+
*
|
|
28
|
+
* @name getReactive
|
|
29
|
+
* @type {Function} A reactive function as defined in Syncro
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Setup Firebase + Firestore + Supabase
|
|
35
|
+
* Default credentials (Firebase + Supabase) will be retrieved from `getCredentials()` unless overriden here
|
|
36
|
+
*
|
|
37
|
+
* @param {Object} options Additional options to mutate behaviour (defaults to the main teraFy settings)
|
|
38
|
+
* @param {String} [options.firebaseApiKey] Firebase API key
|
|
39
|
+
* @param {String} [options.firebaseAuthDomain] Firebase authorized domain
|
|
40
|
+
* @param {String} [options.firebaseProjectId] Firebase project ID
|
|
41
|
+
* @param {String} [options.firebaseAppId] Firebase App ID
|
|
42
|
+
* @param {String} [options.supabaseUrl] Supabase URL
|
|
43
|
+
* @param {String} [options.supabaseKey] Supabase client key
|
|
44
|
+
*
|
|
45
|
+
* @returns {Promise} A Promise which will resolve when the init process has completed
|
|
46
|
+
*/
|
|
47
|
+
async init(options) {
|
|
48
|
+
let settings = {
|
|
49
|
+
firebaseApiKey: null,
|
|
50
|
+
firebaseAuthDomain: null,
|
|
51
|
+
firebaseProjectId: null,
|
|
52
|
+
firebaseAppId: null,
|
|
53
|
+
supabaseUrl: null,
|
|
54
|
+
supabaseKey: null,
|
|
55
|
+
...await this.getCredentials(),
|
|
56
|
+
...options,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let emptyValues = Object.keys(settings).filter(k => k === null);
|
|
60
|
+
if (emptyValues.length > 0)
|
|
61
|
+
throw new Error('Firebase plugin requires mandatory options: ' + emptyValues.join(', '));
|
|
62
|
+
|
|
63
|
+
Syncro.firebase = Firebase({
|
|
64
|
+
apiKey: settings.firebaseApiKey,
|
|
65
|
+
authDomain: settings.firebaseAuthDomain,
|
|
66
|
+
projectId: settings.firebaseProjectId,
|
|
67
|
+
appId: settings.firebaseAppId,
|
|
68
|
+
});
|
|
69
|
+
Syncro.firestore = Firestore(this.firebase);
|
|
70
|
+
|
|
71
|
+
Syncro.supabase = Supabase(settings.supabaseUrl, settings.supabaseKey);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Mount the given namespace against `namespaces[name]`
|
|
77
|
+
*
|
|
78
|
+
* @param {'_PROJECT'|String} name The name/Syncro path of the namespace to mount (or '_PROJECT' for the project mountpoint)
|
|
79
|
+
*
|
|
80
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
81
|
+
*/
|
|
82
|
+
_mountNamespace(name) {
|
|
83
|
+
let syncro; // The eventually bootstrapped Syncro object
|
|
84
|
+
|
|
85
|
+
return Promise.resolve()
|
|
86
|
+
.then(()=> this.requireProject())
|
|
87
|
+
.then(project => {
|
|
88
|
+
let path = name == '_PROJECT'
|
|
89
|
+
? `projects::${project.id}`
|
|
90
|
+
: `project_namespaces::${project.id}::${name}`;
|
|
91
|
+
|
|
92
|
+
syncro = this.syncros[name] = new Syncro(path, {
|
|
93
|
+
debug: (...msg) => this.debug(`SYNCRO://${path}`, ...msg),
|
|
94
|
+
getReactive: this.getReactive, // Try to inherit this instances getReactive prop, otherwise Syncro will fall back to its default
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Perform the mount action
|
|
98
|
+
return syncro.mount();
|
|
99
|
+
})
|
|
100
|
+
.then(()=> { // Assign local state
|
|
101
|
+
this.namespaces[name] = syncro.value;
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Unmount the given namespace from `namespaces[name]`
|
|
108
|
+
*
|
|
109
|
+
* @param {String} name The name/Syncro path of the namespace to unmount
|
|
110
|
+
*
|
|
111
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
112
|
+
*/
|
|
113
|
+
_unmountNamespace(name) {
|
|
114
|
+
let syncro = this.syncros[name]; // Create local alias for Syncro before we detach it
|
|
115
|
+
|
|
116
|
+
// Detach local state
|
|
117
|
+
delete this.namespaces[name];
|
|
118
|
+
delete this.syncros[name];
|
|
119
|
+
|
|
120
|
+
return syncro.destroy(); // Trigger Syncro distruction
|
|
121
|
+
}
|
|
122
|
+
}
|