@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,1474 @@
|
|
|
1
|
+
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html lang="en">
|
|
4
|
+
|
|
5
|
+
<head>
|
|
6
|
+
<title>Code coverage report for src/modules/crdt/index.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> index.ts</h1>
|
|
23
|
+
<div class='clearfix'>
|
|
24
|
+
|
|
25
|
+
<div class='fl pad1y space-right2'>
|
|
26
|
+
<span class="strong">77.3% </span>
|
|
27
|
+
<span class="quiet">Statements</span>
|
|
28
|
+
<span class='fraction'>218/282</span>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<div class='fl pad1y space-right2'>
|
|
33
|
+
<span class="strong">67.94% </span>
|
|
34
|
+
<span class="quiet">Branches</span>
|
|
35
|
+
<span class='fraction'>53/78</span>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<div class='fl pad1y space-right2'>
|
|
40
|
+
<span class="strong">90% </span>
|
|
41
|
+
<span class="quiet">Functions</span>
|
|
42
|
+
<span class='fraction'>18/20</span>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<div class='fl pad1y space-right2'>
|
|
47
|
+
<span class="strong">77.3% </span>
|
|
48
|
+
<span class="quiet">Lines</span>
|
|
49
|
+
<span class='fraction'>218/282</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 medium'></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>
|
|
361
|
+
<a name='L296'></a><a href='#L296'>296</a>
|
|
362
|
+
<a name='L297'></a><a href='#L297'>297</a>
|
|
363
|
+
<a name='L298'></a><a href='#L298'>298</a>
|
|
364
|
+
<a name='L299'></a><a href='#L299'>299</a>
|
|
365
|
+
<a name='L300'></a><a href='#L300'>300</a>
|
|
366
|
+
<a name='L301'></a><a href='#L301'>301</a>
|
|
367
|
+
<a name='L302'></a><a href='#L302'>302</a>
|
|
368
|
+
<a name='L303'></a><a href='#L303'>303</a>
|
|
369
|
+
<a name='L304'></a><a href='#L304'>304</a>
|
|
370
|
+
<a name='L305'></a><a href='#L305'>305</a>
|
|
371
|
+
<a name='L306'></a><a href='#L306'>306</a>
|
|
372
|
+
<a name='L307'></a><a href='#L307'>307</a>
|
|
373
|
+
<a name='L308'></a><a href='#L308'>308</a>
|
|
374
|
+
<a name='L309'></a><a href='#L309'>309</a>
|
|
375
|
+
<a name='L310'></a><a href='#L310'>310</a>
|
|
376
|
+
<a name='L311'></a><a href='#L311'>311</a>
|
|
377
|
+
<a name='L312'></a><a href='#L312'>312</a>
|
|
378
|
+
<a name='L313'></a><a href='#L313'>313</a>
|
|
379
|
+
<a name='L314'></a><a href='#L314'>314</a>
|
|
380
|
+
<a name='L315'></a><a href='#L315'>315</a>
|
|
381
|
+
<a name='L316'></a><a href='#L316'>316</a>
|
|
382
|
+
<a name='L317'></a><a href='#L317'>317</a>
|
|
383
|
+
<a name='L318'></a><a href='#L318'>318</a>
|
|
384
|
+
<a name='L319'></a><a href='#L319'>319</a>
|
|
385
|
+
<a name='L320'></a><a href='#L320'>320</a>
|
|
386
|
+
<a name='L321'></a><a href='#L321'>321</a>
|
|
387
|
+
<a name='L322'></a><a href='#L322'>322</a>
|
|
388
|
+
<a name='L323'></a><a href='#L323'>323</a>
|
|
389
|
+
<a name='L324'></a><a href='#L324'>324</a>
|
|
390
|
+
<a name='L325'></a><a href='#L325'>325</a>
|
|
391
|
+
<a name='L326'></a><a href='#L326'>326</a>
|
|
392
|
+
<a name='L327'></a><a href='#L327'>327</a>
|
|
393
|
+
<a name='L328'></a><a href='#L328'>328</a>
|
|
394
|
+
<a name='L329'></a><a href='#L329'>329</a>
|
|
395
|
+
<a name='L330'></a><a href='#L330'>330</a>
|
|
396
|
+
<a name='L331'></a><a href='#L331'>331</a>
|
|
397
|
+
<a name='L332'></a><a href='#L332'>332</a>
|
|
398
|
+
<a name='L333'></a><a href='#L333'>333</a>
|
|
399
|
+
<a name='L334'></a><a href='#L334'>334</a>
|
|
400
|
+
<a name='L335'></a><a href='#L335'>335</a>
|
|
401
|
+
<a name='L336'></a><a href='#L336'>336</a>
|
|
402
|
+
<a name='L337'></a><a href='#L337'>337</a>
|
|
403
|
+
<a name='L338'></a><a href='#L338'>338</a>
|
|
404
|
+
<a name='L339'></a><a href='#L339'>339</a>
|
|
405
|
+
<a name='L340'></a><a href='#L340'>340</a>
|
|
406
|
+
<a name='L341'></a><a href='#L341'>341</a>
|
|
407
|
+
<a name='L342'></a><a href='#L342'>342</a>
|
|
408
|
+
<a name='L343'></a><a href='#L343'>343</a>
|
|
409
|
+
<a name='L344'></a><a href='#L344'>344</a>
|
|
410
|
+
<a name='L345'></a><a href='#L345'>345</a>
|
|
411
|
+
<a name='L346'></a><a href='#L346'>346</a>
|
|
412
|
+
<a name='L347'></a><a href='#L347'>347</a>
|
|
413
|
+
<a name='L348'></a><a href='#L348'>348</a>
|
|
414
|
+
<a name='L349'></a><a href='#L349'>349</a>
|
|
415
|
+
<a name='L350'></a><a href='#L350'>350</a>
|
|
416
|
+
<a name='L351'></a><a href='#L351'>351</a>
|
|
417
|
+
<a name='L352'></a><a href='#L352'>352</a>
|
|
418
|
+
<a name='L353'></a><a href='#L353'>353</a>
|
|
419
|
+
<a name='L354'></a><a href='#L354'>354</a>
|
|
420
|
+
<a name='L355'></a><a href='#L355'>355</a>
|
|
421
|
+
<a name='L356'></a><a href='#L356'>356</a>
|
|
422
|
+
<a name='L357'></a><a href='#L357'>357</a>
|
|
423
|
+
<a name='L358'></a><a href='#L358'>358</a>
|
|
424
|
+
<a name='L359'></a><a href='#L359'>359</a>
|
|
425
|
+
<a name='L360'></a><a href='#L360'>360</a>
|
|
426
|
+
<a name='L361'></a><a href='#L361'>361</a>
|
|
427
|
+
<a name='L362'></a><a href='#L362'>362</a>
|
|
428
|
+
<a name='L363'></a><a href='#L363'>363</a>
|
|
429
|
+
<a name='L364'></a><a href='#L364'>364</a>
|
|
430
|
+
<a name='L365'></a><a href='#L365'>365</a>
|
|
431
|
+
<a name='L366'></a><a href='#L366'>366</a>
|
|
432
|
+
<a name='L367'></a><a href='#L367'>367</a>
|
|
433
|
+
<a name='L368'></a><a href='#L368'>368</a>
|
|
434
|
+
<a name='L369'></a><a href='#L369'>369</a>
|
|
435
|
+
<a name='L370'></a><a href='#L370'>370</a>
|
|
436
|
+
<a name='L371'></a><a href='#L371'>371</a>
|
|
437
|
+
<a name='L372'></a><a href='#L372'>372</a>
|
|
438
|
+
<a name='L373'></a><a href='#L373'>373</a>
|
|
439
|
+
<a name='L374'></a><a href='#L374'>374</a>
|
|
440
|
+
<a name='L375'></a><a href='#L375'>375</a>
|
|
441
|
+
<a name='L376'></a><a href='#L376'>376</a>
|
|
442
|
+
<a name='L377'></a><a href='#L377'>377</a>
|
|
443
|
+
<a name='L378'></a><a href='#L378'>378</a>
|
|
444
|
+
<a name='L379'></a><a href='#L379'>379</a>
|
|
445
|
+
<a name='L380'></a><a href='#L380'>380</a>
|
|
446
|
+
<a name='L381'></a><a href='#L381'>381</a>
|
|
447
|
+
<a name='L382'></a><a href='#L382'>382</a>
|
|
448
|
+
<a name='L383'></a><a href='#L383'>383</a>
|
|
449
|
+
<a name='L384'></a><a href='#L384'>384</a>
|
|
450
|
+
<a name='L385'></a><a href='#L385'>385</a>
|
|
451
|
+
<a name='L386'></a><a href='#L386'>386</a>
|
|
452
|
+
<a name='L387'></a><a href='#L387'>387</a>
|
|
453
|
+
<a name='L388'></a><a href='#L388'>388</a>
|
|
454
|
+
<a name='L389'></a><a href='#L389'>389</a>
|
|
455
|
+
<a name='L390'></a><a href='#L390'>390</a>
|
|
456
|
+
<a name='L391'></a><a href='#L391'>391</a>
|
|
457
|
+
<a name='L392'></a><a href='#L392'>392</a>
|
|
458
|
+
<a name='L393'></a><a href='#L393'>393</a>
|
|
459
|
+
<a name='L394'></a><a href='#L394'>394</a>
|
|
460
|
+
<a name='L395'></a><a href='#L395'>395</a>
|
|
461
|
+
<a name='L396'></a><a href='#L396'>396</a>
|
|
462
|
+
<a name='L397'></a><a href='#L397'>397</a>
|
|
463
|
+
<a name='L398'></a><a href='#L398'>398</a>
|
|
464
|
+
<a name='L399'></a><a href='#L399'>399</a>
|
|
465
|
+
<a name='L400'></a><a href='#L400'>400</a>
|
|
466
|
+
<a name='L401'></a><a href='#L401'>401</a>
|
|
467
|
+
<a name='L402'></a><a href='#L402'>402</a>
|
|
468
|
+
<a name='L403'></a><a href='#L403'>403</a>
|
|
469
|
+
<a name='L404'></a><a href='#L404'>404</a>
|
|
470
|
+
<a name='L405'></a><a href='#L405'>405</a>
|
|
471
|
+
<a name='L406'></a><a href='#L406'>406</a>
|
|
472
|
+
<a name='L407'></a><a href='#L407'>407</a>
|
|
473
|
+
<a name='L408'></a><a href='#L408'>408</a>
|
|
474
|
+
<a name='L409'></a><a href='#L409'>409</a>
|
|
475
|
+
<a name='L410'></a><a href='#L410'>410</a>
|
|
476
|
+
<a name='L411'></a><a href='#L411'>411</a>
|
|
477
|
+
<a name='L412'></a><a href='#L412'>412</a>
|
|
478
|
+
<a name='L413'></a><a href='#L413'>413</a>
|
|
479
|
+
<a name='L414'></a><a href='#L414'>414</a>
|
|
480
|
+
<a name='L415'></a><a href='#L415'>415</a>
|
|
481
|
+
<a name='L416'></a><a href='#L416'>416</a>
|
|
482
|
+
<a name='L417'></a><a href='#L417'>417</a>
|
|
483
|
+
<a name='L418'></a><a href='#L418'>418</a>
|
|
484
|
+
<a name='L419'></a><a href='#L419'>419</a>
|
|
485
|
+
<a name='L420'></a><a href='#L420'>420</a>
|
|
486
|
+
<a name='L421'></a><a href='#L421'>421</a>
|
|
487
|
+
<a name='L422'></a><a href='#L422'>422</a>
|
|
488
|
+
<a name='L423'></a><a href='#L423'>423</a>
|
|
489
|
+
<a name='L424'></a><a href='#L424'>424</a>
|
|
490
|
+
<a name='L425'></a><a href='#L425'>425</a>
|
|
491
|
+
<a name='L426'></a><a href='#L426'>426</a>
|
|
492
|
+
<a name='L427'></a><a href='#L427'>427</a>
|
|
493
|
+
<a name='L428'></a><a href='#L428'>428</a>
|
|
494
|
+
<a name='L429'></a><a href='#L429'>429</a>
|
|
495
|
+
<a name='L430'></a><a href='#L430'>430</a>
|
|
496
|
+
<a name='L431'></a><a href='#L431'>431</a>
|
|
497
|
+
<a name='L432'></a><a href='#L432'>432</a>
|
|
498
|
+
<a name='L433'></a><a href='#L433'>433</a>
|
|
499
|
+
<a name='L434'></a><a href='#L434'>434</a>
|
|
500
|
+
<a name='L435'></a><a href='#L435'>435</a>
|
|
501
|
+
<a name='L436'></a><a href='#L436'>436</a>
|
|
502
|
+
<a name='L437'></a><a href='#L437'>437</a>
|
|
503
|
+
<a name='L438'></a><a href='#L438'>438</a>
|
|
504
|
+
<a name='L439'></a><a href='#L439'>439</a>
|
|
505
|
+
<a name='L440'></a><a href='#L440'>440</a>
|
|
506
|
+
<a name='L441'></a><a href='#L441'>441</a>
|
|
507
|
+
<a name='L442'></a><a href='#L442'>442</a>
|
|
508
|
+
<a name='L443'></a><a href='#L443'>443</a>
|
|
509
|
+
<a name='L444'></a><a href='#L444'>444</a>
|
|
510
|
+
<a name='L445'></a><a href='#L445'>445</a>
|
|
511
|
+
<a name='L446'></a><a href='#L446'>446</a>
|
|
512
|
+
<a name='L447'></a><a href='#L447'>447</a>
|
|
513
|
+
<a name='L448'></a><a href='#L448'>448</a>
|
|
514
|
+
<a name='L449'></a><a href='#L449'>449</a>
|
|
515
|
+
<a name='L450'></a><a href='#L450'>450</a>
|
|
516
|
+
<a name='L451'></a><a href='#L451'>451</a>
|
|
517
|
+
<a name='L452'></a><a href='#L452'>452</a>
|
|
518
|
+
<a name='L453'></a><a href='#L453'>453</a>
|
|
519
|
+
<a name='L454'></a><a href='#L454'>454</a>
|
|
520
|
+
<a name='L455'></a><a href='#L455'>455</a>
|
|
521
|
+
<a name='L456'></a><a href='#L456'>456</a>
|
|
522
|
+
<a name='L457'></a><a href='#L457'>457</a>
|
|
523
|
+
<a name='L458'></a><a href='#L458'>458</a>
|
|
524
|
+
<a name='L459'></a><a href='#L459'>459</a>
|
|
525
|
+
<a name='L460'></a><a href='#L460'>460</a>
|
|
526
|
+
<a name='L461'></a><a href='#L461'>461</a>
|
|
527
|
+
<a name='L462'></a><a href='#L462'>462</a>
|
|
528
|
+
<a name='L463'></a><a href='#L463'>463</a>
|
|
529
|
+
<a name='L464'></a><a href='#L464'>464</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</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-neutral"> </span>
|
|
536
|
+
<span class="cline-any cline-neutral"> </span>
|
|
537
|
+
<span class="cline-any cline-neutral"> </span>
|
|
538
|
+
<span class="cline-any cline-neutral"> </span>
|
|
539
|
+
<span class="cline-any cline-neutral"> </span>
|
|
540
|
+
<span class="cline-any cline-neutral"> </span>
|
|
541
|
+
<span class="cline-any cline-neutral"> </span>
|
|
542
|
+
<span class="cline-any cline-neutral"> </span>
|
|
543
|
+
<span class="cline-any cline-neutral"> </span>
|
|
544
|
+
<span class="cline-any cline-neutral"> </span>
|
|
545
|
+
<span class="cline-any cline-neutral"> </span>
|
|
546
|
+
<span class="cline-any cline-neutral"> </span>
|
|
547
|
+
<span class="cline-any cline-neutral"> </span>
|
|
548
|
+
<span class="cline-any cline-neutral"> </span>
|
|
549
|
+
<span class="cline-any cline-neutral"> </span>
|
|
550
|
+
<span class="cline-any cline-neutral"> </span>
|
|
551
|
+
<span class="cline-any cline-neutral"> </span>
|
|
552
|
+
<span class="cline-any cline-neutral"> </span>
|
|
553
|
+
<span class="cline-any cline-neutral"> </span>
|
|
554
|
+
<span class="cline-any cline-neutral"> </span>
|
|
555
|
+
<span class="cline-any cline-neutral"> </span>
|
|
556
|
+
<span class="cline-any cline-neutral"> </span>
|
|
557
|
+
<span class="cline-any cline-neutral"> </span>
|
|
558
|
+
<span class="cline-any cline-neutral"> </span>
|
|
559
|
+
<span class="cline-any cline-neutral"> </span>
|
|
560
|
+
<span class="cline-any cline-yes">1x</span>
|
|
561
|
+
<span class="cline-any cline-yes">9x</span>
|
|
562
|
+
<span class="cline-any cline-neutral"> </span>
|
|
563
|
+
<span class="cline-any cline-yes">9x</span>
|
|
564
|
+
<span class="cline-any cline-neutral"> </span>
|
|
565
|
+
<span class="cline-any cline-neutral"> </span>
|
|
566
|
+
<span class="cline-any cline-neutral"> </span>
|
|
567
|
+
<span class="cline-any cline-yes">9x</span>
|
|
568
|
+
<span class="cline-any cline-neutral"> </span>
|
|
569
|
+
<span class="cline-any cline-neutral"> </span>
|
|
570
|
+
<span class="cline-any cline-neutral"> </span>
|
|
571
|
+
<span class="cline-any cline-yes">9x</span>
|
|
572
|
+
<span class="cline-any cline-neutral"> </span>
|
|
573
|
+
<span class="cline-any cline-neutral"> </span>
|
|
574
|
+
<span class="cline-any cline-neutral"> </span>
|
|
575
|
+
<span class="cline-any cline-yes">9x</span>
|
|
576
|
+
<span class="cline-any cline-yes">9x</span>
|
|
577
|
+
<span class="cline-any cline-yes">9x</span>
|
|
578
|
+
<span class="cline-any cline-neutral"> </span>
|
|
579
|
+
<span class="cline-any cline-yes">9x</span>
|
|
580
|
+
<span class="cline-any cline-neutral"> </span>
|
|
581
|
+
<span class="cline-any cline-yes">9x</span>
|
|
582
|
+
<span class="cline-any cline-yes">9x</span>
|
|
583
|
+
<span class="cline-any cline-yes">9x</span>
|
|
584
|
+
<span class="cline-any cline-yes">9x</span>
|
|
585
|
+
<span class="cline-any cline-yes">9x</span>
|
|
586
|
+
<span class="cline-any cline-yes">9x</span>
|
|
587
|
+
<span class="cline-any cline-yes">9x</span>
|
|
588
|
+
<span class="cline-any cline-yes">9x</span>
|
|
589
|
+
<span class="cline-any cline-yes">9x</span>
|
|
590
|
+
<span class="cline-any cline-yes">9x</span>
|
|
591
|
+
<span class="cline-any cline-neutral"> </span>
|
|
592
|
+
<span class="cline-any cline-neutral"> </span>
|
|
593
|
+
<span class="cline-any cline-neutral"> </span>
|
|
594
|
+
<span class="cline-any cline-neutral"> </span>
|
|
595
|
+
<span class="cline-any cline-neutral"> </span>
|
|
596
|
+
<span class="cline-any cline-neutral"> </span>
|
|
597
|
+
<span class="cline-any cline-neutral"> </span>
|
|
598
|
+
<span class="cline-any cline-neutral"> </span>
|
|
599
|
+
<span class="cline-any cline-neutral"> </span>
|
|
600
|
+
<span class="cline-any cline-neutral"> </span>
|
|
601
|
+
<span class="cline-any cline-neutral"> </span>
|
|
602
|
+
<span class="cline-any cline-neutral"> </span>
|
|
603
|
+
<span class="cline-any cline-neutral"> </span>
|
|
604
|
+
<span class="cline-any cline-neutral"> </span>
|
|
605
|
+
<span class="cline-any cline-yes">9x</span>
|
|
606
|
+
<span class="cline-any cline-yes">9x</span>
|
|
607
|
+
<span class="cline-any cline-yes">4x</span>
|
|
608
|
+
<span class="cline-any cline-yes">4x</span>
|
|
609
|
+
<span class="cline-any cline-neutral"> </span>
|
|
610
|
+
<span class="cline-any cline-neutral"> </span>
|
|
611
|
+
<span class="cline-any cline-yes">4x</span>
|
|
612
|
+
<span class="cline-any cline-neutral"> </span>
|
|
613
|
+
<span class="cline-any cline-neutral"> </span>
|
|
614
|
+
<span class="cline-any cline-yes">4x</span>
|
|
615
|
+
<span class="cline-any cline-yes">4x</span>
|
|
616
|
+
<span class="cline-any cline-yes">9x</span>
|
|
617
|
+
<span class="cline-any cline-yes">9x</span>
|
|
618
|
+
<span class="cline-any cline-yes">9x</span>
|
|
619
|
+
<span class="cline-any cline-yes">9x</span>
|
|
620
|
+
<span class="cline-any cline-yes">4x</span>
|
|
621
|
+
<span class="cline-any cline-yes">4x</span>
|
|
622
|
+
<span class="cline-any cline-yes">4x</span>
|
|
623
|
+
<span class="cline-any cline-neutral"> </span>
|
|
624
|
+
<span class="cline-any cline-neutral"> </span>
|
|
625
|
+
<span class="cline-any cline-yes">3x</span>
|
|
626
|
+
<span class="cline-any cline-yes">3x</span>
|
|
627
|
+
<span class="cline-any cline-yes">3x</span>
|
|
628
|
+
<span class="cline-any cline-yes">9x</span>
|
|
629
|
+
<span class="cline-any cline-yes">9x</span>
|
|
630
|
+
<span class="cline-any cline-yes">9x</span>
|
|
631
|
+
<span class="cline-any cline-neutral"> </span>
|
|
632
|
+
<span class="cline-any cline-neutral"> </span>
|
|
633
|
+
<span class="cline-any cline-neutral"> </span>
|
|
634
|
+
<span class="cline-any cline-yes">9x</span>
|
|
635
|
+
<span class="cline-any cline-yes">1x</span>
|
|
636
|
+
<span class="cline-any cline-yes">3x</span>
|
|
637
|
+
<span class="cline-any cline-yes">3x</span>
|
|
638
|
+
<span class="cline-any cline-yes">3x</span>
|
|
639
|
+
<span class="cline-any cline-neutral"> </span>
|
|
640
|
+
<span class="cline-any cline-no"> </span>
|
|
641
|
+
<span class="cline-any cline-yes">3x</span>
|
|
642
|
+
<span class="cline-any cline-yes">1x</span>
|
|
643
|
+
<span class="cline-any cline-yes">1x</span>
|
|
644
|
+
<span class="cline-any cline-yes">1x</span>
|
|
645
|
+
<span class="cline-any cline-neutral"> </span>
|
|
646
|
+
<span class="cline-any cline-yes">9x</span>
|
|
647
|
+
<span class="cline-any cline-yes">4x</span>
|
|
648
|
+
<span class="cline-any cline-yes">4x</span>
|
|
649
|
+
<span class="cline-any cline-yes">4x</span>
|
|
650
|
+
<span class="cline-any cline-neutral"> </span>
|
|
651
|
+
<span class="cline-any cline-neutral"> </span>
|
|
652
|
+
<span class="cline-any cline-neutral"> </span>
|
|
653
|
+
<span class="cline-any cline-neutral"> </span>
|
|
654
|
+
<span class="cline-any cline-neutral"> </span>
|
|
655
|
+
<span class="cline-any cline-yes">9x</span>
|
|
656
|
+
<span class="cline-any cline-yes">9x</span>
|
|
657
|
+
<span class="cline-any cline-yes">9x</span>
|
|
658
|
+
<span class="cline-any cline-neutral"> </span>
|
|
659
|
+
<span class="cline-any cline-neutral"> </span>
|
|
660
|
+
<span class="cline-any cline-neutral"> </span>
|
|
661
|
+
<span class="cline-any cline-neutral"> </span>
|
|
662
|
+
<span class="cline-any cline-neutral"> </span>
|
|
663
|
+
<span class="cline-any cline-neutral"> </span>
|
|
664
|
+
<span class="cline-any cline-neutral"> </span>
|
|
665
|
+
<span class="cline-any cline-neutral"> </span>
|
|
666
|
+
<span class="cline-any cline-neutral"> </span>
|
|
667
|
+
<span class="cline-any cline-neutral"> </span>
|
|
668
|
+
<span class="cline-any cline-yes">9x</span>
|
|
669
|
+
<span class="cline-any cline-yes">9x</span>
|
|
670
|
+
<span class="cline-any cline-yes">9x</span>
|
|
671
|
+
<span class="cline-any cline-yes">9x</span>
|
|
672
|
+
<span class="cline-any cline-yes">9x</span>
|
|
673
|
+
<span class="cline-any cline-yes">9x</span>
|
|
674
|
+
<span class="cline-any cline-yes">9x</span>
|
|
675
|
+
<span class="cline-any cline-yes">9x</span>
|
|
676
|
+
<span class="cline-any cline-yes">9x</span>
|
|
677
|
+
<span class="cline-any cline-yes">9x</span>
|
|
678
|
+
<span class="cline-any cline-neutral"> </span>
|
|
679
|
+
<span class="cline-any cline-yes">9x</span>
|
|
680
|
+
<span class="cline-any cline-no"> </span>
|
|
681
|
+
<span class="cline-any cline-no"> </span>
|
|
682
|
+
<span class="cline-any cline-neutral"> </span>
|
|
683
|
+
<span class="cline-any cline-neutral"> </span>
|
|
684
|
+
<span class="cline-any cline-neutral"> </span>
|
|
685
|
+
<span class="cline-any cline-neutral"> </span>
|
|
686
|
+
<span class="cline-any cline-neutral"> </span>
|
|
687
|
+
<span class="cline-any cline-neutral"> </span>
|
|
688
|
+
<span class="cline-any cline-neutral"> </span>
|
|
689
|
+
<span class="cline-any cline-yes">9x</span>
|
|
690
|
+
<span class="cline-any cline-yes">9x</span>
|
|
691
|
+
<span class="cline-any cline-yes">9x</span>
|
|
692
|
+
<span class="cline-any cline-yes">9x</span>
|
|
693
|
+
<span class="cline-any cline-yes">9x</span>
|
|
694
|
+
<span class="cline-any cline-yes">9x</span>
|
|
695
|
+
<span class="cline-any cline-yes">9x</span>
|
|
696
|
+
<span class="cline-any cline-yes">9x</span>
|
|
697
|
+
<span class="cline-any cline-yes">9x</span>
|
|
698
|
+
<span class="cline-any cline-no"> </span>
|
|
699
|
+
<span class="cline-any cline-no"> </span>
|
|
700
|
+
<span class="cline-any cline-no"> </span>
|
|
701
|
+
<span class="cline-any cline-no"> </span>
|
|
702
|
+
<span class="cline-any cline-no"> </span>
|
|
703
|
+
<span class="cline-any cline-neutral"> </span>
|
|
704
|
+
<span class="cline-any cline-neutral"> </span>
|
|
705
|
+
<span class="cline-any cline-neutral"> </span>
|
|
706
|
+
<span class="cline-any cline-yes">9x</span>
|
|
707
|
+
<span class="cline-any cline-yes">9x</span>
|
|
708
|
+
<span class="cline-any cline-yes">9x</span>
|
|
709
|
+
<span class="cline-any cline-yes">9x</span>
|
|
710
|
+
<span class="cline-any cline-neutral"> </span>
|
|
711
|
+
<span class="cline-any cline-yes">9x</span>
|
|
712
|
+
<span class="cline-any cline-yes">9x</span>
|
|
713
|
+
<span class="cline-any cline-yes">9x</span>
|
|
714
|
+
<span class="cline-any cline-yes">9x</span>
|
|
715
|
+
<span class="cline-any cline-neutral"> </span>
|
|
716
|
+
<span class="cline-any cline-neutral"> </span>
|
|
717
|
+
<span class="cline-any cline-neutral"> </span>
|
|
718
|
+
<span class="cline-any cline-neutral"> </span>
|
|
719
|
+
<span class="cline-any cline-yes">9x</span>
|
|
720
|
+
<span class="cline-any cline-neutral"> </span>
|
|
721
|
+
<span class="cline-any cline-neutral"> </span>
|
|
722
|
+
<span class="cline-any cline-neutral"> </span>
|
|
723
|
+
<span class="cline-any cline-neutral"> </span>
|
|
724
|
+
<span class="cline-any cline-neutral"> </span>
|
|
725
|
+
<span class="cline-any cline-yes">9x</span>
|
|
726
|
+
<span class="cline-any cline-yes">7x</span>
|
|
727
|
+
<span class="cline-any cline-yes">7x</span>
|
|
728
|
+
<span class="cline-any cline-neutral"> </span>
|
|
729
|
+
<span class="cline-any cline-yes">9x</span>
|
|
730
|
+
<span class="cline-any cline-yes">9x</span>
|
|
731
|
+
<span class="cline-any cline-neutral"> </span>
|
|
732
|
+
<span class="cline-any cline-yes">9x</span>
|
|
733
|
+
<span class="cline-any cline-yes">1x</span>
|
|
734
|
+
<span class="cline-any cline-yes">1x</span>
|
|
735
|
+
<span class="cline-any cline-yes">1x</span>
|
|
736
|
+
<span class="cline-any cline-yes">1x</span>
|
|
737
|
+
<span class="cline-any cline-yes">1x</span>
|
|
738
|
+
<span class="cline-any cline-yes">1x</span>
|
|
739
|
+
<span class="cline-any cline-neutral"> </span>
|
|
740
|
+
<span class="cline-any cline-neutral"> </span>
|
|
741
|
+
<span class="cline-any cline-yes">1x</span>
|
|
742
|
+
<span class="cline-any cline-yes">1x</span>
|
|
743
|
+
<span class="cline-any cline-neutral"> </span>
|
|
744
|
+
<span class="cline-any cline-yes">1x</span>
|
|
745
|
+
<span class="cline-any cline-yes">1x</span>
|
|
746
|
+
<span class="cline-any cline-neutral"> </span>
|
|
747
|
+
<span class="cline-any cline-yes">1x</span>
|
|
748
|
+
<span class="cline-any cline-yes">1x</span>
|
|
749
|
+
<span class="cline-any cline-yes">1x</span>
|
|
750
|
+
<span class="cline-any cline-yes">1x</span>
|
|
751
|
+
<span class="cline-any cline-yes">1x</span>
|
|
752
|
+
<span class="cline-any cline-neutral"> </span>
|
|
753
|
+
<span class="cline-any cline-neutral"> </span>
|
|
754
|
+
<span class="cline-any cline-neutral"> </span>
|
|
755
|
+
<span class="cline-any cline-neutral"> </span>
|
|
756
|
+
<span class="cline-any cline-neutral"> </span>
|
|
757
|
+
<span class="cline-any cline-neutral"> </span>
|
|
758
|
+
<span class="cline-any cline-yes">9x</span>
|
|
759
|
+
<span class="cline-any cline-no"> </span>
|
|
760
|
+
<span class="cline-any cline-no"> </span>
|
|
761
|
+
<span class="cline-any cline-no"> </span>
|
|
762
|
+
<span class="cline-any cline-no"> </span>
|
|
763
|
+
<span class="cline-any cline-no"> </span>
|
|
764
|
+
<span class="cline-any cline-no"> </span>
|
|
765
|
+
<span class="cline-any cline-no"> </span>
|
|
766
|
+
<span class="cline-any cline-neutral"> </span>
|
|
767
|
+
<span class="cline-any cline-neutral"> </span>
|
|
768
|
+
<span class="cline-any cline-no"> </span>
|
|
769
|
+
<span class="cline-any cline-no"> </span>
|
|
770
|
+
<span class="cline-any cline-neutral"> </span>
|
|
771
|
+
<span class="cline-any cline-neutral"> </span>
|
|
772
|
+
<span class="cline-any cline-neutral"> </span>
|
|
773
|
+
<span class="cline-any cline-yes">9x</span>
|
|
774
|
+
<span class="cline-any cline-yes">12x</span>
|
|
775
|
+
<span class="cline-any cline-neutral"> </span>
|
|
776
|
+
<span class="cline-any cline-neutral"> </span>
|
|
777
|
+
<span class="cline-any cline-neutral"> </span>
|
|
778
|
+
<span class="cline-any cline-neutral"> </span>
|
|
779
|
+
<span class="cline-any cline-neutral"> </span>
|
|
780
|
+
<span class="cline-any cline-neutral"> </span>
|
|
781
|
+
<span class="cline-any cline-yes">12x</span>
|
|
782
|
+
<span class="cline-any cline-neutral"> </span>
|
|
783
|
+
<span class="cline-any cline-yes">12x</span>
|
|
784
|
+
<span class="cline-any cline-yes">12x</span>
|
|
785
|
+
<span class="cline-any cline-yes">3x</span>
|
|
786
|
+
<span class="cline-any cline-neutral"> </span>
|
|
787
|
+
<span class="cline-any cline-neutral"> </span>
|
|
788
|
+
<span class="cline-any cline-yes">3x</span>
|
|
789
|
+
<span class="cline-any cline-yes">3x</span>
|
|
790
|
+
<span class="cline-any cline-yes">3x</span>
|
|
791
|
+
<span class="cline-any cline-neutral"> </span>
|
|
792
|
+
<span class="cline-any cline-neutral"> </span>
|
|
793
|
+
<span class="cline-any cline-yes">3x</span>
|
|
794
|
+
<span class="cline-any cline-yes">3x</span>
|
|
795
|
+
<span class="cline-any cline-yes">3x</span>
|
|
796
|
+
<span class="cline-any cline-neutral"> </span>
|
|
797
|
+
<span class="cline-any cline-yes">12x</span>
|
|
798
|
+
<span class="cline-any cline-yes">12x</span>
|
|
799
|
+
<span class="cline-any cline-yes">12x</span>
|
|
800
|
+
<span class="cline-any cline-yes">12x</span>
|
|
801
|
+
<span class="cline-any cline-yes">12x</span>
|
|
802
|
+
<span class="cline-any cline-neutral"> </span>
|
|
803
|
+
<span class="cline-any cline-yes">12x</span>
|
|
804
|
+
<span class="cline-any cline-yes">1x</span>
|
|
805
|
+
<span class="cline-any cline-yes">1x</span>
|
|
806
|
+
<span class="cline-any cline-yes">1x</span>
|
|
807
|
+
<span class="cline-any cline-yes">1x</span>
|
|
808
|
+
<span class="cline-any cline-yes">1x</span>
|
|
809
|
+
<span class="cline-any cline-yes">1x</span>
|
|
810
|
+
<span class="cline-any cline-neutral"> </span>
|
|
811
|
+
<span class="cline-any cline-yes">11x</span>
|
|
812
|
+
<span class="cline-any cline-yes">11x</span>
|
|
813
|
+
<span class="cline-any cline-no"> </span>
|
|
814
|
+
<span class="cline-any cline-no"> </span>
|
|
815
|
+
<span class="cline-any cline-no"> </span>
|
|
816
|
+
<span class="cline-any cline-yes">11x</span>
|
|
817
|
+
<span class="cline-any cline-neutral"> </span>
|
|
818
|
+
<span class="cline-any cline-yes">11x</span>
|
|
819
|
+
<span class="cline-any cline-neutral"> </span>
|
|
820
|
+
<span class="cline-any cline-yes">11x</span>
|
|
821
|
+
<span class="cline-any cline-yes">11x</span>
|
|
822
|
+
<span class="cline-any cline-yes">11x</span>
|
|
823
|
+
<span class="cline-any cline-yes">11x</span>
|
|
824
|
+
<span class="cline-any cline-yes">11x</span>
|
|
825
|
+
<span class="cline-any cline-yes">12x</span>
|
|
826
|
+
<span class="cline-any cline-no"> </span>
|
|
827
|
+
<span class="cline-any cline-no"> </span>
|
|
828
|
+
<span class="cline-any cline-no"> </span>
|
|
829
|
+
<span class="cline-any cline-no"> </span>
|
|
830
|
+
<span class="cline-any cline-no"> </span>
|
|
831
|
+
<span class="cline-any cline-yes">12x</span>
|
|
832
|
+
<span class="cline-any cline-neutral"> </span>
|
|
833
|
+
<span class="cline-any cline-yes">12x</span>
|
|
834
|
+
<span class="cline-any cline-yes">12x</span>
|
|
835
|
+
<span class="cline-any cline-yes">12x</span>
|
|
836
|
+
<span class="cline-any cline-yes">12x</span>
|
|
837
|
+
<span class="cline-any cline-yes">12x</span>
|
|
838
|
+
<span class="cline-any cline-yes">12x</span>
|
|
839
|
+
<span class="cline-any cline-yes">12x</span>
|
|
840
|
+
<span class="cline-any cline-neutral"> </span>
|
|
841
|
+
<span class="cline-any cline-neutral"> </span>
|
|
842
|
+
<span class="cline-any cline-neutral"> </span>
|
|
843
|
+
<span class="cline-any cline-neutral"> </span>
|
|
844
|
+
<span class="cline-any cline-neutral"> </span>
|
|
845
|
+
<span class="cline-any cline-yes">9x</span>
|
|
846
|
+
<span class="cline-any cline-no"> </span>
|
|
847
|
+
<span class="cline-any cline-no"> </span>
|
|
848
|
+
<span class="cline-any cline-neutral"> </span>
|
|
849
|
+
<span class="cline-any cline-neutral"> </span>
|
|
850
|
+
<span class="cline-any cline-neutral"> </span>
|
|
851
|
+
<span class="cline-any cline-neutral"> </span>
|
|
852
|
+
<span class="cline-any cline-neutral"> </span>
|
|
853
|
+
<span class="cline-any cline-yes">9x</span>
|
|
854
|
+
<span class="cline-any cline-yes">2x</span>
|
|
855
|
+
<span class="cline-any cline-yes">2x</span>
|
|
856
|
+
<span class="cline-any cline-neutral"> </span>
|
|
857
|
+
<span class="cline-any cline-yes">2x</span>
|
|
858
|
+
<span class="cline-any cline-yes">2x</span>
|
|
859
|
+
<span class="cline-any cline-yes">2x</span>
|
|
860
|
+
<span class="cline-any cline-yes">2x</span>
|
|
861
|
+
<span class="cline-any cline-yes">2x</span>
|
|
862
|
+
<span class="cline-any cline-yes">2x</span>
|
|
863
|
+
<span class="cline-any cline-yes">2x</span>
|
|
864
|
+
<span class="cline-any cline-yes">2x</span>
|
|
865
|
+
<span class="cline-any cline-neutral"> </span>
|
|
866
|
+
<span class="cline-any cline-yes">2x</span>
|
|
867
|
+
<span class="cline-any cline-yes">1x</span>
|
|
868
|
+
<span class="cline-any cline-yes">1x</span>
|
|
869
|
+
<span class="cline-any cline-yes">1x</span>
|
|
870
|
+
<span class="cline-any cline-no"> </span>
|
|
871
|
+
<span class="cline-any cline-no"> </span>
|
|
872
|
+
<span class="cline-any cline-no"> </span>
|
|
873
|
+
<span class="cline-any cline-no"> </span>
|
|
874
|
+
<span class="cline-any cline-no"> </span>
|
|
875
|
+
<span class="cline-any cline-yes">1x</span>
|
|
876
|
+
<span class="cline-any cline-yes">1x</span>
|
|
877
|
+
<span class="cline-any cline-yes">2x</span>
|
|
878
|
+
<span class="cline-any cline-yes">2x</span>
|
|
879
|
+
<span class="cline-any cline-neutral"> </span>
|
|
880
|
+
<span class="cline-any cline-neutral"> </span>
|
|
881
|
+
<span class="cline-any cline-neutral"> </span>
|
|
882
|
+
<span class="cline-any cline-neutral"> </span>
|
|
883
|
+
<span class="cline-any cline-neutral"> </span>
|
|
884
|
+
<span class="cline-any cline-yes">9x</span>
|
|
885
|
+
<span class="cline-any cline-yes">7x</span>
|
|
886
|
+
<span class="cline-any cline-yes">7x</span>
|
|
887
|
+
<span class="cline-any cline-yes">7x</span>
|
|
888
|
+
<span class="cline-any cline-yes">7x</span>
|
|
889
|
+
<span class="cline-any cline-yes">7x</span>
|
|
890
|
+
<span class="cline-any cline-yes">7x</span>
|
|
891
|
+
<span class="cline-any cline-yes">7x</span>
|
|
892
|
+
<span class="cline-any cline-yes">2x</span>
|
|
893
|
+
<span class="cline-any cline-yes">2x</span>
|
|
894
|
+
<span class="cline-any cline-no"> </span>
|
|
895
|
+
<span class="cline-any cline-no"> </span>
|
|
896
|
+
<span class="cline-any cline-no"> </span>
|
|
897
|
+
<span class="cline-any cline-no"> </span>
|
|
898
|
+
<span class="cline-any cline-no"> </span>
|
|
899
|
+
<span class="cline-any cline-yes">7x</span>
|
|
900
|
+
<span class="cline-any cline-neutral"> </span>
|
|
901
|
+
<span class="cline-any cline-neutral"> </span>
|
|
902
|
+
<span class="cline-any cline-neutral"> </span>
|
|
903
|
+
<span class="cline-any cline-yes">9x</span>
|
|
904
|
+
<span class="cline-any cline-yes">11x</span>
|
|
905
|
+
<span class="cline-any cline-yes">11x</span>
|
|
906
|
+
<span class="cline-any cline-yes">11x</span>
|
|
907
|
+
<span class="cline-any cline-neutral"> </span>
|
|
908
|
+
<span class="cline-any cline-neutral"> </span>
|
|
909
|
+
<span class="cline-any cline-neutral"> </span>
|
|
910
|
+
<span class="cline-any cline-neutral"> </span>
|
|
911
|
+
<span class="cline-any cline-yes">9x</span>
|
|
912
|
+
<span class="cline-any cline-yes">11x</span>
|
|
913
|
+
<span class="cline-any cline-yes">10x</span>
|
|
914
|
+
<span class="cline-any cline-neutral"> </span>
|
|
915
|
+
<span class="cline-any cline-neutral"> </span>
|
|
916
|
+
<span class="cline-any cline-neutral"> </span>
|
|
917
|
+
<span class="cline-any cline-neutral"> </span>
|
|
918
|
+
<span class="cline-any cline-neutral"> </span>
|
|
919
|
+
<span class="cline-any cline-neutral"> </span>
|
|
920
|
+
<span class="cline-any cline-neutral"> </span>
|
|
921
|
+
<span class="cline-any cline-yes">10x</span>
|
|
922
|
+
<span class="cline-any cline-yes">10x</span>
|
|
923
|
+
<span class="cline-any cline-no"> </span>
|
|
924
|
+
<span class="cline-any cline-no"> </span>
|
|
925
|
+
<span class="cline-any cline-no"> </span>
|
|
926
|
+
<span class="cline-any cline-yes">10x</span>
|
|
927
|
+
<span class="cline-any cline-yes">1x</span>
|
|
928
|
+
<span class="cline-any cline-yes">1x</span>
|
|
929
|
+
<span class="cline-any cline-yes">6x</span>
|
|
930
|
+
<span class="cline-any cline-yes">10x</span>
|
|
931
|
+
<span class="cline-any cline-neutral"> </span>
|
|
932
|
+
<span class="cline-any cline-yes">11x</span>
|
|
933
|
+
<span class="cline-any cline-yes">3x</span>
|
|
934
|
+
<span class="cline-any cline-yes">2x</span>
|
|
935
|
+
<span class="cline-any cline-yes">2x</span>
|
|
936
|
+
<span class="cline-any cline-yes">1x</span>
|
|
937
|
+
<span class="cline-any cline-yes">1x</span>
|
|
938
|
+
<span class="cline-any cline-yes">8x</span>
|
|
939
|
+
<span class="cline-any cline-yes">11x</span>
|
|
940
|
+
<span class="cline-any cline-neutral"> </span>
|
|
941
|
+
<span class="cline-any cline-yes">9x</span>
|
|
942
|
+
<span class="cline-any cline-yes">1x</span>
|
|
943
|
+
<span class="cline-any cline-yes">1x</span>
|
|
944
|
+
<span class="cline-any cline-neutral"> </span>
|
|
945
|
+
<span class="cline-any cline-neutral"> </span>
|
|
946
|
+
<span class="cline-any cline-neutral"> </span>
|
|
947
|
+
<span class="cline-any cline-no"> </span>
|
|
948
|
+
<span class="cline-any cline-no"> </span>
|
|
949
|
+
<span class="cline-any cline-no"> </span>
|
|
950
|
+
<span class="cline-any cline-no"> </span>
|
|
951
|
+
<span class="cline-any cline-no"> </span>
|
|
952
|
+
<span class="cline-any cline-no"> </span>
|
|
953
|
+
<span class="cline-any cline-no"> </span>
|
|
954
|
+
<span class="cline-any cline-no"> </span>
|
|
955
|
+
<span class="cline-any cline-yes">1x</span>
|
|
956
|
+
<span class="cline-any cline-neutral"> </span>
|
|
957
|
+
<span class="cline-any cline-yes">9x</span>
|
|
958
|
+
<span class="cline-any cline-yes">10x</span>
|
|
959
|
+
<span class="cline-any cline-yes">10x</span>
|
|
960
|
+
<span class="cline-any cline-neutral"> </span>
|
|
961
|
+
<span class="cline-any cline-neutral"> </span>
|
|
962
|
+
<span class="cline-any cline-neutral"> </span>
|
|
963
|
+
<span class="cline-any cline-neutral"> </span>
|
|
964
|
+
<span class="cline-any cline-neutral"> </span>
|
|
965
|
+
<span class="cline-any cline-neutral"> </span>
|
|
966
|
+
<span class="cline-any cline-yes">9x</span>
|
|
967
|
+
<span class="cline-any cline-yes">9x</span>
|
|
968
|
+
<span class="cline-any cline-no"> </span>
|
|
969
|
+
<span class="cline-any cline-no"> </span>
|
|
970
|
+
<span class="cline-any cline-no"> </span>
|
|
971
|
+
<span class="cline-any cline-no"> </span>
|
|
972
|
+
<span class="cline-any cline-yes">9x</span>
|
|
973
|
+
<span class="cline-any cline-yes">9x</span>
|
|
974
|
+
<span class="cline-any cline-no"> </span>
|
|
975
|
+
<span class="cline-any cline-no"> </span>
|
|
976
|
+
<span class="cline-any cline-no"> </span>
|
|
977
|
+
<span class="cline-any cline-no"> </span>
|
|
978
|
+
<span class="cline-any cline-yes">9x</span>
|
|
979
|
+
<span class="cline-any cline-yes">9x</span>
|
|
980
|
+
<span class="cline-any cline-no"> </span>
|
|
981
|
+
<span class="cline-any cline-no"> </span>
|
|
982
|
+
<span class="cline-any cline-no"> </span>
|
|
983
|
+
<span class="cline-any cline-no"> </span>
|
|
984
|
+
<span class="cline-any cline-yes">9x</span>
|
|
985
|
+
<span class="cline-any cline-no"> </span>
|
|
986
|
+
<span class="cline-any cline-no"> </span>
|
|
987
|
+
<span class="cline-any cline-neutral"> </span>
|
|
988
|
+
<span class="cline-any cline-no"> </span>
|
|
989
|
+
<span class="cline-any cline-no"> </span>
|
|
990
|
+
<span class="cline-any cline-yes">9x</span>
|
|
991
|
+
<span class="cline-any cline-yes">9x</span>
|
|
992
|
+
<span class="cline-any cline-neutral"> </span></td><td class="text"><pre class="prettyprint lang-js">import type { SchemaStructure } from '@spooky-sync/query-builder';
|
|
993
|
+
import type { LocalStore, RemoteDatabaseService } from '../../services/database/index';
|
|
994
|
+
import type { Logger } from '../../services/logger/index';
|
|
995
|
+
import type { Uuid } from 'surrealdb';
|
|
996
|
+
import { CrdtField } from './crdt-field';
|
|
997
|
+
import { loadLoro } from './loro-loader';
|
|
998
|
+
import { parseRecordIdString } from '../../utils/index';
|
|
999
|
+
|
|
1000
|
+
export { CrdtField, cursorColorFromName, CURSOR_COLORS } from './crdt-field';
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* CrdtManager manages active CrdtField instances and their sync channels.
|
|
1004
|
+
*
|
|
1005
|
+
* Collaborative state lives in two dedicated tables (defined in
|
|
1006
|
+
* `apps/cli/src/meta_tables_remote.surql`):
|
|
1007
|
+
* - `_00_crdt` { record_id, field, state } — one row per (record, field)
|
|
1008
|
+
* - `_00_cursor` { record_id, session_id, field, state } — one row per
|
|
1009
|
+
* (record, session, field)
|
|
1010
|
+
*
|
|
1011
|
+
* Splitting them off the parent row is what makes offline edits mergeable:
|
|
1012
|
+
* each (record, field) gets its own row, so concurrent offline writes don't
|
|
1013
|
+
* collide on the parent's last-write-wins semantics.
|
|
1014
|
+
*
|
|
1015
|
+
* Cross-browser delivery still rides the parent table's existing LIVE feed
|
|
1016
|
+
* to avoid SurrealDB v3 LIVE bugs around dereference-based permission rules
|
|
1017
|
+
* (issues 3602, 4026). On every meta UPSERT the writer also bumps the
|
|
1018
|
+
* parent's `_00_rv` (a no-op assignment); that fires the parent's LIVE
|
|
1019
|
+
* feed, and the receiver pulls the matching `_00_crdt` / `_00_cursor` rows
|
|
1020
|
+
* via subquery. Permission inheritance happens server-side via
|
|
1021
|
+
* `record_id.id != NONE` (SELECT) and `fn::can_update_record` (UPDATE).
|
|
1022
|
+
*/
|
|
1023
|
+
export class CrdtManager {
|
|
1024
|
+
private fields = new Map<string, CrdtField>();
|
|
1025
|
+
// One LIVE subscription per parent table (e.g. "thread" → uuid).
|
|
1026
|
+
private liveByTable = new Map<string, Uuid>();
|
|
1027
|
+
// Coalesces concurrent first-time subscribes for the same table. Stamped with
|
|
1028
|
+
// the connection generation it started under, so a restart after a drop can
|
|
1029
|
+
// tell a reusable in-flight attempt from a doomed one.
|
|
1030
|
+
private pendingLive = new Map<string, { promise: Promise<void>; generation: number }>();
|
|
1031
|
+
// Tables whose LIVE died with a dropped socket, awaiting the next `connected`
|
|
1032
|
+
// to be re-subscribed. Held separately because `liveByTable` is cleared on the
|
|
1033
|
+
// drop (those uuids belong to a session that no longer exists).
|
|
1034
|
+
private staleTables = new Set<string>();
|
|
1035
|
+
// Bumped on every socket drop. A LIVE registration that started under an
|
|
1036
|
+
// older generation is discarded rather than recorded, so a drop landing
|
|
1037
|
+
// mid-registration can't leave a uuid from a dead session in `liveByTable`.
|
|
1038
|
+
private connectionGeneration = 0;
|
|
1039
|
+
private connectionUnsubscribes: Array<() => void> = [];
|
|
1040
|
+
private logger: Logger;
|
|
1041
|
+
// SurrealDB session id, used as the per-session key inside `_00_cursor`.
|
|
1042
|
+
private sessionId: string = '';
|
|
1043
|
+
|
|
1044
|
+
constructor(
|
|
1045
|
+
private schema: SchemaStructure,
|
|
1046
|
+
private local: LocalStore,
|
|
1047
|
+
private remote: RemoteDatabaseService,
|
|
1048
|
+
logger: Logger,
|
|
1049
|
+
private debounceMs: number = 500,
|
|
1050
|
+
) {
|
|
1051
|
+
this.logger = logger.child({ service: 'CrdtManager' });
|
|
1052
|
+
this.subscribeToReconnect();
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Re-establish table LIVEs after a socket drop.
|
|
1057
|
+
*
|
|
1058
|
+
* A LIVE subscription lives and dies with its WebSocket session, and
|
|
1059
|
+
* `ensureTableSubscription` is memoized on `liveByTable` — so without this,
|
|
1060
|
+
* the first reconnect leaves CRDT realtime permanently dead: the map still
|
|
1061
|
+
* holds a uuid for a subscription the server has forgotten, so every later
|
|
1062
|
+
* `open()` short-circuits and no LIVE is ever re-issued.
|
|
1063
|
+
*
|
|
1064
|
+
* Both drop events matter: the SDK publishes `reconnecting` (not
|
|
1065
|
+
* `disconnected`) when it intends to recover on its own, and `disconnected`
|
|
1066
|
+
* only once it has given up.
|
|
1067
|
+
*/
|
|
1068
|
+
private subscribeToReconnect(): void {
|
|
1069
|
+
const onDrop = () => {
|
|
1070
|
+
this.connectionGeneration++;
|
|
1071
|
+
for (const table of this.liveByTable.keys()) this.staleTables.add(table);
|
|
1072
|
+
// In-flight registrations count too: they'd otherwise complete against
|
|
1073
|
+
// the dead session and be silently dropped by the generation guard.
|
|
1074
|
+
for (const table of this.pendingLive.keys()) this.staleTables.add(table);
|
|
1075
|
+
// No KILL: the server-side subscriptions are already gone, and these
|
|
1076
|
+
// uuids would resolve against the new session.
|
|
1077
|
+
this.liveByTable.clear();
|
|
1078
|
+
};
|
|
1079
|
+
this.connectionUnsubscribes.push(
|
|
1080
|
+
this.remote.subscribeConnection('reconnecting', onDrop),
|
|
1081
|
+
this.remote.subscribeConnection('disconnected', onDrop),
|
|
1082
|
+
this.remote.subscribeConnection('connected', () => {
|
|
1083
|
+
const tables = Array.from(this.staleTables);
|
|
1084
|
+
this.staleTables.clear();
|
|
1085
|
+
for (const table of tables) {
|
|
1086
|
+
// Only resurrect tables that still have an open field — a drop can
|
|
1087
|
+
// outlive the editor that needed the feed.
|
|
1088
|
+
if (!this.hasOpenFieldFor(table)) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
|
|
1089
|
+
void this.ensureTableSubscription(table);
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** Stop observing transport events. Separate from {@link closeAll}, which also
|
|
1096
|
+
* runs on a bucket switch where the manager keeps being used. */
|
|
1097
|
+
dispose(): void {
|
|
1098
|
+
for (const off of this.connectionUnsubscribes) {
|
|
1099
|
+
try {
|
|
1100
|
+
off();
|
|
1101
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch {</span>
|
|
1102
|
+
/* ignore */
|
|
1103
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1104
|
+
}
|
|
1105
|
+
this.connectionUnsubscribes = [];
|
|
1106
|
+
this.staleTables.clear();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
private hasOpenFieldFor(table: string): boolean {
|
|
1110
|
+
const prefix = `${table}:`;
|
|
1111
|
+
return Array.from(this.fields.keys()).some((k) => k.startsWith(prefix));
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/** Set the session id that scopes this client's cursor entries. Must be
|
|
1115
|
+
* called before `open()` for cursors to be pushed under a stable key.
|
|
1116
|
+
* Passed in from `sp00ky.ts` at boot (it already fetches `session::id()`
|
|
1117
|
+
* for the data-module salt). */
|
|
1118
|
+
setSessionId(sessionId: string): void {
|
|
1119
|
+
this.sessionId = sessionId;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Open a CRDT field for collaborative editing.
|
|
1124
|
+
*
|
|
1125
|
+
* @param table - Table name
|
|
1126
|
+
* @param recordId - Full record ID (e.g., "thread:abc")
|
|
1127
|
+
* @param field - Field name (e.g., "title", "content")
|
|
1128
|
+
* @param fallbackText - Current plain text from the record, used to seed the
|
|
1129
|
+
* LoroDoc if no CRDT state exists yet (migration path)
|
|
1130
|
+
*/
|
|
1131
|
+
async open(
|
|
1132
|
+
table: string,
|
|
1133
|
+
recordId: string,
|
|
1134
|
+
field: string,
|
|
1135
|
+
fallbackText?: string,
|
|
1136
|
+
): Promise<CrdtField> {
|
|
1137
|
+
this.assertCrdtField(table, field);
|
|
1138
|
+
const cursorsEnabled = this.fieldHasCursor(table, field);
|
|
1139
|
+
const key = this.makeKey(table, recordId, field);
|
|
1140
|
+
let crdtField = this.fields.get(key);
|
|
1141
|
+
|
|
1142
|
+
if (crdtField) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1143
|
+
<span class="cstat-no" title="statement not covered" > return crdtField;</span>
|
|
1144
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1145
|
+
|
|
1146
|
+
// Read the snapshot directly off the parent row. `@crdt`-only fields
|
|
1147
|
+
// hold the base64 snapshot inline; `@crdt @cursor` fields hold a
|
|
1148
|
+
// `{ state, cursors }` object so we drill into `.state`. The query
|
|
1149
|
+
// always selects the local row — sync-down already populated it
|
|
1150
|
+
// (the snapshot is a column on the parent, not a sidecar table) so
|
|
1151
|
+
// there is no separate fetch on the happy path.
|
|
1152
|
+
let initialCrdtState: Uint8Array | undefined;
|
|
1153
|
+
try {
|
|
1154
|
+
const [result] = await this.local.query<[unknown]>(
|
|
1155
|
+
`SELECT VALUE ${field} FROM ONLY $id`,
|
|
1156
|
+
{ id: parseRecordIdString(recordId) },
|
|
1157
|
+
);
|
|
1158
|
+
const snapshot = this.extractSnapshot(result, cursorsEnabled);
|
|
1159
|
+
if (snapshot) initialCrdtState = snapshot;
|
|
1160
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
1161
|
+
<span class="cstat-no" title="statement not covered" > this.logger.info(</span>
|
|
1162
|
+
<span class="cstat-no" title="statement not covered" > { error: String(e), recordId, field, Category: 'sp00ky-client::CrdtManager::open' },</span>
|
|
1163
|
+
<span class="cstat-no" title="statement not covered" > 'No existing CRDT state found in local cache (continuing with empty doc)'</span>
|
|
1164
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1165
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1166
|
+
|
|
1167
|
+
// Load loro lazily (usually already resolved — the client preloads it at
|
|
1168
|
+
// startup when `config.crdt` is on).
|
|
1169
|
+
const { LoroDoc } = await loadLoro();
|
|
1170
|
+
crdtField = new CrdtField(field, cursorsEnabled, LoroDoc, initialCrdtState, this.logger);
|
|
1171
|
+
crdtField.startSync(this.local, this.remote, recordId, this.sessionId, this.debounceMs);
|
|
1172
|
+
this.fields.set(key, crdtField);
|
|
1173
|
+
|
|
1174
|
+
this.logger.info(
|
|
1175
|
+
{ key, hasInitialState: !!initialCrdtState, hasFallback: !!fallbackText, Category: 'sp00ky-client::CrdtManager::open' },
|
|
1176
|
+
'CrdtField opened'
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
// Fire-and-forget: the LIVE subscription receives *future* updates;
|
|
1180
|
+
// the initial snapshot is already in hand. `ensureTableSubscription`
|
|
1181
|
+
// coalesces concurrent calls via `pendingLive`, so this is safe.
|
|
1182
|
+
void this.ensureTableSubscription(table);
|
|
1183
|
+
|
|
1184
|
+
// Local was empty — a fresh device, a memory-backed local DB after
|
|
1185
|
+
// reload, or a record that hasn't been sync'd locally yet. Pull the
|
|
1186
|
+
// parent row from remote and dispatch its CRDT field; otherwise the
|
|
1187
|
+
// editor sits empty until the parent's LIVE feed happens to fire.
|
|
1188
|
+
if (!initialCrdtState) {
|
|
1189
|
+
void this.fetchAndDispatchRow(table, recordId);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
return crdtField;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
close(table: string, recordId: string, field: string): void {
|
|
1196
|
+
const key = this.makeKey(table, recordId, field);
|
|
1197
|
+
const crdtField = this.fields.get(key);
|
|
1198
|
+
if (crdtField) {
|
|
1199
|
+
crdtField.stopSync();
|
|
1200
|
+
this.fields.delete(key);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// If no fields on this table remain open, tear down the table-wide LIVE.
|
|
1204
|
+
if (!this.hasOpenFieldFor(table)) {
|
|
1205
|
+
this.killTableSubscription(table);
|
|
1206
|
+
// Also drop any pending post-reconnect restart for it.
|
|
1207
|
+
this.staleTables.delete(table);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
this.logger.debug(
|
|
1211
|
+
{ key, Category: 'sp00ky-client::CrdtManager::close' },
|
|
1212
|
+
'CrdtField closed'
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Close every open field + table LIVE. Fields flush a final remote push by
|
|
1218
|
+
* default; pass `{ flush: false }` on a bucket switch, where that flush
|
|
1219
|
+
* would push the previous user's snapshot under the next user's session.
|
|
1220
|
+
*/
|
|
1221
|
+
<span class="fstat-no" title="function not covered" > closeAll(options: { flush?: boolean } = {}): void {</span>
|
|
1222
|
+
<span class="cstat-no" title="statement not covered" > for (const [_, field] of this.fields) {</span>
|
|
1223
|
+
<span class="cstat-no" title="statement not covered" > field.stopSync(options);</span>
|
|
1224
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1225
|
+
<span class="cstat-no" title="statement not covered" > this.fields.clear();</span>
|
|
1226
|
+
<span class="cstat-no" title="statement not covered" > for (const table of Array.from(this.liveByTable.keys())) {</span>
|
|
1227
|
+
<span class="cstat-no" title="statement not covered" > this.killTableSubscription(table);</span>
|
|
1228
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1229
|
+
// Nothing is open any more, so nothing should be resurrected on the next
|
|
1230
|
+
// reconnect; `open()` will re-subscribe on demand.
|
|
1231
|
+
<span class="cstat-no" title="statement not covered" > this.staleTables.clear();</span>
|
|
1232
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1233
|
+
|
|
1234
|
+
/** Ensure a single `LIVE SELECT * FROM <table>` is running, shared across
|
|
1235
|
+
* every open CrdtField on `table`. */
|
|
1236
|
+
private async ensureTableSubscription(table: string): Promise<void> {
|
|
1237
|
+
if (this.liveByTable.has(table)) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
1238
|
+
|
|
1239
|
+
// Registering a LIVE takes two round-trips, so a socket drop can land
|
|
1240
|
+
// mid-flight. The generation stamp is what keeps that honest: a registration
|
|
1241
|
+
// that started before the drop must never record its uuid, because this
|
|
1242
|
+
// method short-circuits on `liveByTable` and that stale entry (pointing at a
|
|
1243
|
+
// session the server has forgotten) would block every future restart.
|
|
1244
|
+
const generation = this.connectionGeneration;
|
|
1245
|
+
|
|
1246
|
+
const pending = this.pendingLive.get(table);
|
|
1247
|
+
if (pending) {
|
|
1248
|
+
if (pending.generation === generation) <span class="branch-0 cbranch-no" title="branch not covered" >return pending.promise;</span>
|
|
1249
|
+
// A registration from before the drop is still settling. It will discard
|
|
1250
|
+
// itself; wait it out, then register fresh on the new socket.
|
|
1251
|
+
await pending.promise.catch(() => {});
|
|
1252
|
+
if (this.liveByTable.has(table)) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
1253
|
+
if (generation !== this.connectionGeneration) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
1254
|
+
// Another caller may have won the race to re-register during that await;
|
|
1255
|
+
// join it rather than opening a second LIVE on the same table.
|
|
1256
|
+
const successor = this.pendingLive.get(table);
|
|
1257
|
+
if (<span class="branch-0 cbranch-no" title="branch not covered" >successor && successor.generation === generation) <span class="branch-0 cbranch-no" title="branch not covered" >r</span>eturn successor.promise;</span>
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const start = (async () => {
|
|
1261
|
+
try {
|
|
1262
|
+
const [uuid] = await this.remote.query<[Uuid]>(
|
|
1263
|
+
`LIVE SELECT * FROM ${table}`,
|
|
1264
|
+
);
|
|
1265
|
+
|
|
1266
|
+
if (generation !== this.connectionGeneration) {
|
|
1267
|
+
this.logger.debug(
|
|
1268
|
+
{ table, Category: 'sp00ky-client::CrdtManager::ensureTableSubscription' },
|
|
1269
|
+
'Socket dropped while registering LIVE; discarding it'
|
|
1270
|
+
);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const subscription = await this.remote.getClient().liveOf(uuid);
|
|
1275
|
+
subscription.subscribe((message) => {
|
|
1276
|
+
<span class="cstat-no" title="statement not covered" > if (message.action === 'KILLED') return;</span>
|
|
1277
|
+
<span class="cstat-no" title="statement not covered" > if (message.action !== 'CREATE' && message.action !== 'UPDATE') return;</span>
|
|
1278
|
+
<span class="cstat-no" title="statement not covered" > this.dispatchRow(table, message.value as Record<string, unknown>);</span>
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
if (generation !== this.connectionGeneration) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
1282
|
+
|
|
1283
|
+
this.liveByTable.set(table, uuid);
|
|
1284
|
+
this.logger.info(
|
|
1285
|
+
{ table, Category: 'sp00ky-client::CrdtManager::ensureTableSubscription' },
|
|
1286
|
+
'LIVE SELECT started'
|
|
1287
|
+
);
|
|
1288
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
1289
|
+
<span class="cstat-no" title="statement not covered" > this.logger.warn(</span>
|
|
1290
|
+
<span class="cstat-no" title="statement not covered" > { error: e, table, Category: 'sp00ky-client::CrdtManager::ensureTableSubscription' },</span>
|
|
1291
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to start LIVE SELECT'</span>
|
|
1292
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1293
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1294
|
+
})();
|
|
1295
|
+
|
|
1296
|
+
this.pendingLive.set(table, { promise: start, generation });
|
|
1297
|
+
try {
|
|
1298
|
+
await start;
|
|
1299
|
+
} finally {
|
|
1300
|
+
this.pendingLive.delete(table);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/** Apply a parent-row payload from a non-LIVE source (e.g. the
|
|
1305
|
+
* list_ref-driven sync engine, when the cross-user LIVE on the
|
|
1306
|
+
* parent table is filtered out by the SurrealDB cross-session
|
|
1307
|
+
* permission gap). Same semantics as the internal `dispatchRow`. */
|
|
1308
|
+
<span class="fstat-no" title="function not covered" > applyRow(table: string, row: Record<string, unknown>): void {</span>
|
|
1309
|
+
<span class="cstat-no" title="statement not covered" > this.dispatchRow(table, row);</span>
|
|
1310
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1311
|
+
|
|
1312
|
+
/** Dispatch a parent-row LIVE event to every open CrdtField on that
|
|
1313
|
+
* record. Each open field reads its slice of the row directly — the
|
|
1314
|
+
* CRDT snapshot is a column on the parent now, so there is no
|
|
1315
|
+
* follow-up subquery. */
|
|
1316
|
+
private dispatchRow(table: string, row: Record<string, unknown>): void {
|
|
1317
|
+
const id = row.id != null ? String(row.id<span class="branch-0 cbranch-no" title="branch not covered" >) : '';</span>
|
|
1318
|
+
if (!id) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
|
|
1319
|
+
|
|
1320
|
+
const rowKeyPrefix = `${table}:${id}:`;
|
|
1321
|
+
for (const [key, crdtField] of this.fields) {
|
|
1322
|
+
if (!key.startsWith(rowKeyPrefix)) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
|
|
1323
|
+
const fieldName = key.slice(rowKeyPrefix.length);
|
|
1324
|
+
const cursorsEnabled = this.fieldHasCursor(table, fieldName);
|
|
1325
|
+
const slice = row[fieldName];
|
|
1326
|
+
const snapshot = this.extractSnapshot(slice, cursorsEnabled);
|
|
1327
|
+
if (snapshot) crdtField.importRemote(snapshot);
|
|
1328
|
+
|
|
1329
|
+
if (cursorsEnabled && slice && typeof slice === 'object') {
|
|
1330
|
+
const cursors = (slice as { cursors?: unknown }).cursors;
|
|
1331
|
+
if (cursors && typeof cursors === 'object') {
|
|
1332
|
+
for (const [sid, blob] of Object.entries(cursors as Record<string, unknown>)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1333
|
+
<span class="cstat-no" title="statement not covered" > if (sid === this.sessionId) continue;</span>
|
|
1334
|
+
<span class="cstat-no" title="statement not covered" > if (typeof blob === 'string' && blob.length > 0) {</span>
|
|
1335
|
+
<span class="cstat-no" title="statement not covered" > crdtField.importRemoteCursor(blob);</span>
|
|
1336
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1337
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/** One-shot remote fetch for a row whose CRDT field hasn't synced
|
|
1344
|
+
* locally yet (fresh device, memory-backed local DB after reload, …).
|
|
1345
|
+
* Used by `open()` when the local read came up empty. Subsequent
|
|
1346
|
+
* cross-browser updates ride `dispatchRow` via the parent LIVE feed. */
|
|
1347
|
+
private async fetchAndDispatchRow(table: string, id: string): Promise<void> {
|
|
1348
|
+
try {
|
|
1349
|
+
const recordId = parseRecordIdString(id);
|
|
1350
|
+
const [row] = await this.remote.query<[Record<string, unknown> | null]>(
|
|
1351
|
+
`SELECT * FROM ONLY $id`,
|
|
1352
|
+
{ id: recordId },
|
|
1353
|
+
);
|
|
1354
|
+
if (!row || typeof row !== 'object') return;
|
|
1355
|
+
this.dispatchRow(table, row as Record<string, unknown>);
|
|
1356
|
+
<span class="branch-0 cbranch-no" title="branch not covered" > } catch (e) {</span>
|
|
1357
|
+
<span class="cstat-no" title="statement not covered" > this.logger.warn(</span>
|
|
1358
|
+
<span class="cstat-no" title="statement not covered" > { error: e, table, id, Category: 'sp00ky-client::CrdtManager::fetchAndDispatchRow' },</span>
|
|
1359
|
+
<span class="cstat-no" title="statement not covered" > 'Failed to fetch parent row for CRDT hydration'</span>
|
|
1360
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1361
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/** Schema lookup: does `<table>.<field>` carry a `@cursor` annotation?
|
|
1365
|
+
* Determines the on-disk shape (plain snapshot vs. `{ state, cursors }`). */
|
|
1366
|
+
private fieldHasCursor(table: string, field: string): boolean {
|
|
1367
|
+
const tableSchema = this.schema.tables.find((t) => t.name === table);
|
|
1368
|
+
return !!tableSchema?.columns[field]?.cursor;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/** Pull the LoroDoc snapshot bytes out of a row slice. For `@crdt`-only
|
|
1372
|
+
* the slice IS the snapshot (Uint8Array); for `@crdt @cursor` it's
|
|
1373
|
+
* `{ state, cursors }` where `state` carries the snapshot bytes. */
|
|
1374
|
+
private extractSnapshot(value: unknown, cursorsEnabled: boolean): Uint8Array | undefined {
|
|
1375
|
+
const asBytes = (v: unknown): Uint8Array | undefined => {
|
|
1376
|
+
if (v instanceof Uint8Array) return v.length > 0 ? <span class="branch-0 cbranch-no" title="branch not covered" >v : undefined;</span>
|
|
1377
|
+
// SurrealDB ferries bytes through several shapes depending on
|
|
1378
|
+
// transport and on whether the field is a top-level `bytes` column
|
|
1379
|
+
// or bytes nested inside a FLEXIBLE object. Round-tripping bytes
|
|
1380
|
+
// through `option<object> FLEXIBLE` (the `@crdt @cursor` shape) in
|
|
1381
|
+
// particular comes back as a plain `number[]` from the local WASM
|
|
1382
|
+
// DB and as `Uint8Array` from the remote WS engine. Normalize all
|
|
1383
|
+
// recognized variants here so the receiving CrdtField doesn't care.
|
|
1384
|
+
if (v instanceof ArrayBuffer) <span class="branch-0 cbranch-no" title="branch not covered" >return new Uint8Array(v);</span>
|
|
1385
|
+
if (ArrayBuffer.isView(v)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1386
|
+
<span class="cstat-no" title="statement not covered" > const view = v as ArrayBufferView;</span>
|
|
1387
|
+
<span class="cstat-no" title="statement not covered" > return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);</span>
|
|
1388
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1389
|
+
if (Array.isArray(v) && v.length > 0 && v.every((n) => typeof n === 'number')) {
|
|
1390
|
+
return Uint8Array.from(v as number[]);
|
|
1391
|
+
}
|
|
1392
|
+
return undefined;
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
if (cursorsEnabled) {
|
|
1396
|
+
if (value && typeof value === 'object' && !(value instanceof Uint8Array)) {
|
|
1397
|
+
return asBytes((value as { state?: unknown }).state);
|
|
1398
|
+
}
|
|
1399
|
+
return undefined;
|
|
1400
|
+
}
|
|
1401
|
+
return asBytes(value);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
private killTableSubscription(table: string): void {
|
|
1405
|
+
const uuid = this.liveByTable.get(table);
|
|
1406
|
+
if (uuid) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1407
|
+
// We're tearing down: KILL on an already-dead/closed LIVE throws, and the
|
|
1408
|
+
// subscription may have ended on its own, so the failure is expected and
|
|
1409
|
+
// safe to drop.
|
|
1410
|
+
<span class="cstat-no" title="statement not covered" > this.remote.query('KILL $uuid', { uuid }).catch((err) => {</span>
|
|
1411
|
+
<span class="cstat-no" title="statement not covered" > this.logger.debug(</span>
|
|
1412
|
+
<span class="cstat-no" title="statement not covered" > { err, table, Category: 'sp00ky-client::CrdtManager::killTableSubscription' },</span>
|
|
1413
|
+
<span class="cstat-no" title="statement not covered" > 'KILL of table LIVE failed (already closed?)'</span>
|
|
1414
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1415
|
+
<span class="cstat-no" title="statement not covered" > });</span>
|
|
1416
|
+
<span class="cstat-no" title="statement not covered" > this.liveByTable.delete(table);</span>
|
|
1417
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
private makeKey(table: string, recordId: string, field: string): string {
|
|
1421
|
+
return `${table}:${recordId}:${field}`;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* Throws if `<table>.<field>` is not annotated `@crdt` in the schema. Catches
|
|
1426
|
+
* typos, removed annotations, and stale schema codegen at the call site instead
|
|
1427
|
+
* of silently producing a non-CRDT writer.
|
|
1428
|
+
*/
|
|
1429
|
+
private assertCrdtField(table: string, field: string): void {
|
|
1430
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1431
|
+
<span class="cstat-no" title="statement not covered" > throw new Error(</span>
|
|
1432
|
+
<span class="cstat-no" title="statement not covered" > `openCrdtField: refusing unsafe field identifier '${field}' — must match [a-zA-Z_][a-zA-Z0-9_]*`</span>
|
|
1433
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1434
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1435
|
+
const tableSchema = this.schema.tables.find((t) => t.name === table);
|
|
1436
|
+
if (!tableSchema) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1437
|
+
<span class="cstat-no" title="statement not covered" > throw new Error(</span>
|
|
1438
|
+
<span class="cstat-no" title="statement not covered" > `openCrdtField: unknown table '${table}'. Available: ${this.schema.tables.map((t) => t.name).join(', ')}`</span>
|
|
1439
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1440
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1441
|
+
const column = tableSchema.columns[field];
|
|
1442
|
+
if (!column) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1443
|
+
<span class="cstat-no" title="statement not covered" > throw new Error(</span>
|
|
1444
|
+
<span class="cstat-no" title="statement not covered" > `openCrdtField: '${table}.${field}' is not in the schema. Available fields: ${Object.keys(tableSchema.columns).join(', ')}`</span>
|
|
1445
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1446
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1447
|
+
if (!column.crdt) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
|
|
1448
|
+
<span class="cstat-no" title="statement not covered" > throw new Error(</span>
|
|
1449
|
+
<span class="cstat-no" title="statement not covered" > `openCrdtField: '${table}.${field}' is not annotated '@crdt' in the schema. ` +</span>
|
|
1450
|
+
`Add '-- @crdt text' above the field's DEFINE FIELD and regenerate the client schema.`
|
|
1451
|
+
<span class="cstat-no" title="statement not covered" > );</span>
|
|
1452
|
+
<span class="cstat-no" title="statement not covered" > }</span>
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
</pre></td></tr></table></pre>
|
|
1456
|
+
|
|
1457
|
+
<div class='push'></div><!-- for sticky footer -->
|
|
1458
|
+
</div><!-- /wrapper -->
|
|
1459
|
+
<div class='footer quiet pad2 space-top1 center small'>
|
|
1460
|
+
Code coverage generated by
|
|
1461
|
+
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
1462
|
+
at 2026-09-09T09:49:51.780Z
|
|
1463
|
+
</div>
|
|
1464
|
+
<script src="../../../prettify.js"></script>
|
|
1465
|
+
<script>
|
|
1466
|
+
window.onload = function () {
|
|
1467
|
+
prettyPrint();
|
|
1468
|
+
};
|
|
1469
|
+
</script>
|
|
1470
|
+
<script src="../../../sorter.js"></script>
|
|
1471
|
+
<script src="../../../block-navigation.js"></script>
|
|
1472
|
+
</body>
|
|
1473
|
+
</html>
|
|
1474
|
+
|