@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/plugins/vue2.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {cloneDeep
|
|
2
|
-
import
|
|
1
|
+
import {cloneDeep} from 'lodash-es';
|
|
2
|
+
import TeraFyPluginFirebase from './firebase.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* Provides the `bindProjectState()` function for Vue based projects
|
|
5
|
+
* Vue@2 observables plugin
|
|
7
6
|
*
|
|
8
7
|
* This function is expected to be included via the `terafy.use(MODULE, OPTIONS)` syntax rather than directly
|
|
9
8
|
*
|
|
10
|
-
* @class
|
|
11
|
-
* @param {Object} options Options when initalizing
|
|
12
|
-
* @param {Vue} options.Vue Vue instance to bind against
|
|
9
|
+
* @class TeraFyPluginVue2
|
|
13
10
|
*
|
|
14
|
-
* @example Implementation within a
|
|
11
|
+
* @example Implementation within a Vue@2 project `src/main.js`:
|
|
15
12
|
* // Include the main Tera-Fy core
|
|
16
13
|
* import TeraFy from '@iebh/tera-fy';
|
|
17
14
|
* import TerafyVue from '@iebh/tera-fy/plugins/vue2';
|
|
@@ -27,7 +24,7 @@ import TeraFyPluginBase from './base.js';
|
|
|
27
24
|
* app.$mount("#app");
|
|
28
25
|
* await terafy.init({app});
|
|
29
26
|
*/
|
|
30
|
-
export default class TeraFyPluginVue2 extends
|
|
27
|
+
export default class TeraFyPluginVue2 extends TeraFyPluginFirebase {
|
|
31
28
|
|
|
32
29
|
/**
|
|
33
30
|
* Local Vue@2 library to use, set during constuctor
|
|
@@ -37,6 +34,82 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
37
34
|
Vue;
|
|
38
35
|
|
|
39
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The bound, reactive state of the active TERA project
|
|
39
|
+
*
|
|
40
|
+
* @type {Object}
|
|
41
|
+
*/
|
|
42
|
+
project = null;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Simple incrementor to ensure unique IDs for $watch expressions
|
|
47
|
+
*
|
|
48
|
+
* @type {Number{
|
|
49
|
+
*/
|
|
50
|
+
reactiveId = 1001;
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Install into Vue@2
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} options Additional options to mutate behaviour, see TeraFyPluginFirebase
|
|
57
|
+
* @param {Object} options.app Root level Vue app to bind against
|
|
58
|
+
* @param {Vue} options.Vue Vue@2 instance to bind against
|
|
59
|
+
* @param {String} [options.globalName='$tera'] Global property to allocate this service as within Vue2
|
|
60
|
+
* @param {*...} [options...] see TeraFyPluginFirebase
|
|
61
|
+
*
|
|
62
|
+
* @returns {Promise} A Promise which will resolve when the init process has completed
|
|
63
|
+
*/
|
|
64
|
+
async init(options) {
|
|
65
|
+
let settings = {
|
|
66
|
+
app: null,
|
|
67
|
+
Vue: null,
|
|
68
|
+
globalName: '$tera',
|
|
69
|
+
...options,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (!settings.Vue) throw new Error('Vue instance to use must be specified in init options as `Vue`');
|
|
73
|
+
this.Vue = settings.Vue;
|
|
74
|
+
|
|
75
|
+
if (!settings.app) throw new Error('Vue Root / App instance to use must be specified in init options as `app`');
|
|
76
|
+
this.app = settings.app;
|
|
77
|
+
|
|
78
|
+
// Make this module available globally
|
|
79
|
+
if (settings.globalName)
|
|
80
|
+
this.Vue.prototype[settings.globalName] = this;
|
|
81
|
+
|
|
82
|
+
await super.init(settings); // Initalize parent class Firebase functionality
|
|
83
|
+
|
|
84
|
+
this.project = await this.mountNamespace('_PROJECT');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/** @override */
|
|
89
|
+
getReactive(value) {
|
|
90
|
+
let doc = this.Vue.observable(value);
|
|
91
|
+
|
|
92
|
+
let watcherPath = `_teraFy_${this.reactiveId++}`;
|
|
93
|
+
this.app[watcherPath] = doc; // Attach onto app so we can use $watch later on
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
doc,
|
|
97
|
+
setState(state) {
|
|
98
|
+
// Shallow copy all sub-keys into existing object (keeping the object pointer)
|
|
99
|
+
Object.entries(state || {})
|
|
100
|
+
.forEach(([k, v]) => doc[k] = v)
|
|
101
|
+
},
|
|
102
|
+
getState() {
|
|
103
|
+
return cloneDeep(doc);
|
|
104
|
+
},
|
|
105
|
+
watch: cb => {
|
|
106
|
+
this.app.$watch(watcherPath, cb, {deep: true});
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
40
113
|
/**
|
|
41
114
|
* Return a Vue Observable object that can be read/written which whose changes will transparently be written back to the TERA server instance
|
|
42
115
|
*
|
|
@@ -123,10 +196,14 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
123
196
|
let oldVal = cloneDeep(snapshot);
|
|
124
197
|
|
|
125
198
|
// Function to handle the state update (can be debounced)
|
|
126
|
-
let watchHandle =
|
|
199
|
+
let watchHandle = ()=> {
|
|
200
|
+
let newVal = cloneDeep(snapshot);
|
|
201
|
+
|
|
127
202
|
this.debug('INFO', 5, 'Update Vue2 Local->Remote', {new: newVal, old: oldVal});
|
|
128
203
|
this.createProjectStatePatch(newVal, oldVal);
|
|
129
|
-
|
|
204
|
+
|
|
205
|
+
// Set oldVal to the deep clone we just made so we can track the diff
|
|
206
|
+
oldVal = newVal;
|
|
130
207
|
};
|
|
131
208
|
|
|
132
209
|
settings.component.$watch(
|
|
@@ -171,89 +248,4 @@ export default class TeraFyPluginVue2 extends TeraFyPluginBase {
|
|
|
171
248
|
return target;
|
|
172
249
|
}
|
|
173
250
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* List of available projects for the current session
|
|
178
|
-
* Initalized during constructor
|
|
179
|
-
*
|
|
180
|
-
* @type {VueReactive<Array<Object>>}
|
|
181
|
-
*/
|
|
182
|
-
projects;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* The bound, reactive state of a Vue project
|
|
187
|
-
* When loaded this represents the state of a project as an object
|
|
188
|
-
*
|
|
189
|
-
* @type {Object}
|
|
190
|
-
*/
|
|
191
|
-
state = null;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Install into Vue@2
|
|
196
|
-
*
|
|
197
|
-
* @param {Object} options Additional options to mutate behaviour (defaults to the main teraFy settings)
|
|
198
|
-
* @param {Object} options.app Root level Vue app to bind against
|
|
199
|
-
* @param {Vue} options.Vue Vue@2 instance to bind against
|
|
200
|
-
* @param {String} [options.globalName='$tera'] Global property to allocate this service as within Vue2
|
|
201
|
-
* @param {Boolean} [options.requireProject=true] Automatically call requireProject() prior to any operation
|
|
202
|
-
* @param {Boolean} [options.subscribeState=true] Setup `vm.$tera.state` as a live binding on init
|
|
203
|
-
* @param {Boolean} [options.subscribeList=true] Setup `vm.$tera.projects` as a list of accesible projects on init
|
|
204
|
-
* @param {Objecct} [options.stateOptions] Options passed to `bindProjectState()` when setting up the main state
|
|
205
|
-
*
|
|
206
|
-
* @returns {Promise} A Promise which will resolve when the init process has completed
|
|
207
|
-
*/
|
|
208
|
-
init(options) {
|
|
209
|
-
let settings = {
|
|
210
|
-
app: null,
|
|
211
|
-
Vue: null,
|
|
212
|
-
globalName: '$tera',
|
|
213
|
-
requireProject: true,
|
|
214
|
-
subscribeState: true,
|
|
215
|
-
subscribeProjects: true,
|
|
216
|
-
stateOptions: {
|
|
217
|
-
read: true,
|
|
218
|
-
write: true,
|
|
219
|
-
},
|
|
220
|
-
...options,
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
if (!settings.Vue) throw new Error('Vue instance to use must be specified in init options as `Vue`');
|
|
224
|
-
this.Vue = options.Vue;
|
|
225
|
-
|
|
226
|
-
if (!this.settings.app) throw new Error('Need to specify the root level Vue2 app during init');
|
|
227
|
-
settings.stateOptions.app = this.settings.app;
|
|
228
|
-
|
|
229
|
-
// Create observable binding for projects
|
|
230
|
-
this.projects = this.Vue.observable([])
|
|
231
|
-
|
|
232
|
-
// Make this module available globally
|
|
233
|
-
if (settings.globalName)
|
|
234
|
-
this.Vue.prototype[settings.globalName] = this;
|
|
235
|
-
|
|
236
|
-
// Bind `state` to the active project
|
|
237
|
-
// Initialize state to null
|
|
238
|
-
this.state = null;
|
|
239
|
-
|
|
240
|
-
// this.statePromisable becomes the promise we are waiting on to resolve
|
|
241
|
-
return Promise.resolve()
|
|
242
|
-
.then(()=> settings.requireProject && this.requireProject())
|
|
243
|
-
.then(()=> Promise.all([
|
|
244
|
-
// Bind available project and wait on it
|
|
245
|
-
settings.subscribeState && this.bindProjectState({
|
|
246
|
-
...settings.stateOptions,
|
|
247
|
-
component: this.settings.app.$root,
|
|
248
|
-
})
|
|
249
|
-
.then(state => this.state = state)
|
|
250
|
-
.then(()=> this.debug('INFO', 1, 'Loaded initial project state', this.state)),
|
|
251
|
-
|
|
252
|
-
// Fetch available projects
|
|
253
|
-
settings.subscribeProjects && this.getProjects()
|
|
254
|
-
.then(projects => this.projects = this.Vue.observable(projects))
|
|
255
|
-
.then(()=> this.debug('INFO', 2, 'Loaded project list', this.projects)),
|
|
256
|
-
]))
|
|
257
|
-
.then(()=> this.debug('INFO', 1, 'Ready'))
|
|
258
|
-
}
|
|
259
251
|
}
|
package/plugins/vue3.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {cloneDeep
|
|
2
|
-
import
|
|
3
|
-
import {reactive, watch} from 'vue';
|
|
1
|
+
import {cloneDeep} from 'lodash-es';
|
|
2
|
+
import TeraFyPluginFirebase from './firebase.js';
|
|
3
|
+
import {markRaw, reactive as vueReactive, watch as vueWatch} from 'vue';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Vue observables plugin
|
|
7
|
-
* Provides the `bindProjectState()` function for Vue based projects
|
|
8
7
|
*
|
|
9
8
|
* This function is expected to be included via the `terafy.use(MODULE, OPTIONS)` syntax rather than directly
|
|
10
9
|
*
|
|
11
|
-
* @class
|
|
10
|
+
* @class TeraFyPluginVue3
|
|
12
11
|
*
|
|
13
12
|
* @example Implementation within a Vue3 / Vite project within `src/main.js`:
|
|
14
13
|
* import TeraFy from '@iebh/tera-fy';
|
|
@@ -23,120 +22,51 @@ import {reactive, watch} from 'vue';
|
|
|
23
22
|
* app.use(terafy.vuePlugin({
|
|
24
23
|
* globalName: '$tera', // Install as vm.$tera into every component
|
|
25
24
|
* }));
|
|
25
|
+
*
|
|
26
|
+
* @example Accessing project state - within a Vue component
|
|
27
|
+
* this.$tera.active
|
|
26
28
|
*/
|
|
27
|
-
export default class
|
|
29
|
+
export default class TeraFyPluginVue3 extends TeraFyPluginFirebase {
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* @param {Object} [options] Additional options to mutate behaviour
|
|
33
|
-
* @param {Boolean} [options.autoRequire=true] Run `requireProject()` automatically before continuing
|
|
34
|
-
* @param {Boolean} [options.read=true] Allow remote reactivity - update the local state when the server changes
|
|
35
|
-
* @param {Boolean} [options.write=true] Allow local reactivity to writes - send these to the server
|
|
36
|
-
* @param {Object} [options.throttle] Lodash debounce options + `wait` key used to throttle all writes, set to falsy to disable
|
|
32
|
+
* The bound, reactive state of the active TERA project
|
|
37
33
|
*
|
|
38
|
-
* @returns {Promie<Reactive<Object>>} A reactive object representing the project state
|
|
39
|
-
*/
|
|
40
|
-
bindProjectState(options) {
|
|
41
|
-
let settings = {
|
|
42
|
-
autoRequire: true,
|
|
43
|
-
read: true,
|
|
44
|
-
write: true,
|
|
45
|
-
throttle: {
|
|
46
|
-
wait: 200,
|
|
47
|
-
maxWait: 2000,
|
|
48
|
-
leading: false,
|
|
49
|
-
trailing: true,
|
|
50
|
-
},
|
|
51
|
-
...options,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return Promise.resolve()
|
|
55
|
-
.then(()=> this.getProjectState({
|
|
56
|
-
autoRequire: settings.autoRequire ,
|
|
57
|
-
}))
|
|
58
|
-
.then(snapshot => {
|
|
59
|
-
// Create initial reactive
|
|
60
|
-
let stateReactive = reactive(snapshot);
|
|
61
|
-
|
|
62
|
-
// Watch for remote changes and update
|
|
63
|
-
if (settings.read) {
|
|
64
|
-
this.events.on(`update:projects/${stateReactive.id}`, newState => {
|
|
65
|
-
if (
|
|
66
|
-
newState?.lastPatch?.session // Last state change had a session worth noting
|
|
67
|
-
&& newState.lastPatch.session == this.settings.session // The last state update was made FROM INSIDE THE BUILDING! BUWHAHAHA!
|
|
68
|
-
)
|
|
69
|
-
return; // Discard it, we don't care
|
|
70
|
-
|
|
71
|
-
// Everything else - patch the remote state locally
|
|
72
|
-
Object.assign(stateReactive, newState);
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Watch for local writes and react
|
|
77
|
-
if (settings.write) {
|
|
78
|
-
|
|
79
|
-
// NOTE: The below watch function returns two copies of the new value of the observed
|
|
80
|
-
// so we have to keep track of what changed ourselves by initalizing against the
|
|
81
|
-
// snapshot
|
|
82
|
-
let oldVal = cloneDeep(snapshot);
|
|
83
|
-
|
|
84
|
-
// Function to handle the state update (can be debounced)
|
|
85
|
-
let watchHandle = newVal => {
|
|
86
|
-
this.createProjectStatePatch(newVal, oldVal);
|
|
87
|
-
oldVal = cloneDeep(newVal); // Update old state the the last seen value
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
watch(
|
|
91
|
-
stateReactive, // State to watch
|
|
92
|
-
settings.throttle // Pointer to watchHandle which takes the new state (optionally throttled)
|
|
93
|
-
? debounce(watchHandle, settings.throttle.wait, settings.throttle)
|
|
94
|
-
: watchHandle,
|
|
95
|
-
{ // Watch options
|
|
96
|
-
deep: true,
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Return Vue Reactive
|
|
102
|
-
return stateReactive;
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* List of available projects for the current session
|
|
109
|
-
* @type {VueReactive<Array<Object>>}
|
|
110
|
-
*/
|
|
111
|
-
projects = reactive([]);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* The bound, reactive state of a Vue project
|
|
116
|
-
* When loaded this represents the state of a project as an object
|
|
117
34
|
* @type {Object}
|
|
118
35
|
*/
|
|
119
|
-
|
|
36
|
+
project = null;
|
|
120
37
|
|
|
121
38
|
|
|
122
39
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
40
|
+
* Init the project including create a reactive mount for the active project
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} options Additional options to mutate behaviour
|
|
43
|
+
* @param {*...} [options...] see TeraFyPluginFirebase
|
|
125
44
|
*/
|
|
126
|
-
|
|
45
|
+
async init(options) {
|
|
46
|
+
await super.init(options); // Initalize parent class Firebase functionality
|
|
127
47
|
|
|
48
|
+
// Mount the project namespace
|
|
49
|
+
this.project = await this.mountNamespace('_PROJECT');
|
|
50
|
+
}
|
|
128
51
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
52
|
+
|
|
53
|
+
/** @override */
|
|
54
|
+
getReactive(value) {
|
|
55
|
+
let doc = vueReactive(value);
|
|
56
|
+
return {
|
|
57
|
+
doc,
|
|
58
|
+
setState(state) {
|
|
59
|
+
// Shallow copy all sub-keys into existing object (keeping the object pointer)
|
|
60
|
+
Object.entries(state || {})
|
|
61
|
+
.forEach(([k, v]) => doc[k] = v)
|
|
62
|
+
},
|
|
63
|
+
getState() {
|
|
64
|
+
return cloneDeep(doc);
|
|
65
|
+
},
|
|
66
|
+
watch(cb) {
|
|
67
|
+
vueWatch(doc, cb, {deep: true});
|
|
68
|
+
},
|
|
69
|
+
};
|
|
140
70
|
}
|
|
141
71
|
|
|
142
72
|
|
|
@@ -156,43 +86,14 @@ export default class TeraFyPluginVue extends TeraFyPluginBase {
|
|
|
156
86
|
* @param {VueApp} app The Vue top-level app to install against
|
|
157
87
|
*
|
|
158
88
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
159
|
-
* @param {
|
|
160
|
-
* @param {String} [options.globalName='$tera'] Globa property to allocate this service as
|
|
161
|
-
* @param {Objecct} [options.bindOptions] Options passed to `bindProjectState()`
|
|
89
|
+
* @param {String} [options.globalName='$tera'] Global property to allocate this service as
|
|
162
90
|
*/
|
|
163
91
|
install(app, options) {
|
|
164
92
|
let settings = {
|
|
165
|
-
autoInit: true,
|
|
166
93
|
globalName: '$tera',
|
|
167
|
-
subscribeState: true,
|
|
168
|
-
subscribeProjects: true,
|
|
169
|
-
stateOptions: {
|
|
170
|
-
write: true,
|
|
171
|
-
},
|
|
172
94
|
...options,
|
|
173
95
|
};
|
|
174
96
|
|
|
175
|
-
// Bind $tera.state to the active project
|
|
176
|
-
// Initialize state to null
|
|
177
|
-
$tera.state = null;
|
|
178
|
-
|
|
179
|
-
// $tera.statePromisable becomes the promise we are waiting on to resolve
|
|
180
|
-
$tera.statePromisable = Promise.resolve()
|
|
181
|
-
.then(()=> settings.autoInit && $tera.init())
|
|
182
|
-
.then(()=> Promise.all([
|
|
183
|
-
// Bind available project and wait on it
|
|
184
|
-
settings.subscribeState && $tera.bindProjectState(settings.stateOptions)
|
|
185
|
-
.then(state => $tera.state = state)
|
|
186
|
-
.then(()=> $tera.debug('INFO', 1, 'Loaded initial project state', $tera.state)),
|
|
187
|
-
|
|
188
|
-
// Fetch available projects
|
|
189
|
-
settings.subscribeProjects && $tera.getProjects()
|
|
190
|
-
.then(projects => $tera.projects = reactive(projects))
|
|
191
|
-
.then(()=> $tera.debug('INFO', 2, 'Loaded project list', $tera.projects)),
|
|
192
|
-
]))
|
|
193
|
-
.then(()=> $tera.debug('INFO', 1, 'Ready'))
|
|
194
|
-
|
|
195
|
-
|
|
196
97
|
// Make this module available globally
|
|
197
98
|
app.config.globalProperties[settings.globalName] = $tera;
|
|
198
99
|
},
|