@bernierllc/email-sendgrid-plugin 1.3.0 → 1.4.0

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 (36) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/__tests__/converters/attachment-header-mapping.test.ts +337 -0
  3. package/coverage/clover.xml +395 -0
  4. package/coverage/coverage-final.json +12 -0
  5. package/coverage/lcov-report/api/errors.ts.html +232 -0
  6. package/coverage/lcov-report/api/index.html +146 -0
  7. package/coverage/lcov-report/api/rate-limiter.ts.html +322 -0
  8. package/coverage/lcov-report/api/sendgrid-client.ts.html +343 -0
  9. package/coverage/lcov-report/base.css +224 -0
  10. package/coverage/lcov-report/block-navigation.js +87 -0
  11. package/coverage/lcov-report/converters/index.html +131 -0
  12. package/coverage/lcov-report/converters/sender-converter.ts.html +208 -0
  13. package/coverage/lcov-report/converters/template-converter.ts.html +376 -0
  14. package/coverage/lcov-report/favicon.png +0 -0
  15. package/coverage/lcov-report/index.html +161 -0
  16. package/coverage/lcov-report/plugin/index.html +161 -0
  17. package/coverage/lcov-report/plugin/sender-manager.ts.html +583 -0
  18. package/coverage/lcov-report/plugin/sendgrid-plugin.ts.html +1033 -0
  19. package/coverage/lcov-report/plugin/sync-manager.ts.html +430 -0
  20. package/coverage/lcov-report/plugin/template-manager.ts.html +805 -0
  21. package/coverage/lcov-report/prettify.css +1 -0
  22. package/coverage/lcov-report/prettify.js +2 -0
  23. package/coverage/lcov-report/safety/index.html +131 -0
  24. package/coverage/lcov-report/safety/ownership-validator.ts.html +250 -0
  25. package/coverage/lcov-report/safety/prefix-enforcer.ts.html +196 -0
  26. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  27. package/coverage/lcov-report/sorter.js +210 -0
  28. package/coverage/lcov.info +734 -0
  29. package/dist/converters/sender-converter.d.ts +25 -1
  30. package/dist/converters/sender-converter.js +74 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/types/sendgrid-types.d.ts +26 -0
  33. package/package.json +15 -17
  34. package/src/converters/sender-converter.ts +98 -1
  35. package/src/index.ts +4 -1
  36. package/src/types/sendgrid-types.ts +29 -0
