@spooky-sync/core 0.0.1-canary.240 → 0.0.1-canary.241

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 (289) hide show
  1. package/AGENTS.md +34 -15
  2. package/README.md +56 -16
  3. package/dist/index.d.ts +1136 -1846
  4. package/dist/index.js +10168 -11267
  5. package/dist/types.d.ts +22 -79
  6. package/package.json +8 -4
  7. package/skills/sp00ky-core/SKILL.md +18 -0
  8. package/src/boot/auth-flip.saga.test.ts +82 -0
  9. package/src/boot/auth-flip.saga.ts +62 -0
  10. package/src/boot/boot.saga.test.ts +151 -0
  11. package/src/boot/boot.saga.ts +128 -0
  12. package/src/boot/bucket-switch.saga.test.ts +158 -0
  13. package/src/boot/bucket-switch.saga.ts +115 -0
  14. package/src/boot/preload.saga.test.ts +51 -0
  15. package/src/boot/preload.saga.ts +31 -0
  16. package/src/client/bucket-handle.test.ts +117 -0
  17. package/src/client/bucket-handle.ts +227 -0
  18. package/src/client/router.test.ts +56 -0
  19. package/src/client/router.ts +94 -0
  20. package/src/client/runtime.test.ts +192 -0
  21. package/src/client/runtime.ts +296 -0
  22. package/src/client/services.ts +349 -0
  23. package/src/client/sp00ky-client.test.ts +241 -0
  24. package/src/client/sp00ky-client.ts +482 -0
  25. package/src/index.ts +1 -0
  26. package/src/kernel/constants.test.ts +19 -0
  27. package/src/kernel/constants.ts +54 -0
  28. package/src/kernel/effects.test.ts +40 -0
  29. package/src/kernel/effects.ts +159 -0
  30. package/src/kernel/events.ts +55 -0
  31. package/src/kernel/interpreter.test.ts +116 -0
  32. package/src/kernel/interpreter.ts +143 -0
  33. package/src/kernel/saga.test.ts +89 -0
  34. package/src/kernel/saga.ts +73 -0
  35. package/src/modules/app-release/index.ts +13 -5
  36. package/src/modules/devtools/index.ts +10 -3
  37. package/src/modules/devtools/state-shape.test.ts +4 -1
  38. package/src/modules/feature-flag/index.ts +13 -5
  39. package/src/mutation/jobs.test.ts +25 -0
  40. package/src/mutation/jobs.ts +41 -0
  41. package/src/{modules/data → mutation}/mutation-id.test.ts +12 -0
  42. package/src/mutation/push.saga.test.ts +285 -0
  43. package/src/mutation/push.saga.ts +215 -0
  44. package/src/mutation/rows.test.ts +201 -0
  45. package/src/mutation/rows.ts +377 -0
  46. package/src/mutation/tray.saga.test.ts +63 -0
  47. package/src/mutation/tray.saga.ts +48 -0
  48. package/src/mutation/write.saga.test.ts +163 -0
  49. package/src/mutation/write.saga.ts +166 -0
  50. package/src/query/env.test.ts +23 -0
  51. package/src/query/env.ts +56 -0
  52. package/src/query/fetch.saga.test.ts +156 -0
  53. package/src/query/fetch.saga.ts +109 -0
  54. package/src/query/hash.test.ts +14 -0
  55. package/src/query/hash.ts +18 -0
  56. package/src/{modules/data → query}/id-set-plan.test.ts +12 -2
  57. package/src/query/lifecycle.saga.test.ts +122 -0
  58. package/src/query/lifecycle.saga.ts +101 -0
  59. package/src/query/materialize.saga.test.ts +82 -0
  60. package/src/query/materialize.saga.ts +75 -0
  61. package/src/query/materialize.test.ts +50 -0
  62. package/src/query/materialize.ts +44 -0
  63. package/src/query/membership.saga.test.ts +246 -0
  64. package/src/query/membership.saga.ts +208 -0
  65. package/src/query/membership.test.ts +118 -0
  66. package/src/query/membership.ts +158 -0
  67. package/src/query/register.saga.test.ts +251 -0
  68. package/src/query/register.saga.ts +190 -0
  69. package/src/query/render-set.test.ts +36 -0
  70. package/src/query/render-set.ts +60 -0
  71. package/src/query/sql.test.ts +62 -0
  72. package/src/query/sql.ts +79 -0
  73. package/src/{modules/data → query}/window-query.test.ts +18 -0
  74. package/src/{modules/data → query}/window-query.ts +2 -1
  75. package/src/services/database/connection-supervisor.test.ts +29 -0
  76. package/src/services/database/connection-supervisor.ts +7 -0
  77. package/src/services/database/database.responses.test.ts +43 -0
  78. package/src/services/database/database.ts +25 -10
  79. package/src/services/database/local-migrator.ts +2 -2
  80. package/src/services/database/plan-render.test.ts +1 -1
  81. package/src/services/database/sqlite-cache-engine.test.ts +4 -3
  82. package/src/services/database/sqlite-cache-engine.ts +2 -11
  83. package/src/services/database/sqlite-devtools-queries.integration.test.ts +1 -1
  84. package/src/services/tabs/protocol.ts +7 -3
  85. package/src/sp00ky.ts +5 -1854
  86. package/src/state/client-state.ts +184 -0
  87. package/src/state/lifecycle.test.ts +118 -0
  88. package/src/state/lifecycle.ts +86 -0
  89. package/src/state/reducers.test.ts +280 -0
  90. package/src/state/reducers.ts +476 -0
  91. package/src/state/selectors.test.ts +149 -0
  92. package/src/state/selectors.ts +184 -0
  93. package/src/sync/connection.saga.test.ts +86 -0
  94. package/src/sync/connection.saga.ts +85 -0
  95. package/src/sync/live.saga.test.ts +69 -0
  96. package/src/sync/live.saga.ts +49 -0
  97. package/src/sync/policy.health.test.ts +56 -0
  98. package/src/{modules/sync/utils.test.ts → sync/policy.test.ts} +31 -3
  99. package/src/{modules/sync/utils.ts → sync/policy.ts} +67 -5
  100. package/src/sync/poll.saga.test.ts +51 -0
  101. package/src/sync/poll.saga.ts +36 -0
  102. package/src/sync/tabs.saga.test.ts +74 -0
  103. package/src/sync/tabs.saga.ts +92 -0
  104. package/src/testing/adapters.ts +133 -0
  105. package/src/testing/build.ts +61 -0
  106. package/src/testing/fake-services.ts +34 -0
  107. package/src/testing/run-pure.test.ts +92 -0
  108. package/src/testing/run-pure.ts +128 -0
  109. package/src/testing/services.ts +17 -0
  110. package/src/types.ts +8 -7
  111. package/src/utils/utils.test.ts +2 -1
  112. package/vitest.config.ts +48 -0
  113. package/coverage/lcov-report/base.css +0 -224
  114. package/coverage/lcov-report/block-navigation.js +0 -87
  115. package/coverage/lcov-report/favicon.png +0 -0
  116. package/coverage/lcov-report/index.html +0 -476
  117. package/coverage/lcov-report/prettify.css +0 -1
  118. package/coverage/lcov-report/prettify.js +0 -2
  119. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  120. package/coverage/lcov-report/sorter.js +0 -210
  121. package/coverage/lcov-report/src/boot/auth-flip.saga.ts.html +0 -271
  122. package/coverage/lcov-report/src/boot/boot.saga.ts.html +0 -469
  123. package/coverage/lcov-report/src/boot/bucket-switch.saga.ts.html +0 -430
  124. package/coverage/lcov-report/src/boot/index.html +0 -161
  125. package/coverage/lcov-report/src/boot/preload.saga.ts.html +0 -178
  126. package/coverage/lcov-report/src/client/bucket-handle.ts.html +0 -766
  127. package/coverage/lcov-report/src/client/index.html +0 -161
  128. package/coverage/lcov-report/src/client/router.ts.html +0 -367
  129. package/coverage/lcov-report/src/client/runtime.ts.html +0 -940
  130. package/coverage/lcov-report/src/client/sp00ky-client.ts.html +0 -1531
  131. package/coverage/lcov-report/src/events/index.html +0 -116
  132. package/coverage/lcov-report/src/events/index.ts.html +0 -877
  133. package/coverage/lcov-report/src/index.html +0 -146
  134. package/coverage/lcov-report/src/index.ts.html +0 -202
  135. package/coverage/lcov-report/src/kernel/constants.ts.html +0 -247
  136. package/coverage/lcov-report/src/kernel/effects.ts.html +0 -562
  137. package/coverage/lcov-report/src/kernel/events.ts.html +0 -250
  138. package/coverage/lcov-report/src/kernel/index.html +0 -176
  139. package/coverage/lcov-report/src/kernel/interpreter.ts.html +0 -514
  140. package/coverage/lcov-report/src/kernel/saga.ts.html +0 -304
  141. package/coverage/lcov-report/src/modules/app-release/index.html +0 -116
  142. package/coverage/lcov-report/src/modules/app-release/index.ts.html +0 -712
  143. package/coverage/lcov-report/src/modules/auth/events/index.html +0 -116
  144. package/coverage/lcov-report/src/modules/auth/events/index.ts.html +0 -142
  145. package/coverage/lcov-report/src/modules/auth/index.html +0 -116
  146. package/coverage/lcov-report/src/modules/auth/index.ts.html +0 -1225
  147. package/coverage/lcov-report/src/modules/crdt/crdt-field.ts.html +0 -967
  148. package/coverage/lcov-report/src/modules/crdt/index.html +0 -146
  149. package/coverage/lcov-report/src/modules/crdt/index.ts.html +0 -1474
  150. package/coverage/lcov-report/src/modules/crdt/loro-loader.ts.html +0 -160
  151. package/coverage/lcov-report/src/modules/devtools/flags.ts.html +0 -1132
  152. package/coverage/lcov-report/src/modules/devtools/index.html +0 -161
  153. package/coverage/lcov-report/src/modules/devtools/index.ts.html +0 -2413
  154. package/coverage/lcov-report/src/modules/devtools/storage-info.ts.html +0 -589
  155. package/coverage/lcov-report/src/modules/devtools/versions.ts.html +0 -415
  156. package/coverage/lcov-report/src/modules/feature-flag/index.html +0 -116
  157. package/coverage/lcov-report/src/modules/feature-flag/index.ts.html +0 -1033
  158. package/coverage/lcov-report/src/modules/index.html +0 -116
  159. package/coverage/lcov-report/src/modules/ref-tables.ts.html +0 -349
  160. package/coverage/lcov-report/src/mutation/index.html +0 -191
  161. package/coverage/lcov-report/src/mutation/jobs.ts.html +0 -208
  162. package/coverage/lcov-report/src/mutation/mutation-id.ts.html +0 -190
  163. package/coverage/lcov-report/src/mutation/push.saga.ts.html +0 -730
  164. package/coverage/lcov-report/src/mutation/rows.ts.html +0 -1216
  165. package/coverage/lcov-report/src/mutation/tray.saga.ts.html +0 -229
  166. package/coverage/lcov-report/src/mutation/write.saga.ts.html +0 -583
  167. package/coverage/lcov-report/src/otel/index.html +0 -116
  168. package/coverage/lcov-report/src/otel/index.ts.html +0 -466
  169. package/coverage/lcov-report/src/query/env.ts.html +0 -253
  170. package/coverage/lcov-report/src/query/fetch.saga.ts.html +0 -412
  171. package/coverage/lcov-report/src/query/hash.ts.html +0 -139
  172. package/coverage/lcov-report/src/query/index.html +0 -281
  173. package/coverage/lcov-report/src/query/lifecycle.saga.ts.html +0 -388
  174. package/coverage/lcov-report/src/query/materialize.saga.ts.html +0 -310
  175. package/coverage/lcov-report/src/query/materialize.ts.html +0 -217
  176. package/coverage/lcov-report/src/query/membership.saga.ts.html +0 -709
  177. package/coverage/lcov-report/src/query/membership.ts.html +0 -559
  178. package/coverage/lcov-report/src/query/register.saga.ts.html +0 -655
  179. package/coverage/lcov-report/src/query/render-set.ts.html +0 -265
  180. package/coverage/lcov-report/src/query/sql.ts.html +0 -322
  181. package/coverage/lcov-report/src/query/window-query.ts.html +0 -670
  182. package/coverage/lcov-report/src/services/blobs/blob-cache.ts.html +0 -1894
  183. package/coverage/lcov-report/src/services/blobs/blob-manifest.ts.html +0 -766
  184. package/coverage/lcov-report/src/services/blobs/blob-store.ts.html +0 -1162
  185. package/coverage/lcov-report/src/services/blobs/index.html +0 -161
  186. package/coverage/lcov-report/src/services/blobs/index.ts.html +0 -295
  187. package/coverage/lcov-report/src/services/database/cache-engine.ts.html +0 -664
  188. package/coverage/lcov-report/src/services/database/connection-supervisor.ts.html +0 -1330
  189. package/coverage/lcov-report/src/services/database/database.ts.html +0 -658
  190. package/coverage/lcov-report/src/services/database/engine-factory.ts.html +0 -184
  191. package/coverage/lcov-report/src/services/database/errors.ts.html +0 -187
  192. package/coverage/lcov-report/src/services/database/events/index.html +0 -116
  193. package/coverage/lcov-report/src/services/database/events/index.ts.html +0 -184
  194. package/coverage/lcov-report/src/services/database/index.html +0 -386
  195. package/coverage/lcov-report/src/services/database/index.ts.html +0 -121
  196. package/coverage/lcov-report/src/services/database/local-migrator.ts.html +0 -703
  197. package/coverage/lcov-report/src/services/database/local.ts.html +0 -1633
  198. package/coverage/lcov-report/src/services/database/plan-render.ts.html +0 -409
  199. package/coverage/lcov-report/src/services/database/relation-resolver.ts.html +0 -682
  200. package/coverage/lcov-report/src/services/database/remote.ts.html +0 -1036
  201. package/coverage/lcov-report/src/services/database/sqlite-cache-engine.ts.html +0 -4132
  202. package/coverage/lcov-report/src/services/database/sqlite-lock-verify.ts.html +0 -220
  203. package/coverage/lcov-report/src/services/database/sqlite-open.ts.html +0 -577
  204. package/coverage/lcov-report/src/services/database/sqlite-plan-sql.ts.html +0 -499
  205. package/coverage/lcov-report/src/services/database/sqlite-select.ts.html +0 -478
  206. package/coverage/lcov-report/src/services/database/sqlite-transport.ts.html +0 -757
  207. package/coverage/lcov-report/src/services/database/surql-translate.ts.html +0 -1333
  208. package/coverage/lcov-report/src/services/database/surreal-cache-engine.ts.html +0 -568
  209. package/coverage/lcov-report/src/services/logger/index.html +0 -116
  210. package/coverage/lcov-report/src/services/logger/index.ts.html +0 -154
  211. package/coverage/lcov-report/src/services/persistence/index.html +0 -146
  212. package/coverage/lcov-report/src/services/persistence/localstorage.ts.html +0 -163
  213. package/coverage/lcov-report/src/services/persistence/resilient.ts.html +0 -208
  214. package/coverage/lcov-report/src/services/persistence/surrealdb.ts.html +0 -271
  215. package/coverage/lcov-report/src/services/stream-processor/index.html +0 -146
  216. package/coverage/lcov-report/src/services/stream-processor/index.ts.html +0 -3313
  217. package/coverage/lcov-report/src/services/stream-processor/permissions.ts.html +0 -244
  218. package/coverage/lcov-report/src/services/stream-processor/wasm-types.ts.html +0 -346
  219. package/coverage/lcov-report/src/services/tabs/broker-client.ts.html +0 -934
  220. package/coverage/lcov-report/src/services/tabs/coordinator.ts.html +0 -1984
  221. package/coverage/lcov-report/src/services/tabs/index.html +0 -191
  222. package/coverage/lcov-report/src/services/tabs/leader-locks.ts.html +0 -310
  223. package/coverage/lcov-report/src/services/tabs/protocol.ts.html +0 -871
  224. package/coverage/lcov-report/src/services/tabs/support.ts.html +0 -193
  225. package/coverage/lcov-report/src/services/tabs/tabs-broker-worker.ts.html +0 -2005
  226. package/coverage/lcov-report/src/sp00ky.ts.html +0 -100
  227. package/coverage/lcov-report/src/state/client-state.ts.html +0 -637
  228. package/coverage/lcov-report/src/state/index.html +0 -161
  229. package/coverage/lcov-report/src/state/lifecycle.ts.html +0 -343
  230. package/coverage/lcov-report/src/state/reducers.ts.html +0 -1471
  231. package/coverage/lcov-report/src/state/selectors.ts.html +0 -637
  232. package/coverage/lcov-report/src/sync/connection.saga.ts.html +0 -343
  233. package/coverage/lcov-report/src/sync/index.html +0 -176
  234. package/coverage/lcov-report/src/sync/live.saga.ts.html +0 -232
  235. package/coverage/lcov-report/src/sync/policy.ts.html +0 -1606
  236. package/coverage/lcov-report/src/sync/poll.saga.ts.html +0 -193
  237. package/coverage/lcov-report/src/sync/tabs.saga.ts.html +0 -361
  238. package/coverage/lcov-report/src/types.ts.html +0 -2419
  239. package/coverage/lcov-report/src/utils/blurhash.ts.html +0 -355
  240. package/coverage/lcov-report/src/utils/error-classification.ts.html +0 -244
  241. package/coverage/lcov-report/src/utils/index.html +0 -191
  242. package/coverage/lcov-report/src/utils/index.ts.html +0 -820
  243. package/coverage/lcov-report/src/utils/parser.ts.html +0 -313
  244. package/coverage/lcov-report/src/utils/semver.ts.html +0 -175
  245. package/coverage/lcov-report/src/utils/surql.ts.html +0 -667
  246. package/coverage/lcov.info +0 -19079
  247. package/src/modules/cache/cache.relay.test.ts +0 -95
  248. package/src/modules/cache/index.ts +0 -361
  249. package/src/modules/cache/types.ts +0 -19
  250. package/src/modules/data/data.activity.test.ts +0 -109
  251. package/src/modules/data/data.authority.test.ts +0 -122
  252. package/src/modules/data/data.heartbeat-wake.test.ts +0 -115
  253. package/src/modules/data/data.hydration.test.ts +0 -142
  254. package/src/modules/data/data.membership.test.ts +0 -623
  255. package/src/modules/data/data.notify-table.test.ts +0 -41
  256. package/src/modules/data/data.pending-ids.test.ts +0 -199
  257. package/src/modules/data/data.rebind.test.ts +0 -170
  258. package/src/modules/data/data.rematerialize.test.ts +0 -114
  259. package/src/modules/data/data.run.test.ts +0 -113
  260. package/src/modules/data/data.settled-writes.test.ts +0 -235
  261. package/src/modules/data/data.status.test.ts +0 -249
  262. package/src/modules/data/data.test.ts +0 -58
  263. package/src/modules/data/index.ts +0 -2745
  264. package/src/modules/sync/engine.ts +0 -250
  265. package/src/modules/sync/events/index.ts +0 -84
  266. package/src/modules/sync/index.ts +0 -3
  267. package/src/modules/sync/queue/index.ts +0 -2
  268. package/src/modules/sync/queue/queue-down.test.ts +0 -228
  269. package/src/modules/sync/queue/queue-down.ts +0 -184
  270. package/src/modules/sync/queue/queue-up.forwarded.test.ts +0 -164
  271. package/src/modules/sync/queue/queue-up.ts +0 -407
  272. package/src/modules/sync/scheduler.pause.test.ts +0 -109
  273. package/src/modules/sync/scheduler.retry.test.ts +0 -237
  274. package/src/modules/sync/scheduler.ts +0 -286
  275. package/src/modules/sync/sync.cleanup.test.ts +0 -116
  276. package/src/modules/sync/sync.health.test.ts +0 -167
  277. package/src/modules/sync/sync.heartbeat.test.ts +0 -80
  278. package/src/modules/sync/sync.list-ref-meta.test.ts +0 -193
  279. package/src/modules/sync/sync.live-removal.test.ts +0 -289
  280. package/src/modules/sync/sync.reconnect.test.ts +0 -145
  281. package/src/modules/sync/sync.rematerialize-after-fetch.test.ts +0 -93
  282. package/src/modules/sync/sync.settled-poll.test.ts +0 -58
  283. package/src/modules/sync/sync.subquery.test.ts +0 -82
  284. package/src/modules/sync/sync.tabs.test.ts +0 -249
  285. package/src/modules/sync/sync.ts +0 -2626
  286. package/src/sp00ky.auth-order.test.ts +0 -112
  287. package/src/sp00ky.init-query.test.ts +0 -183
  288. package/src/sp00ky.local-first.test.ts +0 -60
  289. /package/src/{modules/data → mutation}/mutation-id.ts +0 -0
