@fedify/fedify 2.3.0-dev.1114 → 2.3.0-dev.1131

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 (69) hide show
  1. package/dist/{builder-YlEusQth.mjs → builder-DckAhD27.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-cSUMk2da.d.ts → context-Cq18Gplu.d.ts} +3 -208
  6. package/dist/{context-Ch-ZLyTQ.d.cts → context-tc6VOOOL.d.cts} +3 -208
  7. package/dist/{deno-CF3jMgip.mjs → deno--CS-SBS9.mjs} +1 -1
  8. package/dist/{docloader-BENj6vQ4.mjs → docloader-k6huZLQL.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.d.mts +2 -0
  13. package/dist/federation/metrics.test.mjs +335 -0
  14. package/dist/federation/middleware.test.mjs +444 -6
  15. package/dist/federation/mod.cjs +1 -1
  16. package/dist/federation/mod.d.cts +3 -2
  17. package/dist/federation/mod.d.ts +3 -2
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/send.test.mjs +3 -3
  20. package/dist/federation/webfinger.test.mjs +1 -1
  21. package/dist/{http-CKCgOPkX.cjs → http-CJfvRL7D.cjs} +352 -22
  22. package/dist/{http-BmOZYc-8.mjs → http-IywnQdiX.mjs} +7 -5
  23. package/dist/{http-D6LP89UO.d.ts → http-VyDTd4G3.d.cts} +8 -1
  24. package/dist/{http-CpzZ9zsb.js → http-cqujdCRz.js} +323 -23
  25. package/dist/{http-D6aw3j2U.d.cts → http-lf8Hsd91.d.ts} +8 -1
  26. package/dist/{key-B4I8H5Lc.mjs → key-Df3tMleh.mjs} +42 -17
  27. package/dist/{kv-cache-DY-XWOqM.cjs → kv-cache-L0SMQkcd.cjs} +19 -2
  28. package/dist/{kv-cache-Wc5ezcVW.js → kv-cache-pEejzYq4.js} +19 -2
  29. package/dist/{kv-cache-DihufyAQ.mjs → kv-cache-q9Ec2ryS.mjs} +19 -1
  30. package/dist/{ld-B5D5THhl.mjs → ld-BGwiJpl3.mjs} +3 -3
  31. package/dist/{metrics-ek3ilf6c.mjs → metrics-BTOMkW8C.mjs} +280 -5
  32. package/dist/{middleware-EqTYPG4F.cjs → middleware-B2rtdpFV.cjs} +75 -28
  33. package/dist/{middleware-DlcecZMq.mjs → middleware-BB0IbDow.mjs} +84 -37
  34. package/dist/{middleware-EI7OU6BR.mjs → middleware-Dnql59Y8.mjs} +1 -1
  35. package/dist/{middleware-CuZbBw-N.js → middleware-DtOddSVg.js} +75 -28
  36. package/dist/{mod-BDhgfjP7.d.cts → mod-B0hW12_O.d.cts} +1 -1
  37. package/dist/{mod-B-Lin9Sy.d.ts → mod-COIAjwRS.d.ts} +1 -1
  38. package/dist/{mod-C6E8rkcz.d.ts → mod-CajNYYkt.d.ts} +1 -1
  39. package/dist/{mod-DLrRb0dx.d.ts → mod-DFvNJcNb.d.ts} +54 -3
  40. package/dist/{mod-P9tE2WmM.d.cts → mod-DnzgcPcy.d.cts} +1 -1
  41. package/dist/{mod-BR_BB0bh.d.cts → mod-yvIXFAEi.d.cts} +54 -3
  42. package/dist/mod.cjs +4 -4
  43. package/dist/mod.d.cts +6 -5
  44. package/dist/mod.d.ts +6 -5
  45. package/dist/mod.js +4 -4
  46. package/dist/mq-D-nlpY04.d.ts +208 -0
  47. package/dist/mq-D8uSFzxe.d.cts +208 -0
  48. package/dist/nodeinfo/handler.test.mjs +1 -1
  49. package/dist/{owner-DO810N24.mjs → owner-CIt4hvmM.mjs} +2 -2
  50. package/dist/{proof-DIoqrKnX.cjs → proof-B1_u25UV.cjs} +1 -1
  51. package/dist/{proof-BgfyWv7b.mjs → proof-BYlrRSmZ.mjs} +3 -3
  52. package/dist/{proof-Vd8-1EWh.js → proof-DMGIjHYH.js} +1 -1
  53. package/dist/{send-CAYXdUTk.mjs → send-DJFpze7B.mjs} +3 -3
  54. package/dist/sig/http.test.mjs +6 -2
  55. package/dist/sig/key.test.mjs +99 -2
  56. package/dist/sig/ld.test.mjs +2 -2
  57. package/dist/sig/mod.cjs +2 -2
  58. package/dist/sig/mod.d.cts +2 -2
  59. package/dist/sig/mod.d.ts +2 -2
  60. package/dist/sig/mod.js +2 -2
  61. package/dist/sig/owner.test.mjs +1 -1
  62. package/dist/sig/proof.test.mjs +1 -1
  63. package/dist/utils/docloader.test.mjs +2 -2
  64. package/dist/utils/kv-cache.test.mjs +67 -2
  65. package/dist/utils/mod.cjs +1 -1
  66. package/dist/utils/mod.d.cts +1 -1
  67. package/dist/utils/mod.d.ts +1 -1
  68. package/dist/utils/mod.js +1 -1
  69. package/package.json +6 -6
@@ -0,0 +1,335 @@
1
+ import "@js-temporal/polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
+ import "../std__assert-BTEgfoJo.mjs";
6
+ import { t as assertRejects } from "../assert_rejects-DQP-q39h.mjs";
7
+ import { a as instrumentDocumentLoader, c as recordDocumentCache, d as recordInboxActivity, f as recordKeyLookup, l as recordDocumentFetch, m as recordOutboxEnqueue, p as recordOutboxActivity, t as classifyFetchError, u as recordFanoutRecipients } from "../metrics-BTOMkW8C.mjs";
8
+ import { createTestMeterProvider, test } from "@fedify/fixture";
9
+ import { FetchError } from "@fedify/vocab-runtime";
10
+ //#region src/federation/metrics.test.ts
11
+ const noopQueue = {
12
+ enqueue() {
13
+ return Promise.resolve();
14
+ },
15
+ listen() {
16
+ return Promise.resolve();
17
+ }
18
+ };
19
+ test("recordFanoutRecipients() records the recipient count with activity type", () => {
20
+ const [meterProvider, recorder] = createTestMeterProvider();
21
+ recordFanoutRecipients(meterProvider, 7, "https://www.w3.org/ns/activitystreams#Create");
22
+ const measurements = recorder.getMeasurements("activitypub.fanout.recipients");
23
+ assertEquals(measurements.length, 1);
24
+ assertEquals(measurements[0].type, "histogram");
25
+ assertEquals(measurements[0].value, 7);
26
+ assertEquals(measurements[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
27
+ });
28
+ test("recordFanoutRecipients() omits activity type when unknown", () => {
29
+ const [meterProvider, recorder] = createTestMeterProvider();
30
+ recordFanoutRecipients(meterProvider, 0);
31
+ const measurements = recorder.getMeasurements("activitypub.fanout.recipients");
32
+ assertEquals(measurements.length, 1);
33
+ assertEquals(measurements[0].value, 0);
34
+ assertEquals("activitypub.activity.type" in measurements[0].attributes, false);
35
+ });
36
+ test("recordInboxActivity() records counter with result and activity type", () => {
37
+ const [meterProvider, recorder] = createTestMeterProvider();
38
+ for (const result of [
39
+ "queued",
40
+ "processed",
41
+ "retried",
42
+ "rejected",
43
+ "abandoned"
44
+ ]) recordInboxActivity(meterProvider, result, "https://www.w3.org/ns/activitystreams#Follow");
45
+ const measurements = recorder.getMeasurements("activitypub.inbox.activity");
46
+ assertEquals(measurements.length, 5);
47
+ for (const m of measurements) {
48
+ assertEquals(m.type, "counter");
49
+ assertEquals(m.value, 1);
50
+ assertEquals(m.attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Follow");
51
+ }
52
+ assertEquals(measurements.map((m) => m.attributes["activitypub.processing.result"]), [
53
+ "queued",
54
+ "processed",
55
+ "retried",
56
+ "rejected",
57
+ "abandoned"
58
+ ]);
59
+ });
60
+ test("recordInboxActivity() omits activity type when unknown", () => {
61
+ const [meterProvider, recorder] = createTestMeterProvider();
62
+ recordInboxActivity(meterProvider, "rejected");
63
+ const measurements = recorder.getMeasurements("activitypub.inbox.activity");
64
+ assertEquals(measurements.length, 1);
65
+ assertEquals(measurements[0].attributes["activitypub.processing.result"], "rejected");
66
+ assertEquals("activitypub.activity.type" in measurements[0].attributes, false);
67
+ });
68
+ test("recordOutboxEnqueue() also records activitypub.outbox.activity{queued} on initial enqueue", () => {
69
+ const [meterProvider, recorder] = createTestMeterProvider();
70
+ recordOutboxEnqueue(meterProvider, noopQueue, {
71
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
72
+ attempt: 0
73
+ });
74
+ const queued = recorder.getMeasurements("activitypub.outbox.activity");
75
+ assertEquals(queued.length, 1);
76
+ assertEquals(queued[0].type, "counter");
77
+ assertEquals(queued[0].attributes["activitypub.processing.result"], "queued");
78
+ assertEquals(queued[0].attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Create");
79
+ });
80
+ test("recordOutboxEnqueue() does not record outbox.activity{queued} on retry enqueues", () => {
81
+ const [meterProvider, recorder] = createTestMeterProvider();
82
+ recordOutboxEnqueue(meterProvider, noopQueue, {
83
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
84
+ attempt: 1
85
+ });
86
+ assertEquals(recorder.getMeasurements("activitypub.outbox.activity").length, 0);
87
+ });
88
+ test("recordOutboxActivity() records counter with result and activity type", () => {
89
+ const [meterProvider, recorder] = createTestMeterProvider();
90
+ for (const result of [
91
+ "queued",
92
+ "retried",
93
+ "abandoned"
94
+ ]) recordOutboxActivity(meterProvider, result, "https://www.w3.org/ns/activitystreams#Announce");
95
+ const measurements = recorder.getMeasurements("activitypub.outbox.activity");
96
+ assertEquals(measurements.length, 3);
97
+ for (const m of measurements) {
98
+ assertEquals(m.type, "counter");
99
+ assertEquals(m.value, 1);
100
+ assertEquals(m.attributes["activitypub.activity.type"], "https://www.w3.org/ns/activitystreams#Announce");
101
+ }
102
+ assertEquals(measurements.map((m) => m.attributes["activitypub.processing.result"]), [
103
+ "queued",
104
+ "retried",
105
+ "abandoned"
106
+ ]);
107
+ });
108
+ test("recordKeyLookup() records counter and duration with all attributes", () => {
109
+ const [meterProvider, recorder] = createTestMeterProvider();
110
+ recordKeyLookup(meterProvider, {
111
+ durationMs: 42,
112
+ result: "fetched",
113
+ remoteUrl: new URL("https://example.com/users/alice#main-key"),
114
+ cacheEnabled: true,
115
+ statusCode: 200
116
+ });
117
+ const counters = recorder.getMeasurements("activitypub.key.lookup");
118
+ assertEquals(counters.length, 1);
119
+ assertEquals(counters[0].type, "counter");
120
+ assertEquals(counters[0].value, 1);
121
+ assertEquals(counters[0].attributes["activitypub.lookup.kind"], "public_key");
122
+ assertEquals(counters[0].attributes["activitypub.lookup.result"], "fetched");
123
+ assertEquals(counters[0].attributes["activitypub.remote.host"], "example.com");
124
+ assertEquals(counters[0].attributes["activitypub.cache.enabled"], true);
125
+ assertEquals(counters[0].attributes["http.response.status_code"], 200);
126
+ const durations = recorder.getMeasurements("activitypub.key.lookup.duration");
127
+ assertEquals(durations.length, 1);
128
+ assertEquals(durations[0].type, "histogram");
129
+ assertEquals(durations[0].value, 42);
130
+ assertEquals(durations[0].attributes["activitypub.lookup.kind"], "public_key");
131
+ assertEquals(durations[0].attributes["activitypub.lookup.result"], "fetched");
132
+ assertEquals(durations[0].attributes["activitypub.remote.host"], "example.com");
133
+ assertEquals(durations[0].attributes["activitypub.cache.enabled"], true);
134
+ assertEquals(durations[0].attributes["http.response.status_code"], 200);
135
+ });
136
+ test("recordKeyLookup() omits optional attributes when not provided", () => {
137
+ const [meterProvider, recorder] = createTestMeterProvider();
138
+ recordKeyLookup(meterProvider, {
139
+ durationMs: 0,
140
+ result: "error",
141
+ cacheEnabled: false
142
+ });
143
+ const counter = recorder.getMeasurement("activitypub.key.lookup");
144
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "error");
145
+ assertEquals(counter?.attributes["activitypub.cache.enabled"], false);
146
+ assertEquals("activitypub.remote.host" in counter.attributes, false);
147
+ assertEquals("http.response.status_code" in counter.attributes, false);
148
+ const duration = recorder.getMeasurement("activitypub.key.lookup.duration");
149
+ assertEquals("activitypub.remote.host" in duration.attributes, false);
150
+ assertEquals("http.response.status_code" in duration.attributes, false);
151
+ });
152
+ test("recordDocumentFetch() records counter and duration with all attributes", () => {
153
+ const [meterProvider, recorder] = createTestMeterProvider();
154
+ recordDocumentFetch(meterProvider, {
155
+ durationMs: 123,
156
+ kind: "object",
157
+ result: "not_found",
158
+ remoteUrl: new URL("https://remote.test/objects/123"),
159
+ cacheEnabled: true,
160
+ statusCode: 404
161
+ });
162
+ const counter = recorder.getMeasurement("activitypub.document.fetch");
163
+ assertEquals(counter?.type, "counter");
164
+ assertEquals(counter?.value, 1);
165
+ assertEquals(counter?.attributes["activitypub.lookup.kind"], "object");
166
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "not_found");
167
+ assertEquals(counter?.attributes["activitypub.remote.host"], "remote.test");
168
+ assertEquals(counter?.attributes["activitypub.cache.enabled"], true);
169
+ assertEquals(counter?.attributes["http.response.status_code"], 404);
170
+ const duration = recorder.getMeasurement("activitypub.document.fetch.duration");
171
+ assertEquals(duration?.type, "histogram");
172
+ assertEquals(duration?.value, 123);
173
+ assertEquals(duration?.attributes["activitypub.lookup.kind"], "object");
174
+ assertEquals(duration?.attributes["activitypub.lookup.result"], "not_found");
175
+ });
176
+ test("recordDocumentFetch() omits optional attributes when not provided", () => {
177
+ const [meterProvider, recorder] = createTestMeterProvider();
178
+ recordDocumentFetch(meterProvider, {
179
+ durationMs: 5,
180
+ kind: "context",
181
+ result: "fetched"
182
+ });
183
+ const counter = recorder.getMeasurement("activitypub.document.fetch");
184
+ assertEquals(counter?.attributes["activitypub.lookup.kind"], "context");
185
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "fetched");
186
+ assertEquals("activitypub.remote.host" in counter.attributes, false);
187
+ assertEquals("activitypub.cache.enabled" in counter.attributes, false);
188
+ assertEquals("http.response.status_code" in counter.attributes, false);
189
+ assertEquals(recorder.getMeasurement("activitypub.document.fetch.duration")?.value, 5);
190
+ });
191
+ test("recordDocumentCache() records hit and miss as a counter", () => {
192
+ const [meterProvider, recorder] = createTestMeterProvider();
193
+ recordDocumentCache(meterProvider, {
194
+ kind: "object",
195
+ result: "hit",
196
+ remoteUrl: new URL("https://remote.test/objects/1")
197
+ });
198
+ recordDocumentCache(meterProvider, {
199
+ kind: "context",
200
+ result: "miss",
201
+ remoteUrl: new URL("https://w3id.org/security/v1")
202
+ });
203
+ const measurements = recorder.getMeasurements("activitypub.document.cache");
204
+ assertEquals(measurements.length, 2);
205
+ for (const m of measurements) {
206
+ assertEquals(m.type, "counter");
207
+ assertEquals(m.value, 1);
208
+ }
209
+ assertEquals(measurements[0].attributes["activitypub.lookup.kind"], "object");
210
+ assertEquals(measurements[0].attributes["activitypub.lookup.result"], "hit");
211
+ assertEquals(measurements[0].attributes["activitypub.remote.host"], "remote.test");
212
+ assertEquals(measurements[1].attributes["activitypub.lookup.kind"], "context");
213
+ assertEquals(measurements[1].attributes["activitypub.lookup.result"], "miss");
214
+ assertEquals(measurements[1].attributes["activitypub.remote.host"], "w3id.org");
215
+ });
216
+ test("classifyFetchError() classifies FetchError with 404 as not_found", () => {
217
+ assertEquals(classifyFetchError(new FetchError("https://example.com/k", "not found", new Response("", { status: 404 }))), {
218
+ result: "not_found",
219
+ statusCode: 404
220
+ });
221
+ });
222
+ test("classifyFetchError() classifies FetchError with 410 as not_found", () => {
223
+ assertEquals(classifyFetchError(new FetchError("https://example.com/k", "gone", new Response("", { status: 410 }))), {
224
+ result: "not_found",
225
+ statusCode: 410
226
+ });
227
+ });
228
+ test("classifyFetchError() classifies FetchError with 500 as error", () => {
229
+ assertEquals(classifyFetchError(new FetchError("https://example.com/k", "server error", new Response("", { status: 500 }))), {
230
+ result: "error",
231
+ statusCode: 500
232
+ });
233
+ });
234
+ test("classifyFetchError() classifies FetchError without response as network_error", () => {
235
+ assertEquals(classifyFetchError(new FetchError("https://example.com/k", "boom")), { result: "network_error" });
236
+ });
237
+ test("classifyFetchError() classifies a bare TypeError as network_error", () => {
238
+ assertEquals(classifyFetchError(/* @__PURE__ */ new TypeError("connect failed")), { result: "network_error" });
239
+ });
240
+ test("classifyFetchError() classifies an AbortError as network_error", () => {
241
+ const abort = /* @__PURE__ */ new Error("aborted");
242
+ abort.name = "AbortError";
243
+ assertEquals(classifyFetchError(abort), { result: "network_error" });
244
+ });
245
+ test("classifyFetchError() classifies any other thrown value as error", () => {
246
+ assertEquals(classifyFetchError(/* @__PURE__ */ new Error("nope")), { result: "error" });
247
+ assertEquals(classifyFetchError("string error"), { result: "error" });
248
+ assertEquals(classifyFetchError(void 0), { result: "error" });
249
+ });
250
+ test("instrumentDocumentLoader() records fetched on success", async () => {
251
+ const [meterProvider, recorder] = createTestMeterProvider();
252
+ const inner = (url) => Promise.resolve({
253
+ contextUrl: null,
254
+ documentUrl: url,
255
+ document: { ok: true }
256
+ });
257
+ assertEquals((await instrumentDocumentLoader(inner, {
258
+ meterProvider,
259
+ kind: "object",
260
+ cacheEnabled: true
261
+ })("https://example.com/o")).document, { ok: true });
262
+ const counter = recorder.getMeasurement("activitypub.document.fetch");
263
+ assertEquals(counter?.attributes["activitypub.lookup.kind"], "object");
264
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "fetched");
265
+ assertEquals(counter?.attributes["activitypub.remote.host"], "example.com");
266
+ assertEquals(counter?.attributes["activitypub.cache.enabled"], true);
267
+ assertEquals("http.response.status_code" in counter.attributes, false);
268
+ const duration = recorder.getMeasurement("activitypub.document.fetch.duration");
269
+ assertEquals(duration?.type, "histogram");
270
+ assertEquals(duration?.attributes["activitypub.lookup.result"], "fetched");
271
+ });
272
+ test("instrumentDocumentLoader() records not_found on FetchError 404", async () => {
273
+ const [meterProvider, recorder] = createTestMeterProvider();
274
+ const inner = (url) => Promise.reject(new FetchError(url, "HTTP 404", new Response("", { status: 404 })));
275
+ const wrapped = instrumentDocumentLoader(inner, {
276
+ meterProvider,
277
+ kind: "context",
278
+ cacheEnabled: false
279
+ });
280
+ await assertRejects(() => wrapped("https://example.com/missing"), FetchError);
281
+ const counter = recorder.getMeasurement("activitypub.document.fetch");
282
+ assertEquals(counter?.attributes["activitypub.lookup.kind"], "context");
283
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "not_found");
284
+ assertEquals(counter?.attributes["activitypub.remote.host"], "example.com");
285
+ assertEquals(counter?.attributes["activitypub.cache.enabled"], false);
286
+ assertEquals(counter?.attributes["http.response.status_code"], 404);
287
+ });
288
+ test("instrumentDocumentLoader() records network_error on TypeError", async () => {
289
+ const [meterProvider, recorder] = createTestMeterProvider();
290
+ const inner = () => Promise.reject(/* @__PURE__ */ new TypeError("fetch failed"));
291
+ const wrapped = instrumentDocumentLoader(inner, {
292
+ meterProvider,
293
+ kind: "object"
294
+ });
295
+ await assertRejects(() => wrapped("https://example.com/o"), TypeError);
296
+ const counter = recorder.getMeasurement("activitypub.document.fetch");
297
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "network_error");
298
+ assertEquals("activitypub.cache.enabled" in counter.attributes, false);
299
+ });
300
+ test("instrumentDocumentLoader() returns inner loader unchanged when meterProvider is omitted", async () => {
301
+ const [, recorder] = createTestMeterProvider();
302
+ let callCount = 0;
303
+ const inner = (url) => {
304
+ callCount++;
305
+ return Promise.resolve({
306
+ contextUrl: null,
307
+ documentUrl: url,
308
+ document: { ok: true }
309
+ });
310
+ };
311
+ const wrapped = instrumentDocumentLoader(inner, { kind: "object" });
312
+ assertEquals(wrapped, inner);
313
+ await wrapped("https://example.com/o");
314
+ assertEquals(callCount, 1);
315
+ assertEquals(recorder.getMeasurements("activitypub.document.fetch").length, 0);
316
+ assertEquals(recorder.getMeasurements("activitypub.document.fetch.duration").length, 0);
317
+ });
318
+ test("instrumentDocumentLoader() omits remote.host when URL is unparseable", async () => {
319
+ const [meterProvider, recorder] = createTestMeterProvider();
320
+ const inner = (url) => Promise.resolve({
321
+ contextUrl: null,
322
+ documentUrl: url,
323
+ document: {}
324
+ });
325
+ await instrumentDocumentLoader(inner, {
326
+ meterProvider,
327
+ kind: "other"
328
+ })("not a url");
329
+ const counter = recorder.getMeasurement("activitypub.document.fetch");
330
+ assertEquals(counter?.attributes["activitypub.lookup.kind"], "other");
331
+ assertEquals(counter?.attributes["activitypub.lookup.result"], "fetched");
332
+ assertEquals("activitypub.remote.host" in counter.attributes, false);
333
+ });
334
+ //#endregion
335
+ export {};