@spooky-sync/core 0.0.1-canary.239 → 0.0.1-canary.240
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/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +476 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/src/boot/auth-flip.saga.ts.html +271 -0
- package/coverage/lcov-report/src/boot/boot.saga.ts.html +469 -0
- package/coverage/lcov-report/src/boot/bucket-switch.saga.ts.html +430 -0
- package/coverage/lcov-report/src/boot/index.html +161 -0
- package/coverage/lcov-report/src/boot/preload.saga.ts.html +178 -0
- package/coverage/lcov-report/src/client/bucket-handle.ts.html +766 -0
- package/coverage/lcov-report/src/client/index.html +161 -0
- package/coverage/lcov-report/src/client/router.ts.html +367 -0
- package/coverage/lcov-report/src/client/runtime.ts.html +940 -0
- package/coverage/lcov-report/src/client/sp00ky-client.ts.html +1531 -0
- package/coverage/lcov-report/src/events/index.html +116 -0
- package/coverage/lcov-report/src/events/index.ts.html +877 -0
- package/coverage/lcov-report/src/index.html +146 -0
- package/coverage/lcov-report/src/index.ts.html +202 -0
- package/coverage/lcov-report/src/kernel/constants.ts.html +247 -0
- package/coverage/lcov-report/src/kernel/effects.ts.html +562 -0
- package/coverage/lcov-report/src/kernel/events.ts.html +250 -0
- package/coverage/lcov-report/src/kernel/index.html +176 -0
- package/coverage/lcov-report/src/kernel/interpreter.ts.html +514 -0
- package/coverage/lcov-report/src/kernel/saga.ts.html +304 -0
- package/coverage/lcov-report/src/modules/app-release/index.html +116 -0
- package/coverage/lcov-report/src/modules/app-release/index.ts.html +712 -0
- package/coverage/lcov-report/src/modules/auth/events/index.html +116 -0
- package/coverage/lcov-report/src/modules/auth/events/index.ts.html +142 -0
- package/coverage/lcov-report/src/modules/auth/index.html +116 -0
- package/coverage/lcov-report/src/modules/auth/index.ts.html +1225 -0
- package/coverage/lcov-report/src/modules/crdt/crdt-field.ts.html +967 -0
- package/coverage/lcov-report/src/modules/crdt/index.html +146 -0
- package/coverage/lcov-report/src/modules/crdt/index.ts.html +1474 -0
- package/coverage/lcov-report/src/modules/crdt/loro-loader.ts.html +160 -0
- package/coverage/lcov-report/src/modules/devtools/flags.ts.html +1132 -0
- package/coverage/lcov-report/src/modules/devtools/index.html +161 -0
- package/coverage/lcov-report/src/modules/devtools/index.ts.html +2413 -0
- package/coverage/lcov-report/src/modules/devtools/storage-info.ts.html +589 -0
- package/coverage/lcov-report/src/modules/devtools/versions.ts.html +415 -0
- package/coverage/lcov-report/src/modules/feature-flag/index.html +116 -0
- package/coverage/lcov-report/src/modules/feature-flag/index.ts.html +1033 -0
- package/coverage/lcov-report/src/modules/index.html +116 -0
- package/coverage/lcov-report/src/modules/ref-tables.ts.html +349 -0
- package/coverage/lcov-report/src/mutation/index.html +191 -0
- package/coverage/lcov-report/src/mutation/jobs.ts.html +208 -0
- package/coverage/lcov-report/src/mutation/mutation-id.ts.html +190 -0
- package/coverage/lcov-report/src/mutation/push.saga.ts.html +730 -0
- package/coverage/lcov-report/src/mutation/rows.ts.html +1216 -0
- package/coverage/lcov-report/src/mutation/tray.saga.ts.html +229 -0
- package/coverage/lcov-report/src/mutation/write.saga.ts.html +583 -0
- package/coverage/lcov-report/src/otel/index.html +116 -0
- package/coverage/lcov-report/src/otel/index.ts.html +466 -0
- package/coverage/lcov-report/src/query/env.ts.html +253 -0
- package/coverage/lcov-report/src/query/fetch.saga.ts.html +412 -0
- package/coverage/lcov-report/src/query/hash.ts.html +139 -0
- package/coverage/lcov-report/src/query/index.html +281 -0
- package/coverage/lcov-report/src/query/lifecycle.saga.ts.html +388 -0
- package/coverage/lcov-report/src/query/materialize.saga.ts.html +310 -0
- package/coverage/lcov-report/src/query/materialize.ts.html +217 -0
- package/coverage/lcov-report/src/query/membership.saga.ts.html +709 -0
- package/coverage/lcov-report/src/query/membership.ts.html +559 -0
- package/coverage/lcov-report/src/query/register.saga.ts.html +655 -0
- package/coverage/lcov-report/src/query/render-set.ts.html +265 -0
- package/coverage/lcov-report/src/query/sql.ts.html +322 -0
- package/coverage/lcov-report/src/query/window-query.ts.html +670 -0
- package/coverage/lcov-report/src/services/blobs/blob-cache.ts.html +1894 -0
- package/coverage/lcov-report/src/services/blobs/blob-manifest.ts.html +766 -0
- package/coverage/lcov-report/src/services/blobs/blob-store.ts.html +1162 -0
- package/coverage/lcov-report/src/services/blobs/index.html +161 -0
- package/coverage/lcov-report/src/services/blobs/index.ts.html +295 -0
- package/coverage/lcov-report/src/services/database/cache-engine.ts.html +664 -0
- package/coverage/lcov-report/src/services/database/connection-supervisor.ts.html +1330 -0
- package/coverage/lcov-report/src/services/database/database.ts.html +658 -0
- package/coverage/lcov-report/src/services/database/engine-factory.ts.html +184 -0
- package/coverage/lcov-report/src/services/database/errors.ts.html +187 -0
- package/coverage/lcov-report/src/services/database/events/index.html +116 -0
- package/coverage/lcov-report/src/services/database/events/index.ts.html +184 -0
- package/coverage/lcov-report/src/services/database/index.html +386 -0
- package/coverage/lcov-report/src/services/database/index.ts.html +121 -0
- package/coverage/lcov-report/src/services/database/local-migrator.ts.html +703 -0
- package/coverage/lcov-report/src/services/database/local.ts.html +1633 -0
- package/coverage/lcov-report/src/services/database/plan-render.ts.html +409 -0
- package/coverage/lcov-report/src/services/database/relation-resolver.ts.html +682 -0
- package/coverage/lcov-report/src/services/database/remote.ts.html +1036 -0
- package/coverage/lcov-report/src/services/database/sqlite-cache-engine.ts.html +4132 -0
- package/coverage/lcov-report/src/services/database/sqlite-lock-verify.ts.html +220 -0
- package/coverage/lcov-report/src/services/database/sqlite-open.ts.html +577 -0
- package/coverage/lcov-report/src/services/database/sqlite-plan-sql.ts.html +499 -0
- package/coverage/lcov-report/src/services/database/sqlite-select.ts.html +478 -0
- package/coverage/lcov-report/src/services/database/sqlite-transport.ts.html +757 -0
- package/coverage/lcov-report/src/services/database/surql-translate.ts.html +1333 -0
- package/coverage/lcov-report/src/services/database/surreal-cache-engine.ts.html +568 -0
- package/coverage/lcov-report/src/services/logger/index.html +116 -0
- package/coverage/lcov-report/src/services/logger/index.ts.html +154 -0
- package/coverage/lcov-report/src/services/persistence/index.html +146 -0
- package/coverage/lcov-report/src/services/persistence/localstorage.ts.html +163 -0
- package/coverage/lcov-report/src/services/persistence/resilient.ts.html +208 -0
- package/coverage/lcov-report/src/services/persistence/surrealdb.ts.html +271 -0
- package/coverage/lcov-report/src/services/stream-processor/index.html +146 -0
- package/coverage/lcov-report/src/services/stream-processor/index.ts.html +3313 -0
- package/coverage/lcov-report/src/services/stream-processor/permissions.ts.html +244 -0
- package/coverage/lcov-report/src/services/stream-processor/wasm-types.ts.html +346 -0
- package/coverage/lcov-report/src/services/tabs/broker-client.ts.html +934 -0
- package/coverage/lcov-report/src/services/tabs/coordinator.ts.html +1984 -0
- package/coverage/lcov-report/src/services/tabs/index.html +191 -0
- package/coverage/lcov-report/src/services/tabs/leader-locks.ts.html +310 -0
- package/coverage/lcov-report/src/services/tabs/protocol.ts.html +871 -0
- package/coverage/lcov-report/src/services/tabs/support.ts.html +193 -0
- package/coverage/lcov-report/src/services/tabs/tabs-broker-worker.ts.html +2005 -0
- package/coverage/lcov-report/src/sp00ky.ts.html +100 -0
- package/coverage/lcov-report/src/state/client-state.ts.html +637 -0
- package/coverage/lcov-report/src/state/index.html +161 -0
- package/coverage/lcov-report/src/state/lifecycle.ts.html +343 -0
- package/coverage/lcov-report/src/state/reducers.ts.html +1471 -0
- package/coverage/lcov-report/src/state/selectors.ts.html +637 -0
- package/coverage/lcov-report/src/sync/connection.saga.ts.html +343 -0
- package/coverage/lcov-report/src/sync/index.html +176 -0
- package/coverage/lcov-report/src/sync/live.saga.ts.html +232 -0
- package/coverage/lcov-report/src/sync/policy.ts.html +1606 -0
- package/coverage/lcov-report/src/sync/poll.saga.ts.html +193 -0
- package/coverage/lcov-report/src/sync/tabs.saga.ts.html +361 -0
- package/coverage/lcov-report/src/types.ts.html +2419 -0
- package/coverage/lcov-report/src/utils/blurhash.ts.html +355 -0
- package/coverage/lcov-report/src/utils/error-classification.ts.html +244 -0
- package/coverage/lcov-report/src/utils/index.html +191 -0
- package/coverage/lcov-report/src/utils/index.ts.html +820 -0
- package/coverage/lcov-report/src/utils/parser.ts.html +313 -0
- package/coverage/lcov-report/src/utils/semver.ts.html +175 -0
- package/coverage/lcov-report/src/utils/surql.ts.html +667 -0
- package/coverage/lcov.info +19079 -0
- package/dist/index.js +3 -3
- package/dist/sqlite-open.js +20 -1
- package/package.json +3 -3
- package/src/services/database/relation-resolver.test.ts +55 -1
- package/src/services/database/relation-resolver.ts +0 -0
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
|
|
5
|
+
<head>
|
|
6
|
+
<title>Code coverage report for src/modules/crdt/crdt-field.ts</title>
|
|
7
|
+
<meta charset="utf-8" />
|
|
8
|
+
<link rel="stylesheet" href="../../../prettify.css" />
|
|
9
|
+
<link rel="stylesheet" href="../../../base.css" />
|
|
10
|
+
<link rel="shortcut icon" type="image/x-icon" href="../../../favicon.png" />
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
12
|
+
<style type='text/css'>
|
|
13
|
+
.coverage-summary .sorter {
|
|
14
|
+
background-image: url(../../../sort-arrow-sprite.png);
|
|
15
|
+
}
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
|
|
19
|
+
<body>
|
|
20
|
+
<div class='wrapper'>
|
|
21
|
+
<div class='pad1'>
|
|
22
|
+
<h1><a href="../../../index.html">All files</a> / <a href="index.html">src/modules/crdt</a> crdt-field.ts</h1>
|
|
23
|
+
<div class='clearfix'>
|
|
24
|
+
|
|
25
|
+
<div class='fl pad1y space-right2'>
|
|
26
|
+
<span class="strong">49.76% </span>
|
|
27
|
+
<span class="quiet">Statements</span>
|
|
28
|
+
<span class='fraction'>105/211</span>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<div class='fl pad1y space-right2'>
|
|
33
|
+
<span class="strong">58.33% </span>
|
|
34
|
+
<span class="quiet">Branches</span>
|
|
35
|
+
<span class='fraction'>14/24</span>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<div class='fl pad1y space-right2'>
|
|
40
|
+
<span class="strong">52.63% </span>
|
|
41
|
+
<span class="quiet">Functions</span>
|
|
42
|
+
<span class='fraction'>10/19</span>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<div class='fl pad1y space-right2'>
|
|
47
|
+
<span class="strong">49.76% </span>
|
|
48
|
+
<span class="quiet">Lines</span>
|
|
49
|
+
<span class='fraction'>105/211</span>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
<p class="quiet">
|
|
55
|
+
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
56
|
+
</p>
|
|
57
|
+
<template id="filterTemplate">
|
|
58
|
+
<div class="quiet">
|
|
59
|
+
Filter:
|
|
60
|
+
<input type="search" id="fileSearch">
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
</div>
|
|
64
|
+
<div class='status-line low'></div>
|
|
65
|
+
<pre><table class="coverage">
|
|
66
|
+
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
|
|
67
|
+
<a name='L2'></a><a href='#L2'>2</a>
|
|
68
|
+
<a name='L3'></a><a href='#L3'>3</a>
|
|
69
|
+
<a name='L4'></a><a href='#L4'>4</a>
|
|
70
|
+
<a name='L5'></a><a href='#L5'>5</a>
|
|
71
|
+
<a name='L6'></a><a href='#L6'>6</a>
|
|
72
|
+
<a name='L7'></a><a href='#L7'>7</a>
|
|
73
|
+
<a name='L8'></a><a href='#L8'>8</a>
|
|
74
|
+
<a name='L9'></a><a href='#L9'>9</a>
|
|
75
|
+
<a name='L10'></a><a href='#L10'>10</a>
|
|
76
|
+
<a name='L11'></a><a href='#L11'>11</a>
|
|
77
|
+
<a name='L12'></a><a href='#L12'>12</a>
|
|
78
|
+
<a name='L13'></a><a href='#L13'>13</a>
|
|
79
|
+
<a name='L14'></a><a href='#L14'>14</a>
|
|
80
|
+
<a name='L15'></a><a href='#L15'>15</a>
|
|
81
|
+
<a name='L16'></a><a href='#L16'>16</a>
|
|
82
|
+
<a name='L17'></a><a href='#L17'>17</a>
|
|
83
|
+
<a name='L18'></a><a href='#L18'>18</a>
|
|
84
|
+
<a name='L19'></a><a href='#L19'>19</a>
|
|
85
|
+
<a name='L20'></a><a href='#L20'>20</a>
|
|
86
|
+
<a name='L21'></a><a href='#L21'>21</a>
|
|
87
|
+
<a name='L22'></a><a href='#L22'>22</a>
|
|
88
|
+
<a name='L23'></a><a href='#L23'>23</a>
|
|
89
|
+
<a name='L24'></a><a href='#L24'>24</a>
|
|
90
|
+
<a name='L25'></a><a href='#L25'>25</a>
|
|
91
|
+
<a name='L26'></a><a href='#L26'>26</a>
|
|
92
|
+
<a name='L27'></a><a href='#L27'>27</a>
|
|
93
|
+
<a name='L28'></a><a href='#L28'>28</a>
|
|
94
|
+
<a name='L29'></a><a href='#L29'>29</a>
|
|
95
|
+
<a name='L30'></a><a href='#L30'>30</a>
|
|
96
|
+
<a name='L31'></a><a href='#L31'>31</a>
|
|
97
|
+
<a name='L32'></a><a href='#L32'>32</a>
|
|
98
|
+
<a name='L33'></a><a href='#L33'>33</a>
|
|
99
|
+
<a name='L34'></a><a href='#L34'>34</a>
|
|
100
|
+
<a name='L35'></a><a href='#L35'>35</a>
|
|
101
|
+
<a name='L36'></a><a href='#L36'>36</a>
|
|
102
|
+
<a name='L37'></a><a href='#L37'>37</a>
|
|
103
|
+
<a name='L38'></a><a href='#L38'>38</a>
|
|
104
|
+
<a name='L39'></a><a href='#L39'>39</a>
|
|
105
|
+
<a name='L40'></a><a href='#L40'>40</a>
|
|
106
|
+
<a name='L41'></a><a href='#L41'>41</a>
|
|
107
|
+
<a name='L42'></a><a href='#L42'>42</a>
|
|
108
|
+
<a name='L43'></a><a href='#L43'>43</a>
|
|
109
|
+
<a name='L44'></a><a href='#L44'>44</a>
|
|
110
|
+
<a name='L45'></a><a href='#L45'>45</a>
|
|
111
|
+
<a name='L46'></a><a href='#L46'>46</a>
|
|
112
|
+
<a name='L47'></a><a href='#L47'>47</a>
|
|
113
|
+
<a name='L48'></a><a href='#L48'>48</a>
|
|
114
|
+
<a name='L49'></a><a href='#L49'>49</a>
|
|
115
|
+
<a name='L50'></a><a href='#L50'>50</a>
|
|
116
|
+
<a name='L51'></a><a href='#L51'>51</a>
|
|
117
|
+
<a name='L52'></a><a href='#L52'>52</a>
|
|
118
|
+
<a name='L53'></a><a href='#L53'>53</a>
|
|
119
|
+
<a name='L54'></a><a href='#L54'>54</a>
|
|
120
|
+
<a name='L55'></a><a href='#L55'>55</a>
|
|
121
|
+
<a name='L56'></a><a href='#L56'>56</a>
|
|
122
|
+
<a name='L57'></a><a href='#L57'>57</a>
|
|
123
|
+
<a name='L58'></a><a href='#L58'>58</a>
|
|
124
|
+
<a name='L59'></a><a href='#L59'>59</a>
|
|
125
|
+
<a name='L60'></a><a href='#L60'>60</a>
|
|
126
|
+
<a name='L61'></a><a href='#L61'>61</a>
|
|
127
|
+
<a name='L62'></a><a href='#L62'>62</a>
|
|
128
|
+
<a name='L63'></a><a href='#L63'>63</a>
|
|
129
|
+
<a name='L64'></a><a href='#L64'>64</a>
|
|
130
|
+
<a name='L65'></a><a href='#L65'>65</a>
|
|
131
|
+
<a name='L66'></a><a href='#L66'>66</a>
|
|
132
|
+
<a name='L67'></a><a href='#L67'>67</a>
|
|
133
|
+
<a name='L68'></a><a href='#L68'>68</a>
|
|
134
|
+
<a name='L69'></a><a href='#L69'>69</a>
|
|
135
|
+
<a name='L70'></a><a href='#L70'>70</a>
|
|
136
|
+
<a name='L71'></a><a href='#L71'>71</a>
|
|
137
|
+
<a name='L72'></a><a href='#L72'>72</a>
|
|
138
|
+
<a name='L73'></a><a href='#L73'>73</a>
|
|
139
|
+
<a name='L74'></a><a href='#L74'>74</a>
|
|
140
|
+
<a name='L75'></a><a href='#L75'>75</a>
|
|
141
|
+
<a name='L76'></a><a href='#L76'>76</a>
|
|
142
|
+
<a name='L77'></a><a href='#L77'>77</a>
|
|
143
|
+
<a name='L78'></a><a href='#L78'>78</a>
|
|
144
|
+
<a name='L79'></a><a href='#L79'>79</a>
|
|
145
|
+
<a name='L80'></a><a href='#L80'>80</a>
|
|
146
|
+
<a name='L81'></a><a href='#L81'>81</a>
|
|
147
|
+
<a name='L82'></a><a href='#L82'>82</a>
|
|
148
|
+
<a name='L83'></a><a href='#L83'>83</a>
|
|
149
|
+
<a name='L84'></a><a href='#L84'>84</a>
|
|
150
|
+
<a name='L85'></a><a href='#L85'>85</a>
|
|
151
|
+
<a name='L86'></a><a href='#L86'>86</a>
|
|
152
|
+
<a name='L87'></a><a href='#L87'>87</a>
|
|
153
|
+
<a name='L88'></a><a href='#L88'>88</a>
|
|
154
|
+
<a name='L89'></a><a href='#L89'>89</a>
|
|
155
|
+
<a name='L90'></a><a href='#L90'>90</a>
|
|
156
|
+
<a name='L91'></a><a href='#L91'>91</a>
|
|
157
|
+
<a name='L92'></a><a href='#L92'>92</a>
|
|
158
|
+
<a name='L93'></a><a href='#L93'>93</a>
|
|
159
|
+
<a name='L94'></a><a href='#L94'>94</a>
|
|
160
|
+
<a name='L95'></a><a href='#L95'>95</a>
|
|
161
|
+
<a name='L96'></a><a href='#L96'>96</a>
|
|
162
|
+
<a name='L97'></a><a href='#L97'>97</a>
|
|
163
|
+
<a name='L98'></a><a href='#L98'>98</a>
|
|
164
|
+
<a name='L99'></a><a href='#L99'>99</a>
|
|
165
|
+
<a name='L100'></a><a href='#L100'>100</a>
|
|
166
|
+
<a name='L101'></a><a href='#L101'>101</a>
|
|
167
|
+
<a name='L102'></a><a href='#L102'>102</a>
|
|
168
|
+
<a name='L103'></a><a href='#L103'>103</a>
|
|
169
|
+
<a name='L104'></a><a href='#L104'>104</a>
|
|
170
|
+
<a name='L105'></a><a href='#L105'>105</a>
|
|
171
|
+
<a name='L106'></a><a href='#L106'>106</a>
|
|
172
|
+
<a name='L107'></a><a href='#L107'>107</a>
|
|
173
|
+
<a name='L108'></a><a href='#L108'>108</a>
|
|
174
|
+
<a name='L109'></a><a href='#L109'>109</a>
|
|
175
|
+
<a name='L110'></a><a href='#L110'>110</a>
|
|
176
|
+
<a name='L111'></a><a href='#L111'>111</a>
|
|
177
|
+
<a name='L112'></a><a href='#L112'>112</a>
|
|
178
|
+
<a name='L113'></a><a href='#L113'>113</a>
|
|
179
|
+
<a name='L114'></a><a href='#L114'>114</a>
|
|
180
|
+
<a name='L115'></a><a href='#L115'>115</a>
|
|
181
|
+
<a name='L116'></a><a href='#L116'>116</a>
|
|
182
|
+
<a name='L117'></a><a href='#L117'>117</a>
|
|
183
|
+
<a name='L118'></a><a href='#L118'>118</a>
|
|
184
|
+
<a name='L119'></a><a href='#L119'>119</a>
|
|
185
|
+
<a name='L120'></a><a href='#L120'>120</a>
|
|
186
|
+
<a name='L121'></a><a href='#L121'>121</a>
|
|
187
|
+
<a name='L122'></a><a href='#L122'>122</a>
|
|
188
|
+
<a name='L123'></a><a href='#L123'>123</a>
|
|
189
|
+
<a name='L124'></a><a href='#L124'>124</a>
|
|
190
|
+
<a name='L125'></a><a href='#L125'>125</a>
|
|
191
|
+
<a name='L126'></a><a href='#L126'>126</a>
|
|
192
|
+
<a name='L127'></a><a href='#L127'>127</a>
|
|
193
|
+
<a name='L128'></a><a href='#L128'>128</a>
|
|
194
|
+
<a name='L129'></a><a href='#L129'>129</a>
|
|
195
|
+
<a name='L130'></a><a href='#L130'>130</a>
|
|
196
|
+
<a name='L131'></a><a href='#L131'>131</a>
|
|
197
|
+
<a name='L132'></a><a href='#L132'>132</a>
|
|
198
|
+
<a name='L133'></a><a href='#L133'>133</a>
|
|
199
|
+
<a name='L134'></a><a href='#L134'>134</a>
|
|
200
|
+
<a name='L135'></a><a href='#L135'>135</a>
|
|
201
|
+
<a name='L136'></a><a href='#L136'>136</a>
|
|
202
|
+
<a name='L137'></a><a href='#L137'>137</a>
|
|
203
|
+
<a name='L138'></a><a href='#L138'>138</a>
|
|
204
|
+
<a name='L139'></a><a href='#L139'>139</a>
|
|
205
|
+
<a name='L140'></a><a href='#L140'>140</a>
|
|
206
|
+
<a name='L141'></a><a href='#L141'>141</a>
|
|
207
|
+
<a name='L142'></a><a href='#L142'>142</a>
|
|
208
|
+
<a name='L143'></a><a href='#L143'>143</a>
|
|
209
|
+
<a name='L144'></a><a href='#L144'>144</a>
|
|
210
|
+
<a name='L145'></a><a href='#L145'>145</a>
|
|
211
|
+
<a name='L146'></a><a href='#L146'>146</a>
|
|
212
|
+
<a name='L147'></a><a href='#L147'>147</a>
|
|
213
|
+
<a name='L148'></a><a href='#L148'>148</a>
|
|
214
|
+
<a name='L149'></a><a href='#L149'>149</a>
|
|
215
|
+
<a name='L150'></a><a href='#L150'>150</a>
|
|
216
|
+
<a name='L151'></a><a href='#L151'>151</a>
|
|
217
|
+
<a name='L152'></a><a href='#L152'>152</a>
|
|
218
|
+
<a name='L153'></a><a href='#L153'>153</a>
|
|
219
|
+
<a name='L154'></a><a href='#L154'>154</a>
|
|
220
|
+
<a name='L155'></a><a href='#L155'>155</a>
|
|
221
|
+
<a name='L156'></a><a href='#L156'>156</a>
|
|
222
|
+
<a name='L157'></a><a href='#L157'>157</a>
|
|
223
|
+
<a name='L158'></a><a href='#L158'>158</a>
|
|
224
|
+
<a name='L159'></a><a href='#L159'>159</a>
|
|
225
|
+
<a name='L160'></a><a href='#L160'>160</a>
|
|
226
|
+
<a name='L161'></a><a href='#L161'>161</a>
|
|
227
|
+
<a name='L162'></a><a href='#L162'>162</a>
|
|
228
|
+
<a name='L163'></a><a href='#L163'>163</a>
|
|
229
|
+
<a name='L164'></a><a href='#L164'>164</a>
|
|
230
|
+
<a name='L165'></a><a href='#L165'>165</a>
|
|
231
|
+
<a name='L166'></a><a href='#L166'>166</a>
|
|
232
|
+
<a name='L167'></a><a href='#L167'>167</a>
|
|
233
|
+
<a name='L168'></a><a href='#L168'>168</a>
|
|
234
|
+
<a name='L169'></a><a href='#L169'>169</a>
|
|
235
|
+
<a name='L170'></a><a href='#L170'>170</a>
|
|
236
|
+
<a name='L171'></a><a href='#L171'>171</a>
|
|
237
|
+
<a name='L172'></a><a href='#L172'>172</a>
|
|
238
|
+
<a name='L173'></a><a href='#L173'>173</a>
|
|
239
|
+
<a name='L174'></a><a href='#L174'>174</a>
|
|
240
|
+
<a name='L175'></a><a href='#L175'>175</a>
|
|
241
|
+
<a name='L176'></a><a href='#L176'>176</a>
|
|
242
|
+
<a name='L177'></a><a href='#L177'>177</a>
|
|
243
|
+
<a name='L178'></a><a href='#L178'>178</a>
|
|
244
|
+
<a name='L179'></a><a href='#L179'>179</a>
|
|
245
|
+
<a name='L180'></a><a href='#L180'>180</a>
|
|
246
|
+
<a name='L181'></a><a href='#L181'>181</a>
|
|
247
|
+
<a name='L182'></a><a href='#L182'>182</a>
|
|
248
|
+
<a name='L183'></a><a href='#L183'>183</a>
|
|
249
|
+
<a name='L184'></a><a href='#L184'>184</a>
|
|
250
|
+
<a name='L185'></a><a href='#L185'>185</a>
|
|
251
|
+
<a name='L186'></a><a href='#L186'>186</a>
|
|
252
|
+
<a name='L187'></a><a href='#L187'>187</a>
|
|
253
|
+
<a name='L188'></a><a href='#L188'>188</a>
|
|
254
|
+
<a name='L189'></a><a href='#L189'>189</a>
|
|
255
|
+
<a name='L190'></a><a href='#L190'>190</a>
|
|
256
|
+
<a name='L191'></a><a href='#L191'>191</a>
|
|
257
|
+
<a name='L192'></a><a href='#L192'>192</a>
|
|
258
|
+
<a name='L193'></a><a href='#L193'>193</a>
|
|
259
|
+
<a name='L194'></a><a href='#L194'>194</a>
|
|
260
|
+
<a name='L195'></a><a href='#L195'>195</a>
|
|
261
|
+
<a name='L196'></a><a href='#L196'>196</a>
|
|
262
|
+
<a name='L197'></a><a href='#L197'>197</a>
|
|
263
|
+
<a name='L198'></a><a href='#L198'>198</a>
|
|
264
|
+
<a name='L199'></a><a href='#L199'>199</a>
|
|
265
|
+
<a name='L200'></a><a href='#L200'>200</a>
|
|
266
|
+
<a name='L201'></a><a href='#L201'>201</a>
|
|
267
|
+
<a name='L202'></a><a href='#L202'>202</a>
|
|
268
|
+
<a name='L203'></a><a href='#L203'>203</a>
|
|
269
|
+
<a name='L204'></a><a href='#L204'>204</a>
|
|
270
|
+
<a name='L205'></a><a href='#L205'>205</a>
|
|
271
|
+
<a name='L206'></a><a href='#L206'>206</a>
|
|
272
|
+
<a name='L207'></a><a href='#L207'>207</a>
|
|
273
|
+
<a name='L208'></a><a href='#L208'>208</a>
|
|
274
|
+
<a name='L209'></a><a href='#L209'>209</a>
|
|
275
|
+
<a name='L210'></a><a href='#L210'>210</a>
|
|
276
|
+
<a name='L211'></a><a href='#L211'>211</a>
|
|
277
|
+
<a name='L212'></a><a href='#L212'>212</a>
|
|
278
|
+
<a name='L213'></a><a href='#L213'>213</a>
|
|
279
|
+
<a name='L214'></a><a href='#L214'>214</a>
|
|
280
|
+
<a name='L215'></a><a href='#L215'>215</a>
|
|
281
|
+
<a name='L216'></a><a href='#L216'>216</a>
|
|
282
|
+
<a name='L217'></a><a href='#L217'>217</a>
|
|
283
|
+
<a name='L218'></a><a href='#L218'>218</a>
|
|
284
|
+
<a name='L219'></a><a href='#L219'>219</a>
|
|
285
|
+
<a name='L220'></a><a href='#L220'>220</a>
|
|
286
|
+
<a name='L221'></a><a href='#L221'>221</a>
|
|
287
|
+
<a name='L222'></a><a href='#L222'>222</a>
|
|
288
|
+
<a name='L223'></a><a href='#L223'>223</a>
|
|
289
|
+
<a name='L224'></a><a href='#L224'>224</a>
|
|
290
|
+
<a name='L225'></a><a href='#L225'>225</a>
|
|
291
|
+
<a name='L226'></a><a href='#L226'>226</a>
|
|
292
|
+
<a name='L227'></a><a href='#L227'>227</a>
|
|
293
|
+
<a name='L228'></a><a href='#L228'>228</a>
|
|
294
|
+
<a name='L229'></a><a href='#L229'>229</a>
|
|
295
|
+
<a name='L230'></a><a href='#L230'>230</a>
|
|
296
|
+
<a name='L231'></a><a href='#L231'>231</a>
|
|
297
|
+
<a name='L232'></a><a href='#L232'>232</a>
|
|
298
|
+
<a name='L233'></a><a href='#L233'>233</a>
|
|
299
|
+
<a name='L234'></a><a href='#L234'>234</a>
|
|
300
|
+
<a name='L235'></a><a href='#L235'>235</a>
|
|
301
|
+
<a name='L236'></a><a href='#L236'>236</a>
|
|
302
|
+
<a name='L237'></a><a href='#L237'>237</a>
|
|
303
|
+
<a name='L238'></a><a href='#L238'>238</a>
|
|
304
|
+
<a name='L239'></a><a href='#L239'>239</a>
|
|
305
|
+
<a name='L240'></a><a href='#L240'>240</a>
|
|
306
|
+
<a name='L241'></a><a href='#L241'>241</a>
|
|
307
|
+
<a name='L242'></a><a href='#L242'>242</a>
|
|
308
|
+
<a name='L243'></a><a href='#L243'>243</a>
|
|
309
|
+
<a name='L244'></a><a href='#L244'>244</a>
|
|
310
|
+
<a name='L245'></a><a href='#L245'>245</a>
|
|
311
|
+
<a name='L246'></a><a href='#L246'>246</a>
|
|
312
|
+
<a name='L247'></a><a href='#L247'>247</a>
|
|
313
|
+
<a name='L248'></a><a href='#L248'>248</a>
|
|
314
|
+
<a name='L249'></a><a href='#L249'>249</a>
|
|
315
|
+
<a name='L250'></a><a href='#L250'>250</a>
|
|
316
|
+
<a name='L251'></a><a href='#L251'>251</a>
|
|
317
|
+
<a name='L252'></a><a href='#L252'>252</a>
|
|
318
|
+
<a name='L253'></a><a href='#L253'>253</a>
|
|
319
|
+
<a name='L254'></a><a href='#L254'>254</a>
|
|
320
|
+
<a name='L255'></a><a href='#L255'>255</a>
|
|
321
|
+
<a name='L256'></a><a href='#L256'>256</a>
|
|
322
|
+
<a name='L257'></a><a href='#L257'>257</a>
|
|
323
|
+
<a name='L258'></a><a href='#L258'>258</a>
|
|
324
|
+
<a name='L259'></a><a href='#L259'>259</a>
|
|
325
|
+
<a name='L260'></a><a href='#L260'>260</a>
|
|
326
|
+
<a name='L261'></a><a href='#L261'>261</a>
|
|
327
|
+
<a name='L262'></a><a href='#L262'>262</a>
|
|
328
|
+
<a name='L263'></a><a href='#L263'>263</a>
|
|
329
|
+
<a name='L264'></a><a href='#L264'>264</a>
|
|
330
|
+
<a name='L265'></a><a href='#L265'>265</a>
|
|
331
|
+
<a name='L266'></a><a href='#L266'>266</a>
|
|
332
|
+
<a name='L267'></a><a href='#L267'>267</a>
|
|
333
|
+
<a name='L268'></a><a href='#L268'>268</a>
|
|
334
|
+
<a name='L269'></a><a href='#L269'>269</a>
|
|
335
|
+
<a name='L270'></a><a href='#L270'>270</a>
|
|
336
|
+
<a name='L271'></a><a href='#L271'>271</a>
|
|
337
|
+
<a name='L272'></a><a href='#L272'>272</a>
|
|
338
|
+
<a name='L273'></a><a href='#L273'>273</a>
|
|
339
|
+
<a name='L274'></a><a href='#L274'>274</a>
|
|
340
|
+
<a name='L275'></a><a href='#L275'>275</a>
|
|
341
|
+
<a name='L276'></a><a href='#L276'>276</a>
|
|
342
|
+
<a name='L277'></a><a href='#L277'>277</a>
|
|
343
|
+
<a name='L278'></a><a href='#L278'>278</a>
|
|
344
|
+
<a name='L279'></a><a href='#L279'>279</a>
|
|
345
|
+
<a name='L280'></a><a href='#L280'>280</a>
|
|
346
|
+
<a name='L281'></a><a href='#L281'>281</a>
|
|
347
|
+
<a name='L282'></a><a href='#L282'>282</a>
|
|
348
|
+
<a name='L283'></a><a href='#L283'>283</a>
|
|
349
|
+
<a name='L284'></a><a href='#L284'>284</a>
|
|
350
|
+
<a name='L285'></a><a href='#L285'>285</a>
|
|
351
|
+
<a name='L286'></a><a href='#L286'>286</a>
|
|
352
|
+
<a name='L287'></a><a href='#L287'>287</a>
|
|
353
|
+
<a name='L288'></a><a href='#L288'>288</a>
|
|
354
|
+
<a name='L289'></a><a href='#L289'>289</a>
|
|
355
|
+
<a name='L290'></a><a href='#L290'>290</a>
|
|
356
|
+
<a name='L291'></a><a href='#L291'>291</a>
|
|
357
|
+
<a name='L292'></a><a href='#L292'>292</a>
|
|
358
|
+
<a name='L293'></a><a href='#L293'>293</a>
|
|
359
|
+
<a name='L294'></a><a href='#L294'>294</a>
|
|
360
|
+
<a name='L295'></a><a href='#L295'>295</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
|
|
361
|
+
<span class="cline-any cline-neutral"> </span>
|
|
362
|
+
<span class="cline-any cline-neutral"> </span>
|
|
363
|
+
<span class="cline-any cline-neutral"> </span>
|
|
364
|
+
<span class="cline-any cline-neutral"> </span>
|
|
365
|
+
<span class="cline-any cline-neutral"> </span>
|
|
366
|
+
<span class="cline-any cline-neutral"> </span>
|
|
367
|
+
<span class="cline-any cline-yes">1x</span>
|
|
368
|
+
<span class="cline-any cline-yes">1x</span>
|
|
369
|
+
<span class="cline-any cline-yes">1x</span>
|
|
370
|
+
<span class="cline-any cline-yes">1x</span>
|
|
371
|
+
<span class="cline-any cline-neutral"> </span>
|
|
372
|
+
<span class="cline-any cline-yes">1x</span>
|
|
373
|
+
<span class="cline-any cline-no"> </span>
|
|
374
|
+
<span class="cline-any cline-no"> </span>
|
|
375
|
+
<span class="cline-any cline-no"> </span>
|
|
376
|
+
<span class="cline-any cline-no"> </span>
|
|
377
|
+
<span class="cline-any cline-no"> </span>
|
|
378
|
+
<span class="cline-any cline-no"> </span>
|
|
379
|
+
<span class="cline-any cline-neutral"> </span>
|
|
380
|
+
<span class="cline-any cline-neutral"> </span>
|
|
381
|
+
<span class="cline-any cline-neutral"> </span>
|
|
382
|
+
<span class="cline-any cline-yes">1x</span>
|
|
383
|
+
<span class="cline-any cline-yes">9x</span>
|
|
384
|
+
<span class="cline-any cline-yes">9x</span>
|
|
385
|
+
<span class="cline-any cline-yes">9x</span>
|
|
386
|
+
<span class="cline-any cline-yes">9x</span>
|
|
387
|
+
<span class="cline-any cline-yes">9x</span>
|
|
388
|
+
<span class="cline-any cline-yes">9x</span>
|
|
389
|
+
<span class="cline-any cline-yes">9x</span>
|
|
390
|
+
<span class="cline-any cline-yes">9x</span>
|
|
391
|
+
<span class="cline-any cline-yes">9x</span>
|
|
392
|
+
<span class="cline-any cline-yes">9x</span>
|
|
393
|
+
<span class="cline-any cline-yes">9x</span>
|
|
394
|
+
<span class="cline-any cline-yes">9x</span>
|
|
395
|
+
<span class="cline-any cline-yes">9x</span>
|
|
396
|
+
<span class="cline-any cline-neutral"> </span>
|
|
397
|
+
<span class="cline-any cline-neutral"> </span>
|
|
398
|
+
<span class="cline-any cline-neutral"> </span>
|
|
399
|
+
<span class="cline-any cline-yes">9x</span>
|
|
400
|
+
<span class="cline-any cline-neutral"> </span>
|
|
401
|
+
<span class="cline-any cline-yes">9x</span>
|
|
402
|
+
<span class="cline-any cline-yes">9x</span>
|
|
403
|
+
<span class="cline-any cline-neutral"> </span>
|
|
404
|
+
<span class="cline-any cline-neutral"> </span>
|
|
405
|
+
<span class="cline-any cline-neutral"> </span>
|
|
406
|
+
<span class="cline-any cline-yes">9x</span>
|
|
407
|
+
<span class="cline-any cline-no"> </span>
|
|
408
|
+
<span class="cline-any cline-no"> </span>
|
|
409
|
+
<span class="cline-any cline-no"> </span>
|
|
410
|
+
<span class="cline-any cline-no"> </span>
|
|
411
|
+
<span class="cline-any cline-no"> </span>
|
|
412
|
+
<span class="cline-any cline-no"> </span>
|
|
413
|
+
<span class="cline-any cline-no"> </span>
|
|
414
|
+
<span class="cline-any cline-no"> </span>
|
|
415
|
+
<span class="cline-any cline-no"> </span>
|
|
416
|
+
<span class="cline-any cline-no"> </span>
|
|
417
|
+
<span class="cline-any cline-no"> </span>
|
|
418
|
+
<span class="cline-any cline-neutral"> </span>
|
|
419
|
+
<span class="cline-any cline-yes">9x</span>
|
|
420
|
+
<span class="cline-any cline-neutral"> </span>
|
|
421
|
+
<span class="cline-any cline-neutral"> </span>
|
|
422
|
+
<span class="cline-any cline-neutral"> </span>
|
|
423
|
+
<span class="cline-any cline-neutral"> </span>
|
|
424
|
+
<span class="cline-any cline-neutral"> </span>
|
|
425
|
+
<span class="cline-any cline-neutral"> </span>
|
|
426
|
+
<span class="cline-any cline-yes">9x</span>
|
|
427
|
+
<span class="cline-any cline-yes">9x</span>
|
|
428
|
+
<span class="cline-any cline-yes">9x</span>
|
|
429
|
+
<span class="cline-any cline-yes">9x</span>
|
|
430
|
+
<span class="cline-any cline-yes">9x</span>
|
|
431
|
+
<span class="cline-any cline-yes">9x</span>
|
|
432
|
+
<span class="cline-any cline-yes">9x</span>
|
|
433
|
+
<span class="cline-any cline-yes">9x</span>
|
|
434
|
+
<span class="cline-any cline-no"> </span>
|
|
435
|
+
<span class="cline-any cline-no"> </span>
|
|
436
|
+
<span class="cline-any cline-no"> </span>
|
|
437
|
+
<span class="cline-any cline-no"> </span>
|
|
438
|
+
<span class="cline-any cline-yes">9x</span>
|
|
439
|
+
<span class="cline-any cline-yes">9x</span>
|
|
440
|
+
<span class="cline-any cline-yes">9x</span>
|
|
441
|
+
<span class="cline-any cline-yes">9x</span>
|
|
442
|
+
<span class="cline-any cline-neutral"> </span>
|
|
443
|
+
<span class="cline-any cline-neutral"> </span>
|
|
444
|
+
<span class="cline-any cline-neutral"> </span>
|
|
445
|
+
<span class="cline-any cline-neutral"> </span>
|
|
446
|
+
<span class="cline-any cline-yes">2x</span>
|
|
447
|
+
<span class="cline-any cline-yes">2x</span>
|
|
448
|
+
<span class="cline-any cline-yes">2x</span>
|
|
449
|
+
<span class="cline-any cline-yes">2x</span>
|
|
450
|
+
<span class="cline-any cline-no"> </span>
|
|
451
|
+
<span class="cline-any cline-no"> </span>
|
|
452
|
+
<span class="cline-any cline-no"> </span>
|
|
453
|
+
<span class="cline-any cline-no"> </span>
|
|
454
|
+
<span class="cline-any cline-no"> </span>
|
|
455
|
+
<span class="cline-any cline-no"> </span>
|
|
456
|
+
<span class="cline-any cline-no"> </span>
|
|
457
|
+
<span class="cline-any cline-no"> </span>
|
|
458
|
+
<span class="cline-any cline-no"> </span>
|
|
459
|
+
<span class="cline-any cline-yes">2x</span>
|
|
460
|
+
<span class="cline-any cline-yes">9x</span>
|
|
461
|
+
<span class="cline-any cline-neutral"> </span>
|
|
462
|
+
<span class="cline-any cline-yes">9x</span>
|
|
463
|
+
<span class="cline-any cline-neutral"> </span>
|
|
464
|
+
<span class="cline-any cline-neutral"> </span>
|
|
465
|
+
<span class="cline-any cline-yes">9x</span>
|
|
466
|
+
<span class="cline-any cline-no"> </span>
|
|
467
|
+
<span class="cline-any cline-no"> </span>
|
|
468
|
+
<span class="cline-any cline-neutral"> </span>
|
|
469
|
+
<span class="cline-any cline-yes">9x</span>
|
|
470
|
+
<span class="cline-any cline-yes">9x</span>
|
|
471
|
+
<span class="cline-any cline-yes">9x</span>
|
|
472
|
+
<span class="cline-any cline-yes">9x</span>
|
|
473
|
+
<span class="cline-any cline-yes">9x</span>
|
|
474
|
+
<span class="cline-any cline-yes">9x</span>
|
|
475
|
+
<span class="cline-any cline-yes">9x</span>
|
|
476
|
+
<span class="cline-any cline-yes">9x</span>
|
|
477
|
+
<span class="cline-any cline-yes">9x</span>
|
|
478
|
+
<span class="cline-any cline-yes">9x</span>
|
|
479
|
+
<span class="cline-any cline-yes">9x</span>
|
|
480
|
+
<span class="cline-any cline-yes">9x</span>
|
|
481
|
+
<span class="cline-any cline-neutral"> </span>
|
|
482
|
+
<span class="cline-any cline-neutral"> </span>
|
|
483
|
+
<span class="cline-any cline-neutral"> </span>
|
|
484
|
+
<span class="cline-any cline-neutral"> </span>
|
|
485
|
+
<span class="cline-any cline-neutral"> </span>
|
|
486
|
+
<span class="cline-any cline-yes">9x</span>
|
|
487
|
+
<span class="cline-any cline-no"> </span>
|
|
488
|
+
<span class="cline-any cline-no"> </span>
|
|
489
|
+
<span class="cline-any cline-yes">9x</span>
|
|
490
|
+
<span class="cline-any cline-yes">9x</span>
|
|
491
|
+
<span class="cline-any cline-neutral"> </span>
|
|
492
|
+
<span class="cline-any cline-neutral"> </span>
|
|
493
|
+
<span class="cline-any cline-neutral"> </span>
|
|
494
|
+
<span class="cline-any cline-neutral"> </span>
|
|
495
|
+
<span class="cline-any cline-neutral"> </span>
|
|
496
|
+
<span class="cline-any cline-neutral"> </span>
|
|
497
|
+
<span class="cline-any cline-neutral"> </span>
|
|
498
|
+
<span class="cline-any cline-yes">9x</span>
|
|
499
|
+
<span class="cline-any cline-yes">1x</span>
|
|
500
|
+
<span class="cline-any cline-yes">1x</span>
|
|
501
|
+
<span class="cline-any cline-yes">1x</span>
|
|
502
|
+
<span class="cline-any cline-yes">1x</span>
|
|
503
|
+
<span class="cline-any cline-yes">1x</span>
|
|
504
|
+
<span class="cline-any cline-neutral"> </span>
|
|
505
|
+
<span class="cline-any cline-yes">9x</span>
|
|
506
|
+
<span class="cline-any cline-neutral"> </span>
|
|
507
|
+
<span class="cline-any cline-neutral"> </span>
|
|
508
|
+
<span class="cline-any cline-neutral"> </span>
|
|
509
|
+
<span class="cline-any cline-neutral"> </span>
|
|
510
|
+
<span class="cline-any cline-yes">2x</span>
|
|
511
|
+
<span class="cline-any cline-yes">2x</span>
|
|
512
|
+
<span class="cline-any cline-yes">2x</span>
|
|
513
|
+
<span class="cline-any cline-neutral"> </span>
|
|
514
|
+
<span class="cline-any cline-neutral"> </span>
|
|
515
|
+
<span class="cline-any cline-neutral"> </span>
|
|
516
|
+
<span class="cline-any cline-yes">2x</span>
|
|
517
|
+
<span class="cline-any cline-yes">2x</span>
|
|
518
|
+
<span class="cline-any cline-no"> </span>
|
|
519
|
+
<span class="cline-any cline-no"> </span>
|
|
520
|
+
<span class="cline-any cline-no"> </span>
|
|
521
|
+
<span class="cline-any cline-no"> </span>
|
|
522
|
+
<span class="cline-any cline-no"> </span>
|
|
523
|
+
<span class="cline-any cline-yes">2x</span>
|
|
524
|
+
<span class="cline-any cline-neutral"> </span>
|
|
525
|
+
<span class="cline-any cline-yes">9x</span>
|
|
526
|
+
<span class="cline-any cline-yes">3x</span>
|
|
527
|
+
<span class="cline-any cline-yes">3x</span>
|
|
528
|
+
<span class="cline-any cline-neutral"> </span>
|
|
529
|
+
<span class="cline-any cline-neutral"> </span>
|
|
530
|
+
<span class="cline-any cline-neutral"> </span>
|
|
531
|
+
<span class="cline-any cline-neutral"> </span>
|
|
532
|
+
<span class="cline-any cline-neutral"> </span>
|
|
533
|
+
<span class="cline-any cline-neutral"> </span>
|
|
534
|
+
<span class="cline-any cline-neutral"> </span>
|
|
535
|
+
<span class="cline-any cline-yes">9x</span>
|
|
536
|
+
<span class="cline-any cline-no"> </span>
|
|
537
|
+
<span class="cline-any cline-no"> </span>
|
|
538
|
+
<span class="cline-any cline-no"> </span>
|
|
539
|
+
<span class="cline-any cline-no"> </span>
|
|
540
|
+
<span class="cline-any cline-no"> </span>
|
|
541
|
+
<span class="cline-any cline-no"> </span>
|
|
542
|
+
<span class="cline-any cline-no"> </span>
|
|
543
|
+
<span class="cline-any cline-no"> </span>
|
|
544
|
+
<span class="cline-any cline-no"> </span>
|
|
545
|
+
<span class="cline-any cline-no"> </span>
|
|
546
|
+
<span class="cline-any cline-no"> </span>
|
|
547
|
+
<span class="cline-any cline-no"> </span>
|
|
548
|
+
<span class="cline-any cline-no"> </span>
|
|
549
|
+
<span class="cline-any cline-no"> </span>
|
|
550
|
+
<span class="cline-any cline-no"> </span>
|
|
551
|
+
<span class="cline-any cline-no"> </span>
|
|
552
|
+
<span class="cline-any cline-no"> </span>
|
|
553
|
+
<span class="cline-any cline-no"> </span>
|
|
554
|
+
<span class="cline-any cline-no"> </span>
|
|
555
|
+
<span class="cline-any cline-no"> </span>
|
|
556
|
+
<span class="cline-any cline-neutral"> </span>
|
|
557
|
+
<span class="cline-any cline-neutral"> </span>
|
|
558
|
+
<span class="cline-any cline-yes">9x</span>
|
|
559
|
+
<span class="cline-any cline-no"> </span>
|
|
560
|
+
<span class="cline-any cline-no"> </span>
|
|
561
|
+
<span class="cline-any cline-no"> </span>
|
|
562
|
+
<span class="cline-any cline-no"> </span>
|
|
563
|
+
<span class="cline-any cline-no"> </span>
|
|
564
|
+
<span class="cline-any cline-no"> </span>
|
|
565
|
+
<span class="cline-any cline-neutral"> </span>
|
|
566
|
+
<span class="cline-any cline-no"> </span>
|
|
567
|
+
<span class="cline-any cline-no"> </span>
|
|
568
|
+
<span class="cline-any cline-no"> </span>
|
|
569
|
+
<span class="cline-any cline-no"> </span>
|
|
570
|
+
<span class="cline-any cline-no"> </span>
|
|
571
|
+
<span class="cline-any cline-no"> </span>
|
|
572
|
+
<span class="cline-any cline-no"> </span>
|
|
573
|
+
<span class="cline-any cline-no"> </span>
|
|
574
|
+
<span class="cline-any cline-no"> </span>
|
|
575
|
+
<span class="cline-any cline-neutral"> </span>
|
|
576
|
+
<span class="cline-any cline-yes">9x</span>
|
|
577
|
+
<span class="cline-any cline-no"> </span>
|
|
578
|
+
<span class="cline-any cline-no"> </span>
|
|
579
|
+
<span class="cline-any cline-no"> </span>
|
|
580
|
+
<span class="cline-any cline-neutral"> </span>
|
|
581
|
+
<span class="cline-any cline-neutral"> </span>
|
|
582
|
+
<span class="cline-any cline-neutral"> </span>
|
|
583
|
+
<span class="cline-any cline-neutral"> </span>
|
|
584
|
+
<span class="cline-any cline-neutral"> </span>
|
|
585
|
+
<span class="cline-any cline-yes">9x</span>
|
|
586
|
+
<span class="cline-any cline-yes">3x</span>
|
|
587
|
+
<span class="cline-any cline-yes">3x</span>
|
|
588
|
+
<span class="cline-any cline-neutral"> </span>
|
|
589
|
+
<span class="cline-any cline-neutral"> </span>
|
|
590
|
+
<span class="cline-any cline-neutral"> </span>
|
|
591
|
+
<span class="cline-any cline-neutral"> </span>
|
|
592
|
+
<span class="cline-any cline-neutral"> </span>
|
|
593
|
+
<span class="cline-any cline-yes">9x</span>
|
|
594
|
+
<span class="cline-any cline-yes">2x</span>
|
|
595
|
+
<span class="cline-any cline-yes">2x</span>
|
|
596
|
+
<span class="cline-any cline-yes">2x</span>
|
|
597
|
+
<span class="cline-any cline-yes">2x</span>
|
|
598
|
+
<span class="cline-any cline-yes">2x</span>
|
|
599
|
+
<span class="cline-any cline-yes">2x</span>
|
|
600
|
+
<span class="cline-any cline-yes">2x</span>
|
|
601
|
+
<span class="cline-any cline-no"> </span>
|
|
602
|
+
<span class="cline-any cline-no"> </span>
|
|
603
|
+
<span class="cline-any cline-no"> </span>
|
|
604
|
+
<span class="cline-any cline-no"> </span>
|
|
605
|
+
<span class="cline-any cline-no"> </span>
|
|
606
|
+
<span class="cline-any cline-yes">2x</span>
|
|
607
|
+
<span class="cline-any cline-neutral"> </span>
|
|
608
|
+
<span class="cline-any cline-yes">9x</span>
|
|
609
|
+
<span class="cline-any cline-yes">1x</span>
|
|
610
|
+
<span class="cline-any cline-yes">1x</span>
|
|
611
|
+
<span class="cline-any cline-yes">1x</span>
|
|
612
|
+
<span class="cline-any cline-neutral"> </span>
|
|
613
|
+
<span class="cline-any cline-neutral"> </span>
|
|
614
|
+
<span class="cline-any cline-neutral"> </span>
|
|
615
|
+
<span class="cline-any cline-yes">1x</span>
|
|
616
|
+
<span class="cline-any cline-yes">1x</span>
|
|
617
|
+
<span class="cline-any cline-yes">1x</span>
|
|
618
|
+
<span class="cline-any cline-yes">1x</span>
|
|
619
|
+
<span class="cline-any cline-yes">1x</span>
|
|
620
|
+
<span class="cline-any cline-yes">1x</span>
|
|
621
|
+
<span class="cline-any cline-no"> </span>
|
|
622
|
+
<span class="cline-any cline-no"> </span>
|
|
623
|
+
<span class="cline-any cline-no"> </span>
|
|
624
|
+
<span class="cline-any cline-no"> </span>
|
|
625
|
+
<span class="cline-any cline-neutral"> </span>
|
|
626
|
+
<span class="cline-any cline-neutral"> </span>
|
|
627
|
+
<span class="cline-any cline-neutral"> </span>
|
|
628
|
+
<span class="cline-any cline-no"> </span>
|
|
629
|
+
<span class="cline-any cline-no"> </span>
|
|
630
|
+
<span class="cline-any cline-no"> </span>
|
|
631
|
+
<span class="cline-any cline-no"> </span>
|
|
632
|
+
<span class="cline-any cline-no"> </span>
|
|
633
|
+
<span class="cline-any cline-yes">1x</span>
|
|
634
|
+
<span class="cline-any cline-yes">9x</span>
|
|
635
|
+
<span class="cline-any cline-neutral"> </span>
|
|
636
|
+
<span class="cline-any cline-yes">1x</span>
|
|
637
|
+
<span class="cline-any cline-no"> </span>
|
|
638
|
+
<span class="cline-any cline-no"> </span>
|
|
639
|
+
<span class="cline-any cline-no"> </span>
|
|
640
|
+
<span class="cline-any cline-no"> </span>
|
|
641
|
+
<span class="cline-any cline-no"> </span>
|
|
642
|
+
<span class="cline-any cline-no"> </span>
|
|
643
|
+
<span class="cline-any cline-no"> </span>
|
|
644
|
+
<span class="cline-any cline-no"> </span>
|
|
645
|
+
<span class="cline-any cline-neutral"> </span>
|
|
646
|
+
<span class="cline-any cline-yes">1x</span>
|
|
647
|
+
<span class="cline-any cline-no"> </span>
|
|
648
|
+
<span class="cline-any cline-no"> </span>
|
|
649
|
+
<span class="cline-any cline-no"> </span>
|
|
650
|
+
<span class="cline-any cline-no"> </span>
|
|
651
|
+
<span class="cline-any cline-no"> </span>
|
|
652
|
+
<span class="cline-any cline-no"> </span>
|
|
653
|
+
<span class="cline-any cline-no"> </span>
|
|
654
|
+
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { LoroDoc } from 'loro-crdt';
|
|
655
|
+
import type { LocalStore, RemoteDatabaseService } from '../../services/database/index';
|
|
656
|
+
import type { Logger } from '../../services/logger/index';
|
|
657
|
+
import { parseRecordIdString } from '../../utils/index';
|
|
658
|
+
|
|
659
|
+
// ==================== CURSOR UTILITIES ====================
|
|
660
|
+
|
|
661
|
+
export const CURSOR_COLORS = [
|
|
662
|
+
'#3b82f6', '#ef4444', '#22c55e', '#f59e0b',
|
|
663
|
+
'#8b5cf6', '#ec4899', '#14b8a6', '#f97316',
|
|
664
|
+
];
|
|
665
|
+
|
|
666
|
+
export <span class="fstat-no" title="function not covered" >function cursorColorFromName(name: string): string {</span>
|
|
667
|
+
<span class="cstat-no" title="statement not covered" > let hash = 0;</span>
|
|
668
|
+
<span class="cstat-no" title="statement not covered" > for (let i = 0; i < name.length; i++) {</span>
|
|
669
|
+
<span class="cstat-no" title="statement not covered" > hash = ((hash << 5) - hash + name.charCodeAt(i)) | 0;</span>
|
|
670
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
671
|
+
<span class="cstat-no" title="statement not covered" > return CURSOR_COLORS[Math.abs(hash) % CURSOR_COLORS.length];</span>
|
|
672
|
+
<span class="cstat-no" title="statement not covered" >}</span>
|
|
673
|
+
|
|
674
|
+
// ==================== CRDT FIELD ====================
|
|
675
|
+
|
|
676
|
+
export class CrdtField {
|
|
677
|
+
private doc: LoroDoc;
|
|
678
|
+
private pushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
679
|
+
private local: LocalStore | null = null;
|
|
680
|
+
private remote: RemoteDatabaseService | null = null;
|
|
681
|
+
private recordId: string | null = null;
|
|
682
|
+
private sessionId: string = '';
|
|
683
|
+
private unsubscribe: (() => void) | null = null;
|
|
684
|
+
private lastPushTime = 0;
|
|
685
|
+
private lastCursorPushTime = 0;
|
|
686
|
+
private loadedFromCrdt = false;
|
|
687
|
+
private pushRetryCount = 0;
|
|
688
|
+
private logger: Logger | null;
|
|
689
|
+
private cursorsEnabled: boolean;
|
|
690
|
+
/** Remote-push debounce. Local writes happen immediately on every Loro
|
|
691
|
+
* update; the remote UPSERT is coalesced over this window. Configured
|
|
692
|
+
* via `Sp00kyConfig.crdtDebounceMs`, default 500. */
|
|
693
|
+
private remoteDebounceMs: number = 500;
|
|
694
|
+
|
|
695
|
+
private _onCursorUpdate: ((data: Uint8Array) => void) | null = null;
|
|
696
|
+
private pendingCursorUpdate: Uint8Array | null = null;
|
|
697
|
+
|
|
698
|
+
/** Callback set by the editor to receive remote cursor updates.
|
|
699
|
+
* Any cursor data that arrived before this callback was set will be replayed. */
|
|
700
|
+
<span class="fstat-no" title="function not covered" > set onCursorUpdate(cb: ((data: Uint8Array) => void) | null) {</span>
|
|
701
|
+
<span class="cstat-no" title="statement not covered" > this._onCursorUpdate = cb;</span>
|
|
702
|
+
<span class="cstat-no" title="statement not covered" > if (cb && this.pendingCursorUpdate) {</span>
|
|
703
|
+
<span class="cstat-no" title="statement not covered" > try { cb(this.pendingCursorUpdate); } catch (e) {</span>
|
|
704
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.warn(</span>
|
|
705
|
+
<span class="cstat-no" title="statement not covered" > { error: e, Category: 'sp00ky-client::CrdtField::onCursorUpdate' },</span>
|
|
706
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to replay pending cursor update'</span>
|
|
707
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
708
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
709
|
+
<span class="cstat-no" title="statement not covered" > this.pendingCursorUpdate = null;</span>
|
|
710
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
711
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
712
|
+
|
|
713
|
+
<span class="fstat-no" title="function not covered" > get onCursorUpdate() { return this._onCursorUpdate; }</span>
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* @param LoroDocClass the `LoroDoc` constructor, injected by the caller after
|
|
717
|
+
* awaiting {@link loadLoro} — keeps `loro-crdt` out of this module's static
|
|
718
|
+
* import graph so it only ships to apps that use CRDT fields.
|
|
719
|
+
*/
|
|
720
|
+
constructor(
|
|
721
|
+
private fieldName: string,
|
|
722
|
+
cursorsEnabled: boolean,
|
|
723
|
+
LoroDocClass: typeof LoroDoc,
|
|
724
|
+
initialState?: Uint8Array,
|
|
725
|
+
logger?: Logger | null,
|
|
726
|
+
) {
|
|
727
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(fieldName)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
728
|
+
<span class="cstat-no" title="statement not covered" > throw new Error(</span>
|
|
729
|
+
<span class="cstat-no" title="statement not covered" > `CrdtField: refusing unsafe field identifier '${fieldName}' — must match [a-zA-Z_][a-zA-Z0-9_]*`</span>
|
|
730
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
731
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
732
|
+
this.logger = <span class="branch-0 cbranch-no" title="branch not covered" >logger ?? null;</span>
|
|
733
|
+
this.cursorsEnabled = cursorsEnabled;
|
|
734
|
+
this.doc = new LoroDocClass();
|
|
735
|
+
if (initialState && initialState.length > 0) {
|
|
736
|
+
// Tolerance: catch bad-snapshot data (corrupt blob, stale legacy
|
|
737
|
+
// value left over from a pre-`bytes` migration) so the editor still
|
|
738
|
+
// mounts. Without this guard the rejection bubbles through
|
|
739
|
+
// `useCrdtField` → permanent fallback `<p>`, with no cursor.
|
|
740
|
+
try {
|
|
741
|
+
this.doc.import(initialState);
|
|
742
|
+
this.loadedFromCrdt = true;
|
|
743
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
744
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.warn(</span>
|
|
745
|
+
<span class="cstat-no" title="statement not covered" > {</span>
|
|
746
|
+
<span class="cstat-no" title="statement not covered" > error: e,</span>
|
|
747
|
+
<span class="cstat-no" title="statement not covered" > fieldName,</span>
|
|
748
|
+
<span class="cstat-no" title="statement not covered" > Category: 'sp00ky-client::CrdtField::constructor',</span>
|
|
749
|
+
<span class="cstat-no" title="statement not covered" > },</span>
|
|
750
|
+
<span class="cstat-no" title="statement not covered" > 'Initial CRDT state is not a valid LoroDoc snapshot — starting empty and will seed from fallback text'</span>
|
|
751
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
752
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
getDoc(): LoroDoc { return this.doc; }
|
|
757
|
+
|
|
758
|
+
/** Whether the LoroDoc was loaded from saved CRDT state */
|
|
759
|
+
<span class="fstat-no" title="function not covered" > hasContent(): boolean {</span>
|
|
760
|
+
<span class="cstat-no" title="statement not covered" > return this.loadedFromCrdt;</span>
|
|
761
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
762
|
+
|
|
763
|
+
startSync(
|
|
764
|
+
local: LocalStore,
|
|
765
|
+
remote: RemoteDatabaseService,
|
|
766
|
+
recordId: string,
|
|
767
|
+
sessionId: string,
|
|
768
|
+
debounceMs: number,
|
|
769
|
+
): void {
|
|
770
|
+
this.local = local;
|
|
771
|
+
this.remote = remote;
|
|
772
|
+
this.recordId = recordId;
|
|
773
|
+
this.sessionId = sessionId;
|
|
774
|
+
this.remoteDebounceMs = debounceMs;
|
|
775
|
+
// Every local Loro update writes the snapshot to the local cache
|
|
776
|
+
// *immediately* (so reload/offline see the latest text), then
|
|
777
|
+
// schedules a debounced push to remote. The local UPSERT is cheap —
|
|
778
|
+
// it's an in-memory SurrealKV write — but errors are swallowed so a
|
|
779
|
+
// bad write never blocks user input.
|
|
780
|
+
this.unsubscribe = this.doc.subscribeLocalUpdates(() => {
|
|
781
|
+
<span class="cstat-no" title="statement not covered" > void this.persistLocal();</span>
|
|
782
|
+
<span class="cstat-no" title="statement not covered" > this.scheduleRemotePush();</span>
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Stop syncing this field. Flushes one final remote push by default so the
|
|
788
|
+
* last keystrokes aren't lost. Pass `{ flush: false }` on a bucket switch —
|
|
789
|
+
* the remote session already belongs to the NEXT user, and pushing this
|
|
790
|
+
* (previous user's) snapshot under it would clobber the record remotely.
|
|
791
|
+
*/
|
|
792
|
+
stopSync(options: { flush?: boolean } = {}): void {
|
|
793
|
+
const flush = options.flush ?? true;
|
|
794
|
+
if (this.unsubscribe) { this.unsubscribe(); this.unsubscribe = null; }
|
|
795
|
+
if (this.pushTimer) <span class="branch-0 cbranch-no" title="branch not covered" >{ clearTimeout(this.pushTimer); this.pushTimer = null; }</span>
|
|
796
|
+
if (flush && this.remote && this.recordId) { void this.pushToRemote(); }
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
importRemote(state: Uint8Array): void {
|
|
800
|
+
// Echo suppression: skip imports within `remoteDebounceMs + 200` of
|
|
801
|
+
// our own push. The +200 guards against round-trip jitter where our
|
|
802
|
+
// own write echoes back from the LIVE feed before the debounce
|
|
803
|
+
// window closes.
|
|
804
|
+
if (Date.now() - this.lastPushTime < this.remoteDebounceMs + 200) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
805
|
+
try {
|
|
806
|
+
this.doc.import(state);
|
|
807
|
+
// Persist the merged snapshot locally so the next reload/offline
|
|
808
|
+
// open sees the freshest converged state without waiting for the
|
|
809
|
+
// LIVE feed.
|
|
810
|
+
void this.persistLocal();
|
|
811
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
812
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.warn(</span>
|
|
813
|
+
<span class="cstat-no" title="statement not covered" > { error: e, Category: 'sp00ky-client::CrdtField::importRemote' },</span>
|
|
814
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to import remote CRDT state'</span>
|
|
815
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
816
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
exportSnapshot(): Uint8Array {
|
|
820
|
+
return this.doc.export({ mode: 'snapshot' });
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/** Push this session's cursor blob into the parent row at
|
|
824
|
+
* `<field>.cursors[$sid]`. No-op when cursors aren't enabled on this
|
|
825
|
+
* field — the editor still calls this method optimistically, but
|
|
826
|
+
* without `@cursor` on the schema there's nowhere to store the blob.
|
|
827
|
+
* The UPDATE itself fires the parent table's LIVE feed, so other
|
|
828
|
+
* browsers receive the cursor change without a separate `_00_rv` bump. */
|
|
829
|
+
<span class="fstat-no" title="function not covered" > async pushCursorState(encoded: Uint8Array): Promise<void> {</span>
|
|
830
|
+
<span class="cstat-no" title="statement not covered" > if (!this.remote || !this.recordId) return;</span>
|
|
831
|
+
<span class="cstat-no" title="statement not covered" > if (!this.cursorsEnabled) return;</span>
|
|
832
|
+
<span class="cstat-no" title="statement not covered" > this.lastCursorPushTime = Date.now();</span>
|
|
833
|
+
<span class="cstat-no" title="statement not covered" > try {</span>
|
|
834
|
+
<span class="cstat-no" title="statement not covered" > const state = encodeBase64(encoded);</span>
|
|
835
|
+
<span class="cstat-no" title="statement not covered" > await this.remote.query(</span>
|
|
836
|
+
<span class="cstat-no" title="statement not covered" > `UPDATE $id SET ${this.fieldName}.cursors[$sid] = $state RETURN NONE;`,</span>
|
|
837
|
+
<span class="cstat-no" title="statement not covered" > {</span>
|
|
838
|
+
<span class="cstat-no" title="statement not covered" > id: parseRecordIdString(this.recordId),</span>
|
|
839
|
+
<span class="cstat-no" title="statement not covered" > sid: this.sessionId,</span>
|
|
840
|
+
<span class="cstat-no" title="statement not covered" > state,</span>
|
|
841
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
842
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
843
|
+
<span class="cstat-no" title="statement not covered" > } catch (e) {</span>
|
|
844
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.warn(</span>
|
|
845
|
+
<span class="cstat-no" title="statement not covered" > { error: e, Category: 'sp00ky-client::CrdtField::pushCursorState' },</span>
|
|
846
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to push cursor state'</span>
|
|
847
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
848
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
849
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
850
|
+
|
|
851
|
+
/** Import remote cursor state (called by CrdtManager from LIVE SELECT) */
|
|
852
|
+
<span class="fstat-no" title="function not covered" > importRemoteCursor(base64State: string): void {</span>
|
|
853
|
+
<span class="cstat-no" title="statement not covered" > if (Date.now() - this.lastCursorPushTime < 300) return; // echo suppression</span>
|
|
854
|
+
<span class="cstat-no" title="statement not covered" > try {</span>
|
|
855
|
+
<span class="cstat-no" title="statement not covered" > const data = decodeBase64(base64State);</span>
|
|
856
|
+
<span class="cstat-no" title="statement not covered" > if (this._onCursorUpdate) {</span>
|
|
857
|
+
<span class="cstat-no" title="statement not covered" > this._onCursorUpdate(data);</span>
|
|
858
|
+
<span class="cstat-no" title="statement not covered" > } else {</span>
|
|
859
|
+
// Only keep the latest cursor state — older positions are useless
|
|
860
|
+
<span class="cstat-no" title="statement not covered" > this.pendingCursorUpdate = data;</span>
|
|
861
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
862
|
+
<span class="cstat-no" title="statement not covered" > } catch (e) {</span>
|
|
863
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.warn(</span>
|
|
864
|
+
<span class="cstat-no" title="statement not covered" > { error: e, Category: 'sp00ky-client::CrdtField::importRemoteCursor' },</span>
|
|
865
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to apply remote cursor data'</span>
|
|
866
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
867
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
868
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
869
|
+
|
|
870
|
+
private <span class="fstat-no" title="function not covered" >scheduleRemotePush(): void {</span>
|
|
871
|
+
<span class="cstat-no" title="statement not covered" > if (this.pushTimer) clearTimeout(this.pushTimer);</span>
|
|
872
|
+
<span class="cstat-no" title="statement not covered" > this.pushTimer = setTimeout(() => void this.pushToRemote(), this.remoteDebounceMs);</span>
|
|
873
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
874
|
+
|
|
875
|
+
/** SET path inside a parent row for the current snapshot. `@crdt`-only
|
|
876
|
+
* fields hold the snapshot directly (`<field>`); `@crdt @cursor`
|
|
877
|
+
* fields hold a `{ state, cursors }` object so the snapshot lives at
|
|
878
|
+
* `<field>.state` next to per-session cursor blobs. */
|
|
879
|
+
private statePath(): string {
|
|
880
|
+
return this.cursorsEnabled ? `${this.fieldName}.state` : this.fieldName;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/** Mirror the LoroDoc snapshot into the parent row locally. Runs on
|
|
884
|
+
* every local update and every remote import so reloads (online or
|
|
885
|
+
* offline) see the freshest content immediately. Failures are
|
|
886
|
+
* swallowed — a stale local write must never block user input. */
|
|
887
|
+
private async persistLocal(): Promise<void> {
|
|
888
|
+
if (!this.local || !this.recordId) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
889
|
+
try {
|
|
890
|
+
await this.local.query(
|
|
891
|
+
`UPDATE $id SET ${this.statePath()} = $state RETURN NONE;`,
|
|
892
|
+
{ id: parseRecordIdString(this.recordId), state: this.exportSnapshot() }
|
|
893
|
+
);
|
|
894
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
895
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.debug(</span>
|
|
896
|
+
<span class="cstat-no" title="statement not covered" > { error: e, Category: 'sp00ky-client::CrdtField::persistLocal' },</span>
|
|
897
|
+
<span class="cstat-no" title="statement not covered" > 'Local CRDT persist failed (best-effort)'</span>
|
|
898
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
899
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private async pushToRemote(): Promise<void> {
|
|
903
|
+
if (!this.remote || !this.recordId) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
904
|
+
this.lastPushTime = Date.now();
|
|
905
|
+
try {
|
|
906
|
+
// The UPDATE on the parent fires the parent table's LIVE feed,
|
|
907
|
+
// so cross-browser receivers see this change directly in the LIVE
|
|
908
|
+
// payload — no separate `_00_rv` bump or sidecar UPSERT.
|
|
909
|
+
await this.remote.query(
|
|
910
|
+
`UPDATE $id SET ${this.statePath()} = $state RETURN NONE;`,
|
|
911
|
+
{ id: parseRecordIdString(this.recordId), state: this.exportSnapshot() }
|
|
912
|
+
);
|
|
913
|
+
this.pushRetryCount = 0;
|
|
914
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
915
|
+
<span class="cstat-no" title="statement not covered" > this.logger?.warn(</span>
|
|
916
|
+
<span class="cstat-no" title="statement not covered" > { error: e, Category: 'sp00ky-client::CrdtField::pushToRemote' },</span>
|
|
917
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to push CRDT state to remote'</span>
|
|
918
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
919
|
+
// Bounded retry. Offline first-loads will exhaust this and stop
|
|
920
|
+
// hammering; the next user keystroke (or the next time a remote
|
|
921
|
+
// event lands once we're back online) will kick off another push.
|
|
922
|
+
<span class="cstat-no" title="statement not covered" > if (this.pushRetryCount < 2) {</span>
|
|
923
|
+
<span class="cstat-no" title="statement not covered" > this.pushRetryCount++;</span>
|
|
924
|
+
<span class="cstat-no" title="statement not covered" > this.scheduleRemotePush();</span>
|
|
925
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
926
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
export <span class="fstat-no" title="function not covered" >function decodeBase64(b64: string): Uint8Array {</span>
|
|
931
|
+
<span class="cstat-no" title="statement not covered" > if (typeof atob === 'function') {</span>
|
|
932
|
+
<span class="cstat-no" title="statement not covered" > const binary = atob(b64);</span>
|
|
933
|
+
<span class="cstat-no" title="statement not covered" > const bytes = new Uint8Array(binary.length);</span>
|
|
934
|
+
<span class="cstat-no" title="statement not covered" > for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);</span>
|
|
935
|
+
<span class="cstat-no" title="statement not covered" > return bytes;</span>
|
|
936
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
937
|
+
<span class="cstat-no" title="statement not covered" > return new Uint8Array(Buffer.from(b64, 'base64'));</span>
|
|
938
|
+
<span class="cstat-no" title="statement not covered" >}</span>
|
|
939
|
+
|
|
940
|
+
export <span class="fstat-no" title="function not covered" >function encodeBase64(bytes: Uint8Array): string {</span>
|
|
941
|
+
<span class="cstat-no" title="statement not covered" > if (typeof btoa === 'function') {</span>
|
|
942
|
+
<span class="cstat-no" title="statement not covered" > let binary = '';</span>
|
|
943
|
+
<span class="cstat-no" title="statement not covered" > for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);</span>
|
|
944
|
+
<span class="cstat-no" title="statement not covered" > return btoa(binary);</span>
|
|
945
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
946
|
+
<span class="cstat-no" title="statement not covered" > return Buffer.from(bytes).toString('base64');</span>
|
|
947
|
+
<span class="cstat-no" title="statement not covered" >}</span>
|
|
948
|
+
</pre></td></tr></table></pre>
|
|
949
|
+
|
|
950
|
+
<div class='push'></div><!-- for sticky footer -->
|
|
951
|
+
</div><!-- /wrapper -->
|
|
952
|
+
<div class='footer quiet pad2 space-top1 center small'>
|
|
953
|
+
Code coverage generated by
|
|
954
|
+
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
955
|
+
at 2026-09-09T09:49:51.780Z
|
|
956
|
+
</div>
|
|
957
|
+
<script src="../../../prettify.js"></script>
|
|
958
|
+
<script>
|
|
959
|
+
window.onload = function () {
|
|
960
|
+
prettyPrint();
|
|
961
|
+
};
|
|
962
|
+
</script>
|
|
963
|
+
<script src="../../../sorter.js"></script>
|
|
964
|
+
<script src="../../../block-navigation.js"></script>
|
|
965
|
+
</body>
|
|
966
|
+
</html>
|
|
967
|
+
|