@@ -1,1474 +0,0 @@
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">&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-neutral">&nbsp;</span>
536
- <span class="cline-any cline-neutral">&nbsp;</span>
537
- <span class="cline-any cline-neutral">&nbsp;</span>
538
- <span class="cline-any cline-neutral">&nbsp;</span>
539
- <span class="cline-any cline-neutral">&nbsp;</span>
540
- <span class="cline-any cline-neutral">&nbsp;</span>
541
- <span class="cline-any cline-neutral">&nbsp;</span>
542
- <span class="cline-any cline-neutral">&nbsp;</span>
543
- <span class="cline-any cline-neutral">&nbsp;</span>
544
- <span class="cline-any cline-neutral">&nbsp;</span>
545
- <span class="cline-any cline-neutral">&nbsp;</span>
546
- <span class="cline-any cline-neutral">&nbsp;</span>
547
- <span class="cline-any cline-neutral">&nbsp;</span>
548
- <span class="cline-any cline-neutral">&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-neutral">&nbsp;</span>
552
- <span class="cline-any cline-neutral">&nbsp;</span>
553
- <span class="cline-any cline-neutral">&nbsp;</span>
554
- <span class="cline-any cline-neutral">&nbsp;</span>
555
- <span class="cline-any cline-neutral">&nbsp;</span>
556
- <span class="cline-any cline-neutral">&nbsp;</span>
557
- <span class="cline-any cline-neutral">&nbsp;</span>
558
- <span class="cline-any cline-neutral">&nbsp;</span>
559
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
563
- <span class="cline-any cline-yes">9x</span>
564
- <span class="cline-any cline-neutral">&nbsp;</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-yes">9x</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-yes">9x</span>
572
- <span class="cline-any cline-neutral">&nbsp;</span>
573
- <span class="cline-any cline-neutral">&nbsp;</span>
574
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
579
- <span class="cline-any cline-yes">9x</span>
580
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
592
- <span class="cline-any cline-neutral">&nbsp;</span>
593
- <span class="cline-any cline-neutral">&nbsp;</span>
594
- <span class="cline-any cline-neutral">&nbsp;</span>
595
- <span class="cline-any cline-neutral">&nbsp;</span>
596
- <span class="cline-any cline-neutral">&nbsp;</span>
597
- <span class="cline-any cline-neutral">&nbsp;</span>
598
- <span class="cline-any cline-neutral">&nbsp;</span>
599
- <span class="cline-any cline-neutral">&nbsp;</span>
600
- <span class="cline-any cline-neutral">&nbsp;</span>
601
- <span class="cline-any cline-neutral">&nbsp;</span>
602
- <span class="cline-any cline-neutral">&nbsp;</span>
603
- <span class="cline-any cline-neutral">&nbsp;</span>
604
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
610
- <span class="cline-any cline-neutral">&nbsp;</span>
611
- <span class="cline-any cline-yes">4x</span>
612
- <span class="cline-any cline-neutral">&nbsp;</span>
613
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
624
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
632
- <span class="cline-any cline-neutral">&nbsp;</span>
633
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
640
- <span class="cline-any cline-no">&nbsp;</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">&nbsp;</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">&nbsp;</span>
651
- <span class="cline-any cline-neutral">&nbsp;</span>
652
- <span class="cline-any cline-neutral">&nbsp;</span>
653
- <span class="cline-any cline-neutral">&nbsp;</span>
654
- <span class="cline-any cline-neutral">&nbsp;</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">&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-neutral">&nbsp;</span>
663
- <span class="cline-any cline-neutral">&nbsp;</span>
664
- <span class="cline-any cline-neutral">&nbsp;</span>
665
- <span class="cline-any cline-neutral">&nbsp;</span>
666
- <span class="cline-any cline-neutral">&nbsp;</span>
667
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
679
- <span class="cline-any cline-yes">9x</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-neutral">&nbsp;</span>
683
- <span class="cline-any cline-neutral">&nbsp;</span>
684
- <span class="cline-any cline-neutral">&nbsp;</span>
685
- <span class="cline-any cline-neutral">&nbsp;</span>
686
- <span class="cline-any cline-neutral">&nbsp;</span>
687
- <span class="cline-any cline-neutral">&nbsp;</span>
688
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
699
- <span class="cline-any cline-no">&nbsp;</span>
700
- <span class="cline-any cline-no">&nbsp;</span>
701
- <span class="cline-any cline-no">&nbsp;</span>
702
- <span class="cline-any cline-no">&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-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">&nbsp;</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">&nbsp;</span>
716
- <span class="cline-any cline-neutral">&nbsp;</span>
717
- <span class="cline-any cline-neutral">&nbsp;</span>
718
- <span class="cline-any cline-neutral">&nbsp;</span>
719
- <span class="cline-any cline-yes">9x</span>
720
- <span class="cline-any cline-neutral">&nbsp;</span>
721
- <span class="cline-any cline-neutral">&nbsp;</span>
722
- <span class="cline-any cline-neutral">&nbsp;</span>
723
- <span class="cline-any cline-neutral">&nbsp;</span>
724
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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-yes">1x</span>
743
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</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">&nbsp;</span>
753
- <span class="cline-any cline-neutral">&nbsp;</span>
754
- <span class="cline-any cline-neutral">&nbsp;</span>
755
- <span class="cline-any cline-neutral">&nbsp;</span>
756
- <span class="cline-any cline-neutral">&nbsp;</span>
757
- <span class="cline-any cline-neutral">&nbsp;</span>
758
- <span class="cline-any cline-yes">9x</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-no">&nbsp;</span>
765
- <span class="cline-any cline-no">&nbsp;</span>
766
- <span class="cline-any cline-neutral">&nbsp;</span>
767
- <span class="cline-any cline-neutral">&nbsp;</span>
768
- <span class="cline-any cline-no">&nbsp;</span>
769
- <span class="cline-any cline-no">&nbsp;</span>
770
- <span class="cline-any cline-neutral">&nbsp;</span>
771
- <span class="cline-any cline-neutral">&nbsp;</span>
772
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
776
- <span class="cline-any cline-neutral">&nbsp;</span>
777
- <span class="cline-any cline-neutral">&nbsp;</span>
778
- <span class="cline-any cline-neutral">&nbsp;</span>
779
- <span class="cline-any cline-neutral">&nbsp;</span>
780
- <span class="cline-any cline-neutral">&nbsp;</span>
781
- <span class="cline-any cline-yes">12x</span>
782
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
787
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
792
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</span>
814
- <span class="cline-any cline-no">&nbsp;</span>
815
- <span class="cline-any cline-no">&nbsp;</span>
816
- <span class="cline-any cline-yes">11x</span>
817
- <span class="cline-any cline-neutral">&nbsp;</span>
818
- <span class="cline-any cline-yes">11x</span>
819
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
827
- <span class="cline-any cline-no">&nbsp;</span>
828
- <span class="cline-any cline-no">&nbsp;</span>
829
- <span class="cline-any cline-no">&nbsp;</span>
830
- <span class="cline-any cline-no">&nbsp;</span>
831
- <span class="cline-any cline-yes">12x</span>
832
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
841
- <span class="cline-any cline-neutral">&nbsp;</span>
842
- <span class="cline-any cline-neutral">&nbsp;</span>
843
- <span class="cline-any cline-neutral">&nbsp;</span>
844
- <span class="cline-any cline-neutral">&nbsp;</span>
845
- <span class="cline-any cline-yes">9x</span>
846
- <span class="cline-any cline-no">&nbsp;</span>
847
- <span class="cline-any cline-no">&nbsp;</span>
848
- <span class="cline-any cline-neutral">&nbsp;</span>
849
- <span class="cline-any cline-neutral">&nbsp;</span>
850
- <span class="cline-any cline-neutral">&nbsp;</span>
851
- <span class="cline-any cline-neutral">&nbsp;</span>
852
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</span>
871
- <span class="cline-any cline-no">&nbsp;</span>
872
- <span class="cline-any cline-no">&nbsp;</span>
873
- <span class="cline-any cline-no">&nbsp;</span>
874
- <span class="cline-any cline-no">&nbsp;</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">&nbsp;</span>
880
- <span class="cline-any cline-neutral">&nbsp;</span>
881
- <span class="cline-any cline-neutral">&nbsp;</span>
882
- <span class="cline-any cline-neutral">&nbsp;</span>
883
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
895
- <span class="cline-any cline-no">&nbsp;</span>
896
- <span class="cline-any cline-no">&nbsp;</span>
897
- <span class="cline-any cline-no">&nbsp;</span>
898
- <span class="cline-any cline-no">&nbsp;</span>
899
- <span class="cline-any cline-yes">7x</span>
900
- <span class="cline-any cline-neutral">&nbsp;</span>
901
- <span class="cline-any cline-neutral">&nbsp;</span>
902
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
908
- <span class="cline-any cline-neutral">&nbsp;</span>
909
- <span class="cline-any cline-neutral">&nbsp;</span>
910
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
915
- <span class="cline-any cline-neutral">&nbsp;</span>
916
- <span class="cline-any cline-neutral">&nbsp;</span>
917
- <span class="cline-any cline-neutral">&nbsp;</span>
918
- <span class="cline-any cline-neutral">&nbsp;</span>
919
- <span class="cline-any cline-neutral">&nbsp;</span>
920
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
924
- <span class="cline-any cline-no">&nbsp;</span>
925
- <span class="cline-any cline-no">&nbsp;</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">&nbsp;</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">&nbsp;</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">&nbsp;</span>
945
- <span class="cline-any cline-neutral">&nbsp;</span>
946
- <span class="cline-any cline-neutral">&nbsp;</span>
947
- <span class="cline-any cline-no">&nbsp;</span>
948
- <span class="cline-any cline-no">&nbsp;</span>
949
- <span class="cline-any cline-no">&nbsp;</span>
950
- <span class="cline-any cline-no">&nbsp;</span>
951
- <span class="cline-any cline-no">&nbsp;</span>
952
- <span class="cline-any cline-no">&nbsp;</span>
953
- <span class="cline-any cline-no">&nbsp;</span>
954
- <span class="cline-any cline-no">&nbsp;</span>
955
- <span class="cline-any cline-yes">1x</span>
956
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
961
- <span class="cline-any cline-neutral">&nbsp;</span>
962
- <span class="cline-any cline-neutral">&nbsp;</span>
963
- <span class="cline-any cline-neutral">&nbsp;</span>
964
- <span class="cline-any cline-neutral">&nbsp;</span>
965
- <span class="cline-any cline-neutral">&nbsp;</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">&nbsp;</span>
969
- <span class="cline-any cline-no">&nbsp;</span>
970
- <span class="cline-any cline-no">&nbsp;</span>
971
- <span class="cline-any cline-no">&nbsp;</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">&nbsp;</span>
975
- <span class="cline-any cline-no">&nbsp;</span>
976
- <span class="cline-any cline-no">&nbsp;</span>
977
- <span class="cline-any cline-no">&nbsp;</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">&nbsp;</span>
981
- <span class="cline-any cline-no">&nbsp;</span>
982
- <span class="cline-any cline-no">&nbsp;</span>
983
- <span class="cline-any cline-no">&nbsp;</span>
984
- <span class="cline-any cline-yes">9x</span>
985
- <span class="cline-any cline-no">&nbsp;</span>
986
- <span class="cline-any cline-no">&nbsp;</span>
987
- <span class="cline-any cline-neutral">&nbsp;</span>
988
- <span class="cline-any cline-no">&nbsp;</span>
989
- <span class="cline-any cline-no">&nbsp;</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">&nbsp;</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
- &nbsp;
1000
- export { CrdtField, cursorColorFromName, CURSOR_COLORS } from './crdt-field';
1001
- &nbsp;
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&lt;string, CrdtField&gt;();
1025
- // One LIVE subscription per parent table (e.g. "thread" → uuid).
1026
- private liveByTable = new Map&lt;string, Uuid&gt;();
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&lt;string, { promise: Promise&lt;void&gt;; generation: number }&gt;();
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&lt;string&gt;();
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&lt;() =&gt; void&gt; = [];
1040
- private logger: Logger;
1041
- // SurrealDB session id, used as the per-session key inside `_00_cursor`.
1042
- private sessionId: string = '';
1043
- &nbsp;
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
- &nbsp;
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 = () =&gt; {
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', () =&gt; {
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
- &nbsp;
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
- &nbsp;
1109
- private hasOpenFieldFor(table: string): boolean {
1110
- const prefix = `${table}:`;
1111
- return Array.from(this.fields.keys()).some((k) =&gt; k.startsWith(prefix));
1112
- }
1113
- &nbsp;
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
- &nbsp;
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&lt;CrdtField&gt; {
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
- &nbsp;
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
- &nbsp;
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&lt;[unknown]&gt;(
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
- &nbsp;
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
- &nbsp;
1174
- this.logger.info(
1175
- { key, hasInitialState: !!initialCrdtState, hasFallback: !!fallbackText, Category: 'sp00ky-client::CrdtManager::open' },
1176
- 'CrdtField opened'
1177
- );
1178
- &nbsp;
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
- &nbsp;
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
- &nbsp;
1192
- return crdtField;
1193
- }
1194
- &nbsp;
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
- &nbsp;
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
- &nbsp;
1210
- this.logger.debug(
1211
- { key, Category: 'sp00ky-client::CrdtManager::close' },
1212
- 'CrdtField closed'
1213
- );
1214
- }
1215
- &nbsp;
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
- &nbsp;
1234
- /** Ensure a single `LIVE SELECT * FROM &lt;table&gt;` is running, shared across
1235
- * every open CrdtField on `table`. */
1236
- private async ensureTableSubscription(table: string): Promise&lt;void&gt; {
1237
- if (this.liveByTable.has(table)) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
1238
- &nbsp;
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
- &nbsp;
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(() =&gt; {});
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 &amp;&amp; successor.generation === generation) <span class="branch-0 cbranch-no" title="branch not covered" >r</span>eturn successor.promise;</span>
1258
- }
1259
- &nbsp;
1260
- const start = (async () =&gt; {
1261
- try {
1262
- const [uuid] = await this.remote.query&lt;[Uuid]&gt;(
1263
- `LIVE SELECT * FROM ${table}`,
1264
- );
1265
- &nbsp;
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
- &nbsp;
1274
- const subscription = await this.remote.getClient().liveOf(uuid);
1275
- subscription.subscribe((message) =&gt; {
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' &amp;&amp; message.action !== 'UPDATE') return;</span>
1278
- <span class="cstat-no" title="statement not covered" > this.dispatchRow(table, message.value as Record&lt;string, unknown&gt;);</span>
1279
- });
1280
- &nbsp;
1281
- if (generation !== this.connectionGeneration) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
1282
- &nbsp;
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
- &nbsp;
1296
- this.pendingLive.set(table, { promise: start, generation });
1297
- try {
1298
- await start;
1299
- } finally {
1300
- this.pendingLive.delete(table);
1301
- }
1302
- }
1303
- &nbsp;
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&lt;string, unknown&gt;): 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
- &nbsp;
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&lt;string, unknown&gt;): 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
- &nbsp;
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
- &nbsp;
1329
- if (cursorsEnabled &amp;&amp; slice &amp;&amp; typeof slice === 'object') {
1330
- const cursors = (slice as { cursors?: unknown }).cursors;
1331
- if (cursors &amp;&amp; typeof cursors === 'object') {
1332
- for (const [sid, blob] of Object.entries(cursors as Record&lt;string, unknown&gt;)) <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' &amp;&amp; blob.length &gt; 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
- &nbsp;
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&lt;void&gt; {
1348
- try {
1349
- const recordId = parseRecordIdString(id);
1350
- const [row] = await this.remote.query&lt;[Record&lt;string, unknown&gt; | null]&gt;(
1351
- `SELECT * FROM ONLY $id`,
1352
- { id: recordId },
1353
- );
1354
- if (!row || typeof row !== 'object') return;
1355
- this.dispatchRow(table, row as Record&lt;string, unknown&gt;);
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
- &nbsp;
1364
- /** Schema lookup: does `&lt;table&gt;.&lt;field&gt;` 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) =&gt; t.name === table);
1368
- return !!tableSchema?.columns[field]?.cursor;
1369
- }
1370
- &nbsp;
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 =&gt; {
1376
- if (v instanceof Uint8Array) return v.length &gt; 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&lt;object&gt; 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) &amp;&amp; v.length &gt; 0 &amp;&amp; v.every((n) =&gt; typeof n === 'number')) {
1390
- return Uint8Array.from(v as number[]);
1391
- }
1392
- return undefined;
1393
- };
1394
- &nbsp;
1395
- if (cursorsEnabled) {
1396
- if (value &amp;&amp; typeof value === 'object' &amp;&amp; !(value instanceof Uint8Array)) {
1397
- return asBytes((value as { state?: unknown }).state);
1398
- }
1399
- return undefined;
1400
- }
1401
- return asBytes(value);
1402
- }
1403
- &nbsp;
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) =&gt; {</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
- &nbsp;
1420
- private makeKey(table: string, recordId: string, field: string): string {
1421
- return `${table}:${recordId}:${field}`;
1422
- }
1423
- &nbsp;
1424
- /**
1425
- * Throws if `&lt;table&gt;.&lt;field&gt;` 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) =&gt; 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) =&gt; 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
- &nbsp;</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
-