@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.
Files changed (139) hide show
  1. package/coverage/lcov-report/base.css +224 -0
  2. package/coverage/lcov-report/block-navigation.js +87 -0
  3. package/coverage/lcov-report/favicon.png +0 -0
  4. package/coverage/lcov-report/index.html +476 -0
  5. package/coverage/lcov-report/prettify.css +1 -0
  6. package/coverage/lcov-report/prettify.js +2 -0
  7. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  8. package/coverage/lcov-report/sorter.js +210 -0
  9. package/coverage/lcov-report/src/boot/auth-flip.saga.ts.html +271 -0
  10. package/coverage/lcov-report/src/boot/boot.saga.ts.html +469 -0
  11. package/coverage/lcov-report/src/boot/bucket-switch.saga.ts.html +430 -0
  12. package/coverage/lcov-report/src/boot/index.html +161 -0
  13. package/coverage/lcov-report/src/boot/preload.saga.ts.html +178 -0
  14. package/coverage/lcov-report/src/client/bucket-handle.ts.html +766 -0
  15. package/coverage/lcov-report/src/client/index.html +161 -0
  16. package/coverage/lcov-report/src/client/router.ts.html +367 -0
  17. package/coverage/lcov-report/src/client/runtime.ts.html +940 -0
  18. package/coverage/lcov-report/src/client/sp00ky-client.ts.html +1531 -0
  19. package/coverage/lcov-report/src/events/index.html +116 -0
  20. package/coverage/lcov-report/src/events/index.ts.html +877 -0
  21. package/coverage/lcov-report/src/index.html +146 -0
  22. package/coverage/lcov-report/src/index.ts.html +202 -0
  23. package/coverage/lcov-report/src/kernel/constants.ts.html +247 -0
  24. package/coverage/lcov-report/src/kernel/effects.ts.html +562 -0
  25. package/coverage/lcov-report/src/kernel/events.ts.html +250 -0
  26. package/coverage/lcov-report/src/kernel/index.html +176 -0
  27. package/coverage/lcov-report/src/kernel/interpreter.ts.html +514 -0
  28. package/coverage/lcov-report/src/kernel/saga.ts.html +304 -0
  29. package/coverage/lcov-report/src/modules/app-release/index.html +116 -0
  30. package/coverage/lcov-report/src/modules/app-release/index.ts.html +712 -0
  31. package/coverage/lcov-report/src/modules/auth/events/index.html +116 -0
  32. package/coverage/lcov-report/src/modules/auth/events/index.ts.html +142 -0
  33. package/coverage/lcov-report/src/modules/auth/index.html +116 -0
  34. package/coverage/lcov-report/src/modules/auth/index.ts.html +1225 -0
  35. package/coverage/lcov-report/src/modules/crdt/crdt-field.ts.html +967 -0
  36. package/coverage/lcov-report/src/modules/crdt/index.html +146 -0
  37. package/coverage/lcov-report/src/modules/crdt/index.ts.html +1474 -0
  38. package/coverage/lcov-report/src/modules/crdt/loro-loader.ts.html +160 -0
  39. package/coverage/lcov-report/src/modules/devtools/flags.ts.html +1132 -0
  40. package/coverage/lcov-report/src/modules/devtools/index.html +161 -0
  41. package/coverage/lcov-report/src/modules/devtools/index.ts.html +2413 -0
  42. package/coverage/lcov-report/src/modules/devtools/storage-info.ts.html +589 -0
  43. package/coverage/lcov-report/src/modules/devtools/versions.ts.html +415 -0
  44. package/coverage/lcov-report/src/modules/feature-flag/index.html +116 -0
  45. package/coverage/lcov-report/src/modules/feature-flag/index.ts.html +1033 -0
  46. package/coverage/lcov-report/src/modules/index.html +116 -0
  47. package/coverage/lcov-report/src/modules/ref-tables.ts.html +349 -0
  48. package/coverage/lcov-report/src/mutation/index.html +191 -0
  49. package/coverage/lcov-report/src/mutation/jobs.ts.html +208 -0
  50. package/coverage/lcov-report/src/mutation/mutation-id.ts.html +190 -0
  51. package/coverage/lcov-report/src/mutation/push.saga.ts.html +730 -0
  52. package/coverage/lcov-report/src/mutation/rows.ts.html +1216 -0
  53. package/coverage/lcov-report/src/mutation/tray.saga.ts.html +229 -0
  54. package/coverage/lcov-report/src/mutation/write.saga.ts.html +583 -0
  55. package/coverage/lcov-report/src/otel/index.html +116 -0
  56. package/coverage/lcov-report/src/otel/index.ts.html +466 -0
  57. package/coverage/lcov-report/src/query/env.ts.html +253 -0
  58. package/coverage/lcov-report/src/query/fetch.saga.ts.html +412 -0
  59. package/coverage/lcov-report/src/query/hash.ts.html +139 -0
  60. package/coverage/lcov-report/src/query/index.html +281 -0
  61. package/coverage/lcov-report/src/query/lifecycle.saga.ts.html +388 -0
  62. package/coverage/lcov-report/src/query/materialize.saga.ts.html +310 -0
  63. package/coverage/lcov-report/src/query/materialize.ts.html +217 -0
  64. package/coverage/lcov-report/src/query/membership.saga.ts.html +709 -0
  65. package/coverage/lcov-report/src/query/membership.ts.html +559 -0
  66. package/coverage/lcov-report/src/query/register.saga.ts.html +655 -0
  67. package/coverage/lcov-report/src/query/render-set.ts.html +265 -0
  68. package/coverage/lcov-report/src/query/sql.ts.html +322 -0
  69. package/coverage/lcov-report/src/query/window-query.ts.html +670 -0
  70. package/coverage/lcov-report/src/services/blobs/blob-cache.ts.html +1894 -0
  71. package/coverage/lcov-report/src/services/blobs/blob-manifest.ts.html +766 -0
  72. package/coverage/lcov-report/src/services/blobs/blob-store.ts.html +1162 -0
  73. package/coverage/lcov-report/src/services/blobs/index.html +161 -0
  74. package/coverage/lcov-report/src/services/blobs/index.ts.html +295 -0
  75. package/coverage/lcov-report/src/services/database/cache-engine.ts.html +664 -0
  76. package/coverage/lcov-report/src/services/database/connection-supervisor.ts.html +1330 -0
  77. package/coverage/lcov-report/src/services/database/database.ts.html +658 -0
  78. package/coverage/lcov-report/src/services/database/engine-factory.ts.html +184 -0
  79. package/coverage/lcov-report/src/services/database/errors.ts.html +187 -0
  80. package/coverage/lcov-report/src/services/database/events/index.html +116 -0
  81. package/coverage/lcov-report/src/services/database/events/index.ts.html +184 -0
  82. package/coverage/lcov-report/src/services/database/index.html +386 -0
  83. package/coverage/lcov-report/src/services/database/index.ts.html +121 -0
  84. package/coverage/lcov-report/src/services/database/local-migrator.ts.html +703 -0
  85. package/coverage/lcov-report/src/services/database/local.ts.html +1633 -0
  86. package/coverage/lcov-report/src/services/database/plan-render.ts.html +409 -0
  87. package/coverage/lcov-report/src/services/database/relation-resolver.ts.html +682 -0
  88. package/coverage/lcov-report/src/services/database/remote.ts.html +1036 -0
  89. package/coverage/lcov-report/src/services/database/sqlite-cache-engine.ts.html +4132 -0
  90. package/coverage/lcov-report/src/services/database/sqlite-lock-verify.ts.html +220 -0
  91. package/coverage/lcov-report/src/services/database/sqlite-open.ts.html +577 -0
  92. package/coverage/lcov-report/src/services/database/sqlite-plan-sql.ts.html +499 -0
  93. package/coverage/lcov-report/src/services/database/sqlite-select.ts.html +478 -0
  94. package/coverage/lcov-report/src/services/database/sqlite-transport.ts.html +757 -0
  95. package/coverage/lcov-report/src/services/database/surql-translate.ts.html +1333 -0
  96. package/coverage/lcov-report/src/services/database/surreal-cache-engine.ts.html +568 -0
  97. package/coverage/lcov-report/src/services/logger/index.html +116 -0
  98. package/coverage/lcov-report/src/services/logger/index.ts.html +154 -0
  99. package/coverage/lcov-report/src/services/persistence/index.html +146 -0
  100. package/coverage/lcov-report/src/services/persistence/localstorage.ts.html +163 -0
  101. package/coverage/lcov-report/src/services/persistence/resilient.ts.html +208 -0
  102. package/coverage/lcov-report/src/services/persistence/surrealdb.ts.html +271 -0
  103. package/coverage/lcov-report/src/services/stream-processor/index.html +146 -0
  104. package/coverage/lcov-report/src/services/stream-processor/index.ts.html +3313 -0
  105. package/coverage/lcov-report/src/services/stream-processor/permissions.ts.html +244 -0
  106. package/coverage/lcov-report/src/services/stream-processor/wasm-types.ts.html +346 -0
  107. package/coverage/lcov-report/src/services/tabs/broker-client.ts.html +934 -0
  108. package/coverage/lcov-report/src/services/tabs/coordinator.ts.html +1984 -0
  109. package/coverage/lcov-report/src/services/tabs/index.html +191 -0
  110. package/coverage/lcov-report/src/services/tabs/leader-locks.ts.html +310 -0
  111. package/coverage/lcov-report/src/services/tabs/protocol.ts.html +871 -0
  112. package/coverage/lcov-report/src/services/tabs/support.ts.html +193 -0
  113. package/coverage/lcov-report/src/services/tabs/tabs-broker-worker.ts.html +2005 -0
  114. package/coverage/lcov-report/src/sp00ky.ts.html +100 -0
  115. package/coverage/lcov-report/src/state/client-state.ts.html +637 -0
  116. package/coverage/lcov-report/src/state/index.html +161 -0
  117. package/coverage/lcov-report/src/state/lifecycle.ts.html +343 -0
  118. package/coverage/lcov-report/src/state/reducers.ts.html +1471 -0
  119. package/coverage/lcov-report/src/state/selectors.ts.html +637 -0
  120. package/coverage/lcov-report/src/sync/connection.saga.ts.html +343 -0
  121. package/coverage/lcov-report/src/sync/index.html +176 -0
  122. package/coverage/lcov-report/src/sync/live.saga.ts.html +232 -0
  123. package/coverage/lcov-report/src/sync/policy.ts.html +1606 -0
  124. package/coverage/lcov-report/src/sync/poll.saga.ts.html +193 -0
  125. package/coverage/lcov-report/src/sync/tabs.saga.ts.html +361 -0
  126. package/coverage/lcov-report/src/types.ts.html +2419 -0
  127. package/coverage/lcov-report/src/utils/blurhash.ts.html +355 -0
  128. package/coverage/lcov-report/src/utils/error-classification.ts.html +244 -0
  129. package/coverage/lcov-report/src/utils/index.html +191 -0
  130. package/coverage/lcov-report/src/utils/index.ts.html +820 -0
  131. package/coverage/lcov-report/src/utils/parser.ts.html +313 -0
  132. package/coverage/lcov-report/src/utils/semver.ts.html +175 -0
  133. package/coverage/lcov-report/src/utils/surql.ts.html +667 -0
  134. package/coverage/lcov.info +19079 -0
  135. package/dist/index.js +3 -3
  136. package/dist/sqlite-open.js +20 -1
  137. package/package.json +3 -3
  138. package/src/services/database/relation-resolver.test.ts +55 -1
  139. package/src/services/database/relation-resolver.ts +0 -0