@@ -0,0 +1,805 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for plugin/template-manager.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">plugin</a> template-manager.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">49.23% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>32/65</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">28.57% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>4/14</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">53.84% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>7/13</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">49.23% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>32/65</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line low'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a>
145
+ <a name='L80'></a><a href='#L80'>80</a>
146
+ <a name='L81'></a><a href='#L81'>81</a>
147
+ <a name='L82'></a><a href='#L82'>82</a>
148
+ <a name='L83'></a><a href='#L83'>83</a>
149
+ <a name='L84'></a><a href='#L84'>84</a>
150
+ <a name='L85'></a><a href='#L85'>85</a>
151
+ <a name='L86'></a><a href='#L86'>86</a>
152
+ <a name='L87'></a><a href='#L87'>87</a>
153
+ <a name='L88'></a><a href='#L88'>88</a>
154
+ <a name='L89'></a><a href='#L89'>89</a>
155
+ <a name='L90'></a><a href='#L90'>90</a>
156
+ <a name='L91'></a><a href='#L91'>91</a>
157
+ <a name='L92'></a><a href='#L92'>92</a>
158
+ <a name='L93'></a><a href='#L93'>93</a>
159
+ <a name='L94'></a><a href='#L94'>94</a>
160
+ <a name='L95'></a><a href='#L95'>95</a>
161
+ <a name='L96'></a><a href='#L96'>96</a>
162
+ <a name='L97'></a><a href='#L97'>97</a>
163
+ <a name='L98'></a><a href='#L98'>98</a>
164
+ <a name='L99'></a><a href='#L99'>99</a>
165
+ <a name='L100'></a><a href='#L100'>100</a>
166
+ <a name='L101'></a><a href='#L101'>101</a>
167
+ <a name='L102'></a><a href='#L102'>102</a>
168
+ <a name='L103'></a><a href='#L103'>103</a>
169
+ <a name='L104'></a><a href='#L104'>104</a>
170
+ <a name='L105'></a><a href='#L105'>105</a>
171
+ <a name='L106'></a><a href='#L106'>106</a>
172
+ <a name='L107'></a><a href='#L107'>107</a>
173
+ <a name='L108'></a><a href='#L108'>108</a>
174
+ <a name='L109'></a><a href='#L109'>109</a>
175
+ <a name='L110'></a><a href='#L110'>110</a>
176
+ <a name='L111'></a><a href='#L111'>111</a>
177
+ <a name='L112'></a><a href='#L112'>112</a>
178
+ <a name='L113'></a><a href='#L113'>113</a>
179
+ <a name='L114'></a><a href='#L114'>114</a>
180
+ <a name='L115'></a><a href='#L115'>115</a>
181
+ <a name='L116'></a><a href='#L116'>116</a>
182
+ <a name='L117'></a><a href='#L117'>117</a>
183
+ <a name='L118'></a><a href='#L118'>118</a>
184
+ <a name='L119'></a><a href='#L119'>119</a>
185
+ <a name='L120'></a><a href='#L120'>120</a>
186
+ <a name='L121'></a><a href='#L121'>121</a>
187
+ <a name='L122'></a><a href='#L122'>122</a>
188
+ <a name='L123'></a><a href='#L123'>123</a>
189
+ <a name='L124'></a><a href='#L124'>124</a>
190
+ <a name='L125'></a><a href='#L125'>125</a>
191
+ <a name='L126'></a><a href='#L126'>126</a>
192
+ <a name='L127'></a><a href='#L127'>127</a>
193
+ <a name='L128'></a><a href='#L128'>128</a>
194
+ <a name='L129'></a><a href='#L129'>129</a>
195
+ <a name='L130'></a><a href='#L130'>130</a>
196
+ <a name='L131'></a><a href='#L131'>131</a>
197
+ <a name='L132'></a><a href='#L132'>132</a>
198
+ <a name='L133'></a><a href='#L133'>133</a>
199
+ <a name='L134'></a><a href='#L134'>134</a>
200
+ <a name='L135'></a><a href='#L135'>135</a>
201
+ <a name='L136'></a><a href='#L136'>136</a>
202
+ <a name='L137'></a><a href='#L137'>137</a>
203
+ <a name='L138'></a><a href='#L138'>138</a>
204
+ <a name='L139'></a><a href='#L139'>139</a>
205
+ <a name='L140'></a><a href='#L140'>140</a>
206
+ <a name='L141'></a><a href='#L141'>141</a>
207
+ <a name='L142'></a><a href='#L142'>142</a>
208
+ <a name='L143'></a><a href='#L143'>143</a>
209
+ <a name='L144'></a><a href='#L144'>144</a>
210
+ <a name='L145'></a><a href='#L145'>145</a>
211
+ <a name='L146'></a><a href='#L146'>146</a>
212
+ <a name='L147'></a><a href='#L147'>147</a>
213
+ <a name='L148'></a><a href='#L148'>148</a>
214
+ <a name='L149'></a><a href='#L149'>149</a>
215
+ <a name='L150'></a><a href='#L150'>150</a>
216
+ <a name='L151'></a><a href='#L151'>151</a>
217
+ <a name='L152'></a><a href='#L152'>152</a>
218
+ <a name='L153'></a><a href='#L153'>153</a>
219
+ <a name='L154'></a><a href='#L154'>154</a>
220
+ <a name='L155'></a><a href='#L155'>155</a>
221
+ <a name='L156'></a><a href='#L156'>156</a>
222
+ <a name='L157'></a><a href='#L157'>157</a>
223
+ <a name='L158'></a><a href='#L158'>158</a>
224
+ <a name='L159'></a><a href='#L159'>159</a>
225
+ <a name='L160'></a><a href='#L160'>160</a>
226
+ <a name='L161'></a><a href='#L161'>161</a>
227
+ <a name='L162'></a><a href='#L162'>162</a>
228
+ <a name='L163'></a><a href='#L163'>163</a>
229
+ <a name='L164'></a><a href='#L164'>164</a>
230
+ <a name='L165'></a><a href='#L165'>165</a>
231
+ <a name='L166'></a><a href='#L166'>166</a>
232
+ <a name='L167'></a><a href='#L167'>167</a>
233
+ <a name='L168'></a><a href='#L168'>168</a>
234
+ <a name='L169'></a><a href='#L169'>169</a>
235
+ <a name='L170'></a><a href='#L170'>170</a>
236
+ <a name='L171'></a><a href='#L171'>171</a>
237
+ <a name='L172'></a><a href='#L172'>172</a>
238
+ <a name='L173'></a><a href='#L173'>173</a>
239
+ <a name='L174'></a><a href='#L174'>174</a>
240
+ <a name='L175'></a><a href='#L175'>175</a>
241
+ <a name='L176'></a><a href='#L176'>176</a>
242
+ <a name='L177'></a><a href='#L177'>177</a>
243
+ <a name='L178'></a><a href='#L178'>178</a>
244
+ <a name='L179'></a><a href='#L179'>179</a>
245
+ <a name='L180'></a><a href='#L180'>180</a>
246
+ <a name='L181'></a><a href='#L181'>181</a>
247
+ <a name='L182'></a><a href='#L182'>182</a>
248
+ <a name='L183'></a><a href='#L183'>183</a>
249
+ <a name='L184'></a><a href='#L184'>184</a>
250
+ <a name='L185'></a><a href='#L185'>185</a>
251
+ <a name='L186'></a><a href='#L186'>186</a>
252
+ <a name='L187'></a><a href='#L187'>187</a>
253
+ <a name='L188'></a><a href='#L188'>188</a>
254
+ <a name='L189'></a><a href='#L189'>189</a>
255
+ <a name='L190'></a><a href='#L190'>190</a>
256
+ <a name='L191'></a><a href='#L191'>191</a>
257
+ <a name='L192'></a><a href='#L192'>192</a>
258
+ <a name='L193'></a><a href='#L193'>193</a>
259
+ <a name='L194'></a><a href='#L194'>194</a>
260
+ <a name='L195'></a><a href='#L195'>195</a>
261
+ <a name='L196'></a><a href='#L196'>196</a>
262
+ <a name='L197'></a><a href='#L197'>197</a>
263
+ <a name='L198'></a><a href='#L198'>198</a>
264
+ <a name='L199'></a><a href='#L199'>199</a>
265
+ <a name='L200'></a><a href='#L200'>200</a>
266
+ <a name='L201'></a><a href='#L201'>201</a>
267
+ <a name='L202'></a><a href='#L202'>202</a>
268
+ <a name='L203'></a><a href='#L203'>203</a>
269
+ <a name='L204'></a><a href='#L204'>204</a>
270
+ <a name='L205'></a><a href='#L205'>205</a>
271
+ <a name='L206'></a><a href='#L206'>206</a>
272
+ <a name='L207'></a><a href='#L207'>207</a>
273
+ <a name='L208'></a><a href='#L208'>208</a>
274
+ <a name='L209'></a><a href='#L209'>209</a>
275
+ <a name='L210'></a><a href='#L210'>210</a>
276
+ <a name='L211'></a><a href='#L211'>211</a>
277
+ <a name='L212'></a><a href='#L212'>212</a>
278
+ <a name='L213'></a><a href='#L213'>213</a>
279
+ <a name='L214'></a><a href='#L214'>214</a>
280
+ <a name='L215'></a><a href='#L215'>215</a>
281
+ <a name='L216'></a><a href='#L216'>216</a>
282
+ <a name='L217'></a><a href='#L217'>217</a>
283
+ <a name='L218'></a><a href='#L218'>218</a>
284
+ <a name='L219'></a><a href='#L219'>219</a>
285
+ <a name='L220'></a><a href='#L220'>220</a>
286
+ <a name='L221'></a><a href='#L221'>221</a>
287
+ <a name='L222'></a><a href='#L222'>222</a>
288
+ <a name='L223'></a><a href='#L223'>223</a>
289
+ <a name='L224'></a><a href='#L224'>224</a>
290
+ <a name='L225'></a><a href='#L225'>225</a>
291
+ <a name='L226'></a><a href='#L226'>226</a>
292
+ <a name='L227'></a><a href='#L227'>227</a>
293
+ <a name='L228'></a><a href='#L228'>228</a>
294
+ <a name='L229'></a><a href='#L229'>229</a>
295
+ <a name='L230'></a><a href='#L230'>230</a>
296
+ <a name='L231'></a><a href='#L231'>231</a>
297
+ <a name='L232'></a><a href='#L232'>232</a>
298
+ <a name='L233'></a><a href='#L233'>233</a>
299
+ <a name='L234'></a><a href='#L234'>234</a>
300
+ <a name='L235'></a><a href='#L235'>235</a>
301
+ <a name='L236'></a><a href='#L236'>236</a>
302
+ <a name='L237'></a><a href='#L237'>237</a>
303
+ <a name='L238'></a><a href='#L238'>238</a>
304
+ <a name='L239'></a><a href='#L239'>239</a>
305
+ <a name='L240'></a><a href='#L240'>240</a>
306
+ <a name='L241'></a><a href='#L241'>241</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
307
+ <span class="cline-any cline-neutral">&nbsp;</span>
308
+ <span class="cline-any cline-neutral">&nbsp;</span>
309
+ <span class="cline-any cline-neutral">&nbsp;</span>
310
+ <span class="cline-any cline-neutral">&nbsp;</span>
311
+ <span class="cline-any cline-neutral">&nbsp;</span>
312
+ <span class="cline-any cline-neutral">&nbsp;</span>
313
+ <span class="cline-any cline-neutral">&nbsp;</span>
314
+ <span class="cline-any cline-neutral">&nbsp;</span>
315
+ <span class="cline-any cline-yes">2x</span>
316
+ <span class="cline-any cline-neutral">&nbsp;</span>
317
+ <span class="cline-any cline-neutral">&nbsp;</span>
318
+ <span class="cline-any cline-neutral">&nbsp;</span>
319
+ <span class="cline-any cline-neutral">&nbsp;</span>
320
+ <span class="cline-any cline-neutral">&nbsp;</span>
321
+ <span class="cline-any cline-neutral">&nbsp;</span>
322
+ <span class="cline-any cline-neutral">&nbsp;</span>
323
+ <span class="cline-any cline-neutral">&nbsp;</span>
324
+ <span class="cline-any cline-neutral">&nbsp;</span>
325
+ <span class="cline-any cline-yes">2x</span>
326
+ <span class="cline-any cline-neutral">&nbsp;</span>
327
+ <span class="cline-any cline-yes">8x</span>
328
+ <span class="cline-any cline-yes">8x</span>
329
+ <span class="cline-any cline-yes">8x</span>
330
+ <span class="cline-any cline-yes">8x</span>
331
+ <span class="cline-any cline-yes">8x</span>
332
+ <span class="cline-any cline-neutral">&nbsp;</span>
333
+ <span class="cline-any cline-neutral">&nbsp;</span>
334
+ <span class="cline-any cline-neutral">&nbsp;</span>
335
+ <span class="cline-any cline-neutral">&nbsp;</span>
336
+ <span class="cline-any cline-neutral">&nbsp;</span>
337
+ <span class="cline-any cline-neutral">&nbsp;</span>
338
+ <span class="cline-any cline-neutral">&nbsp;</span>
339
+ <span class="cline-any cline-yes">1x</span>
340
+ <span class="cline-any cline-neutral">&nbsp;</span>
341
+ <span class="cline-any cline-neutral">&nbsp;</span>
342
+ <span class="cline-any cline-yes">1x</span>
343
+ <span class="cline-any cline-neutral">&nbsp;</span>
344
+ <span class="cline-any cline-neutral">&nbsp;</span>
345
+ <span class="cline-any cline-neutral">&nbsp;</span>
346
+ <span class="cline-any cline-neutral">&nbsp;</span>
347
+ <span class="cline-any cline-yes">1x</span>
348
+ <span class="cline-any cline-neutral">&nbsp;</span>
349
+ <span class="cline-any cline-neutral">&nbsp;</span>
350
+ <span class="cline-any cline-neutral">&nbsp;</span>
351
+ <span class="cline-any cline-neutral">&nbsp;</span>
352
+ <span class="cline-any cline-yes">1x</span>
353
+ <span class="cline-any cline-no">&nbsp;</span>
354
+ <span class="cline-any cline-neutral">&nbsp;</span>
355
+ <span class="cline-any cline-neutral">&nbsp;</span>
356
+ <span class="cline-any cline-neutral">&nbsp;</span>
357
+ <span class="cline-any cline-neutral">&nbsp;</span>
358
+ <span class="cline-any cline-neutral">&nbsp;</span>
359
+ <span class="cline-any cline-neutral">&nbsp;</span>
360
+ <span class="cline-any cline-yes">1x</span>
361
+ <span class="cline-any cline-yes">1x</span>
362
+ <span class="cline-any cline-neutral">&nbsp;</span>
363
+ <span class="cline-any cline-neutral">&nbsp;</span>
364
+ <span class="cline-any cline-yes">1x</span>
365
+ <span class="cline-any cline-neutral">&nbsp;</span>
366
+ <span class="cline-any cline-neutral">&nbsp;</span>
367
+ <span class="cline-any cline-yes">1x</span>
368
+ <span class="cline-any cline-neutral">&nbsp;</span>
369
+ <span class="cline-any cline-neutral">&nbsp;</span>
370
+ <span class="cline-any cline-neutral">&nbsp;</span>
371
+ <span class="cline-any cline-neutral">&nbsp;</span>
372
+ <span class="cline-any cline-neutral">&nbsp;</span>
373
+ <span class="cline-any cline-neutral">&nbsp;</span>
374
+ <span class="cline-any cline-neutral">&nbsp;</span>
375
+ <span class="cline-any cline-neutral">&nbsp;</span>
376
+ <span class="cline-any cline-yes">1x</span>
377
+ <span class="cline-any cline-neutral">&nbsp;</span>
378
+ <span class="cline-any cline-yes">1x</span>
379
+ <span class="cline-any cline-neutral">&nbsp;</span>
380
+ <span class="cline-any cline-neutral">&nbsp;</span>
381
+ <span class="cline-any cline-neutral">&nbsp;</span>
382
+ <span class="cline-any cline-neutral">&nbsp;</span>
383
+ <span class="cline-any cline-no">&nbsp;</span>
384
+ <span class="cline-any cline-neutral">&nbsp;</span>
385
+ <span class="cline-any cline-neutral">&nbsp;</span>
386
+ <span class="cline-any cline-no">&nbsp;</span>
387
+ <span class="cline-any cline-neutral">&nbsp;</span>
388
+ <span class="cline-any cline-neutral">&nbsp;</span>
389
+ <span class="cline-any cline-no">&nbsp;</span>
390
+ <span class="cline-any cline-no">&nbsp;</span>
391
+ <span class="cline-any cline-neutral">&nbsp;</span>
392
+ <span class="cline-any cline-neutral">&nbsp;</span>
393
+ <span class="cline-any cline-neutral">&nbsp;</span>
394
+ <span class="cline-any cline-neutral">&nbsp;</span>
395
+ <span class="cline-any cline-neutral">&nbsp;</span>
396
+ <span class="cline-any cline-neutral">&nbsp;</span>
397
+ <span class="cline-any cline-no">&nbsp;</span>
398
+ <span class="cline-any cline-neutral">&nbsp;</span>
399
+ <span class="cline-any cline-neutral">&nbsp;</span>
400
+ <span class="cline-any cline-neutral">&nbsp;</span>
401
+ <span class="cline-any cline-neutral">&nbsp;</span>
402
+ <span class="cline-any cline-neutral">&nbsp;</span>
403
+ <span class="cline-any cline-neutral">&nbsp;</span>
404
+ <span class="cline-any cline-neutral">&nbsp;</span>
405
+ <span class="cline-any cline-no">&nbsp;</span>
406
+ <span class="cline-any cline-neutral">&nbsp;</span>
407
+ <span class="cline-any cline-neutral">&nbsp;</span>
408
+ <span class="cline-any cline-neutral">&nbsp;</span>
409
+ <span class="cline-any cline-no">&nbsp;</span>
410
+ <span class="cline-any cline-neutral">&nbsp;</span>
411
+ <span class="cline-any cline-no">&nbsp;</span>
412
+ <span class="cline-any cline-neutral">&nbsp;</span>
413
+ <span class="cline-any cline-neutral">&nbsp;</span>
414
+ <span class="cline-any cline-neutral">&nbsp;</span>
415
+ <span class="cline-any cline-no">&nbsp;</span>
416
+ <span class="cline-any cline-no">&nbsp;</span>
417
+ <span class="cline-any cline-neutral">&nbsp;</span>
418
+ <span class="cline-any cline-neutral">&nbsp;</span>
419
+ <span class="cline-any cline-neutral">&nbsp;</span>
420
+ <span class="cline-any cline-neutral">&nbsp;</span>
421
+ <span class="cline-any cline-neutral">&nbsp;</span>
422
+ <span class="cline-any cline-neutral">&nbsp;</span>
423
+ <span class="cline-any cline-no">&nbsp;</span>
424
+ <span class="cline-any cline-no">&nbsp;</span>
425
+ <span class="cline-any cline-neutral">&nbsp;</span>
426
+ <span class="cline-any cline-neutral">&nbsp;</span>
427
+ <span class="cline-any cline-neutral">&nbsp;</span>
428
+ <span class="cline-any cline-no">&nbsp;</span>
429
+ <span class="cline-any cline-neutral">&nbsp;</span>
430
+ <span class="cline-any cline-no">&nbsp;</span>
431
+ <span class="cline-any cline-neutral">&nbsp;</span>
432
+ <span class="cline-any cline-neutral">&nbsp;</span>
433
+ <span class="cline-any cline-neutral">&nbsp;</span>
434
+ <span class="cline-any cline-no">&nbsp;</span>
435
+ <span class="cline-any cline-no">&nbsp;</span>
436
+ <span class="cline-any cline-neutral">&nbsp;</span>
437
+ <span class="cline-any cline-neutral">&nbsp;</span>
438
+ <span class="cline-any cline-no">&nbsp;</span>
439
+ <span class="cline-any cline-no">&nbsp;</span>
440
+ <span class="cline-any cline-neutral">&nbsp;</span>
441
+ <span class="cline-any cline-neutral">&nbsp;</span>
442
+ <span class="cline-any cline-neutral">&nbsp;</span>
443
+ <span class="cline-any cline-neutral">&nbsp;</span>
444
+ <span class="cline-any cline-neutral">&nbsp;</span>
445
+ <span class="cline-any cline-neutral">&nbsp;</span>
446
+ <span class="cline-any cline-no">&nbsp;</span>
447
+ <span class="cline-any cline-no">&nbsp;</span>
448
+ <span class="cline-any cline-neutral">&nbsp;</span>
449
+ <span class="cline-any cline-neutral">&nbsp;</span>
450
+ <span class="cline-any cline-neutral">&nbsp;</span>
451
+ <span class="cline-any cline-yes">7x</span>
452
+ <span class="cline-any cline-neutral">&nbsp;</span>
453
+ <span class="cline-any cline-neutral">&nbsp;</span>
454
+ <span class="cline-any cline-neutral">&nbsp;</span>
455
+ <span class="cline-any cline-yes">7x</span>
456
+ <span class="cline-any cline-yes">6x</span>
457
+ <span class="cline-any cline-neutral">&nbsp;</span>
458
+ <span class="cline-any cline-neutral">&nbsp;</span>
459
+ <span class="cline-any cline-neutral">&nbsp;</span>
460
+ <span class="cline-any cline-neutral">&nbsp;</span>
461
+ <span class="cline-any cline-neutral">&nbsp;</span>
462
+ <span class="cline-any cline-neutral">&nbsp;</span>
463
+ <span class="cline-any cline-yes">1x</span>
464
+ <span class="cline-any cline-neutral">&nbsp;</span>
465
+ <span class="cline-any cline-neutral">&nbsp;</span>
466
+ <span class="cline-any cline-yes">1x</span>
467
+ <span class="cline-any cline-yes">1x</span>
468
+ <span class="cline-any cline-neutral">&nbsp;</span>
469
+ <span class="cline-any cline-neutral">&nbsp;</span>
470
+ <span class="cline-any cline-neutral">&nbsp;</span>
471
+ <span class="cline-any cline-yes">1x</span>
472
+ <span class="cline-any cline-yes">1x</span>
473
+ <span class="cline-any cline-neutral">&nbsp;</span>
474
+ <span class="cline-any cline-neutral">&nbsp;</span>
475
+ <span class="cline-any cline-yes">1x</span>
476
+ <span class="cline-any cline-yes">1x</span>
477
+ <span class="cline-any cline-neutral">&nbsp;</span>
478
+ <span class="cline-any cline-neutral">&nbsp;</span>
479
+ <span class="cline-any cline-neutral">&nbsp;</span>
480
+ <span class="cline-any cline-neutral">&nbsp;</span>
481
+ <span class="cline-any cline-neutral">&nbsp;</span>
482
+ <span class="cline-any cline-neutral">&nbsp;</span>
483
+ <span class="cline-any cline-neutral">&nbsp;</span>
484
+ <span class="cline-any cline-yes">1x</span>
485
+ <span class="cline-any cline-neutral">&nbsp;</span>
486
+ <span class="cline-any cline-neutral">&nbsp;</span>
487
+ <span class="cline-any cline-neutral">&nbsp;</span>
488
+ <span class="cline-any cline-neutral">&nbsp;</span>
489
+ <span class="cline-any cline-neutral">&nbsp;</span>
490
+ <span class="cline-any cline-neutral">&nbsp;</span>
491
+ <span class="cline-any cline-neutral">&nbsp;</span>
492
+ <span class="cline-any cline-neutral">&nbsp;</span>
493
+ <span class="cline-any cline-yes">1x</span>
494
+ <span class="cline-any cline-neutral">&nbsp;</span>
495
+ <span class="cline-any cline-neutral">&nbsp;</span>
496
+ <span class="cline-any cline-neutral">&nbsp;</span>
497
+ <span class="cline-any cline-neutral">&nbsp;</span>
498
+ <span class="cline-any cline-yes">1x</span>
499
+ <span class="cline-any cline-no">&nbsp;</span>
500
+ <span class="cline-any cline-neutral">&nbsp;</span>
501
+ <span class="cline-any cline-neutral">&nbsp;</span>
502
+ <span class="cline-any cline-neutral">&nbsp;</span>
503
+ <span class="cline-any cline-neutral">&nbsp;</span>
504
+ <span class="cline-any cline-neutral">&nbsp;</span>
505
+ <span class="cline-any cline-neutral">&nbsp;</span>
506
+ <span class="cline-any cline-yes">1x</span>
507
+ <span class="cline-any cline-yes">1x</span>
508
+ <span class="cline-any cline-neutral">&nbsp;</span>
509
+ <span class="cline-any cline-neutral">&nbsp;</span>
510
+ <span class="cline-any cline-neutral">&nbsp;</span>
511
+ <span class="cline-any cline-no">&nbsp;</span>
512
+ <span class="cline-any cline-neutral">&nbsp;</span>
513
+ <span class="cline-any cline-neutral">&nbsp;</span>
514
+ <span class="cline-any cline-neutral">&nbsp;</span>
515
+ <span class="cline-any cline-neutral">&nbsp;</span>
516
+ <span class="cline-any cline-no">&nbsp;</span>
517
+ <span class="cline-any cline-no">&nbsp;</span>
518
+ <span class="cline-any cline-neutral">&nbsp;</span>
519
+ <span class="cline-any cline-neutral">&nbsp;</span>
520
+ <span class="cline-any cline-neutral">&nbsp;</span>
521
+ <span class="cline-any cline-neutral">&nbsp;</span>
522
+ <span class="cline-any cline-neutral">&nbsp;</span>
523
+ <span class="cline-any cline-neutral">&nbsp;</span>
524
+ <span class="cline-any cline-no">&nbsp;</span>
525
+ <span class="cline-any cline-neutral">&nbsp;</span>
526
+ <span class="cline-any cline-neutral">&nbsp;</span>
527
+ <span class="cline-any cline-neutral">&nbsp;</span>
528
+ <span class="cline-any cline-neutral">&nbsp;</span>
529
+ <span class="cline-any cline-no">&nbsp;</span>
530
+ <span class="cline-any cline-neutral">&nbsp;</span>
531
+ <span class="cline-any cline-neutral">&nbsp;</span>
532
+ <span class="cline-any cline-neutral">&nbsp;</span>
533
+ <span class="cline-any cline-no">&nbsp;</span>
534
+ <span class="cline-any cline-neutral">&nbsp;</span>
535
+ <span class="cline-any cline-no">&nbsp;</span>
536
+ <span class="cline-any cline-no">&nbsp;</span>
537
+ <span class="cline-any cline-neutral">&nbsp;</span>
538
+ <span class="cline-any cline-neutral">&nbsp;</span>
539
+ <span class="cline-any cline-no">&nbsp;</span>
540
+ <span class="cline-any cline-no">&nbsp;</span>
541
+ <span class="cline-any cline-neutral">&nbsp;</span>
542
+ <span class="cline-any cline-neutral">&nbsp;</span>
543
+ <span class="cline-any cline-no">&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></td><td class="text"><pre class="prettyprint lang-js">/*
547
+ Copyright (c) 2025 Bernier LLC
548
+ &nbsp;
549
+ This file is licensed to the client under a limited-use license.
550
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
551
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
552
+ */
553
+ &nbsp;
554
+ import { SendGridClient } from '../api/sendgrid-client';
555
+ import { SendGridApiError, TemplateNotFoundError, TemplateConflictError } from '../api/errors';
556
+ import { OwnershipValidator } from '../safety/ownership-validator';
557
+ import { PrefixEnforcer } from '../safety/prefix-enforcer';
558
+ import { TemplateConverter } from '../converters/template-converter';
559
+ import { Template, ProviderTemplate, TemplateVersion } from '../types/plugin-types';
560
+ import {
561
+ SendGridTemplateData,
562
+ SendGridTemplateVersion
563
+ } from '../types/sendgrid-types';
564
+ &nbsp;
565
+ export class TemplateManager {
566
+ constructor(
567
+ private readonly client: SendGridClient,
568
+ private readonly ownershipValidator: OwnershipValidator,
569
+ private readonly prefixEnforcer: PrefixEnforcer,
570
+ private readonly converter: TemplateConverter,
571
+ private readonly logger?: {
572
+ info: (message: string) =&gt; void;
573
+ error: (message: string) =&gt; void;
574
+ }
575
+ ) {}
576
+ &nbsp;
577
+ async createTemplate(template: Template): Promise&lt;string&gt; {
578
+ // Apply template naming convention
579
+ const templateName = this.prefixEnforcer.applyPrefix(template.name);
580
+ &nbsp;
581
+ // Create base template in SendGrid
582
+ const createRequest = {
583
+ name: templateName,
584
+ generation: 'dynamic'
585
+ };
586
+ &nbsp;
587
+ const response = await this.client.request('/templates', {
588
+ method: 'POST',
589
+ body: JSON.stringify(createRequest)
590
+ });
591
+ &nbsp;
592
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!this.client.isSuccessResponse(response)) {
593
+ <span class="cstat-no" title="statement not covered" > throw new SendGridApiError(</span>
594
+ `Failed to create template: ${response.statusCode}`,
595
+ response.statusCode,
596
+ response.body
597
+ );
598
+ }
599
+ &nbsp;
600
+ const templateData = this.client.parseResponseBody&lt;SendGridTemplateData&gt;(response);
601
+ const templateId = templateData.id;
602
+ &nbsp;
603
+ // Register as app-managed
604
+ this.ownershipValidator.registerTemplate(templateId);
605
+ &nbsp;
606
+ // Create initial version with content
607
+ await this.createTemplateVersion(templateId, {
608
+ name: 'v1.0.0',
609
+ subject: template.subject,
610
+ htmlContent: template.htmlContent,
611
+ textContent: template.textContent,
612
+ active: true
613
+ });
614
+ &nbsp;
615
+ // Log operation
616
+ this.logger?.info(`Created SendGrid template: ${templateName} (ID: ${templateId})`);
617
+ &nbsp;
618
+ return templateId;
619
+ }
620
+ &nbsp;
621
+ <span class="fstat-no" title="function not covered" > async </span>updateTemplate(providerTemplateId: string, template: Template): Promise&lt;void&gt; {
622
+ // Validate template ownership
623
+ <span class="cstat-no" title="statement not covered" > this.ownershipValidator.validateOwnership(providerTemplateId);</span>
624
+ &nbsp;
625
+ // Get current template state
626
+ const currentTemplate = <span class="cstat-no" title="statement not covered" >await this.getTemplate(providerTemplateId);</span>
627
+ &nbsp;
628
+ // Check for conflicts
629
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (this.hasConflicts(currentTemplate, template)) {</span>
630
+ <span class="cstat-no" title="statement not covered" > throw new TemplateConflictError(</span>
631
+ providerTemplateId,
632
+ this.detectConflicts(currentTemplate, template)
633
+ );
634
+ }
635
+ &nbsp;
636
+ // Create new version with updated content
637
+ <span class="cstat-no" title="statement not covered" > await this.createTemplateVersion(providerTemplateId, {</span>
638
+ name: `v${template.version}`,
639
+ subject: template.subject,
640
+ htmlContent: template.htmlContent,
641
+ textContent: template.textContent,
642
+ active: template.isActive
643
+ });
644
+ &nbsp;
645
+ <span class="cstat-no" title="statement not covered" > this.logger?.info(`Updated SendGrid template: ${providerTemplateId}`);</span>
646
+ }
647
+ &nbsp;
648
+ <span class="fstat-no" title="function not covered" > async </span>deleteTemplate(providerTemplateId: string): Promise&lt;void&gt; {
649
+ <span class="cstat-no" title="statement not covered" > this.ownershipValidator.validateOwnership(providerTemplateId);</span>
650
+ &nbsp;
651
+ const response = <span class="cstat-no" title="statement not covered" >await this.client.request(`/templates/${providerTemplateId}`, {</span>
652
+ method: 'DELETE'
653
+ });
654
+ &nbsp;
655
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!this.client.isSuccessResponse(response)) {</span>
656
+ <span class="cstat-no" title="statement not covered" > throw new SendGridApiError(</span>
657
+ `Failed to delete template: ${response.statusCode}`,
658
+ response.statusCode,
659
+ response.body
660
+ );
661
+ }
662
+ &nbsp;
663
+ <span class="cstat-no" title="statement not covered" > this.ownershipValidator.unregisterTemplate(providerTemplateId);</span>
664
+ <span class="cstat-no" title="statement not covered" > this.logger?.info(`Deleted SendGrid template: ${providerTemplateId}`);</span>
665
+ }
666
+ &nbsp;
667
+ <span class="fstat-no" title="function not covered" > async </span>getTemplate(providerTemplateId: string): Promise&lt;ProviderTemplate&gt; {
668
+ <span class="cstat-no" title="statement not covered" > this.ownershipValidator.validateOwnership(providerTemplateId);</span>
669
+ &nbsp;
670
+ const response = <span class="cstat-no" title="statement not covered" >await this.client.request(`/templates/${providerTemplateId}`, {</span>
671
+ method: 'GET'
672
+ });
673
+ &nbsp;
674
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (response.statusCode === 404) {</span>
675
+ <span class="cstat-no" title="statement not covered" > throw new TemplateNotFoundError(providerTemplateId);</span>
676
+ }
677
+ &nbsp;
678
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!this.client.isSuccessResponse(response)) {</span>
679
+ <span class="cstat-no" title="statement not covered" > throw new SendGridApiError(</span>
680
+ `Failed to get template: ${response.statusCode}`,
681
+ response.statusCode,
682
+ response.body
683
+ );
684
+ }
685
+ &nbsp;
686
+ const templateData = <span class="cstat-no" title="statement not covered" >this.client.parseResponseBody&lt;SendGridTemplateData&gt;(response);</span>
687
+ <span class="cstat-no" title="statement not covered" > return this.converter.convertToProviderTemplate(templateData);</span>
688
+ }
689
+ &nbsp;
690
+ async listTemplates(): Promise&lt;ProviderTemplate[]&gt; {
691
+ const response = await this.client.request('/templates?generations=dynamic', {
692
+ method: 'GET'
693
+ });
694
+ &nbsp;
695
+ if (!this.client.isSuccessResponse(response)) {
696
+ throw new SendGridApiError(
697
+ `Failed to list templates: ${response.statusCode}`,
698
+ response.statusCode,
699
+ response.body
700
+ );
701
+ }
702
+ &nbsp;
703
+ const data = this.client.parseResponseBody&lt;{ result: SendGridTemplateData[] }&gt;(response);
704
+ &nbsp;
705
+ // Filter to only app-managed templates
706
+ const appTemplates = data.result.filter(template =&gt;
707
+ this.prefixEnforcer.hasPrefix(template.name)
708
+ );
709
+ &nbsp;
710
+ // Register all app templates
711
+ appTemplates.forEach(template =&gt; {
712
+ this.ownershipValidator.registerTemplate(template.id);
713
+ });
714
+ &nbsp;
715
+ return appTemplates.map(template =&gt;
716
+ this.converter.convertToProviderTemplate(template)
717
+ );
718
+ }
719
+ &nbsp;
720
+ async createTemplateVersion(
721
+ templateId: string,
722
+ version: TemplateVersion
723
+ ): Promise&lt;string&gt; {
724
+ const versionRequest = {
725
+ template_id: templateId,
726
+ name: version.name,
727
+ subject: version.subject,
728
+ html_content: version.htmlContent,
729
+ plain_content: version.textContent || '',
730
+ active: version.active ? 1 : <span class="branch-1 cbranch-no" title="branch not covered" >0</span>
731
+ };
732
+ &nbsp;
733
+ const response = await this.client.request(`/templates/${templateId}/versions`, {
734
+ method: 'POST',
735
+ body: JSON.stringify(versionRequest)
736
+ });
737
+ &nbsp;
738
+ <span class="missing-if-branch" title="if path not taken" >I</span>if (!this.client.isSuccessResponse(response)) {
739
+ <span class="cstat-no" title="statement not covered" > throw new SendGridApiError(</span>
740
+ `Failed to create template version: ${response.statusCode}`,
741
+ response.statusCode,
742
+ response.body
743
+ );
744
+ }
745
+ &nbsp;
746
+ const versionData = this.client.parseResponseBody&lt;SendGridTemplateVersion&gt;(response);
747
+ return versionData.id;
748
+ }
749
+ &nbsp;
750
+ <span class="fstat-no" title="function not covered" > async </span>activateTemplateVersion(templateId: string, versionId: string): Promise&lt;void&gt; {
751
+ const response = <span class="cstat-no" title="statement not covered" >await this.client.request(</span>
752
+ `/templates/${templateId}/versions/${versionId}/activate`,
753
+ { method: 'POST' }
754
+ );
755
+ &nbsp;
756
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (!this.client.isSuccessResponse(response)) {</span>
757
+ <span class="cstat-no" title="statement not covered" > throw new SendGridApiError(</span>
758
+ `Failed to activate template version: ${response.statusCode}`,
759
+ response.statusCode,
760
+ response.body
761
+ );
762
+ }
763
+ &nbsp;
764
+ <span class="cstat-no" title="statement not covered" > this.logger?.info(`Activated template version: ${templateId}/${versionId}`);</span>
765
+ }
766
+ &nbsp;
767
+ private <span class="fstat-no" title="function not covered" >hasConflicts(</span>current: ProviderTemplate, updated: Template): boolean {
768
+ // Simple conflict detection - can be enhanced
769
+ <span class="cstat-no" title="statement not covered" > return current.updatedAt &gt; updated.updatedAt;</span>
770
+ }
771
+ &nbsp;
772
+ private <span class="fstat-no" title="function not covered" >detectConflicts(</span>current: ProviderTemplate, updated: Template): string[] {
773
+ const conflicts: string[] = <span class="cstat-no" title="statement not covered" >[];</span>
774
+ &nbsp;
775
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (current.subject !== updated.subject) {</span>
776
+ <span class="cstat-no" title="statement not covered" > conflicts.push('subject modified');</span>
777
+ }
778
+ &nbsp;
779
+ <span class="cstat-no" title="statement not covered" > <span class="missing-if-branch" title="if path not taken" >I</span>if (current.htmlContent !== updated.htmlContent) {</span>
780
+ <span class="cstat-no" title="statement not covered" > conflicts.push('HTML content modified');</span>
781
+ }
782
+ &nbsp;
783
+ <span class="cstat-no" title="statement not covered" > return conflicts;</span>
784
+ }
785
+ }
786
+ &nbsp;</pre></td></tr></table></pre>
787
+
788
+ <div class='push'></div><!-- for sticky footer -->
789
+ </div><!-- /wrapper -->
790
+ <div class='footer quiet pad2 space-top1 center small'>
791
+ Code coverage generated by
792
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
793
+ at 2025-11-02T03:45:26.083Z
794
+ </div>
795
+ <script src="../prettify.js"></script>
796
+ <script>
797
+ window.onload = function () {
798
+ prettyPrint();
799
+ };
800
+ </script>
801
+ <script src="../sorter.js"></script>
802
+ <script src="../block-navigation.js"></script>
803
+ </body>
804
+ </html>
805
+