@dbcube/schema-builder 1.0.16 → 1.0.18
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.
- package/dist/index.cjs +609 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +108 -3
- package/dist/index.d.ts +108 -3
- package/dist/index.js +605 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,9 +6,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
// src/lib/Schema.ts
|
|
9
|
-
import
|
|
9
|
+
import fs5 from "fs";
|
|
10
10
|
import { Engine, TableProcessor, Config as ConfigClass } from "@dbcube/core";
|
|
11
|
-
import
|
|
11
|
+
import path4 from "path";
|
|
12
12
|
|
|
13
13
|
// src/lib/FileUtils.ts
|
|
14
14
|
import * as fs from "fs";
|
|
@@ -255,6 +255,550 @@ ${chalk.red("\u{1F6AB}")} ${chalk.bold.red("ERRORS FOUND")}`);
|
|
|
255
255
|
}
|
|
256
256
|
};
|
|
257
257
|
|
|
258
|
+
// src/lib/CubeValidator.ts
|
|
259
|
+
import fs3 from "fs";
|
|
260
|
+
import path2 from "path";
|
|
261
|
+
var CubeValidator = class {
|
|
262
|
+
validTypes = ["varchar", "int", "string", "text", "boolean", "date", "datetime", "timestamp", "decimal", "float", "double", "enum", "json"];
|
|
263
|
+
validOptions = ["not null", "primary", "autoincrement", "unique", "zerofill", "index", "required", "unsigned"];
|
|
264
|
+
validProperties = ["type", "length", "options", "value", "defaultValue", "foreign", "enumValues", "description"];
|
|
265
|
+
knownAnnotations = ["database", "table", "meta", "columns", "fields", "dataset", "beforeAdd", "afterAdd", "beforeUpdate", "afterUpdate", "beforeDelete", "afterDelete", "compute", "column"];
|
|
266
|
+
/**
|
|
267
|
+
* Validates a cube file comprehensively
|
|
268
|
+
*/
|
|
269
|
+
validateCubeFile(filePath) {
|
|
270
|
+
const errors = [];
|
|
271
|
+
try {
|
|
272
|
+
const content = fs3.readFileSync(filePath, "utf8");
|
|
273
|
+
const lines = content.split("\n");
|
|
274
|
+
const fileName = path2.basename(filePath, path2.extname(filePath));
|
|
275
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
276
|
+
const line = lines[lineIndex];
|
|
277
|
+
if (line.trim() === "" || line.trim().startsWith("//")) {
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
this.validateAnnotations(line, lineIndex + 1, filePath, fileName, errors);
|
|
281
|
+
this.validateDataTypes(line, lineIndex + 1, filePath, fileName, errors, content);
|
|
282
|
+
this.validateColumnOptions(line, lineIndex + 1, filePath, fileName, errors, lines);
|
|
283
|
+
this.validateColumnProperties(line, lineIndex + 1, filePath, fileName, errors, content);
|
|
284
|
+
this.validateRequiredColumnProperties(lines, lineIndex + 1, filePath, fileName, errors);
|
|
285
|
+
this.validateGeneralSyntax(line, lineIndex + 1, filePath, fileName, errors);
|
|
286
|
+
}
|
|
287
|
+
this.validateOverallStructure(content, filePath, fileName, errors);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
errors.push({
|
|
290
|
+
itemName: path2.basename(filePath, path2.extname(filePath)),
|
|
291
|
+
error: `Failed to read cube file: ${error.message}`,
|
|
292
|
+
filePath,
|
|
293
|
+
lineNumber: 1
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
isValid: errors.length === 0,
|
|
298
|
+
errors
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
validateAnnotations(line, lineNumber, filePath, fileName, errors) {
|
|
302
|
+
const annotationRegex = /@(\w+)/g;
|
|
303
|
+
let match;
|
|
304
|
+
while ((match = annotationRegex.exec(line)) !== null) {
|
|
305
|
+
const annotation = match[1];
|
|
306
|
+
if (!this.knownAnnotations.includes(annotation)) {
|
|
307
|
+
errors.push({
|
|
308
|
+
itemName: fileName,
|
|
309
|
+
error: `Unknown annotation '@${annotation}'. Valid annotations: ${this.knownAnnotations.join(", ")}`,
|
|
310
|
+
filePath,
|
|
311
|
+
lineNumber
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
validateDataTypes(line, lineNumber, filePath, fileName, errors, content) {
|
|
317
|
+
const typeRegex = /type:\s*["'](\w+)["']/g;
|
|
318
|
+
let match;
|
|
319
|
+
while ((match = typeRegex.exec(line)) !== null) {
|
|
320
|
+
const type = match[1];
|
|
321
|
+
if (!this.validTypes.includes(type)) {
|
|
322
|
+
errors.push({
|
|
323
|
+
itemName: fileName,
|
|
324
|
+
error: `Invalid data type '${type}'. Valid types: ${this.validTypes.join(", ")}`,
|
|
325
|
+
filePath,
|
|
326
|
+
lineNumber
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (line.includes('type: "varchar"')) {
|
|
331
|
+
const lines = content.split("\n");
|
|
332
|
+
const hasLengthNearby = lines.slice(Math.max(0, lineNumber - 1), Math.min(lineNumber + 4, lines.length)).some((nextLine) => nextLine.includes("length:"));
|
|
333
|
+
if (!hasLengthNearby) {
|
|
334
|
+
errors.push({
|
|
335
|
+
itemName: fileName,
|
|
336
|
+
error: "VARCHAR type requires a length specification",
|
|
337
|
+
filePath,
|
|
338
|
+
lineNumber
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
validateColumnOptions(line, lineNumber, filePath, fileName, errors, lines) {
|
|
344
|
+
const optionsMatch = line.match(/^\s*options\s*:\s*\[(.*)\]\s*;?\s*$/);
|
|
345
|
+
if (!optionsMatch) return;
|
|
346
|
+
const optionsContent = optionsMatch[1].trim();
|
|
347
|
+
const invalidSyntaxMatch = optionsContent.match(/[^",\s]+(?![^"]*")/);
|
|
348
|
+
if (invalidSyntaxMatch) {
|
|
349
|
+
errors.push({
|
|
350
|
+
itemName: fileName,
|
|
351
|
+
error: `Invalid syntax '${invalidSyntaxMatch[0]}' in options array. All values must be quoted strings`,
|
|
352
|
+
filePath,
|
|
353
|
+
lineNumber
|
|
354
|
+
});
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const optionMatches = optionsContent.match(/"([^"]*)"/g);
|
|
358
|
+
if (optionMatches) {
|
|
359
|
+
const columnType = this.getColumnTypeForOptions(lines, lineNumber - 1);
|
|
360
|
+
optionMatches.forEach((optionMatch) => {
|
|
361
|
+
const option = optionMatch.replace(/"/g, "");
|
|
362
|
+
if (option.trim() === "") {
|
|
363
|
+
errors.push({
|
|
364
|
+
itemName: fileName,
|
|
365
|
+
error: "Empty option found in options array. All options must have a value",
|
|
366
|
+
filePath,
|
|
367
|
+
lineNumber
|
|
368
|
+
});
|
|
369
|
+
} else if (!this.validOptions.includes(option)) {
|
|
370
|
+
errors.push({
|
|
371
|
+
itemName: fileName,
|
|
372
|
+
error: `Invalid option '${option}'. Valid options: ${this.validOptions.join(", ")}`,
|
|
373
|
+
filePath,
|
|
374
|
+
lineNumber
|
|
375
|
+
});
|
|
376
|
+
} else if (columnType !== "unknown" && !this.isOptionCompatibleWithType(option, columnType)) {
|
|
377
|
+
errors.push({
|
|
378
|
+
itemName: fileName,
|
|
379
|
+
error: `Option '${option}' is not compatible with type '${columnType}'`,
|
|
380
|
+
filePath,
|
|
381
|
+
lineNumber
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
validateColumnProperties(line, lineNumber, filePath, fileName, errors, content) {
|
|
388
|
+
const propertyKeyRegex = /^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:/;
|
|
389
|
+
const propMatch = propertyKeyRegex.exec(line);
|
|
390
|
+
if (!propMatch) return;
|
|
391
|
+
const propertyName = propMatch[1];
|
|
392
|
+
if (/^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:\s*\{/.test(line)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (this.isInsideForeignKeyObject(content, lineNumber - 1)) {
|
|
396
|
+
const validForeignKeyProperties = ["table", "column"];
|
|
397
|
+
if (!validForeignKeyProperties.includes(propertyName)) {
|
|
398
|
+
errors.push({
|
|
399
|
+
itemName: fileName,
|
|
400
|
+
error: `Invalid foreign key property '${propertyName}'. Valid foreign key properties: ${validForeignKeyProperties.join(", ")}`,
|
|
401
|
+
filePath,
|
|
402
|
+
lineNumber
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (this.isInsideColumnsBlock(content, lineNumber - 1)) {
|
|
408
|
+
if (!this.validProperties.includes(propertyName)) {
|
|
409
|
+
errors.push({
|
|
410
|
+
itemName: fileName,
|
|
411
|
+
error: `Invalid property '${propertyName}'. Valid properties: ${this.validProperties.join(", ")}`,
|
|
412
|
+
filePath,
|
|
413
|
+
lineNumber
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*$/.test(line)) {
|
|
418
|
+
errors.push({
|
|
419
|
+
itemName: fileName,
|
|
420
|
+
error: `Property '${propertyName}' is missing a value`,
|
|
421
|
+
filePath,
|
|
422
|
+
lineNumber
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
validateRequiredColumnProperties(lines, lineNumber, filePath, fileName, errors) {
|
|
427
|
+
const line = lines[lineNumber - 1];
|
|
428
|
+
if (!/^\s*\}\s*;?\s*$/.test(line)) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
let columnStartLine = -1;
|
|
432
|
+
let columnName = "";
|
|
433
|
+
for (let i = lineNumber - 2; i >= 0; i--) {
|
|
434
|
+
const currentLine = lines[i];
|
|
435
|
+
const columnDefMatch = currentLine.match(/^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*\{/);
|
|
436
|
+
if (columnDefMatch) {
|
|
437
|
+
let openBraces = 0;
|
|
438
|
+
let closeBraces = 0;
|
|
439
|
+
for (let j = i; j < lineNumber; j++) {
|
|
440
|
+
openBraces += (lines[j].match(/\{/g) || []).length;
|
|
441
|
+
closeBraces += (lines[j].match(/\}/g) || []).length;
|
|
442
|
+
}
|
|
443
|
+
if (openBraces === closeBraces) {
|
|
444
|
+
columnStartLine = i;
|
|
445
|
+
columnName = columnDefMatch[1];
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (columnStartLine === -1 || !columnName) return;
|
|
451
|
+
let hasType = false;
|
|
452
|
+
for (let i = columnStartLine + 1; i < lineNumber - 1; i++) {
|
|
453
|
+
if (lines[i].match(/^\s*type\s*:/)) {
|
|
454
|
+
hasType = true;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (!hasType && columnName !== "foreign" && columnName !== "defaultValue") {
|
|
459
|
+
errors.push({
|
|
460
|
+
itemName: fileName,
|
|
461
|
+
error: `Column '${columnName}' is missing required 'type' property`,
|
|
462
|
+
filePath,
|
|
463
|
+
lineNumber: columnStartLine + 1
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
validateGeneralSyntax(line, lineNumber, filePath, fileName, errors) {
|
|
468
|
+
const quotes = line.match(/["']/g);
|
|
469
|
+
if (quotes && quotes.length % 2 !== 0) {
|
|
470
|
+
errors.push({
|
|
471
|
+
itemName: fileName,
|
|
472
|
+
error: "Mismatched quotes detected",
|
|
473
|
+
filePath,
|
|
474
|
+
lineNumber
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (line.includes("@database") || line.includes("@table")) {
|
|
478
|
+
const stringAnnotationRegex = /@(database|table)\s*\(\s*"([^"]*)"\s*\)/;
|
|
479
|
+
if (!stringAnnotationRegex.test(line)) {
|
|
480
|
+
errors.push({
|
|
481
|
+
itemName: fileName,
|
|
482
|
+
error: 'Invalid annotation syntax. Expected format: @annotation("value")',
|
|
483
|
+
filePath,
|
|
484
|
+
lineNumber
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (line.includes("@meta")) {
|
|
489
|
+
const metaObjectRegex = /@meta\s*\(\s*\{/;
|
|
490
|
+
if (!metaObjectRegex.test(line)) {
|
|
491
|
+
errors.push({
|
|
492
|
+
itemName: fileName,
|
|
493
|
+
error: "Invalid @meta syntax. Expected format: @meta({ ... })",
|
|
494
|
+
filePath,
|
|
495
|
+
lineNumber
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
validateOverallStructure(content, filePath, fileName, errors) {
|
|
501
|
+
const lines = content.split("\n");
|
|
502
|
+
const hasDatabase = lines.some((line) => line.includes("@database"));
|
|
503
|
+
if (!hasDatabase) {
|
|
504
|
+
errors.push({
|
|
505
|
+
itemName: fileName,
|
|
506
|
+
error: "Missing required @database annotation",
|
|
507
|
+
filePath,
|
|
508
|
+
lineNumber: 1
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
if (filePath.includes(".table.cube")) {
|
|
512
|
+
const hasColumns = lines.some((line) => line.includes("@columns"));
|
|
513
|
+
if (!hasColumns) {
|
|
514
|
+
errors.push({
|
|
515
|
+
itemName: fileName,
|
|
516
|
+
error: "Table cube files require @columns annotation",
|
|
517
|
+
filePath,
|
|
518
|
+
lineNumber: 1
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
getColumnTypeForOptions(lines, optionsLineIndex) {
|
|
524
|
+
for (let i = optionsLineIndex - 1; i >= 0; i--) {
|
|
525
|
+
const line = lines[i];
|
|
526
|
+
const typeMatch = line.match(/^\s*type\s*:\s*"([^"]+)"/);
|
|
527
|
+
if (typeMatch) {
|
|
528
|
+
return typeMatch[1];
|
|
529
|
+
}
|
|
530
|
+
if (/^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:\s*\{/.test(line)) {
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return "unknown";
|
|
535
|
+
}
|
|
536
|
+
isOptionCompatibleWithType(option, type) {
|
|
537
|
+
const compatibilityRules = {
|
|
538
|
+
"zerofill": ["int", "decimal", "float", "double"],
|
|
539
|
+
"unsigned": ["int", "decimal", "float", "double"],
|
|
540
|
+
"autoincrement": ["int"],
|
|
541
|
+
"primary": ["int", "varchar", "string"],
|
|
542
|
+
"not null": ["int", "varchar", "string", "text", "boolean", "date", "datetime", "timestamp", "decimal", "float", "double"],
|
|
543
|
+
"unique": ["int", "varchar", "string", "text"],
|
|
544
|
+
"index": ["int", "varchar", "string", "text", "date", "datetime", "timestamp"],
|
|
545
|
+
"required": ["int", "varchar", "string", "text", "boolean", "date", "datetime", "timestamp", "decimal", "float", "double"]
|
|
546
|
+
};
|
|
547
|
+
const compatibleTypes = compatibilityRules[option];
|
|
548
|
+
if (!compatibleTypes) {
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
return compatibleTypes.includes(type);
|
|
552
|
+
}
|
|
553
|
+
isInsideColumnsBlock(content, lineIndex) {
|
|
554
|
+
const lines = content.split("\n");
|
|
555
|
+
let columnsStartLine = -1;
|
|
556
|
+
let columnsEndLine = -1;
|
|
557
|
+
for (let i = 0; i < lines.length; i++) {
|
|
558
|
+
if (lines[i].includes("@columns")) {
|
|
559
|
+
columnsStartLine = i;
|
|
560
|
+
let braceCount = 0;
|
|
561
|
+
for (let j = i; j < lines.length; j++) {
|
|
562
|
+
const currentLine = lines[j];
|
|
563
|
+
braceCount += (currentLine.match(/\{/g) || []).length;
|
|
564
|
+
braceCount -= (currentLine.match(/\}/g) || []).length;
|
|
565
|
+
if (braceCount === 0 && j > i) {
|
|
566
|
+
columnsEndLine = j;
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return columnsStartLine !== -1 && columnsEndLine !== -1 && lineIndex > columnsStartLine && lineIndex < columnsEndLine;
|
|
574
|
+
}
|
|
575
|
+
isInsideForeignKeyObject(content, lineIndex) {
|
|
576
|
+
const lines = content.split("\n");
|
|
577
|
+
for (let i = lineIndex; i >= 0; i--) {
|
|
578
|
+
const line = lines[i];
|
|
579
|
+
if (/foreign\s*:\s*\{/.test(line)) {
|
|
580
|
+
let braceCount = 0;
|
|
581
|
+
for (let j = i; j <= lineIndex; j++) {
|
|
582
|
+
const currentLine = lines[j];
|
|
583
|
+
const openBraces = (currentLine.match(/\{/g) || []).length;
|
|
584
|
+
const closeBraces = (currentLine.match(/\}/g) || []).length;
|
|
585
|
+
braceCount += openBraces - closeBraces;
|
|
586
|
+
if (braceCount === 0 && j > i) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return braceCount > 0;
|
|
591
|
+
}
|
|
592
|
+
if (line.trim() === "}" || line.includes("};")) {
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return false;
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// src/lib/DependencyResolver.ts
|
|
601
|
+
import fs4 from "fs";
|
|
602
|
+
import path3 from "path";
|
|
603
|
+
var DependencyResolver = class {
|
|
604
|
+
/**
|
|
605
|
+
* Resolves table dependencies and creates execution order
|
|
606
|
+
*/
|
|
607
|
+
static resolveDependencies(cubeFiles, cubeType = "table") {
|
|
608
|
+
const tableDependencies = this.extractDependencies(cubeFiles, cubeType);
|
|
609
|
+
const orderedTables = this.topologicalSort(tableDependencies);
|
|
610
|
+
const executionOrder = {
|
|
611
|
+
tables: cubeType === "table" ? orderedTables : [],
|
|
612
|
+
seeders: cubeType === "seeder" ? orderedTables : [],
|
|
613
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
614
|
+
};
|
|
615
|
+
this.saveExecutionOrder(executionOrder);
|
|
616
|
+
return executionOrder;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Extracts dependencies from cube files
|
|
620
|
+
*/
|
|
621
|
+
static extractDependencies(cubeFiles, cubeType) {
|
|
622
|
+
const dependencies = [];
|
|
623
|
+
for (const file of cubeFiles) {
|
|
624
|
+
let filePath;
|
|
625
|
+
if (path3.isAbsolute(file)) {
|
|
626
|
+
filePath = file;
|
|
627
|
+
} else if (fs4.existsSync(file)) {
|
|
628
|
+
filePath = path3.resolve(file);
|
|
629
|
+
} else {
|
|
630
|
+
filePath = path3.join(process.cwd(), "dbcube", "cubes", file);
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
const tableNameResult = FileUtils_default.extracTableNameFromCube(filePath);
|
|
634
|
+
const tableName = tableNameResult.status === 200 ? tableNameResult.message : path3.basename(file, `.${cubeType}.cube`);
|
|
635
|
+
const deps = this.extractForeignKeyReferences(filePath);
|
|
636
|
+
dependencies.push({
|
|
637
|
+
tableName,
|
|
638
|
+
filePath,
|
|
639
|
+
dependencies: deps
|
|
640
|
+
});
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error(`Error processing ${filePath}:`, error);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return dependencies;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Extracts foreign key references from a cube file
|
|
649
|
+
*/
|
|
650
|
+
static extractForeignKeyReferences(filePath) {
|
|
651
|
+
const dependencies = [];
|
|
652
|
+
try {
|
|
653
|
+
const content = fs4.readFileSync(filePath, "utf8");
|
|
654
|
+
const lines = content.split("\n");
|
|
655
|
+
let insideForeignKey = false;
|
|
656
|
+
let braceCount = 0;
|
|
657
|
+
for (const line of lines) {
|
|
658
|
+
if (/foreign\s*:\s*\{/.test(line)) {
|
|
659
|
+
insideForeignKey = true;
|
|
660
|
+
braceCount = 1;
|
|
661
|
+
const sameLineMatch = line.match(/table\s*:\s*["']([^"']+)["']/);
|
|
662
|
+
if (sameLineMatch) {
|
|
663
|
+
dependencies.push(sameLineMatch[1]);
|
|
664
|
+
insideForeignKey = false;
|
|
665
|
+
braceCount = 0;
|
|
666
|
+
}
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (insideForeignKey) {
|
|
670
|
+
braceCount += (line.match(/\{/g) || []).length;
|
|
671
|
+
braceCount -= (line.match(/\}/g) || []).length;
|
|
672
|
+
const tableMatch = line.match(/table\s*:\s*["']([^"']+)["']/);
|
|
673
|
+
if (tableMatch) {
|
|
674
|
+
dependencies.push(tableMatch[1]);
|
|
675
|
+
}
|
|
676
|
+
if (braceCount === 0) {
|
|
677
|
+
insideForeignKey = false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
} catch (error) {
|
|
682
|
+
console.error(`Error reading file ${filePath}:`, error);
|
|
683
|
+
}
|
|
684
|
+
return dependencies;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Performs topological sort to determine execution order
|
|
688
|
+
*/
|
|
689
|
+
static topologicalSort(dependencies) {
|
|
690
|
+
const graph = /* @__PURE__ */ new Map();
|
|
691
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
692
|
+
const tableMap = /* @__PURE__ */ new Map();
|
|
693
|
+
for (const dep of dependencies) {
|
|
694
|
+
graph.set(dep.tableName, dep.dependencies);
|
|
695
|
+
inDegree.set(dep.tableName, 0);
|
|
696
|
+
tableMap.set(dep.tableName, dep);
|
|
697
|
+
}
|
|
698
|
+
for (const dep of dependencies) {
|
|
699
|
+
for (const dependency of dep.dependencies) {
|
|
700
|
+
if (inDegree.has(dependency)) {
|
|
701
|
+
inDegree.set(dep.tableName, (inDegree.get(dep.tableName) || 0) + 1);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const queue = [];
|
|
706
|
+
const result = [];
|
|
707
|
+
for (const [table, degree] of inDegree) {
|
|
708
|
+
if (degree === 0) {
|
|
709
|
+
queue.push(table);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
while (queue.length > 0) {
|
|
713
|
+
const current = queue.shift();
|
|
714
|
+
result.push(current);
|
|
715
|
+
const currentDeps = graph.get(current) || [];
|
|
716
|
+
for (const neighbor of currentDeps) {
|
|
717
|
+
if (inDegree.has(neighbor)) {
|
|
718
|
+
const newDegree = (inDegree.get(neighbor) || 0) - 1;
|
|
719
|
+
inDegree.set(neighbor, newDegree);
|
|
720
|
+
if (newDegree === 0) {
|
|
721
|
+
queue.push(neighbor);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (result.length !== dependencies.length) {
|
|
727
|
+
console.warn("\u26A0\uFE0F Circular dependencies detected in tables. Some tables may not execute in optimal order.");
|
|
728
|
+
for (const dep of dependencies) {
|
|
729
|
+
if (!result.includes(dep.tableName)) {
|
|
730
|
+
result.push(dep.tableName);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Saves the execution order to .dbcube/orderexecute.json
|
|
738
|
+
*/
|
|
739
|
+
static saveExecutionOrder(order) {
|
|
740
|
+
try {
|
|
741
|
+
const projectRoot = process.cwd();
|
|
742
|
+
const dbcubeDir = path3.join(projectRoot, ".dbcube");
|
|
743
|
+
const orderFile = path3.join(dbcubeDir, "orderexecute.json");
|
|
744
|
+
if (!fs4.existsSync(dbcubeDir)) {
|
|
745
|
+
fs4.mkdirSync(dbcubeDir, { recursive: true });
|
|
746
|
+
}
|
|
747
|
+
fs4.writeFileSync(orderFile, JSON.stringify(order, null, 2), "utf8");
|
|
748
|
+
console.log(`\u{1F4C4} Execution order saved to: ${path3.relative(projectRoot, orderFile)}`);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error("\u274C Failed to save execution order:", error);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Loads the execution order from .dbcube/orderexecute.json
|
|
755
|
+
*/
|
|
756
|
+
static loadExecutionOrder() {
|
|
757
|
+
try {
|
|
758
|
+
const projectRoot = process.cwd();
|
|
759
|
+
const orderFile = path3.join(projectRoot, ".dbcube", "orderexecute.json");
|
|
760
|
+
if (!fs4.existsSync(orderFile)) {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
const content = fs4.readFileSync(orderFile, "utf8");
|
|
764
|
+
return JSON.parse(content);
|
|
765
|
+
} catch (error) {
|
|
766
|
+
console.error("\u274C Failed to load execution order:", error);
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Orders cube files based on saved execution order
|
|
772
|
+
*/
|
|
773
|
+
static orderCubeFiles(cubeFiles, cubeType) {
|
|
774
|
+
const executionOrder = this.loadExecutionOrder();
|
|
775
|
+
if (!executionOrder) {
|
|
776
|
+
console.log("\u{1F4C4} No execution order found, processing files in current order");
|
|
777
|
+
return cubeFiles;
|
|
778
|
+
}
|
|
779
|
+
const orderList = cubeType === "table" ? executionOrder.tables : executionOrder.seeders;
|
|
780
|
+
const orderedFiles = [];
|
|
781
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
782
|
+
for (const file of cubeFiles) {
|
|
783
|
+
const filePath = path3.isAbsolute(file) ? file : path3.join(process.cwd(), "dbcube", "cubes", file);
|
|
784
|
+
const tableNameResult = FileUtils_default.extracTableNameFromCube(filePath);
|
|
785
|
+
const tableName = tableNameResult.status === 200 ? tableNameResult.message : path3.basename(file, `.${cubeType}.cube`);
|
|
786
|
+
fileMap.set(tableName, file);
|
|
787
|
+
}
|
|
788
|
+
for (const tableName of orderList) {
|
|
789
|
+
if (fileMap.has(tableName)) {
|
|
790
|
+
orderedFiles.push(fileMap.get(tableName));
|
|
791
|
+
fileMap.delete(tableName);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
for (const [, file] of fileMap) {
|
|
795
|
+
orderedFiles.push(file);
|
|
796
|
+
}
|
|
797
|
+
console.log(`\u{1F4CB} Using dependency order: ${orderList.join(" \u2192 ")}`);
|
|
798
|
+
return orderedFiles;
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
258
802
|
// src/lib/Schema.ts
|
|
259
803
|
var Schema = class {
|
|
260
804
|
name;
|
|
@@ -264,18 +808,27 @@ var Schema = class {
|
|
|
264
808
|
this.engine = new Engine(name);
|
|
265
809
|
}
|
|
266
810
|
/**
|
|
267
|
-
* Validates
|
|
811
|
+
* Validates cube file comprehensively including syntax, database configuration, and structure
|
|
268
812
|
* @param filePath - Path to the cube file
|
|
269
|
-
* @returns
|
|
813
|
+
* @returns validation result with any errors found
|
|
270
814
|
*/
|
|
271
815
|
validateDatabaseConfiguration(filePath) {
|
|
272
816
|
try {
|
|
817
|
+
const cubeValidator = new CubeValidator();
|
|
818
|
+
const cubeValidation = cubeValidator.validateCubeFile(filePath);
|
|
819
|
+
if (!cubeValidation.isValid && cubeValidation.errors.length > 0) {
|
|
820
|
+
return {
|
|
821
|
+
isValid: false,
|
|
822
|
+
error: cubeValidation.errors[0]
|
|
823
|
+
// Return the first error found
|
|
824
|
+
};
|
|
825
|
+
}
|
|
273
826
|
const dbResult = FileUtils_default.extractDatabaseNameFromCube(filePath);
|
|
274
827
|
if (dbResult.status !== 200) {
|
|
275
828
|
return {
|
|
276
829
|
isValid: false,
|
|
277
830
|
error: {
|
|
278
|
-
itemName:
|
|
831
|
+
itemName: path4.basename(filePath, path4.extname(filePath)),
|
|
279
832
|
error: `Error reading database directive: ${dbResult.message}`,
|
|
280
833
|
filePath,
|
|
281
834
|
lineNumber: this.findDatabaseLineNumber(filePath)
|
|
@@ -284,7 +837,7 @@ var Schema = class {
|
|
|
284
837
|
}
|
|
285
838
|
const cubeDbName = dbResult.message;
|
|
286
839
|
const configInstance = new ConfigClass();
|
|
287
|
-
const configFilePath =
|
|
840
|
+
const configFilePath = path4.resolve(process.cwd(), "dbcube.config.js");
|
|
288
841
|
const configFn = __require(configFilePath);
|
|
289
842
|
if (typeof configFn === "function") {
|
|
290
843
|
configFn(configInstance);
|
|
@@ -311,7 +864,7 @@ var Schema = class {
|
|
|
311
864
|
return {
|
|
312
865
|
isValid: false,
|
|
313
866
|
error: {
|
|
314
|
-
itemName:
|
|
867
|
+
itemName: path4.basename(filePath, path4.extname(filePath)),
|
|
315
868
|
error: `Database configuration '${cubeDbName}' not found in dbcube.config.js. Available: ${availableText}`,
|
|
316
869
|
filePath,
|
|
317
870
|
lineNumber: this.findDatabaseLineNumber(filePath)
|
|
@@ -323,7 +876,7 @@ var Schema = class {
|
|
|
323
876
|
return {
|
|
324
877
|
isValid: false,
|
|
325
878
|
error: {
|
|
326
|
-
itemName:
|
|
879
|
+
itemName: path4.basename(filePath, path4.extname(filePath)),
|
|
327
880
|
error: `Database configuration validation failed: ${error.message}`,
|
|
328
881
|
filePath,
|
|
329
882
|
lineNumber: this.findDatabaseLineNumber(filePath)
|
|
@@ -336,7 +889,7 @@ var Schema = class {
|
|
|
336
889
|
*/
|
|
337
890
|
findDatabaseLineNumber(filePath) {
|
|
338
891
|
try {
|
|
339
|
-
const content =
|
|
892
|
+
const content = fs5.readFileSync(filePath, "utf8");
|
|
340
893
|
const lines = content.split("\n");
|
|
341
894
|
for (let i = 0; i < lines.length; i++) {
|
|
342
895
|
if (lines[i].includes("@database")) {
|
|
@@ -350,7 +903,7 @@ var Schema = class {
|
|
|
350
903
|
}
|
|
351
904
|
async createDatabase() {
|
|
352
905
|
const startTime = Date.now();
|
|
353
|
-
const rootPath =
|
|
906
|
+
const rootPath = path4.resolve(process.cwd());
|
|
354
907
|
UIUtils.showOperationHeader(" CREATING DATABASE", this.name, "\u{1F5C4}\uFE0F");
|
|
355
908
|
await UIUtils.showItemProgress("Preparando e instalando base de datos", 1, 1);
|
|
356
909
|
try {
|
|
@@ -394,28 +947,31 @@ var Schema = class {
|
|
|
394
947
|
}
|
|
395
948
|
async refreshTables() {
|
|
396
949
|
const startTime = Date.now();
|
|
397
|
-
const cubesDir =
|
|
398
|
-
if (!
|
|
950
|
+
const cubesDir = path4.join(process.cwd(), "dbcube", "cubes");
|
|
951
|
+
if (!fs5.existsSync(cubesDir)) {
|
|
399
952
|
throw new Error("\u274C The cubes folder does not exist");
|
|
400
953
|
}
|
|
401
954
|
const cubeFiles = FileUtils_default.getCubeFilesRecursively("dbcube", "table.cube");
|
|
402
955
|
if (cubeFiles.length === 0) {
|
|
403
956
|
throw new Error("\u274C There are no cubes to execute");
|
|
404
957
|
}
|
|
958
|
+
console.log("\u{1F504} Resolving table dependencies...");
|
|
959
|
+
DependencyResolver.resolveDependencies(cubeFiles, "table");
|
|
960
|
+
const orderedCubeFiles = DependencyResolver.orderCubeFiles(cubeFiles, "table");
|
|
405
961
|
UIUtils.showOperationHeader("EXECUTING REFRESH TABLES", this.name, "\u{1F504}");
|
|
406
962
|
let totalTablesProcessed = 0;
|
|
407
963
|
let successCount = 0;
|
|
408
964
|
let errorCount = 0;
|
|
409
965
|
const processedTables = [];
|
|
410
966
|
const errors = [];
|
|
411
|
-
for (let index = 0; index <
|
|
412
|
-
const file =
|
|
413
|
-
const filePath =
|
|
414
|
-
const stats =
|
|
967
|
+
for (let index = 0; index < orderedCubeFiles.length; index++) {
|
|
968
|
+
const file = orderedCubeFiles[index];
|
|
969
|
+
const filePath = path4.isAbsolute(file) ? file : path4.join(cubesDir, file);
|
|
970
|
+
const stats = fs5.statSync(filePath);
|
|
415
971
|
if (stats.isFile()) {
|
|
416
972
|
const getTableName = FileUtils_default.extracTableNameFromCube(filePath);
|
|
417
|
-
const tableName = getTableName.status === 200 ? getTableName.message :
|
|
418
|
-
await UIUtils.showItemProgress(tableName, index + 1,
|
|
973
|
+
const tableName = getTableName.status === 200 ? getTableName.message : path4.basename(file, ".table.cube");
|
|
974
|
+
await UIUtils.showItemProgress(tableName, index + 1, orderedCubeFiles.length);
|
|
419
975
|
try {
|
|
420
976
|
const validation = this.validateDatabaseConfiguration(filePath);
|
|
421
977
|
if (!validation.isValid && validation.error) {
|
|
@@ -496,28 +1052,31 @@ var Schema = class {
|
|
|
496
1052
|
}
|
|
497
1053
|
async freshTables() {
|
|
498
1054
|
const startTime = Date.now();
|
|
499
|
-
const cubesDir =
|
|
500
|
-
if (!
|
|
1055
|
+
const cubesDir = path4.join(process.cwd(), "dbcube", "cubes");
|
|
1056
|
+
if (!fs5.existsSync(cubesDir)) {
|
|
501
1057
|
throw new Error("\u274C The cubes folder does not exist");
|
|
502
1058
|
}
|
|
503
1059
|
const cubeFiles = FileUtils_default.getCubeFilesRecursively("dbcube", "table.cube");
|
|
504
1060
|
if (cubeFiles.length === 0) {
|
|
505
1061
|
throw new Error("\u274C There are no cubes to execute");
|
|
506
1062
|
}
|
|
1063
|
+
console.log("\u{1F504} Resolving table dependencies...");
|
|
1064
|
+
DependencyResolver.resolveDependencies(cubeFiles, "table");
|
|
1065
|
+
const orderedCubeFiles = DependencyResolver.orderCubeFiles(cubeFiles, "table");
|
|
507
1066
|
UIUtils.showOperationHeader("EXECUTING FRESH TABLES", this.name);
|
|
508
1067
|
let totalTablesProcessed = 0;
|
|
509
1068
|
let successCount = 0;
|
|
510
1069
|
let errorCount = 0;
|
|
511
1070
|
const processedTables = [];
|
|
512
1071
|
const errors = [];
|
|
513
|
-
for (let index = 0; index <
|
|
514
|
-
const file =
|
|
515
|
-
const filePath =
|
|
516
|
-
const stats =
|
|
1072
|
+
for (let index = 0; index < orderedCubeFiles.length; index++) {
|
|
1073
|
+
const file = orderedCubeFiles[index];
|
|
1074
|
+
const filePath = path4.isAbsolute(file) ? file : path4.join(cubesDir, file);
|
|
1075
|
+
const stats = fs5.statSync(filePath);
|
|
517
1076
|
if (stats.isFile()) {
|
|
518
1077
|
const getTableName = FileUtils_default.extracTableNameFromCube(filePath);
|
|
519
|
-
const tableName = getTableName.status === 200 ? getTableName.message :
|
|
520
|
-
await UIUtils.showItemProgress(tableName, index + 1,
|
|
1078
|
+
const tableName = getTableName.status === 200 ? getTableName.message : path4.basename(file, ".table.cube");
|
|
1079
|
+
await UIUtils.showItemProgress(tableName, index + 1, orderedCubeFiles.length);
|
|
521
1080
|
try {
|
|
522
1081
|
const validation = this.validateDatabaseConfiguration(filePath);
|
|
523
1082
|
if (!validation.isValid && validation.error) {
|
|
@@ -596,28 +1155,29 @@ var Schema = class {
|
|
|
596
1155
|
}
|
|
597
1156
|
async executeSeeders() {
|
|
598
1157
|
const startTime = Date.now();
|
|
599
|
-
const cubesDir =
|
|
600
|
-
if (!
|
|
1158
|
+
const cubesDir = path4.join(process.cwd(), "dbcube", "cubes");
|
|
1159
|
+
if (!fs5.existsSync(cubesDir)) {
|
|
601
1160
|
throw new Error("\u274C The cubes folder does not exist");
|
|
602
1161
|
}
|
|
603
1162
|
const cubeFiles = FileUtils_default.getCubeFilesRecursively("dbcube", "seeder.cube");
|
|
604
1163
|
if (cubeFiles.length === 0) {
|
|
605
1164
|
throw new Error("\u274C There are no cubes to execute");
|
|
606
1165
|
}
|
|
1166
|
+
const orderedCubeFiles = DependencyResolver.orderCubeFiles(cubeFiles, "seeder");
|
|
607
1167
|
UIUtils.showOperationHeader("EXECUTING SEEDERS", this.name, "\u{1F331}");
|
|
608
1168
|
let totalSeedersProcessed = 0;
|
|
609
1169
|
let successCount = 0;
|
|
610
1170
|
let errorCount = 0;
|
|
611
1171
|
const processedSeeders = [];
|
|
612
1172
|
const errors = [];
|
|
613
|
-
for (let index = 0; index <
|
|
614
|
-
const file =
|
|
615
|
-
const filePath =
|
|
616
|
-
const stats =
|
|
1173
|
+
for (let index = 0; index < orderedCubeFiles.length; index++) {
|
|
1174
|
+
const file = orderedCubeFiles[index];
|
|
1175
|
+
const filePath = path4.isAbsolute(file) ? file : path4.join(cubesDir, file);
|
|
1176
|
+
const stats = fs5.statSync(filePath);
|
|
617
1177
|
if (stats.isFile()) {
|
|
618
1178
|
const getSeederName = FileUtils_default.extracTableNameFromCube(filePath);
|
|
619
|
-
const seederName = getSeederName.status === 200 ? getSeederName.message :
|
|
620
|
-
await UIUtils.showItemProgress(seederName, index + 1,
|
|
1179
|
+
const seederName = getSeederName.status === 200 ? getSeederName.message : path4.basename(file, ".seeder.cube");
|
|
1180
|
+
await UIUtils.showItemProgress(seederName, index + 1, orderedCubeFiles.length);
|
|
621
1181
|
try {
|
|
622
1182
|
const validation = this.validateDatabaseConfiguration(filePath);
|
|
623
1183
|
if (!validation.isValid && validation.error) {
|
|
@@ -667,9 +1227,9 @@ var Schema = class {
|
|
|
667
1227
|
}
|
|
668
1228
|
async executeTriggers() {
|
|
669
1229
|
const startTime = Date.now();
|
|
670
|
-
const cubesDir =
|
|
671
|
-
const triggersDirExit =
|
|
672
|
-
if (!
|
|
1230
|
+
const cubesDir = path4.join(process.cwd(), "dbcube", "cubes");
|
|
1231
|
+
const triggersDirExit = path4.join(process.cwd(), "dbcube", "triggers");
|
|
1232
|
+
if (!fs5.existsSync(cubesDir)) {
|
|
673
1233
|
throw new Error("\u274C The cubes folder does not exist");
|
|
674
1234
|
}
|
|
675
1235
|
const cubeFiles = FileUtils_default.getCubeFilesRecursively("dbcube", "trigger.cube");
|
|
@@ -684,11 +1244,11 @@ var Schema = class {
|
|
|
684
1244
|
const errors = [];
|
|
685
1245
|
for (let index = 0; index < cubeFiles.length; index++) {
|
|
686
1246
|
const file = cubeFiles[index];
|
|
687
|
-
const filePath =
|
|
688
|
-
const stats =
|
|
1247
|
+
const filePath = path4.isAbsolute(file) ? file : path4.join(cubesDir, file);
|
|
1248
|
+
const stats = fs5.statSync(filePath);
|
|
689
1249
|
if (stats.isFile()) {
|
|
690
1250
|
const getTriggerName = FileUtils_default.extracTableNameFromCube(filePath);
|
|
691
|
-
const triggerName = getTriggerName.status === 200 ? getTriggerName.message :
|
|
1251
|
+
const triggerName = getTriggerName.status === 200 ? getTriggerName.message : path4.basename(file, ".trigger.cube");
|
|
692
1252
|
await UIUtils.showItemProgress(triggerName, index + 1, cubeFiles.length);
|
|
693
1253
|
try {
|
|
694
1254
|
const validation = this.validateDatabaseConfiguration(filePath);
|
|
@@ -759,7 +1319,7 @@ ${chalk2.red("\u{1F6AB}")} ${chalk2.bold.red("ERRORS FOUND")}`);
|
|
|
759
1319
|
const errorLocation = `${filePath}:${lineStr}:${columnStr}`;
|
|
760
1320
|
console.log(`${chalk2.cyan("[code]")} ${chalk2.yellow(errorLocation)}`);
|
|
761
1321
|
try {
|
|
762
|
-
const codeLines =
|
|
1322
|
+
const codeLines = fs5.readFileSync(filePath, "utf-8").split("\n");
|
|
763
1323
|
const start = Math.max(0, lineNum - 3);
|
|
764
1324
|
const end = Math.min(codeLines.length, lineNum + 2);
|
|
765
1325
|
for (let i = start; i < end; i++) {
|
|
@@ -773,13 +1333,17 @@ ${chalk2.red("\u{1F6AB}")} ${chalk2.bold.red("ERRORS FOUND")}`);
|
|
|
773
1333
|
}
|
|
774
1334
|
}
|
|
775
1335
|
}
|
|
1336
|
+
console.log("");
|
|
776
1337
|
process.exit(1);
|
|
777
1338
|
}
|
|
778
1339
|
|
|
779
1340
|
// src/index.ts
|
|
780
1341
|
var index_default = Schema;
|
|
781
1342
|
export {
|
|
1343
|
+
CubeValidator,
|
|
1344
|
+
DependencyResolver,
|
|
782
1345
|
Schema,
|
|
1346
|
+
UIUtils,
|
|
783
1347
|
index_default as default
|
|
784
1348
|
};
|
|
785
1349
|
//# sourceMappingURL=index.js.map
|