@@ -0,0 +1,1132 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for src/modules/devtools/flags.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/devtools</a> flags.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">7.59% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>12/158</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">100% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>1/1</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">6.66% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>1/15</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">7.59% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>12/158</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line low'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a>
307
+ <a name='L242'></a><a href='#L242'>242</a>
308
+ <a name='L243'></a><a href='#L243'>243</a>
309
+ <a name='L244'></a><a href='#L244'>244</a>
310
+ <a name='L245'></a><a href='#L245'>245</a>
311
+ <a name='L246'></a><a href='#L246'>246</a>
312
+ <a name='L247'></a><a href='#L247'>247</a>
313
+ <a name='L248'></a><a href='#L248'>248</a>
314
+ <a name='L249'></a><a href='#L249'>249</a>
315
+ <a name='L250'></a><a href='#L250'>250</a>
316
+ <a name='L251'></a><a href='#L251'>251</a>
317
+ <a name='L252'></a><a href='#L252'>252</a>
318
+ <a name='L253'></a><a href='#L253'>253</a>
319
+ <a name='L254'></a><a href='#L254'>254</a>
320
+ <a name='L255'></a><a href='#L255'>255</a>
321
+ <a name='L256'></a><a href='#L256'>256</a>
322
+ <a name='L257'></a><a href='#L257'>257</a>
323
+ <a name='L258'></a><a href='#L258'>258</a>
324
+ <a name='L259'></a><a href='#L259'>259</a>
325
+ <a name='L260'></a><a href='#L260'>260</a>
326
+ <a name='L261'></a><a href='#L261'>261</a>
327
+ <a name='L262'></a><a href='#L262'>262</a>
328
+ <a name='L263'></a><a href='#L263'>263</a>
329
+ <a name='L264'></a><a href='#L264'>264</a>
330
+ <a name='L265'></a><a href='#L265'>265</a>
331
+ <a name='L266'></a><a href='#L266'>266</a>
332
+ <a name='L267'></a><a href='#L267'>267</a>
333
+ <a name='L268'></a><a href='#L268'>268</a>
334
+ <a name='L269'></a><a href='#L269'>269</a>
335
+ <a name='L270'></a><a href='#L270'>270</a>
336
+ <a name='L271'></a><a href='#L271'>271</a>
337
+ <a name='L272'></a><a href='#L272'>272</a>
338
+ <a name='L273'></a><a href='#L273'>273</a>
339
+ <a name='L274'></a><a href='#L274'>274</a>
340
+ <a name='L275'></a><a href='#L275'>275</a>
341
+ <a name='L276'></a><a href='#L276'>276</a>
342
+ <a name='L277'></a><a href='#L277'>277</a>
343
+ <a name='L278'></a><a href='#L278'>278</a>
344
+ <a name='L279'></a><a href='#L279'>279</a>
345
+ <a name='L280'></a><a href='#L280'>280</a>
346
+ <a name='L281'></a><a href='#L281'>281</a>
347
+ <a name='L282'></a><a href='#L282'>282</a>
348
+ <a name='L283'></a><a href='#L283'>283</a>
349
+ <a name='L284'></a><a href='#L284'>284</a>
350
+ <a name='L285'></a><a href='#L285'>285</a>
351
+ <a name='L286'></a><a href='#L286'>286</a>
352
+ <a name='L287'></a><a href='#L287'>287</a>
353
+ <a name='L288'></a><a href='#L288'>288</a>
354
+ <a name='L289'></a><a href='#L289'>289</a>
355
+ <a name='L290'></a><a href='#L290'>290</a>
356
+ <a name='L291'></a><a href='#L291'>291</a>
357
+ <a name='L292'></a><a href='#L292'>292</a>
358
+ <a name='L293'></a><a href='#L293'>293</a>
359
+ <a name='L294'></a><a href='#L294'>294</a>
360
+ <a name='L295'></a><a href='#L295'>295</a>
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></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
416
+ <span class="cline-any cline-neutral">&nbsp;</span>
417
+ <span class="cline-any cline-neutral">&nbsp;</span>
418
+ <span class="cline-any cline-neutral">&nbsp;</span>
419
+ <span class="cline-any cline-neutral">&nbsp;</span>
420
+ <span class="cline-any cline-neutral">&nbsp;</span>
421
+ <span class="cline-any cline-neutral">&nbsp;</span>
422
+ <span class="cline-any cline-neutral">&nbsp;</span>
423
+ <span class="cline-any cline-neutral">&nbsp;</span>
424
+ <span class="cline-any cline-neutral">&nbsp;</span>
425
+ <span class="cline-any cline-neutral">&nbsp;</span>
426
+ <span class="cline-any cline-neutral">&nbsp;</span>
427
+ <span class="cline-any cline-neutral">&nbsp;</span>
428
+ <span class="cline-any cline-neutral">&nbsp;</span>
429
+ <span class="cline-any cline-neutral">&nbsp;</span>
430
+ <span class="cline-any cline-neutral">&nbsp;</span>
431
+ <span class="cline-any cline-neutral">&nbsp;</span>
432
+ <span class="cline-any cline-neutral">&nbsp;</span>
433
+ <span class="cline-any cline-neutral">&nbsp;</span>
434
+ <span class="cline-any cline-neutral">&nbsp;</span>
435
+ <span class="cline-any cline-neutral">&nbsp;</span>
436
+ <span class="cline-any cline-neutral">&nbsp;</span>
437
+ <span class="cline-any cline-neutral">&nbsp;</span>
438
+ <span class="cline-any cline-neutral">&nbsp;</span>
439
+ <span class="cline-any cline-neutral">&nbsp;</span>
440
+ <span class="cline-any cline-neutral">&nbsp;</span>
441
+ <span class="cline-any cline-neutral">&nbsp;</span>
442
+ <span class="cline-any cline-neutral">&nbsp;</span>
443
+ <span class="cline-any cline-neutral">&nbsp;</span>
444
+ <span class="cline-any cline-neutral">&nbsp;</span>
445
+ <span class="cline-any cline-neutral">&nbsp;</span>
446
+ <span class="cline-any cline-neutral">&nbsp;</span>
447
+ <span class="cline-any cline-neutral">&nbsp;</span>
448
+ <span class="cline-any cline-neutral">&nbsp;</span>
449
+ <span class="cline-any cline-neutral">&nbsp;</span>
450
+ <span class="cline-any cline-neutral">&nbsp;</span>
451
+ <span class="cline-any cline-neutral">&nbsp;</span>
452
+ <span class="cline-any cline-neutral">&nbsp;</span>
453
+ <span class="cline-any cline-neutral">&nbsp;</span>
454
+ <span class="cline-any cline-neutral">&nbsp;</span>
455
+ <span class="cline-any cline-neutral">&nbsp;</span>
456
+ <span class="cline-any cline-neutral">&nbsp;</span>
457
+ <span class="cline-any cline-neutral">&nbsp;</span>
458
+ <span class="cline-any cline-neutral">&nbsp;</span>
459
+ <span class="cline-any cline-neutral">&nbsp;</span>
460
+ <span class="cline-any cline-neutral">&nbsp;</span>
461
+ <span class="cline-any cline-neutral">&nbsp;</span>
462
+ <span class="cline-any cline-neutral">&nbsp;</span>
463
+ <span class="cline-any cline-neutral">&nbsp;</span>
464
+ <span class="cline-any cline-neutral">&nbsp;</span>
465
+ <span class="cline-any cline-neutral">&nbsp;</span>
466
+ <span class="cline-any cline-neutral">&nbsp;</span>
467
+ <span class="cline-any cline-neutral">&nbsp;</span>
468
+ <span class="cline-any cline-neutral">&nbsp;</span>
469
+ <span class="cline-any cline-neutral">&nbsp;</span>
470
+ <span class="cline-any cline-neutral">&nbsp;</span>
471
+ <span class="cline-any cline-neutral">&nbsp;</span>
472
+ <span class="cline-any cline-neutral">&nbsp;</span>
473
+ <span class="cline-any cline-neutral">&nbsp;</span>
474
+ <span class="cline-any cline-neutral">&nbsp;</span>
475
+ <span class="cline-any cline-neutral">&nbsp;</span>
476
+ <span class="cline-any cline-neutral">&nbsp;</span>
477
+ <span class="cline-any cline-neutral">&nbsp;</span>
478
+ <span class="cline-any cline-neutral">&nbsp;</span>
479
+ <span class="cline-any cline-neutral">&nbsp;</span>
480
+ <span class="cline-any cline-neutral">&nbsp;</span>
481
+ <span class="cline-any cline-neutral">&nbsp;</span>
482
+ <span class="cline-any cline-neutral">&nbsp;</span>
483
+ <span class="cline-any cline-neutral">&nbsp;</span>
484
+ <span class="cline-any cline-neutral">&nbsp;</span>
485
+ <span class="cline-any cline-neutral">&nbsp;</span>
486
+ <span class="cline-any cline-neutral">&nbsp;</span>
487
+ <span class="cline-any cline-neutral">&nbsp;</span>
488
+ <span class="cline-any cline-neutral">&nbsp;</span>
489
+ <span class="cline-any cline-neutral">&nbsp;</span>
490
+ <span class="cline-any cline-neutral">&nbsp;</span>
491
+ <span class="cline-any cline-neutral">&nbsp;</span>
492
+ <span class="cline-any cline-neutral">&nbsp;</span>
493
+ <span class="cline-any cline-neutral">&nbsp;</span>
494
+ <span class="cline-any cline-neutral">&nbsp;</span>
495
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
+ <span class="cline-any cline-neutral">&nbsp;</span>
497
+ <span class="cline-any cline-neutral">&nbsp;</span>
498
+ <span class="cline-any cline-neutral">&nbsp;</span>
499
+ <span class="cline-any cline-neutral">&nbsp;</span>
500
+ <span class="cline-any cline-neutral">&nbsp;</span>
501
+ <span class="cline-any cline-neutral">&nbsp;</span>
502
+ <span class="cline-any cline-neutral">&nbsp;</span>
503
+ <span class="cline-any cline-neutral">&nbsp;</span>
504
+ <span class="cline-any cline-neutral">&nbsp;</span>
505
+ <span class="cline-any cline-neutral">&nbsp;</span>
506
+ <span class="cline-any cline-neutral">&nbsp;</span>
507
+ <span class="cline-any cline-neutral">&nbsp;</span>
508
+ <span class="cline-any cline-neutral">&nbsp;</span>
509
+ <span class="cline-any cline-neutral">&nbsp;</span>
510
+ <span class="cline-any cline-neutral">&nbsp;</span>
511
+ <span class="cline-any cline-neutral">&nbsp;</span>
512
+ <span class="cline-any cline-neutral">&nbsp;</span>
513
+ <span class="cline-any cline-neutral">&nbsp;</span>
514
+ <span class="cline-any cline-neutral">&nbsp;</span>
515
+ <span class="cline-any cline-neutral">&nbsp;</span>
516
+ <span class="cline-any cline-neutral">&nbsp;</span>
517
+ <span class="cline-any cline-neutral">&nbsp;</span>
518
+ <span class="cline-any cline-neutral">&nbsp;</span>
519
+ <span class="cline-any cline-neutral">&nbsp;</span>
520
+ <span class="cline-any cline-neutral">&nbsp;</span>
521
+ <span class="cline-any cline-neutral">&nbsp;</span>
522
+ <span class="cline-any cline-neutral">&nbsp;</span>
523
+ <span class="cline-any cline-neutral">&nbsp;</span>
524
+ <span class="cline-any cline-neutral">&nbsp;</span>
525
+ <span class="cline-any cline-neutral">&nbsp;</span>
526
+ <span class="cline-any cline-neutral">&nbsp;</span>
527
+ <span class="cline-any cline-neutral">&nbsp;</span>
528
+ <span class="cline-any cline-neutral">&nbsp;</span>
529
+ <span class="cline-any cline-neutral">&nbsp;</span>
530
+ <span class="cline-any cline-neutral">&nbsp;</span>
531
+ <span class="cline-any cline-neutral">&nbsp;</span>
532
+ <span class="cline-any cline-neutral">&nbsp;</span>
533
+ <span class="cline-any cline-neutral">&nbsp;</span>
534
+ <span class="cline-any cline-neutral">&nbsp;</span>
535
+ <span class="cline-any cline-no">&nbsp;</span>
536
+ <span class="cline-any cline-no">&nbsp;</span>
537
+ <span class="cline-any cline-no">&nbsp;</span>
538
+ <span class="cline-any cline-no">&nbsp;</span>
539
+ <span class="cline-any cline-no">&nbsp;</span>
540
+ <span class="cline-any cline-no">&nbsp;</span>
541
+ <span class="cline-any cline-no">&nbsp;</span>
542
+ <span class="cline-any cline-no">&nbsp;</span>
543
+ <span class="cline-any cline-no">&nbsp;</span>
544
+ <span class="cline-any cline-no">&nbsp;</span>
545
+ <span class="cline-any cline-neutral">&nbsp;</span>
546
+ <span class="cline-any cline-no">&nbsp;</span>
547
+ <span class="cline-any cline-no">&nbsp;</span>
548
+ <span class="cline-any cline-no">&nbsp;</span>
549
+ <span class="cline-any cline-neutral">&nbsp;</span>
550
+ <span class="cline-any cline-neutral">&nbsp;</span>
551
+ <span class="cline-any cline-no">&nbsp;</span>
552
+ <span class="cline-any cline-no">&nbsp;</span>
553
+ <span class="cline-any cline-no">&nbsp;</span>
554
+ <span class="cline-any cline-neutral">&nbsp;</span>
555
+ <span class="cline-any cline-no">&nbsp;</span>
556
+ <span class="cline-any cline-no">&nbsp;</span>
557
+ <span class="cline-any cline-no">&nbsp;</span>
558
+ <span class="cline-any cline-neutral">&nbsp;</span>
559
+ <span class="cline-any cline-no">&nbsp;</span>
560
+ <span class="cline-any cline-no">&nbsp;</span>
561
+ <span class="cline-any cline-no">&nbsp;</span>
562
+ <span class="cline-any cline-neutral">&nbsp;</span>
563
+ <span class="cline-any cline-yes">1x</span>
564
+ <span class="cline-any cline-yes">1x</span>
565
+ <span class="cline-any cline-neutral">&nbsp;</span>
566
+ <span class="cline-any cline-neutral">&nbsp;</span>
567
+ <span class="cline-any cline-neutral">&nbsp;</span>
568
+ <span class="cline-any cline-neutral">&nbsp;</span>
569
+ <span class="cline-any cline-neutral">&nbsp;</span>
570
+ <span class="cline-any cline-neutral">&nbsp;</span>
571
+ <span class="cline-any cline-neutral">&nbsp;</span>
572
+ <span class="cline-any cline-neutral">&nbsp;</span>
573
+ <span class="cline-any cline-yes">1x</span>
574
+ <span class="cline-any cline-no">&nbsp;</span>
575
+ <span class="cline-any cline-no">&nbsp;</span>
576
+ <span class="cline-any cline-no">&nbsp;</span>
577
+ <span class="cline-any cline-no">&nbsp;</span>
578
+ <span class="cline-any cline-no">&nbsp;</span>
579
+ <span class="cline-any cline-no">&nbsp;</span>
580
+ <span class="cline-any cline-no">&nbsp;</span>
581
+ <span class="cline-any cline-no">&nbsp;</span>
582
+ <span class="cline-any cline-no">&nbsp;</span>
583
+ <span class="cline-any cline-neutral">&nbsp;</span>
584
+ <span class="cline-any cline-neutral">&nbsp;</span>
585
+ <span class="cline-any cline-no">&nbsp;</span>
586
+ <span class="cline-any cline-no">&nbsp;</span>
587
+ <span class="cline-any cline-no">&nbsp;</span>
588
+ <span class="cline-any cline-no">&nbsp;</span>
589
+ <span class="cline-any cline-no">&nbsp;</span>
590
+ <span class="cline-any cline-no">&nbsp;</span>
591
+ <span class="cline-any cline-no">&nbsp;</span>
592
+ <span class="cline-any cline-no">&nbsp;</span>
593
+ <span class="cline-any cline-no">&nbsp;</span>
594
+ <span class="cline-any cline-no">&nbsp;</span>
595
+ <span class="cline-any cline-no">&nbsp;</span>
596
+ <span class="cline-any cline-no">&nbsp;</span>
597
+ <span class="cline-any cline-no">&nbsp;</span>
598
+ <span class="cline-any cline-no">&nbsp;</span>
599
+ <span class="cline-any cline-no">&nbsp;</span>
600
+ <span class="cline-any cline-neutral">&nbsp;</span>
601
+ <span class="cline-any cline-no">&nbsp;</span>
602
+ <span class="cline-any cline-neutral">&nbsp;</span>
603
+ <span class="cline-any cline-no">&nbsp;</span>
604
+ <span class="cline-any cline-neutral">&nbsp;</span>
605
+ <span class="cline-any cline-neutral">&nbsp;</span>
606
+ <span class="cline-any cline-no">&nbsp;</span>
607
+ <span class="cline-any cline-no">&nbsp;</span>
608
+ <span class="cline-any cline-no">&nbsp;</span>
609
+ <span class="cline-any cline-no">&nbsp;</span>
610
+ <span class="cline-any cline-no">&nbsp;</span>
611
+ <span class="cline-any cline-neutral">&nbsp;</span>
612
+ <span class="cline-any cline-neutral">&nbsp;</span>
613
+ <span class="cline-any cline-no">&nbsp;</span>
614
+ <span class="cline-any cline-no">&nbsp;</span>
615
+ <span class="cline-any cline-no">&nbsp;</span>
616
+ <span class="cline-any cline-neutral">&nbsp;</span>
617
+ <span class="cline-any cline-no">&nbsp;</span>
618
+ <span class="cline-any cline-neutral">&nbsp;</span>
619
+ <span class="cline-any cline-no">&nbsp;</span>
620
+ <span class="cline-any cline-no">&nbsp;</span>
621
+ <span class="cline-any cline-no">&nbsp;</span>
622
+ <span class="cline-any cline-no">&nbsp;</span>
623
+ <span class="cline-any cline-neutral">&nbsp;</span>
624
+ <span class="cline-any cline-no">&nbsp;</span>
625
+ <span class="cline-any cline-no">&nbsp;</span>
626
+ <span class="cline-any cline-no">&nbsp;</span>
627
+ <span class="cline-any cline-no">&nbsp;</span>
628
+ <span class="cline-any cline-no">&nbsp;</span>
629
+ <span class="cline-any cline-no">&nbsp;</span>
630
+ <span class="cline-any cline-no">&nbsp;</span>
631
+ <span class="cline-any cline-no">&nbsp;</span>
632
+ <span class="cline-any cline-no">&nbsp;</span>
633
+ <span class="cline-any cline-no">&nbsp;</span>
634
+ <span class="cline-any cline-no">&nbsp;</span>
635
+ <span class="cline-any cline-no">&nbsp;</span>
636
+ <span class="cline-any cline-no">&nbsp;</span>
637
+ <span class="cline-any cline-neutral">&nbsp;</span>
638
+ <span class="cline-any cline-no">&nbsp;</span>
639
+ <span class="cline-any cline-no">&nbsp;</span>
640
+ <span class="cline-any cline-neutral">&nbsp;</span>
641
+ <span class="cline-any cline-neutral">&nbsp;</span>
642
+ <span class="cline-any cline-neutral">&nbsp;</span>
643
+ <span class="cline-any cline-neutral">&nbsp;</span>
644
+ <span class="cline-any cline-neutral">&nbsp;</span>
645
+ <span class="cline-any cline-neutral">&nbsp;</span>
646
+ <span class="cline-any cline-neutral">&nbsp;</span>
647
+ <span class="cline-any cline-neutral">&nbsp;</span>
648
+ <span class="cline-any cline-yes">1x</span>
649
+ <span class="cline-any cline-no">&nbsp;</span>
650
+ <span class="cline-any cline-no">&nbsp;</span>
651
+ <span class="cline-any cline-neutral">&nbsp;</span>
652
+ <span class="cline-any cline-no">&nbsp;</span>
653
+ <span class="cline-any cline-no">&nbsp;</span>
654
+ <span class="cline-any cline-no">&nbsp;</span>
655
+ <span class="cline-any cline-no">&nbsp;</span>
656
+ <span class="cline-any cline-neutral">&nbsp;</span>
657
+ <span class="cline-any cline-neutral">&nbsp;</span>
658
+ <span class="cline-any cline-neutral">&nbsp;</span>
659
+ <span class="cline-any cline-neutral">&nbsp;</span>
660
+ <span class="cline-any cline-neutral">&nbsp;</span>
661
+ <span class="cline-any cline-neutral">&nbsp;</span>
662
+ <span class="cline-any cline-yes">1x</span>
663
+ <span class="cline-any cline-no">&nbsp;</span>
664
+ <span class="cline-any cline-no">&nbsp;</span>
665
+ <span class="cline-any cline-no">&nbsp;</span>
666
+ <span class="cline-any cline-no">&nbsp;</span>
667
+ <span class="cline-any cline-no">&nbsp;</span>
668
+ <span class="cline-any cline-no">&nbsp;</span>
669
+ <span class="cline-any cline-no">&nbsp;</span>
670
+ <span class="cline-any cline-neutral">&nbsp;</span>
671
+ <span class="cline-any cline-neutral">&nbsp;</span>
672
+ <span class="cline-any cline-neutral">&nbsp;</span>
673
+ <span class="cline-any cline-no">&nbsp;</span>
674
+ <span class="cline-any cline-no">&nbsp;</span>
675
+ <span class="cline-any cline-no">&nbsp;</span>
676
+ <span class="cline-any cline-no">&nbsp;</span>
677
+ <span class="cline-any cline-no">&nbsp;</span>
678
+ <span class="cline-any cline-no">&nbsp;</span>
679
+ <span class="cline-any cline-neutral">&nbsp;</span>
680
+ <span class="cline-any cline-no">&nbsp;</span>
681
+ <span class="cline-any cline-no">&nbsp;</span>
682
+ <span class="cline-any cline-no">&nbsp;</span>
683
+ <span class="cline-any cline-no">&nbsp;</span>
684
+ <span class="cline-any cline-neutral">&nbsp;</span>
685
+ <span class="cline-any cline-yes">1x</span>
686
+ <span class="cline-any cline-no">&nbsp;</span>
687
+ <span class="cline-any cline-no">&nbsp;</span>
688
+ <span class="cline-any cline-no">&nbsp;</span>
689
+ <span class="cline-any cline-no">&nbsp;</span>
690
+ <span class="cline-any cline-no">&nbsp;</span>
691
+ <span class="cline-any cline-no">&nbsp;</span>
692
+ <span class="cline-any cline-no">&nbsp;</span>
693
+ <span class="cline-any cline-no">&nbsp;</span>
694
+ <span class="cline-any cline-neutral">&nbsp;</span>
695
+ <span class="cline-any cline-yes">1x</span>
696
+ <span class="cline-any cline-no">&nbsp;</span>
697
+ <span class="cline-any cline-no">&nbsp;</span>
698
+ <span class="cline-any cline-no">&nbsp;</span>
699
+ <span class="cline-any cline-no">&nbsp;</span>
700
+ <span class="cline-any cline-neutral">&nbsp;</span>
701
+ <span class="cline-any cline-neutral">&nbsp;</span>
702
+ <span class="cline-any cline-neutral">&nbsp;</span>
703
+ <span class="cline-any cline-neutral">&nbsp;</span>
704
+ <span class="cline-any cline-neutral">&nbsp;</span>
705
+ <span class="cline-any cline-neutral">&nbsp;</span>
706
+ <span class="cline-any cline-neutral">&nbsp;</span>
707
+ <span class="cline-any cline-neutral">&nbsp;</span>
708
+ <span class="cline-any cline-neutral">&nbsp;</span>
709
+ <span class="cline-any cline-neutral">&nbsp;</span>
710
+ <span class="cline-any cline-neutral">&nbsp;</span>
711
+ <span class="cline-any cline-neutral">&nbsp;</span>
712
+ <span class="cline-any cline-yes">1x</span>
713
+ <span class="cline-any cline-no">&nbsp;</span>
714
+ <span class="cline-any cline-no">&nbsp;</span>
715
+ <span class="cline-any cline-no">&nbsp;</span>
716
+ <span class="cline-any cline-no">&nbsp;</span>
717
+ <span class="cline-any cline-no">&nbsp;</span>
718
+ <span class="cline-any cline-no">&nbsp;</span>
719
+ <span class="cline-any cline-no">&nbsp;</span>
720
+ <span class="cline-any cline-no">&nbsp;</span>
721
+ <span class="cline-any cline-no">&nbsp;</span>
722
+ <span class="cline-any cline-no">&nbsp;</span>
723
+ <span class="cline-any cline-no">&nbsp;</span>
724
+ <span class="cline-any cline-no">&nbsp;</span>
725
+ <span class="cline-any cline-no">&nbsp;</span>
726
+ <span class="cline-any cline-neutral">&nbsp;</span>
727
+ <span class="cline-any cline-no">&nbsp;</span>
728
+ <span class="cline-any cline-no">&nbsp;</span>
729
+ <span class="cline-any cline-no">&nbsp;</span>
730
+ <span class="cline-any cline-neutral">&nbsp;</span>
731
+ <span class="cline-any cline-no">&nbsp;</span>
732
+ <span class="cline-any cline-no">&nbsp;</span>
733
+ <span class="cline-any cline-no">&nbsp;</span>
734
+ <span class="cline-any cline-no">&nbsp;</span>
735
+ <span class="cline-any cline-no">&nbsp;</span>
736
+ <span class="cline-any cline-no">&nbsp;</span>
737
+ <span class="cline-any cline-yes">1x</span>
738
+ <span class="cline-any cline-neutral">&nbsp;</span>
739
+ <span class="cline-any cline-yes">1x</span>
740
+ <span class="cline-any cline-neutral">&nbsp;</span>
741
+ <span class="cline-any cline-yes">1x</span>
742
+ <span class="cline-any cline-neutral">&nbsp;</span>
743
+ <span class="cline-any cline-no">&nbsp;</span>
744
+ <span class="cline-any cline-no">&nbsp;</span>
745
+ <span class="cline-any cline-no">&nbsp;</span>
746
+ <span class="cline-any cline-neutral">&nbsp;</span>
747
+ <span class="cline-any cline-neutral">&nbsp;</span>
748
+ <span class="cline-any cline-neutral">&nbsp;</span>
749
+ <span class="cline-any cline-neutral">&nbsp;</span>
750
+ <span class="cline-any cline-neutral">&nbsp;</span>
751
+ <span class="cline-any cline-neutral">&nbsp;</span>
752
+ <span class="cline-any cline-neutral">&nbsp;</span>
753
+ <span class="cline-any cline-neutral">&nbsp;</span>
754
+ <span class="cline-any cline-no">&nbsp;</span>
755
+ <span class="cline-any cline-no">&nbsp;</span>
756
+ <span class="cline-any cline-no">&nbsp;</span>
757
+ <span class="cline-any cline-no">&nbsp;</span>
758
+ <span class="cline-any cline-no">&nbsp;</span>
759
+ <span class="cline-any cline-no">&nbsp;</span>
760
+ <span class="cline-any cline-no">&nbsp;</span>
761
+ <span class="cline-any cline-no">&nbsp;</span>
762
+ <span class="cline-any cline-no">&nbsp;</span>
763
+ <span class="cline-any cline-no">&nbsp;</span>
764
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
765
+ * Feature flag administration for the DevTools panel.
766
+ *
767
+ * Two independent capabilities, deliberately kept apart:
768
+ *
769
+ * - **Local overrides** force a variant in THIS browser. No auth, no network,
770
+ * works signed out and offline. Delegated straight to `FeatureFlagModule`.
771
+ * - **Remote changes** flip a flag for EVERY user. These require the caller to
772
+ * be listed in `_00_admin` (`spky admin add &lt;user&gt;`); SurrealDB enforces
773
+ * that, not this file. A non-admin's SELECT returns `[]` rather than an
774
+ * error, and the `fn::feature::*` calls are hard-denied.
775
+ *
776
+ * Everything remote goes through `RemoteDatabaseService` with BOUND params.
777
+ * The panel's generic `runQuery` bridge is not usable here: it interpolates
778
+ * the SurrealQL into an `inspectedWindow.eval` string (so a DB-sourced flag
779
+ * key or record id would be concatenated into code) and it rejects after 10s,
780
+ * which a materialize over a real user table will exceed.
781
+ *
782
+ * Definitions live in `_00_feature_flag`, which is NOT synced to clients, so
783
+ * flags must be read remotely. Per-user assignments live in `_00_user_feature`,
784
+ * which IS live-synced, so those come from the local store for free.
785
+ */
786
+ &nbsp;
787
+ import type { Logger } from '../../services/logger/index';
788
+ import { parseRecordIdString } from '../../utils/index';
789
+ &nbsp;
790
+ /**
791
+ * The only thing this service needs from a database. Structural rather than
792
+ * `AbstractDatabaseService`, because the local side is a `LocalStore` (which
793
+ * wraps a database rather than extending it) and both must satisfy it.
794
+ */
795
+ export interface FlagQueryable {
796
+ query&lt;T extends unknown[]&gt;(query: string, vars?: Record&lt;string, unknown&gt;): Promise&lt;T&gt;;
797
+ }
798
+ &nbsp;
799
+ /** A targeting rule as written by `spky flag` / `fn::feature::allow`. */
800
+ export interface DevToolsFlagRule {
801
+ kind: 'allowlist' | 'rollout' | string;
802
+ variant: string;
803
+ /** Allowlist only. Stored as record-id STRINGS, not records. */
804
+ users?: string[];
805
+ /** Rollout only, 0..100. */
806
+ percent?: number;
807
+ priority: number;
808
+ }
809
+ &nbsp;
810
+ export interface DevToolsFlagRow {
811
+ key: string;
812
+ description?: string;
813
+ variants: string[];
814
+ default_variant: string;
815
+ enabled: boolean;
816
+ payloads?: Record&lt;string, unknown&gt;;
817
+ rules: DevToolsFlagRule[];
818
+ updated_at?: string;
819
+ /** Derived: the variant the signed-in user is allowlisted into, if any. */
820
+ selfAllowlistedVariant?: string;
821
+ }
822
+ &nbsp;
823
+ export interface DevToolsFlagAssignment {
824
+ key: string;
825
+ variant: string;
826
+ payload?: unknown;
827
+ }
828
+ &nbsp;
829
+ export interface DevToolsFlagOverride {
830
+ variant: string;
831
+ payload?: unknown;
832
+ }
833
+ &nbsp;
834
+ export interface DevToolsFlagsSnapshot {
835
+ at: number;
836
+ /** Null when signed out. */
837
+ userId: string | null;
838
+ /** True when `_00_admin` has a row for the signed-in user. */
839
+ isAdmin: boolean;
840
+ /** Empty for non-admins — SurrealDB filters the rows, it does not error. */
841
+ flags: DevToolsFlagRow[];
842
+ /** From the LOCAL `_00_user_feature`: what this browser actually resolves. */
843
+ assignments: DevToolsFlagAssignment[];
844
+ /** Local-only forced variants, page-origin localStorage. */
845
+ overrides: Record&lt;string, DevToolsFlagOverride&gt;;
846
+ /**
847
+ * Set when the internal schema predates this feature (no `_00_admin` table),
848
+ * or the remote read failed. The panel shows it instead of a bare empty
849
+ * state, so "not migrated" doesn't look like "you're not an admin".
850
+ */
851
+ error?: string;
852
+ }
853
+ &nbsp;
854
+ export interface DevToolsFlagResult {
855
+ success: boolean;
856
+ error?: string;
857
+ /** Users re-evaluated by `fn::feature::materialize`. */
858
+ users?: number;
859
+ }
860
+ &nbsp;
861
+ /** The slice of `FeatureFlagModule` this service needs. */
862
+ export interface LocalOverrideStore {
863
+ setLocalOverride(key: string, variant: string | null, payload?: unknown): void;
864
+ clearLocalOverrides(): void;
865
+ getLocalOverrides(): Record&lt;string, DevToolsFlagOverride&gt;;
866
+ }
867
+ &nbsp;
868
+ export interface FlagsAdminDeps {
869
+ remote: FlagQueryable;
870
+ local: FlagQueryable;
871
+ logger: Logger;
872
+ /** Resolves the signed-in user's record id, or null. */
873
+ currentUserId: () =&gt; string | null;
874
+ /** Set once the client has built its FeatureFlagModule. */
875
+ overrides: () =&gt; LocalOverrideStore | null;
876
+ }
877
+ &nbsp;
878
+ /**
879
+ * SurrealDB's SDK returns one entry per statement; each entry is the rows for
880
+ * that statement, but older/local shapes wrap them as `{ status, result }`.
881
+ * `getTableData` in `index.ts` unwraps the same three shapes inline — this is
882
+ * that logic, reusable.
883
+ */
884
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function statementRows(result: unknown, index = 0): unknown[] {</span></span>
885
+ <span class="cstat-no" title="statement not covered" > if (!Array.isArray(result)) return [];</span>
886
+ <span class="cstat-no" title="statement not covered" > const entry = result[index];</span>
887
+ <span class="cstat-no" title="statement not covered" > if (Array.isArray(entry)) return entry;</span>
888
+ <span class="cstat-no" title="statement not covered" > if (entry &amp;&amp; typeof entry === 'object' &amp;&amp; 'result' in entry) {</span>
889
+ <span class="cstat-no" title="statement not covered" > const inner = (entry as { result?: unknown }).result;</span>
890
+ <span class="cstat-no" title="statement not covered" > return Array.isArray(inner) ? inner : inner === undefined || inner === null ? [] : [inner];</span>
891
+ <span class="cstat-no" title="statement not covered" > }</span>
892
+ <span class="cstat-no" title="statement not covered" > return entry === undefined || entry === null ? [] : [entry];</span>
893
+ <span class="cstat-no" title="statement not covered" >}</span>
894
+ &nbsp;
895
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function message(err: unknown): string {</span></span>
896
+ <span class="cstat-no" title="statement not covered" > return err instanceof Error ? err.message : String(err);</span>
897
+ <span class="cstat-no" title="statement not covered" >}</span>
898
+ &nbsp;
899
+ /** Row shape guards. `key` is the only field the panel truly can't do without. */
900
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function hasStringKey(row: unknown): row is { key: string } {</span></span>
901
+ <span class="cstat-no" title="statement not covered" > return !!row &amp;&amp; typeof row === 'object' &amp;&amp; typeof (row as { key?: unknown }).key === 'string';</span>
902
+ <span class="cstat-no" title="statement not covered" >}</span>
903
+ &nbsp;
904
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function isAssignment(row: unknown): row is DevToolsFlagAssignment {</span></span>
905
+ <span class="cstat-no" title="statement not covered" > return hasStringKey(row);</span>
906
+ <span class="cstat-no" title="statement not covered" >}</span>
907
+ &nbsp;
908
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function isFlagRow(row: unknown): row is DevToolsFlagRow {</span></span>
909
+ <span class="cstat-no" title="statement not covered" > return hasStringKey(row);</span>
910
+ <span class="cstat-no" title="statement not covered" >}</span>
911
+ &nbsp;
912
+ export class FlagsAdminService {
913
+ constructor(private deps: FlagsAdminDeps) {}
914
+ &nbsp;
915
+ /**
916
+ * Everything the Access tab renders, in one round trip per source.
917
+ *
918
+ * Each section fails independently: a remote read that throws downgrades to
919
+ * `isAdmin: false` plus an `error`, while local assignments and overrides
920
+ * still render. Signing out must not blank the whole tab.
921
+ */
922
+ <span class="fstat-no" title="function not covered" > async getFlags(): Promise&lt;DevToolsFlagsSnapshot&gt; {</span>
923
+ <span class="cstat-no" title="statement not covered" > const userId = this.deps.currentUserId();</span>
924
+ <span class="cstat-no" title="statement not covered" > const snapshot: DevToolsFlagsSnapshot = {</span>
925
+ <span class="cstat-no" title="statement not covered" > at: Date.now(),</span>
926
+ <span class="cstat-no" title="statement not covered" > userId,</span>
927
+ <span class="cstat-no" title="statement not covered" > isAdmin: false,</span>
928
+ <span class="cstat-no" title="statement not covered" > flags: [],</span>
929
+ <span class="cstat-no" title="statement not covered" > assignments: [],</span>
930
+ <span class="cstat-no" title="statement not covered" > overrides: this.deps.overrides()?.getLocalOverrides() ?? {},</span>
931
+ <span class="cstat-no" title="statement not covered" > };</span>
932
+ &nbsp;
933
+ // Local assignments: available signed out and offline.
934
+ <span class="cstat-no" title="statement not covered" > try {</span>
935
+ <span class="cstat-no" title="statement not covered" > const rows = statementRows(</span>
936
+ <span class="cstat-no" title="statement not covered" > await this.deps.local.query('SELECT key, variant, payload FROM _00_user_feature')</span>
937
+ <span class="cstat-no" title="statement not covered" > );</span>
938
+ <span class="cstat-no" title="statement not covered" > snapshot.assignments = rows.filter(isAssignment).map((r) =&gt; ({</span>
939
+ <span class="cstat-no" title="statement not covered" > key: r.key,</span>
940
+ <span class="cstat-no" title="statement not covered" > variant: r.variant,</span>
941
+ <span class="cstat-no" title="statement not covered" > payload: r.payload,</span>
942
+ <span class="cstat-no" title="statement not covered" > }));</span>
943
+ <span class="cstat-no" title="statement not covered" > } catch (err) {</span>
944
+ <span class="cstat-no" title="statement not covered" > this.deps.logger.debug(</span>
945
+ <span class="cstat-no" title="statement not covered" > { err, Category: 'sp00ky-client::FlagsAdminService::getFlags' },</span>
946
+ <span class="cstat-no" title="statement not covered" > 'Local feature assignments unavailable'</span>
947
+ <span class="cstat-no" title="statement not covered" > );</span>
948
+ <span class="cstat-no" title="statement not covered" > }</span>
949
+ &nbsp;
950
+ <span class="cstat-no" title="statement not covered" > if (!userId) return snapshot;</span>
951
+ &nbsp;
952
+ <span class="cstat-no" title="statement not covered" > try {</span>
953
+ // Self-scoped by `_00_admin`'s own select rule: an admin sees exactly
954
+ // their own row, a non-admin sees nothing. The roster never leaks.
955
+ <span class="cstat-no" title="statement not covered" > const admin = statementRows(</span>
956
+ <span class="cstat-no" title="statement not covered" > await this.deps.remote.query('SELECT VALUE id FROM _00_admin WHERE user = $auth.id LIMIT 1')</span>
957
+ <span class="cstat-no" title="statement not covered" > );</span>
958
+ <span class="cstat-no" title="statement not covered" > snapshot.isAdmin = admin.length &gt; 0;</span>
959
+ <span class="cstat-no" title="statement not covered" > } catch (err) {</span>
960
+ // A missing `_00_admin` table means the deployment hasn't applied the
961
+ // internal schema yet. Say so — otherwise it reads as "not an admin".
962
+ <span class="cstat-no" title="statement not covered" > snapshot.error = `Could not check admin status: ${message(err)}. If this deployment predates the Access tab, run \`spky migrate\` (or redeploy) to apply the internal schema.`;</span>
963
+ <span class="cstat-no" title="statement not covered" > return snapshot;</span>
964
+ <span class="cstat-no" title="statement not covered" > }</span>
965
+ &nbsp;
966
+ <span class="cstat-no" title="statement not covered" > if (!snapshot.isAdmin) return snapshot;</span>
967
+ &nbsp;
968
+ <span class="cstat-no" title="statement not covered" > try {</span>
969
+ <span class="cstat-no" title="statement not covered" > const rows = statementRows(</span>
970
+ <span class="cstat-no" title="statement not covered" > await this.deps.remote.query(</span>
971
+ <span class="cstat-no" title="statement not covered" > 'SELECT key, description, variants, default_variant, enabled, payloads, rules, updated_at ' +</span>
972
+ 'FROM _00_feature_flag ORDER BY key ASC'
973
+ <span class="cstat-no" title="statement not covered" > )</span>
974
+ <span class="cstat-no" title="statement not covered" > );</span>
975
+ <span class="cstat-no" title="statement not covered" > snapshot.flags = rows</span>
976
+ <span class="cstat-no" title="statement not covered" > .filter(isFlagRow)</span>
977
+ <span class="cstat-no" title="statement not covered" > .map((flag) =&gt; ({</span>
978
+ <span class="cstat-no" title="statement not covered" > ...flag,</span>
979
+ <span class="cstat-no" title="statement not covered" > rules: Array.isArray(flag.rules) ? flag.rules : [],</span>
980
+ <span class="cstat-no" title="statement not covered" > variants: Array.isArray(flag.variants) ? flag.variants : [],</span>
981
+ <span class="cstat-no" title="statement not covered" > selfAllowlistedVariant: selfAllowlistedVariant(flag, userId),</span>
982
+ <span class="cstat-no" title="statement not covered" > }));</span>
983
+ <span class="cstat-no" title="statement not covered" > } catch (err) {</span>
984
+ <span class="cstat-no" title="statement not covered" > snapshot.error = `Could not read feature flags: ${message(err)}`;</span>
985
+ <span class="cstat-no" title="statement not covered" > }</span>
986
+ &nbsp;
987
+ <span class="cstat-no" title="statement not covered" > return snapshot;</span>
988
+ <span class="cstat-no" title="statement not covered" > }</span>
989
+ &nbsp;
990
+ /**
991
+ * Flip a flag's global `enabled` bit for EVERY user, then re-materialize.
992
+ *
993
+ * Both statements are one request so they share a transaction: if the
994
+ * materialize fails, the `enabled` change rolls back rather than leaving the
995
+ * definition and the assignments disagreeing.
996
+ */
997
+ <span class="fstat-no" title="function not covered" > async setFlagEnabled(key: string, enabled: boolean): Promise&lt;DevToolsFlagResult&gt; {</span>
998
+ <span class="cstat-no" title="statement not covered" > return this.mutate(</span>
999
+ <span class="cstat-no" title="statement not covered" > 'UPDATE _00_feature_flag SET enabled = $enabled WHERE key = $key; ' +</span>
1000
+ 'RETURN fn::feature::materialize($key);',
1001
+ <span class="cstat-no" title="statement not covered" > { key, enabled },</span>
1002
+ <span class="cstat-no" title="statement not covered" > 1</span>
1003
+ <span class="cstat-no" title="statement not covered" > );</span>
1004
+ <span class="cstat-no" title="statement not covered" > }</span>
1005
+ &nbsp;
1006
+ /**
1007
+ * Add or remove a user from `$key`'s allowlist for `$variant`, then
1008
+ * re-materialize. Defaults to the signed-in user, so the common case
1009
+ * ("turn this on for me, for real") needs no user picker.
1010
+ */
1011
+ <span class="fstat-no" title="function not covered" > async setFlagUserVariant(</span>
1012
+ <span class="cstat-no" title="statement not covered" > key: string,</span>
1013
+ <span class="cstat-no" title="statement not covered" > variant: string,</span>
1014
+ <span class="cstat-no" title="statement not covered" > remove: boolean,</span>
1015
+ <span class="cstat-no" title="statement not covered" > userId?: string</span>
1016
+ <span class="cstat-no" title="statement not covered" > ): Promise&lt;DevToolsFlagResult&gt; {</span>
1017
+ <span class="cstat-no" title="statement not covered" > const target = userId ?? this.deps.currentUserId();</span>
1018
+ <span class="cstat-no" title="statement not covered" > if (!target) return { success: false, error: 'Not signed in' };</span>
1019
+ &nbsp;
1020
+ // `fn::feature::allow/disallow` declare `$user: record`. A string binding
1021
+ // is rejected by the type check, so parse it into a real RecordId.
1022
+ <span class="cstat-no" title="statement not covered" > let user;</span>
1023
+ <span class="cstat-no" title="statement not covered" > try {</span>
1024
+ <span class="cstat-no" title="statement not covered" > user = parseRecordIdString(target);</span>
1025
+ <span class="cstat-no" title="statement not covered" > } catch (err) {</span>
1026
+ <span class="cstat-no" title="statement not covered" > return { success: false, error: `Invalid user id '${target}': ${message(err)}` };</span>
1027
+ <span class="cstat-no" title="statement not covered" > }</span>
1028
+ &nbsp;
1029
+ <span class="cstat-no" title="statement not covered" > return remove</span>
1030
+ <span class="cstat-no" title="statement not covered" > ? this.mutate('RETURN fn::feature::disallow($key, $user);', { key, user }, 0)</span>
1031
+ <span class="cstat-no" title="statement not covered" > : this.mutate('RETURN fn::feature::allow($key, $variant, $user);', { key, variant, user }, 0);</span>
1032
+ <span class="cstat-no" title="statement not covered" > }</span>
1033
+ &nbsp;
1034
+ <span class="fstat-no" title="function not covered" > setLocalFlagOverride(</span>
1035
+ <span class="cstat-no" title="statement not covered" > key: string,</span>
1036
+ <span class="cstat-no" title="statement not covered" > variant: string | null,</span>
1037
+ <span class="cstat-no" title="statement not covered" > payload?: unknown</span>
1038
+ <span class="cstat-no" title="statement not covered" > ): { overrides: Record&lt;string, DevToolsFlagOverride&gt; } {</span>
1039
+ <span class="cstat-no" title="statement not covered" > const store = this.deps.overrides();</span>
1040
+ <span class="cstat-no" title="statement not covered" > store?.setLocalOverride(key, variant, payload);</span>
1041
+ <span class="cstat-no" title="statement not covered" > return { overrides: store?.getLocalOverrides() ?? {} };</span>
1042
+ <span class="cstat-no" title="statement not covered" > }</span>
1043
+ &nbsp;
1044
+ <span class="fstat-no" title="function not covered" > clearLocalFlagOverrides(): { overrides: Record&lt;string, DevToolsFlagOverride&gt; } {</span>
1045
+ <span class="cstat-no" title="statement not covered" > const store = this.deps.overrides();</span>
1046
+ <span class="cstat-no" title="statement not covered" > store?.clearLocalOverrides();</span>
1047
+ <span class="cstat-no" title="statement not covered" > return { overrides: store?.getLocalOverrides() ?? {} };</span>
1048
+ <span class="cstat-no" title="statement not covered" > }</span>
1049
+ &nbsp;
1050
+ /**
1051
+ * Run a remote mutation, reporting the materialize count from `$index`.
1052
+ *
1053
+ * Retries on a transaction conflict. `fn::feature::allow` / `disallow` are
1054
+ * read-modify-write over `_00_feature_flag.rules`, so two admins acting on
1055
+ * the same flag at once collide. SurrealDB detects this and fails the loser
1056
+ * with "Transaction conflict ... can be retried" rather than losing the
1057
+ * write — verified against 3.1 — so nothing is silently dropped. Retrying
1058
+ * turns that into the outcome the user expected instead of a raw engine
1059
+ * error they can do nothing with.
1060
+ */
1061
+ <span class="fstat-no" title="function not covered" > private async mutate(</span>
1062
+ <span class="cstat-no" title="statement not covered" > sql: string,</span>
1063
+ <span class="cstat-no" title="statement not covered" > vars: Record&lt;string, unknown&gt;,</span>
1064
+ <span class="cstat-no" title="statement not covered" > index: number</span>
1065
+ <span class="cstat-no" title="statement not covered" > ): Promise&lt;DevToolsFlagResult&gt; {</span>
1066
+ <span class="cstat-no" title="statement not covered" > let lastError: unknown;</span>
1067
+ <span class="cstat-no" title="statement not covered" > for (let attempt = 0; attempt &lt; MUTATE_ATTEMPTS; attempt++) {</span>
1068
+ <span class="cstat-no" title="statement not covered" > try {</span>
1069
+ <span class="cstat-no" title="statement not covered" > const result = await this.deps.remote.query(sql, vars);</span>
1070
+ <span class="cstat-no" title="statement not covered" > const materialized = statementRows(result, index)[0] as { users?: number } | undefined;</span>
1071
+ <span class="cstat-no" title="statement not covered" > return { success: true, users: materialized?.users };</span>
1072
+ <span class="cstat-no" title="statement not covered" > } catch (err) {</span>
1073
+ <span class="cstat-no" title="statement not covered" > lastError = err;</span>
1074
+ <span class="cstat-no" title="statement not covered" > if (!isRetryableConflict(err) || attempt === MUTATE_ATTEMPTS - 1) break;</span>
1075
+ // Staggered so two collided clients don't line up again on the retry.
1076
+ <span class="cstat-no" title="statement not covered" > await sleep(40 * (attempt + 1) + Math.random() * 40);</span>
1077
+ <span class="cstat-no" title="statement not covered" > }</span>
1078
+ <span class="cstat-no" title="statement not covered" > }</span>
1079
+ &nbsp;
1080
+ <span class="cstat-no" title="statement not covered" > this.deps.logger.warn(</span>
1081
+ <span class="cstat-no" title="statement not covered" > { err: lastError, sql, Category: 'sp00ky-client::FlagsAdminService::mutate' },</span>
1082
+ <span class="cstat-no" title="statement not covered" > 'Feature flag mutation failed'</span>
1083
+ <span class="cstat-no" title="statement not covered" > );</span>
1084
+ <span class="cstat-no" title="statement not covered" > return { success: false, error: message(lastError) };</span>
1085
+ <span class="cstat-no" title="statement not covered" > }</span>
1086
+ }
1087
+ &nbsp;
1088
+ const MUTATE_ATTEMPTS = 3;
1089
+ &nbsp;
1090
+ const sleep = <span class="fstat-no" title="function not covered" >(ms: number) =&gt; new Promise((resolve) =&gt; setTimeout(resolve, ms));</span>
1091
+ &nbsp;
1092
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function isRetryableConflict(err: unknown): boolean {</span></span>
1093
+ <span class="cstat-no" title="statement not covered" > return /transaction (write )?conflict/i.test(message(err));</span>
1094
+ <span class="cstat-no" title="statement not covered" >}</span>
1095
+ &nbsp;
1096
+ /**
1097
+ * Which variant, if any, this user is explicitly allowlisted into.
1098
+ *
1099
+ * `rules[].users` holds record-id strings (`flag.rs` serialises them as JSON),
1100
+ * so compare as strings. Lets the panel show "you're on the list" without
1101
+ * making the user reason about a raw rules blob.
1102
+ */
1103
+ <span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function selfAllowlistedVariant(flag: DevToolsFlagRow, userId: string): string | undefined {</span></span>
1104
+ <span class="cstat-no" title="statement not covered" > const rules = Array.isArray(flag.rules) ? flag.rules : [];</span>
1105
+ <span class="cstat-no" title="statement not covered" > const hit = rules.find(</span>
1106
+ <span class="cstat-no" title="statement not covered" > (rule) =&gt;</span>
1107
+ <span class="cstat-no" title="statement not covered" > rule?.kind === 'allowlist' &amp;&amp;</span>
1108
+ <span class="cstat-no" title="statement not covered" > Array.isArray(rule.users) &amp;&amp;</span>
1109
+ <span class="cstat-no" title="statement not covered" > rule.users.some((u) =&gt; String(u) === userId)</span>
1110
+ <span class="cstat-no" title="statement not covered" > );</span>
1111
+ <span class="cstat-no" title="statement not covered" > return hit?.variant;</span>
1112
+ <span class="cstat-no" title="statement not covered" >}</span>
1113
+ &nbsp;</pre></td></tr></table></pre>
1114
+
1115
+ <div class='push'></div><!-- for sticky footer -->
1116
+ </div><!-- /wrapper -->
1117
+ <div class='footer quiet pad2 space-top1 center small'>
1118
+ Code coverage generated by
1119
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1120
+ at 2026-09-09T09:49:51.780Z
1121
+ </div>
1122
+ <script src="../../../prettify.js"></script>
1123
+ <script>
1124
+ window.onload = function () {
1125
+ prettyPrint();
1126
+ };
1127
+ </script>
1128
+ <script src="../../../sorter.js"></script>
1129
+ <script src="../../../block-navigation.js"></script>
1130
+ </body>
1131
+ </html>
1132
+