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