@iebh/tera-fy 1.15.8 → 2.0.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/CHANGELOG.md +38 -0
- package/api.md +621 -488
- package/dist/plugin.vue2.es2019.js +2294 -1
- package/dist/terafy.es2019.js +2 -2
- package/dist/terafy.js +2 -2
- package/eslint.config.js +5 -0
- package/lib/syncro.js +452 -0
- package/lib/terafy.client.js +161 -26
- package/lib/terafy.server.js +66 -21
- package/package.json +6 -3
- package/plugins/base.js +2 -3
- package/plugins/firebase.js +122 -0
- package/plugins/vue2.js +88 -96
- package/plugins/vue3.js +38 -137
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,68 @@ 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
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
329
|
+
*
|
|
330
|
+
* @returns {Promise<Reactive>} A promise which resolves to the reactive object
|
|
331
|
+
*/
|
|
332
|
+
mountNamespace(name) {
|
|
333
|
+
if (!/^[\w-]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
|
|
334
|
+
if (this.namespaces[name]) return Promise.resolve(this.namespaces[name]); // Already mounted
|
|
335
|
+
|
|
336
|
+
return Promise.resolve()
|
|
337
|
+
.then(()=> this._mountNamespace(name))
|
|
338
|
+
.then(()=> this.namespaces[name] || Promise.reject(`teraFy.mountNamespace('${name}') resolved but no namespace has been mounted`))
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* @interface
|
|
344
|
+
* Actual namespace mounting function designed to be overriden by plugins
|
|
345
|
+
*
|
|
346
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
347
|
+
*
|
|
348
|
+
* @returns {Promise} A promise which resolves when the mount operation has completed
|
|
349
|
+
*/
|
|
350
|
+
_mountNamespace(name) {
|
|
351
|
+
console.warn('teraFy._mountNamespace() has not been overriden by a TERA-fy plugin, load one to add this functionality for your preferred framework');
|
|
352
|
+
throw new Error('teraFy._mountNamespace() is not supported');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Release a locally mounted namespace
|
|
358
|
+
* This function will remove the namespace from `namespaces`, cleaning up any memory / subscription hooks
|
|
359
|
+
*
|
|
360
|
+
* @interface
|
|
361
|
+
*
|
|
362
|
+
* @param {String} name The name of the namespace to unmount
|
|
363
|
+
*
|
|
364
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
365
|
+
*/
|
|
366
|
+
unmountNamespace(name) {
|
|
367
|
+
if (!this.namespaces[name]) return Promise.resolve(); // Already unmounted
|
|
368
|
+
return this._unmountNamespace(name);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @interface
|
|
374
|
+
* Actual namespace unmounting function designed to be overriden by plugins
|
|
375
|
+
*
|
|
376
|
+
* @param {String} name The name of the namespace to unmount
|
|
377
|
+
*
|
|
378
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
379
|
+
*/
|
|
380
|
+
_unmountNamespace(name) {
|
|
381
|
+
console.warn('teraFy.unbindNamespace() has not been overriden by a TERA-fy plugin, load one to add this functionality for your preferred framework');
|
|
382
|
+
}
|
|
383
|
+
// }}}
|
|
384
|
+
|
|
306
385
|
// Project state - createProjectStatePatch(), applyProjectStatePatchLocal() {{{
|
|
307
386
|
/**
|
|
308
387
|
* Create + transmit a new project state patch base on the current and previous states
|
|
@@ -316,8 +395,13 @@ export default class TeraFy {
|
|
|
316
395
|
*/
|
|
317
396
|
createProjectStatePatch(newState, oldState) {
|
|
318
397
|
let patch = diff(oldState, newState);
|
|
319
|
-
|
|
320
|
-
|
|
398
|
+
if (patch.length == 0) {
|
|
399
|
+
this.debug('INFO', 4, 'Skipping empty project patch', {patch, newState, oldState});
|
|
400
|
+
return Promise.resolve();
|
|
401
|
+
} else {
|
|
402
|
+
this.debug('INFO', 3, 'Created project patch', {patch, newState, oldState});
|
|
403
|
+
return this.applyProjectStatePatch(patch);
|
|
404
|
+
}
|
|
321
405
|
}
|
|
322
406
|
|
|
323
407
|
|
|
@@ -329,6 +413,7 @@ export default class TeraFy {
|
|
|
329
413
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
330
414
|
*/
|
|
331
415
|
applyProjectStatePatch(patch) {
|
|
416
|
+
// watchedPaths guards {{{
|
|
332
417
|
if (this.settings.devMode && this.settings.debugPaths) {
|
|
333
418
|
if (!Array.isArray(this.settings.debugPaths)) throw new Error('teraFyClient.settings.debugPaths should be either null or an Array<String>');
|
|
334
419
|
|
|
@@ -343,6 +428,7 @@ export default class TeraFy {
|
|
|
343
428
|
debugger; // eslint-disable-line no-debugger
|
|
344
429
|
}
|
|
345
430
|
}
|
|
431
|
+
// }}}
|
|
346
432
|
|
|
347
433
|
return this.rpc('applyProjectStatePatch', patch, {
|
|
348
434
|
session: this.settings.session,
|
|
@@ -831,19 +917,35 @@ export default class TeraFy {
|
|
|
831
917
|
* @param {Object} source Initalized source object to extend from
|
|
832
918
|
*/
|
|
833
919
|
mixin(target, source) {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
920
|
+
// Iterate through the source object upwards extracting each prototype
|
|
921
|
+
let prototypeStack = [];
|
|
922
|
+
let node = source;
|
|
923
|
+
do {
|
|
924
|
+
prototypeStack.unshift(node);
|
|
925
|
+
} while (node = Object.getPrototypeOf(node)); // Walk upwards until we hit null (no more inherited classes)
|
|
926
|
+
|
|
927
|
+
// Iterate through stacks inheriting each prop into the target
|
|
928
|
+
prototypeStack.forEach(stack =>
|
|
929
|
+
Object.getOwnPropertyNames(stack)
|
|
930
|
+
.filter(prop =>
|
|
931
|
+
!['constructor', 'init', 'prototype', 'name'].includes(prop) // Ignore forbidden properties
|
|
932
|
+
&& !prop.startsWith('__') // Ignore double underscore meta properties
|
|
933
|
+
)
|
|
934
|
+
.forEach(prop => {
|
|
935
|
+
if (typeof source[prop] == 'function') { // Inheriting function - glue onto object as non-editable, non-enumerable property
|
|
936
|
+
Object.defineProperty(
|
|
937
|
+
target,
|
|
938
|
+
prop,
|
|
939
|
+
{
|
|
940
|
+
enumerable: false,
|
|
941
|
+
value: source[prop].bind(target), // Rebind functions
|
|
942
|
+
},
|
|
943
|
+
);
|
|
944
|
+
} else { // Everything else, just glue onto the object
|
|
945
|
+
target[prop] = source[prop];
|
|
946
|
+
}
|
|
947
|
+
})
|
|
948
|
+
)
|
|
847
949
|
}
|
|
848
950
|
|
|
849
951
|
|
|
@@ -948,6 +1050,14 @@ export default class TeraFy {
|
|
|
948
1050
|
*/
|
|
949
1051
|
|
|
950
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* Provide an object of credentials for 3rd party services like Firebase/Supabase
|
|
1055
|
+
*
|
|
1056
|
+
* @functions getCredentials
|
|
1057
|
+
* @returns {Object} An object containing 3rd party service credentials
|
|
1058
|
+
*/
|
|
1059
|
+
|
|
1060
|
+
|
|
951
1061
|
/**
|
|
952
1062
|
* Require a user login to TERA
|
|
953
1063
|
* If there is no user OR they are not logged in a prompt is shown to go and do so
|
|
@@ -1031,6 +1141,40 @@ export default class TeraFy {
|
|
|
1031
1141
|
*/
|
|
1032
1142
|
|
|
1033
1143
|
|
|
1144
|
+
/**
|
|
1145
|
+
* Get a one-off snapshot of a namespace without mounting it
|
|
1146
|
+
* This can be used for simpler apps which don't have their own reactive / observer equivelent
|
|
1147
|
+
*
|
|
1148
|
+
* @function getNamespace
|
|
1149
|
+
* @param {String} name The alias of the namespace, this should be alphanumeric + hyphens + underscores
|
|
1150
|
+
*
|
|
1151
|
+
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
1152
|
+
*/
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Set (or merge by default) a one-off snapshot over an existing namespace
|
|
1157
|
+
* This can be used for simpler apps which don't have their own reactive / observer equivelent and just want to quickly set something
|
|
1158
|
+
*
|
|
1159
|
+
* @function setNamespace
|
|
1160
|
+
* @param {String} name The name of the namespace
|
|
1161
|
+
* @param {Object} state The state to merge
|
|
1162
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
1163
|
+
* @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)
|
|
1164
|
+
*
|
|
1165
|
+
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
1166
|
+
*/
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* Return a list of namespaces available to the current project
|
|
1171
|
+
*
|
|
1172
|
+
* @function listNamespaces
|
|
1173
|
+
* @returns {Promise<Array<Object>>} Collection of available namespaces for the current project
|
|
1174
|
+
* @property {String} name The name of the namespace
|
|
1175
|
+
*/
|
|
1176
|
+
|
|
1177
|
+
|
|
1034
1178
|
/**
|
|
1035
1179
|
* Return the current, full snapshot state of the active project
|
|
1036
1180
|
*
|
|
@@ -1074,15 +1218,6 @@ export default class TeraFy {
|
|
|
1074
1218
|
*/
|
|
1075
1219
|
|
|
1076
1220
|
|
|
1077
|
-
/**
|
|
1078
|
-
* Force copying local changes to the server
|
|
1079
|
-
* This is only ever needed when saving large quantities of data that need to be immediately available
|
|
1080
|
-
*
|
|
1081
|
-
* @function setProjectStateFlush
|
|
1082
|
-
* @returns {Promise} A promise which resolves when the operation has completed
|
|
1083
|
-
*/
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
1221
|
/**
|
|
1087
1222
|
* Force refetching the remote project state into local
|
|
1088
1223
|
* This is only ever needed when saving large quantities of data that need to be immediately available
|
|
@@ -1421,9 +1556,9 @@ export default class TeraFy {
|
|
|
1421
1556
|
*
|
|
1422
1557
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
1423
1558
|
* @param {String} [options.body] Optional additional body text
|
|
1559
|
+
* @param {Boolean} [options.isHtml=false] If truthy, treat the body as HTML
|
|
1424
1560
|
* @param {String} [options.value] Current or default value to display pre-filled
|
|
1425
1561
|
* @param {String} [options.title='Input required'] The dialog title to display
|
|
1426
|
-
* @param {Boolean} [options.bodyHtml=false] If truthy, treat the body as HTML
|
|
1427
1562
|
* @param {String} [options.placeholder] Optional placeholder text
|
|
1428
1563
|
* @param {Boolean} [options.required=true] Treat nullish or empty inputs as a cancel operation
|
|
1429
1564
|
*
|
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
|
*
|
|
@@ -928,6 +968,11 @@ export default class TeraFyServer {
|
|
|
928
968
|
*/
|
|
929
969
|
applyProjectStatePatch(patch, options) {
|
|
930
970
|
if (!app.service('$projects').active) throw new Error('No active project to patch');
|
|
971
|
+
if (patch.length == 0) {
|
|
972
|
+
this.debug('INFO', 4, 'Skipping empty project state patch', patch);
|
|
973
|
+
return Promise.resolve();
|
|
974
|
+
}
|
|
975
|
+
|
|
931
976
|
this.debug('INFO', 1, 'Applying', patch.length, 'project state patch', patch);
|
|
932
977
|
|
|
933
978
|
let $projects = app.service('$projects');
|
|
@@ -1639,9 +1684,9 @@ export default class TeraFyServer {
|
|
|
1639
1684
|
*
|
|
1640
1685
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
1641
1686
|
* @param {String} [options.body] Optional additional body text
|
|
1687
|
+
* @param {Boolean} [options.isHtml=false] If truthy, treat the body as HTML
|
|
1642
1688
|
* @param {String} [options.value] Current or default value to display pre-filled
|
|
1643
1689
|
* @param {String} [options.title='Input required'] The dialog title to display
|
|
1644
|
-
* @param {Boolean} [options.bodyHtml=false] If truthy, treat the body as HTML
|
|
1645
1690
|
* @param {String} [options.placeholder] Optional placeholder text
|
|
1646
1691
|
* @param {Boolean} [options.required=true] Treat nullish or empty inputs as a cancel operation
|
|
1647
1692
|
*
|
|
@@ -1650,7 +1695,7 @@ export default class TeraFyServer {
|
|
|
1650
1695
|
uiPrompt(text, options) {
|
|
1651
1696
|
let settings = {
|
|
1652
1697
|
body: '',
|
|
1653
|
-
|
|
1698
|
+
isHtml: false,
|
|
1654
1699
|
title: 'Input required',
|
|
1655
1700
|
value: '',
|
|
1656
1701
|
placeholder: '',
|
|
@@ -1669,7 +1714,7 @@ export default class TeraFyServer {
|
|
|
1669
1714
|
component: 'UiPrompt',
|
|
1670
1715
|
componentProps: {
|
|
1671
1716
|
body: settings.body,
|
|
1672
|
-
|
|
1717
|
+
isHtml: settings.isHtml,
|
|
1673
1718
|
placeholder: settings.placeholder,
|
|
1674
1719
|
value: settings.value,
|
|
1675
1720
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/tera-fy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
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=.",
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"node": ">=18"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
+
"@momsfriendlydevco/marshal": "^2.1.1",
|
|
75
76
|
"detect-port": "^1.6.1",
|
|
76
77
|
"filesize": "^10.1.4",
|
|
77
78
|
"http-proxy": "^1.18.1",
|
|
@@ -82,15 +83,17 @@
|
|
|
82
83
|
"release-it": "^17.6.0"
|
|
83
84
|
},
|
|
84
85
|
"devDependencies": {
|
|
85
|
-
"@momsfriendlydevco/eslint-config": "^2.0
|
|
86
|
+
"@momsfriendlydevco/eslint-config": "^2.1.0",
|
|
86
87
|
"@release-it/conventional-changelog": "^8.0.1",
|
|
87
88
|
"concurrently": "^8.2.2",
|
|
88
89
|
"documentation": "^14.0.3",
|
|
89
90
|
"esbuild": "^0.23.0",
|
|
90
|
-
"eslint": "^9.
|
|
91
|
+
"eslint": "^9.18.0",
|
|
91
92
|
"nodemon": "^3.1.7"
|
|
92
93
|
},
|
|
93
94
|
"peerDependencies": {
|
|
95
|
+
"@supabase/supabase-js": "^2.48.1",
|
|
96
|
+
"firebase": "^11.3.1",
|
|
94
97
|
"vue": "^3.0.0"
|
|
95
98
|
},
|
|
96
99
|
"optionalDependencies": {
|
package/plugins/base.js
CHANGED
|
@@ -9,8 +9,7 @@ export default class TeraFyPlugin {
|
|
|
9
9
|
/**
|
|
10
10
|
* Optional function to be included when the main TeraFyClient is initalized
|
|
11
11
|
*/
|
|
12
|
-
init() {
|
|
13
|
-
}
|
|
12
|
+
init() {}
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -19,6 +18,6 @@ export default class TeraFyPlugin {
|
|
|
19
18
|
* @param {TeraFy} terafy The TeraFy client this plugin is being initalized against
|
|
20
19
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
21
20
|
*/
|
|
22
|
-
constructor(terafy, options) { // eslint-disable-line
|
|
21
|
+
constructor(terafy, options) { // eslint-disable-line no-unused-vars
|
|
23
22
|
}
|
|
24
23
|
}
|
|
@@ -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
|
+
}
|