@iebh/tera-fy 2.0.1 → 2.0.2
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 +20 -0
- package/dist/plugin.vue2.es2019.js +15 -15
- package/dist/terafy.es2019.js +1 -1
- package/dist/terafy.js +1 -1
- package/lib/syncro.js +130 -23
- package/package.json +2 -2
package/lib/syncro.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isEmpty,
|
|
3
3
|
cloneDeep,
|
|
4
|
+
random,
|
|
5
|
+
sample,
|
|
4
6
|
throttle,
|
|
5
7
|
} from 'lodash-es';
|
|
6
8
|
import {
|
|
@@ -11,6 +13,7 @@ import {
|
|
|
11
13
|
updateDoc as FirestoreUpdateDoc,
|
|
12
14
|
} from 'firebase/firestore';
|
|
13
15
|
import marshal from '@momsfriendlydevco/marshal';
|
|
16
|
+
import {nanoid} from 'nanoid';
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
/**
|
|
@@ -114,7 +117,7 @@ export default class Syncro {
|
|
|
114
117
|
Object.assign(this, options);
|
|
115
118
|
|
|
116
119
|
if (!Syncro.session) // Assign a random session ID if we don't already have one
|
|
117
|
-
Syncro.session = `syncro_${
|
|
120
|
+
Syncro.session = `syncro_${nanoid()}`;
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
|
|
@@ -221,6 +224,7 @@ export default class Syncro {
|
|
|
221
224
|
*/
|
|
222
225
|
static toFirestore(snapshot = {}) {
|
|
223
226
|
return marshal.serialize(snapshot, {
|
|
227
|
+
circular: false,
|
|
224
228
|
clone: true, // Clone away from the original Vue Reactive so we dont mangle it while traversing
|
|
225
229
|
modules: [
|
|
226
230
|
marshalFlattenArrays,
|
|
@@ -242,6 +246,7 @@ export default class Syncro {
|
|
|
242
246
|
*/
|
|
243
247
|
static fromFirestore(snapshot = {}) {
|
|
244
248
|
return marshal.deserialize(snapshot, {
|
|
249
|
+
circular: false,
|
|
245
250
|
clone: true, // Clone away from original so we don't trigger a loop within Firebase
|
|
246
251
|
modules: [
|
|
247
252
|
marshalFlattenArrays,
|
|
@@ -419,9 +424,8 @@ export default class Syncro {
|
|
|
419
424
|
.then(()=> syncEntities[entity] || Promise.reject(`Unknown Sync entity "${entity}"`))
|
|
420
425
|
.then(()=> syncEntities[entity].initState({
|
|
421
426
|
supabase: Syncro.supabase,
|
|
422
|
-
|
|
423
|
-
id,
|
|
424
|
-
relation,
|
|
427
|
+
fsCollection, fsId,
|
|
428
|
+
entity, id, relation,
|
|
425
429
|
}))
|
|
426
430
|
.then(state => FirestoreSetDoc(
|
|
427
431
|
this.docRef,
|
|
@@ -437,7 +441,9 @@ export default class Syncro {
|
|
|
437
441
|
);
|
|
438
442
|
}, this.throttle));
|
|
439
443
|
})
|
|
440
|
-
.then(()=> this.setHeartbeat(true
|
|
444
|
+
.then(()=> this.setHeartbeat(true, {
|
|
445
|
+
immediate: true,
|
|
446
|
+
}))
|
|
441
447
|
.then(()=> this)
|
|
442
448
|
}
|
|
443
449
|
|
|
@@ -447,27 +453,82 @@ export default class Syncro {
|
|
|
447
453
|
* This populates the `sync` presence meta-information
|
|
448
454
|
*
|
|
449
455
|
* @param {Boolean} [enable=true] Whether to enable heartbeating
|
|
456
|
+
*
|
|
457
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
458
|
+
* @param {Boolean} [options.immediate=false] Fire a heartbeat as soon as this function is called, this is only really useful on mount
|
|
450
459
|
*/
|
|
451
|
-
setHeartbeat(enable = true) {
|
|
460
|
+
setHeartbeat(enable = true, options) {
|
|
461
|
+
let settings = {
|
|
462
|
+
immediate: true,
|
|
463
|
+
...options,
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// Clear existing heartbeat timer, if there is one
|
|
452
467
|
clearTimeout(this._heartbeatTimer);
|
|
453
468
|
|
|
454
|
-
|
|
455
|
-
this.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
469
|
+
if (enable) {
|
|
470
|
+
this._heartbeatTimer = setTimeout(async ()=> {
|
|
471
|
+
// Perform the heartbeat
|
|
472
|
+
await this.heartbeat();
|
|
473
|
+
|
|
474
|
+
// If we're enabled - schedule the next heartbeat timer
|
|
475
|
+
enable && this.setHeartbeat(true);
|
|
476
|
+
}, this.heartbeatInterval);
|
|
477
|
+
|
|
478
|
+
if (settings.immediate) this.heartbeat();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Perform one heartbeat pulse to the server to indicate presense within this Syncro
|
|
485
|
+
* This function is automatically called by a timer if `setHeartbeat(true)` (the default behaviour)
|
|
486
|
+
*
|
|
487
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
488
|
+
*/
|
|
489
|
+
async heartbeat() {
|
|
490
|
+
this.debug('heartbeat!');
|
|
491
|
+
let timestamp = (new Date()).toISOString();
|
|
492
|
+
|
|
493
|
+
let docRef = FirestoreDocRef(Syncro.firestore, 'presence', this.path);
|
|
494
|
+
let doc = await FirestoreGetDoc(docRef);
|
|
495
|
+
|
|
496
|
+
await FirestoreSetDoc(
|
|
497
|
+
docRef,
|
|
498
|
+
{
|
|
499
|
+
latest: timestamp,
|
|
500
|
+
...(!doc.exists() && { // New doc - populate some base fields
|
|
501
|
+
created: timestamp,
|
|
502
|
+
lastFlush: null,
|
|
503
|
+
}),
|
|
504
|
+
sessions: {
|
|
505
|
+
[Syncro.session]: timestamp,
|
|
464
506
|
},
|
|
465
|
-
|
|
466
|
-
|
|
507
|
+
},
|
|
508
|
+
{merge: true},
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
467
512
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
513
|
+
/**
|
|
514
|
+
* Force the server to flush state
|
|
515
|
+
* This is only really useful for debugging as this happens automatically anyway
|
|
516
|
+
*
|
|
517
|
+
* @param {Object} [options] Additional options to mutate behaviour
|
|
518
|
+
* @param {Boolean} [options.destroy=false] Instruct the server to also dispose of the Syncro state
|
|
519
|
+
*
|
|
520
|
+
* @returns {Promise} A promise which resolves when the operation has completed
|
|
521
|
+
*/
|
|
522
|
+
flush(options) {
|
|
523
|
+
let settings = {
|
|
524
|
+
destroy: false,
|
|
525
|
+
...options,
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
return fetch(`https://tera-tools.com/api/sync/${this.path}/flush` + (settings.destroy ? '?destroy=1' : ''), {
|
|
529
|
+
method: 'post',
|
|
530
|
+
})
|
|
531
|
+
.then(response => response.ok ? null : Promise.reject(response.statusText || 'An error occured'))
|
|
471
532
|
}
|
|
472
533
|
|
|
473
534
|
|
|
@@ -480,6 +541,39 @@ export default class Syncro {
|
|
|
480
541
|
}
|
|
481
542
|
|
|
482
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Build a chaotic random tree structure based on dice rolls
|
|
546
|
+
* This funciton is mainly used for sync testing
|
|
547
|
+
*
|
|
548
|
+
* @param {Number} [depth=0] The current depth we are starting at, changes the nature of branches based on probability
|
|
549
|
+
*
|
|
550
|
+
* @returns {*} The current branch conotents
|
|
551
|
+
*/
|
|
552
|
+
export function randomBranch(depth = 0) {
|
|
553
|
+
let dice = // Roll a dice to pick the content
|
|
554
|
+
depth == 0 ? 10 // first roll is always '10'
|
|
555
|
+
: random(0, 11 - depth, false); // Subsequent rolls bias downwards based on depth (to avoid recursion)
|
|
556
|
+
|
|
557
|
+
return (
|
|
558
|
+
dice == 0 ? false
|
|
559
|
+
: dice == 1 ? true
|
|
560
|
+
: dice == 2 ? random(1, 10_000)
|
|
561
|
+
: dice == 3 ? (new Date(random(1_000_000_000_000, 1_777_777_777_777))).toISOString()
|
|
562
|
+
: dice == 5 ? Array.from({length: random(1, 10)}, ()=> random(1, 10))
|
|
563
|
+
: dice == 6 ? null
|
|
564
|
+
: dice < 8 ? Array.from({length: random(1, 10)}, ()=> randomBranch(depth+1))
|
|
565
|
+
: Object.fromEntries(
|
|
566
|
+
Array.from({length: random(1, 5)})
|
|
567
|
+
.map((v, k) => [
|
|
568
|
+
sample(['foo', 'bar', 'baz', 'quz', 'flarp', 'corge', 'grault', 'garply', 'waldo', 'fred', 'plugh', 'thud'])
|
|
569
|
+
+ `_${k}`,
|
|
570
|
+
randomBranch(depth+1),
|
|
571
|
+
])
|
|
572
|
+
)
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
|
|
483
577
|
/**
|
|
484
578
|
* Entities we support syncro paths on, each needs to correspond with a Firebase/Firestore collection name
|
|
485
579
|
*
|
|
@@ -519,12 +613,25 @@ export const syncEntities = {
|
|
|
519
613
|
.eq('project', id)
|
|
520
614
|
.eq('name', relation)
|
|
521
615
|
)
|
|
522
|
-
.then(rows => rows.length == 1
|
|
616
|
+
.then(rows => rows.length == 1
|
|
617
|
+
? rows[0]
|
|
618
|
+
: Syncro.wrapSupabase(supabase.from('project_namespaces') // Doesn't exist - create it
|
|
619
|
+
.insert({
|
|
620
|
+
project: id,
|
|
621
|
+
name: relation,
|
|
622
|
+
data: {},
|
|
623
|
+
})
|
|
624
|
+
.select('data')
|
|
625
|
+
)
|
|
626
|
+
)
|
|
523
627
|
.then(item => item.data);
|
|
524
628
|
},
|
|
525
629
|
flushState({supabase, state, id, relation}) {
|
|
526
630
|
return Syncro.wrapSupabase(supabase.from('project_namespaces')
|
|
527
|
-
.update(
|
|
631
|
+
.update({
|
|
632
|
+
edited_at: new Date(),
|
|
633
|
+
data: state,
|
|
634
|
+
})
|
|
528
635
|
.eq('project', id)
|
|
529
636
|
.eq('name', relation)
|
|
530
637
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iebh/tera-fy",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
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=.",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"just-diff": "^6.0.2",
|
|
81
81
|
"lodash-es": "^4.17.21",
|
|
82
82
|
"mitt": "^3.0.1",
|
|
83
|
-
"nanoid": "^5.0
|
|
83
|
+
"nanoid": "^5.1.0",
|
|
84
84
|
"release-it": "^17.6.0"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|