@nbreak/datasources 0.1.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.
@@ -0,0 +1,1081 @@
1
+ import { defineDataSourceExtension } from "@nbreak/sdk";
2
+ //#region src/adapters/staticAdapter.js
3
+ /**
4
+ * @nbreak/datasources - 静态数据源适配器
5
+ */
6
+ /**
7
+ * 裸取数函数:返回传入的静态数据,返回 { data, meta }
8
+ * @param {Object} config
9
+ * @returns {Promise<{data: any, meta: Object}>}
10
+ */
11
+ async function fetchData$5(config) {
12
+ const { data = [], transform } = config;
13
+ let result = data;
14
+ if (typeof transform === "function") result = transform(data);
15
+ return {
16
+ data: result,
17
+ meta: { total: Array.isArray(result) ? result.length : 1 }
18
+ };
19
+ }
20
+ var StaticAdapter = class {
21
+ async fetchData(config) {
22
+ return fetchData$5(config);
23
+ }
24
+ async subscribe(_config, callback) {
25
+ return () => {};
26
+ }
27
+ async test() {
28
+ return {
29
+ success: true,
30
+ message: "静态数据源总是可用"
31
+ };
32
+ }
33
+ };
34
+ var staticAdapter_default = defineDataSourceExtension({
35
+ type: "static",
36
+ name: "静态数据",
37
+ adapter: new StaticAdapter(),
38
+ configSchema: [{
39
+ key: "data",
40
+ label: "数据内容",
41
+ type: "json",
42
+ group: "基础",
43
+ default: [],
44
+ description: "JSON 格式的静态数据"
45
+ }],
46
+ defaults: { data: [] },
47
+ description: "JSON 静态数据源,适用于测试与简单场景"
48
+ });
49
+ //#endregion
50
+ //#region src/adapters/apiAdapter.js
51
+ /**
52
+ * @nbreak/datasources - REST API 数据源适配器
53
+ */
54
+ /**
55
+ * 裸取数函数:执行 REST API 请求并返回 { data, meta }
56
+ * 支持 optional signal(外部取消)/ responseType(显式覆盖)/ credentials
57
+ * @param {Object} config
58
+ * @returns {Promise<{data: any, meta: Object}>}
59
+ */
60
+ async function fetchData(config) {
61
+ const { url, method = "GET", headers = {}, body, params = {}, timeout = 1e4, transform, signal, responseType, credentials } = config;
62
+ if (!url) throw new Error("API 数据源需要配置 url");
63
+ const urlObj = new URL(url, typeof window !== "undefined" ? window.location.origin : "http://localhost");
64
+ Object.entries(params).forEach(([k, v]) => {
65
+ if (v !== void 0 && v !== null) urlObj.searchParams.append(k, v);
66
+ });
67
+ const controller = signal ? null : new AbortController();
68
+ const fetchSignal = signal || controller.signal;
69
+ const timer = setTimeout(signal ? () => signal.abort() : () => controller.abort(), timeout);
70
+ try {
71
+ const fetchOptions = {
72
+ method,
73
+ headers: {
74
+ "Content-Type": "application/json",
75
+ ...headers
76
+ },
77
+ body: method !== "GET" && body ? JSON.stringify(body) : void 0,
78
+ signal: fetchSignal
79
+ };
80
+ if (credentials) fetchOptions.credentials = credentials;
81
+ const response = await fetch(urlObj.toString(), fetchOptions);
82
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
83
+ let data;
84
+ if (responseType) switch (responseType) {
85
+ case "json":
86
+ data = await response.json();
87
+ break;
88
+ case "text":
89
+ data = await response.text();
90
+ break;
91
+ case "blob":
92
+ data = await response.blob();
93
+ break;
94
+ case "arrayBuffer":
95
+ data = await response.arrayBuffer();
96
+ break;
97
+ case "formData":
98
+ data = await response.formData();
99
+ break;
100
+ default: data = await response.json();
101
+ }
102
+ else if ((response.headers.get("content-type") || "").includes("application/json")) data = await response.json();
103
+ else data = await response.text();
104
+ if (typeof transform === "function") data = transform(data);
105
+ return {
106
+ data,
107
+ meta: { status: response.status }
108
+ };
109
+ } finally {
110
+ clearTimeout(timer);
111
+ }
112
+ }
113
+ var ApiAdapter = class {
114
+ async fetchData(config) {
115
+ return fetchData(config);
116
+ }
117
+ async subscribe(config, callback) {
118
+ if (!config.pollInterval || config.pollInterval < 1e3) return () => {};
119
+ let active = true;
120
+ const poll = async () => {
121
+ if (!active) return;
122
+ try {
123
+ const { data } = await this.fetchData(config);
124
+ callback(data);
125
+ } catch (e) {}
126
+ if (active) setTimeout(poll, config.pollInterval);
127
+ };
128
+ poll();
129
+ return () => {
130
+ active = false;
131
+ };
132
+ }
133
+ async test(config) {
134
+ try {
135
+ await this.fetchData({
136
+ ...config,
137
+ timeout: 5e3
138
+ });
139
+ return {
140
+ success: true,
141
+ message: "连接成功"
142
+ };
143
+ } catch (e) {
144
+ return {
145
+ success: false,
146
+ message: e.message
147
+ };
148
+ }
149
+ }
150
+ };
151
+ var apiAdapter_default = defineDataSourceExtension({
152
+ type: "api",
153
+ name: "REST API",
154
+ adapter: new ApiAdapter(),
155
+ configSchema: [
156
+ {
157
+ key: "url",
158
+ label: "接口地址",
159
+ type: "text",
160
+ group: "基础",
161
+ default: "",
162
+ required: true
163
+ },
164
+ {
165
+ key: "method",
166
+ label: "请求方法",
167
+ type: "select",
168
+ group: "基础",
169
+ default: "GET",
170
+ options: [
171
+ "GET",
172
+ "POST",
173
+ "PUT",
174
+ "DELETE"
175
+ ].map((m) => ({
176
+ label: m,
177
+ value: m
178
+ }))
179
+ },
180
+ {
181
+ key: "headers",
182
+ label: "请求头",
183
+ type: "json",
184
+ group: "高级",
185
+ default: {}
186
+ },
187
+ {
188
+ key: "body",
189
+ label: "请求体",
190
+ type: "json",
191
+ group: "高级",
192
+ default: null,
193
+ visible: (props) => props.method !== "GET"
194
+ },
195
+ {
196
+ key: "params",
197
+ label: "查询参数",
198
+ type: "json",
199
+ group: "高级",
200
+ default: {}
201
+ },
202
+ {
203
+ key: "pollInterval",
204
+ label: "轮询间隔(ms)",
205
+ type: "number",
206
+ group: "实时",
207
+ default: 0,
208
+ min: 0,
209
+ description: "大于 1000 时启用轮询"
210
+ },
211
+ {
212
+ key: "timeout",
213
+ label: "超时(ms)",
214
+ type: "number",
215
+ group: "高级",
216
+ default: 1e4,
217
+ min: 1e3,
218
+ max: 6e4
219
+ }
220
+ ],
221
+ defaults: {
222
+ url: "",
223
+ method: "GET",
224
+ headers: {},
225
+ params: {},
226
+ pollInterval: 0,
227
+ timeout: 1e4
228
+ },
229
+ description: "REST API 数据源,支持 GET/POST/PUT/DELETE 与轮询"
230
+ });
231
+ //#endregion
232
+ //#region src/adapters/websocketAdapter.js
233
+ /**
234
+ * @nbreak/datasources - WebSocket 实时数据源适配器
235
+ */
236
+ /**
237
+ * 裸取数函数:WebSocket 不支持单次拉取,返回 null 标记实时模式
238
+ * @param {Object} _config
239
+ * @returns {Promise<{data: null, meta: Object}>}
240
+ */
241
+ async function fetchData$6(_config) {
242
+ return {
243
+ data: null,
244
+ meta: { realtime: true }
245
+ };
246
+ }
247
+ var WebSocketAdapter = class {
248
+ async fetchData(config) {
249
+ return fetchData$6(config);
250
+ }
251
+ async subscribe(config, callback) {
252
+ const { url, protocols, reconnect = true, maxRetries = 5 } = config;
253
+ if (!url) throw new Error("WebSocket 数据源需要配置 url");
254
+ let ws = null;
255
+ let retryCount = 0;
256
+ let closed = false;
257
+ const connect = () => {
258
+ if (closed) return;
259
+ try {
260
+ ws = new WebSocket(url, protocols);
261
+ ws.onopen = () => {
262
+ retryCount = 0;
263
+ };
264
+ ws.onmessage = (event) => {
265
+ try {
266
+ callback(JSON.parse(event.data));
267
+ } catch (e) {
268
+ callback(event.data);
269
+ }
270
+ };
271
+ ws.onclose = () => {
272
+ if (reconnect && !closed && retryCount < maxRetries) {
273
+ retryCount++;
274
+ const delay = Math.min(1e3 * Math.pow(2, retryCount), 3e4);
275
+ setTimeout(connect, delay);
276
+ }
277
+ };
278
+ ws.onerror = (e) => {
279
+ console.error("[WebSocket Adapter] error:", e);
280
+ };
281
+ } catch (e) {
282
+ console.error("[WebSocket Adapter] connect failed:", e);
283
+ }
284
+ };
285
+ connect();
286
+ return () => {
287
+ closed = true;
288
+ if (ws) ws.close();
289
+ };
290
+ }
291
+ async test(config) {
292
+ return new Promise((resolve) => {
293
+ try {
294
+ const ws = new WebSocket(config.url);
295
+ const timer = setTimeout(() => {
296
+ ws.close();
297
+ resolve({
298
+ success: false,
299
+ message: "连接超时"
300
+ });
301
+ }, 5e3);
302
+ ws.onopen = () => {
303
+ clearTimeout(timer);
304
+ ws.close();
305
+ resolve({
306
+ success: true,
307
+ message: "WebSocket 连接成功"
308
+ });
309
+ };
310
+ ws.onerror = () => {
311
+ clearTimeout(timer);
312
+ resolve({
313
+ success: false,
314
+ message: "WebSocket 连接失败"
315
+ });
316
+ };
317
+ } catch (e) {
318
+ resolve({
319
+ success: false,
320
+ message: e.message
321
+ });
322
+ }
323
+ });
324
+ }
325
+ };
326
+ var websocketAdapter_default = defineDataSourceExtension({
327
+ type: "websocket",
328
+ name: "WebSocket 实时",
329
+ adapter: new WebSocketAdapter(),
330
+ configSchema: [
331
+ {
332
+ key: "url",
333
+ label: "WebSocket 地址",
334
+ type: "text",
335
+ group: "基础",
336
+ default: "",
337
+ required: true,
338
+ placeholder: "ws://localhost:8080/ws"
339
+ },
340
+ {
341
+ key: "protocols",
342
+ label: "子协议",
343
+ type: "text",
344
+ group: "高级",
345
+ default: ""
346
+ },
347
+ {
348
+ key: "reconnect",
349
+ label: "自动重连",
350
+ type: "switch",
351
+ group: "高级",
352
+ default: true
353
+ },
354
+ {
355
+ key: "maxRetries",
356
+ label: "最大重试次数",
357
+ type: "number",
358
+ group: "高级",
359
+ default: 5,
360
+ min: 0,
361
+ max: 100
362
+ }
363
+ ],
364
+ defaults: {
365
+ url: "",
366
+ protocols: "",
367
+ reconnect: true,
368
+ maxRetries: 5
369
+ },
370
+ description: "WebSocket 实时数据源,支持自动重连与指数退避"
371
+ });
372
+ //#endregion
373
+ //#region src/adapters/excelAdapter.js
374
+ /**
375
+ * @nbreak/datasources - Excel 文件数据源适配器
376
+ *
377
+ * 基于 xlsx 库解析 Excel 文件,支持 .xlsx/.xls 格式
378
+ */
379
+ /**
380
+ * 读取文件内容为 ArrayBuffer
381
+ * @param {string|Blob|ArrayBuffer|Uint8Array} file - 文件源
382
+ * @returns {Promise<ArrayBuffer>}
383
+ */
384
+ async function readFile(file) {
385
+ if (typeof file === "string") return await (await fetch(file)).arrayBuffer();
386
+ if (file instanceof ArrayBuffer || file instanceof Uint8Array) return file;
387
+ if (file instanceof Blob) return await file.arrayBuffer();
388
+ throw new Error("不支持的 file 类型");
389
+ }
390
+ /**
391
+ * 裸取数函数:基于 xlsx 库解析 Excel 文件,返回 { data, meta }
392
+ * @param {Object} config
393
+ * @returns {Promise<{data: Array, meta: Object}>}
394
+ */
395
+ async function fetchData$3(config) {
396
+ const { file, sheetName, range, transform } = config;
397
+ if (!file && !config.fileContent) throw new Error("Excel 数据源需要配置 file(File 对象)或 fileContent(ArrayBuffer/base64 字符串)");
398
+ let XLSX;
399
+ try {
400
+ XLSX = (await import("xlsx")).default || await import("xlsx");
401
+ } catch (e) {
402
+ throw new Error("未安装 xlsx 库,请运行 pnpm add xlsx");
403
+ }
404
+ const data = config.fileContent || await readFile(file);
405
+ let workbook;
406
+ if (typeof data === "string") workbook = XLSX.read(data, { type: "base64" });
407
+ else if (data instanceof ArrayBuffer || data instanceof Uint8Array) workbook = XLSX.read(data, { type: "array" });
408
+ else throw new Error("fileContent 无法解析(旧配置中的文件内容已丢失,请重新上传 Excel 文件)");
409
+ const sheet = sheetName ? workbook.Sheets[sheetName] : workbook.Sheets[workbook.SheetNames[0]];
410
+ if (!sheet) throw new Error(`工作表 "${sheetName || "[默认]"}" 不存在`);
411
+ const options = {};
412
+ if (range) options.range = range;
413
+ let result = XLSX.utils.sheet_to_json(sheet, options);
414
+ if (typeof transform === "function") result = transform(result);
415
+ return {
416
+ data: result,
417
+ meta: {
418
+ total: result.length,
419
+ sheetName: sheetName || workbook.SheetNames[0],
420
+ sheets: workbook.SheetNames
421
+ }
422
+ };
423
+ }
424
+ var ExcelAdapter = class {
425
+ async fetchData(config) {
426
+ return fetchData$3(config);
427
+ }
428
+ async _readFile(file) {
429
+ return readFile(file);
430
+ }
431
+ async subscribe(_config, _callback) {
432
+ return () => {};
433
+ }
434
+ async test(config) {
435
+ try {
436
+ if (!config.file && !config.fileContent) return {
437
+ success: false,
438
+ message: "需要配置 file"
439
+ };
440
+ return {
441
+ success: true,
442
+ message: "Excel 数据源配置有效"
443
+ };
444
+ } catch (e) {
445
+ return {
446
+ success: false,
447
+ message: e.message
448
+ };
449
+ }
450
+ }
451
+ };
452
+ var excelAdapter_default = defineDataSourceExtension({
453
+ type: "excel",
454
+ name: "Excel 文件",
455
+ adapter: new ExcelAdapter(),
456
+ configSchema: [
457
+ {
458
+ key: "file",
459
+ label: "Excel 文件",
460
+ type: "text",
461
+ group: "基础",
462
+ default: "",
463
+ description: "文件 URL 或 File 对象"
464
+ },
465
+ {
466
+ key: "sheetName",
467
+ label: "工作表名",
468
+ type: "text",
469
+ group: "基础",
470
+ default: "",
471
+ placeholder: "留空使用第一个工作表"
472
+ },
473
+ {
474
+ key: "range",
475
+ label: "数据范围",
476
+ type: "text",
477
+ group: "高级",
478
+ default: "",
479
+ placeholder: "A1:D100"
480
+ }
481
+ ],
482
+ defaults: {
483
+ file: "",
484
+ sheetName: "",
485
+ range: ""
486
+ },
487
+ description: "Excel 文件数据源,支持 .xlsx/.xls 格式解析"
488
+ });
489
+ //#endregion
490
+ //#region src/adapters/csvAdapter.js
491
+ /**
492
+ * @nbreak/datasources - CSV 文本数据源适配器
493
+ *
494
+ * 解析 CSV 文本为对象数组,支持自定义分隔符与表头
495
+ */
496
+ /**
497
+ * 解析 CSV 文本为二维数组
498
+ * @param {string} text - CSV 文本
499
+ * @param {string} delimiter - 分隔符
500
+ * @param {string} quote - 引号字符
501
+ * @returns {Array<Array<string>>}
502
+ */
503
+ function parseCsv(text, delimiter, quote) {
504
+ const rows = [];
505
+ let currentRow = [];
506
+ let currentField = "";
507
+ let inQuotes = false;
508
+ for (let i = 0; i < text.length; i++) {
509
+ const char = text[i];
510
+ const nextChar = text[i + 1];
511
+ if (inQuotes) if (char === quote) if (nextChar === quote) {
512
+ currentField += quote;
513
+ i++;
514
+ } else inQuotes = false;
515
+ else currentField += char;
516
+ else if (char === quote) inQuotes = true;
517
+ else if (char === delimiter) {
518
+ currentRow.push(currentField);
519
+ currentField = "";
520
+ } else if (char === "\n" || char === "\r") {
521
+ if (char === "\r" && nextChar === "\n") i++;
522
+ currentRow.push(currentField);
523
+ currentField = "";
524
+ if (currentRow.length > 1 || currentRow[0] !== "") rows.push(currentRow);
525
+ currentRow = [];
526
+ } else currentField += char;
527
+ }
528
+ if (currentField !== "" || currentRow.length > 0) {
529
+ currentRow.push(currentField);
530
+ rows.push(currentRow);
531
+ }
532
+ return rows;
533
+ }
534
+ /**
535
+ * 裸取数函数:解析 CSV 文本为对象数组,返回 { data, meta }
536
+ * @param {Object} config
537
+ * @returns {Promise<{data: Array, meta: Object}>}
538
+ */
539
+ async function fetchData$1(config) {
540
+ const { content = "", url = "", delimiter = ",", hasHeader = true, quote = "\"", transform } = config;
541
+ let csvText = content;
542
+ if (!csvText && url) csvText = await (await fetch(url)).text();
543
+ if (!csvText) return {
544
+ data: [],
545
+ meta: { total: 0 }
546
+ };
547
+ const rows = parseCsv(csvText, delimiter, quote);
548
+ if (rows.length === 0) return {
549
+ data: [],
550
+ meta: { total: 0 }
551
+ };
552
+ let result;
553
+ if (hasHeader) {
554
+ const headers = rows[0];
555
+ result = rows.slice(1).map((row) => {
556
+ const obj = {};
557
+ headers.forEach((h, i) => {
558
+ obj[h] = row[i] !== void 0 ? row[i] : "";
559
+ });
560
+ return obj;
561
+ });
562
+ } else result = rows.map((row) => ({ values: row }));
563
+ if (typeof transform === "function") result = transform(result);
564
+ return {
565
+ data: result,
566
+ meta: { total: result.length }
567
+ };
568
+ }
569
+ var CsvAdapter = class {
570
+ async fetchData(config) {
571
+ return fetchData$1(config);
572
+ }
573
+ _parse(text, delimiter, quote) {
574
+ return parseCsv(text, delimiter, quote);
575
+ }
576
+ async subscribe(_config, _callback) {
577
+ return () => {};
578
+ }
579
+ async test(config) {
580
+ if (!config.content && !config.url) return {
581
+ success: false,
582
+ message: "需要配置 content 或 url"
583
+ };
584
+ return {
585
+ success: true,
586
+ message: "CSV 数据源配置有效"
587
+ };
588
+ }
589
+ };
590
+ var csvAdapter_default = defineDataSourceExtension({
591
+ type: "csv",
592
+ name: "CSV 文本",
593
+ adapter: new CsvAdapter(),
594
+ configSchema: [
595
+ {
596
+ key: "content",
597
+ label: "CSV 内容",
598
+ type: "textarea",
599
+ group: "基础",
600
+ default: "",
601
+ description: "直接粘贴 CSV 文本"
602
+ },
603
+ {
604
+ key: "url",
605
+ label: "文件 URL",
606
+ type: "text",
607
+ group: "基础",
608
+ default: "",
609
+ description: "远程 CSV 文件地址(content 为空时使用)"
610
+ },
611
+ {
612
+ key: "delimiter",
613
+ label: "分隔符",
614
+ type: "select",
615
+ group: "解析",
616
+ default: ",",
617
+ options: [
618
+ {
619
+ label: "逗号 (,)",
620
+ value: ","
621
+ },
622
+ {
623
+ label: "分号 (;)",
624
+ value: ";"
625
+ },
626
+ {
627
+ label: "制表符 (\\t)",
628
+ value: " "
629
+ },
630
+ {
631
+ label: "竖线 (|)",
632
+ value: "|"
633
+ }
634
+ ]
635
+ },
636
+ {
637
+ key: "hasHeader",
638
+ label: "首行表头",
639
+ type: "switch",
640
+ group: "解析",
641
+ default: true
642
+ },
643
+ {
644
+ key: "quote",
645
+ label: "引号字符",
646
+ type: "text",
647
+ group: "高级",
648
+ default: "\""
649
+ }
650
+ ],
651
+ defaults: {
652
+ content: "",
653
+ url: "",
654
+ delimiter: ",",
655
+ hasHeader: true,
656
+ quote: "\""
657
+ },
658
+ description: "CSV 文本数据源,支持自定义分隔符与表头解析"
659
+ });
660
+ //#endregion
661
+ //#region src/adapters/mockAdapter.js
662
+ /**
663
+ * @nbreak/datasources - Mock 模拟数据源适配器
664
+ *
665
+ * 用于开发测试,支持生成随机数据与定时刷新
666
+ */
667
+ /**
668
+ * 生成单个对象
669
+ * @param {Array} schema - 数据结构
670
+ * @param {Function} random - 随机数生成函数
671
+ * @param {number} index - 索引
672
+ * @returns {Object}
673
+ */
674
+ function generateObject(schema, random, index = 0) {
675
+ if (!schema || schema.length === 0) return {
676
+ id: index,
677
+ label: `项目 ${index + 1}`,
678
+ value: Math.floor(random() * 1e3)
679
+ };
680
+ const obj = {};
681
+ for (const field of schema) {
682
+ const { key, type = "number" } = field;
683
+ switch (type) {
684
+ case "number":
685
+ obj[key] = Math.floor(random() * (field.max || 1e3));
686
+ break;
687
+ case "string":
688
+ obj[key] = `${field.prefix || "item"}_${index}`;
689
+ break;
690
+ case "boolean":
691
+ obj[key] = random() > .5;
692
+ break;
693
+ case "date":
694
+ obj[key] = (/* @__PURE__ */ new Date(Date.now() - random() * 30 * 24 * 60 * 60 * 1e3)).toISOString();
695
+ break;
696
+ case "category":
697
+ obj[key] = field.options ? field.options[Math.floor(random() * field.options.length)] : `类别${Math.floor(random() * 5)}`;
698
+ break;
699
+ default: obj[key] = random();
700
+ }
701
+ }
702
+ return obj;
703
+ }
704
+ /**
705
+ * 生成 Mock 数据
706
+ * @param {string} dataType - 数据类型 (array/object)
707
+ * @param {number} count - 数据条数
708
+ * @param {Array} schema - 数据结构
709
+ * @param {number} seed - 随机种子
710
+ * @returns {any}
711
+ */
712
+ function generate(dataType, count, schema, seed) {
713
+ let s = seed;
714
+ const random = () => {
715
+ s = (s * 9301 + 49297) % 233280;
716
+ return s / 233280;
717
+ };
718
+ if (dataType === "object") return generateObject(schema, random);
719
+ const arr = [];
720
+ for (let i = 0; i < count; i++) arr.push(generateObject(schema, random, i));
721
+ return arr;
722
+ }
723
+ /**
724
+ * 裸取数函数:生成 Mock 数据,返回 { data, meta }
725
+ * @param {Object} config
726
+ * @returns {Promise<{data: any, meta: Object}>}
727
+ */
728
+ async function fetchData$4(config) {
729
+ const { type: dataType = "array", count = 10, interval = 0, schema = [], seed = 42 } = config;
730
+ const data = generate(dataType, count, schema, seed);
731
+ return {
732
+ data,
733
+ meta: {
734
+ total: Array.isArray(data) ? data.length : 1,
735
+ generatedAt: Date.now()
736
+ }
737
+ };
738
+ }
739
+ var MockAdapter = class {
740
+ async fetchData(config) {
741
+ return fetchData$4(config);
742
+ }
743
+ _generate(dataType, count, schema, seed) {
744
+ return generate(dataType, count, schema, seed);
745
+ }
746
+ _generateObject(schema, random, index = 0) {
747
+ return generateObject(schema, random, index);
748
+ }
749
+ async subscribe(config, callback) {
750
+ if (!config.interval || config.interval < 500) return () => {};
751
+ let active = true;
752
+ const poll = async () => {
753
+ if (!active) return;
754
+ try {
755
+ const { data } = await this.fetchData(config);
756
+ callback(data);
757
+ } catch (e) {}
758
+ if (active) setTimeout(poll, config.interval);
759
+ };
760
+ poll();
761
+ return () => {
762
+ active = false;
763
+ };
764
+ }
765
+ async test() {
766
+ return {
767
+ success: true,
768
+ message: "Mock 数据源总是可用"
769
+ };
770
+ }
771
+ };
772
+ var mockAdapter_default = defineDataSourceExtension({
773
+ type: "mock",
774
+ name: "Mock 模拟数据",
775
+ adapter: new MockAdapter(),
776
+ configSchema: [
777
+ {
778
+ key: "type",
779
+ label: "数据类型",
780
+ type: "select",
781
+ group: "基础",
782
+ default: "array",
783
+ options: [{
784
+ label: "数组",
785
+ value: "array"
786
+ }, {
787
+ label: "对象",
788
+ value: "object"
789
+ }]
790
+ },
791
+ {
792
+ key: "count",
793
+ label: "数据条数",
794
+ type: "slider",
795
+ group: "基础",
796
+ default: 10,
797
+ min: 1,
798
+ max: 100
799
+ },
800
+ {
801
+ key: "seed",
802
+ label: "随机种子",
803
+ type: "number",
804
+ group: "基础",
805
+ default: 42,
806
+ description: "相同种子生成相同数据"
807
+ },
808
+ {
809
+ key: "interval",
810
+ label: "刷新间隔(ms)",
811
+ type: "number",
812
+ group: "实时",
813
+ default: 0,
814
+ min: 0,
815
+ description: "大于 500 时启用定时刷新"
816
+ },
817
+ {
818
+ key: "schema",
819
+ label: "数据结构",
820
+ type: "json",
821
+ group: "高级",
822
+ default: [],
823
+ description: "定义每条数据的字段结构"
824
+ }
825
+ ],
826
+ defaults: {
827
+ type: "array",
828
+ count: 10,
829
+ seed: 42,
830
+ interval: 0,
831
+ schema: []
832
+ },
833
+ description: "Mock 模拟数据源,支持随机数据生成与定时刷新,适用于开发测试"
834
+ });
835
+ //#endregion
836
+ //#region src/adapters/databaseAdapter.js
837
+ /**
838
+ * @nbreak/datasources - 数据库直连数据源适配器
839
+ *
840
+ * 浏览器无法直连 MySQL/PostgreSQL,本适配器通过 HTTP 调用后端代理
841
+ * (默认 /api/db/query)完成取数。后端持有真实驱动与连接池。
842
+ */
843
+ var DEFAULT_PROXY_URL = "/api/db/query";
844
+ var DEFAULT_TEST_URL = "/api/db/test";
845
+ /**
846
+ * 裸取数函数:调用后端代理执行 SQL 查询,返回 { data, meta }
847
+ * @param {Object} config
848
+ * @param {string} config.dbType - 数据库类型:mysql | postgresql
849
+ * @param {string} config.host - 主机地址
850
+ * @param {number} config.port - 端口
851
+ * @param {string} config.database - 数据库名
852
+ * @param {string} config.username - 用户名
853
+ * @param {string} config.password - 密码
854
+ * @param {string} config.query - SQL 查询语句(仅允许 SELECT)
855
+ * @param {Array} config.params - 参数化查询占位符
856
+ * @param {string} [config.proxyUrl] - 自定义代理地址
857
+ * @param {number} [config.timeout] - 超时(ms)
858
+ * @param {AbortSignal} [config.signal] - 外部取消信号
859
+ * @returns {Promise<{data: any, meta: Object}>}
860
+ */
861
+ async function fetchData$2(config) {
862
+ const { dbType, host, port, database, username, password, query, params = [], proxyUrl = DEFAULT_PROXY_URL, timeout = 3e4, connectionOptions = {}, signal } = config;
863
+ if (!dbType) throw new Error("数据库数据源需要配置 dbType");
864
+ if (!query) throw new Error("数据库数据源需要配置 query (SQL 语句)");
865
+ const controller = signal ? null : new AbortController();
866
+ const fetchSignal = signal || controller.signal;
867
+ const timer = setTimeout(signal ? () => signal.abort() : () => controller.abort(), timeout);
868
+ try {
869
+ const response = await fetch(proxyUrl, {
870
+ method: "POST",
871
+ headers: { "Content-Type": "application/json" },
872
+ body: JSON.stringify({
873
+ dbType,
874
+ host,
875
+ port,
876
+ database,
877
+ username,
878
+ password,
879
+ query,
880
+ params,
881
+ connectionOptions
882
+ }),
883
+ signal: fetchSignal
884
+ });
885
+ if (!response.ok) {
886
+ let errMsg = `HTTP ${response.status}: ${response.statusText}`;
887
+ try {
888
+ const errBody = await response.json();
889
+ if (errBody.error) errMsg = errBody.error;
890
+ } catch (_) {}
891
+ throw new Error(errMsg);
892
+ }
893
+ const result = await response.json();
894
+ return {
895
+ data: result.data,
896
+ meta: result.meta || { rowCount: Array.isArray(result.data) ? result.data.length : 0 }
897
+ };
898
+ } finally {
899
+ clearTimeout(timer);
900
+ }
901
+ }
902
+ var DatabaseAdapter = class {
903
+ async fetchData(config) {
904
+ return fetchData$2(config);
905
+ }
906
+ async test(config) {
907
+ try {
908
+ const testUrl = config.testUrl || DEFAULT_TEST_URL;
909
+ const response = await fetch(testUrl, {
910
+ method: "POST",
911
+ headers: { "Content-Type": "application/json" },
912
+ body: JSON.stringify({
913
+ dbType: config.dbType,
914
+ host: config.host,
915
+ port: config.port,
916
+ database: config.database,
917
+ username: config.username,
918
+ password: config.password,
919
+ connectionOptions: config.connectionOptions
920
+ })
921
+ });
922
+ if (!response.ok) return {
923
+ success: false,
924
+ message: (await response.json().catch(() => ({}))).error || `HTTP ${response.status}`
925
+ };
926
+ const result = await response.json();
927
+ return {
928
+ success: result.success !== false,
929
+ message: result.message || "连接成功"
930
+ };
931
+ } catch (e) {
932
+ return {
933
+ success: false,
934
+ message: e.message
935
+ };
936
+ }
937
+ }
938
+ };
939
+ var databaseAdapter_default = defineDataSourceExtension({
940
+ type: "database",
941
+ name: "数据库",
942
+ adapter: new DatabaseAdapter(),
943
+ configSchema: [
944
+ {
945
+ key: "dbType",
946
+ label: "数据库类型",
947
+ type: "select",
948
+ group: "连接",
949
+ default: "mysql",
950
+ required: true,
951
+ options: [{
952
+ label: "MySQL",
953
+ value: "mysql"
954
+ }, {
955
+ label: "PostgreSQL",
956
+ value: "postgresql"
957
+ }]
958
+ },
959
+ {
960
+ key: "host",
961
+ label: "主机地址",
962
+ type: "text",
963
+ group: "连接",
964
+ default: "localhost",
965
+ required: true
966
+ },
967
+ {
968
+ key: "port",
969
+ label: "端口",
970
+ type: "number",
971
+ group: "连接",
972
+ default: 3306
973
+ },
974
+ {
975
+ key: "database",
976
+ label: "数据库名",
977
+ type: "text",
978
+ group: "连接",
979
+ default: "",
980
+ required: true
981
+ },
982
+ {
983
+ key: "username",
984
+ label: "用户名",
985
+ type: "text",
986
+ group: "连接",
987
+ default: "root",
988
+ required: true
989
+ },
990
+ {
991
+ key: "password",
992
+ label: "密码",
993
+ type: "password",
994
+ group: "连接",
995
+ default: ""
996
+ },
997
+ {
998
+ key: "query",
999
+ label: "SQL 查询",
1000
+ type: "textarea",
1001
+ group: "查询",
1002
+ default: "SELECT 1",
1003
+ required: true,
1004
+ description: "仅允许 SELECT / WITH / SHOW / DESCRIBE / EXPLAIN"
1005
+ },
1006
+ {
1007
+ key: "params",
1008
+ label: "参数化查询",
1009
+ type: "json",
1010
+ group: "查询",
1011
+ default: []
1012
+ },
1013
+ {
1014
+ key: "timeout",
1015
+ label: "超时(ms)",
1016
+ type: "number",
1017
+ group: "高级",
1018
+ default: 3e4,
1019
+ min: 1e3,
1020
+ max: 12e4
1021
+ }
1022
+ ],
1023
+ defaults: {
1024
+ dbType: "mysql",
1025
+ host: "localhost",
1026
+ port: 3306,
1027
+ database: "",
1028
+ username: "root",
1029
+ password: "",
1030
+ query: "SELECT 1",
1031
+ params: [],
1032
+ timeout: 3e4
1033
+ },
1034
+ description: "数据库直连数据源,支持 MySQL / PostgreSQL,通过后端代理执行查询"
1035
+ });
1036
+ //#endregion
1037
+ //#region src/index.js
1038
+ /**
1039
+ * @nbreak/datasources - 入口
1040
+ *
1041
+ * 导出 7 个内置数据源扩展:
1042
+ * - Static 静态 JSON
1043
+ * - API RESTful
1044
+ * - WebSocket 实时推送
1045
+ * - Excel 文件解析
1046
+ * - CSV 文本解析
1047
+ * - Mock 模拟数据
1048
+ * - Database 数据库查询
1049
+ *
1050
+ * 应用方使用方式:
1051
+ * import datasources from '@nbreak/datasources'
1052
+ * app.use(DataScreenVue, { extensions: datasources.extensions })
1053
+ *
1054
+ * B 层运行态适配器(src/datasources/adapters)可通过命名导出复用 A 层裸取数函数:
1055
+ * import { fetchApiData, fetchCsvData } from '@nbreak/datasources'
1056
+ */
1057
+ var extensions = [
1058
+ staticAdapter_default,
1059
+ apiAdapter_default,
1060
+ websocketAdapter_default,
1061
+ excelAdapter_default,
1062
+ csvAdapter_default,
1063
+ mockAdapter_default,
1064
+ databaseAdapter_default
1065
+ ];
1066
+ var adapters = {
1067
+ static: staticAdapter_default.definition.adapter,
1068
+ api: apiAdapter_default.definition.adapter,
1069
+ websocket: websocketAdapter_default.definition.adapter,
1070
+ excel: excelAdapter_default.definition.adapter,
1071
+ csv: csvAdapter_default.definition.adapter,
1072
+ mock: mockAdapter_default.definition.adapter,
1073
+ database: databaseAdapter_default.definition.adapter
1074
+ };
1075
+ var src_default = { extensions };
1076
+ var VERSION = "0.1.0";
1077
+ var PACKAGE_NAME = "@nbreak/datasources";
1078
+ //#endregion
1079
+ export { PACKAGE_NAME, VERSION, adapters, apiAdapter_default as apiAdapter, csvAdapter_default as csvAdapter, databaseAdapter_default as databaseAdapter, src_default as default, excelAdapter_default as excelAdapter, extensions, fetchData as fetchApiData, fetchData$1 as fetchCsvData, fetchData$2 as fetchDatabaseData, fetchData$3 as fetchExcelData, fetchData$4 as fetchMockData, fetchData$5 as fetchStaticData, fetchData$6 as fetchWebSocketData, mockAdapter_default as mockAdapter, staticAdapter_default as staticAdapter, websocketAdapter_default as websocketAdapter };
1080
+
1081
+ //# sourceMappingURL=index.js.map