tina4-nodejs 3.10.98 → 3.11.1
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/CLAUDE.md +1 -1
- package/package.json +5 -1
- package/packages/core/src/scss.ts +11 -3
- package/packages/frond/src/engine.ts +14 -1
- package/packages/orm/src/baseModel.ts +13 -11
package/CLAUDE.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tina4-nodejs",
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
"version": "3.11.1",
|
|
7
|
+
|
|
4
8
|
"type": "module",
|
|
5
9
|
"description": "Tina4 for Node.js/TypeScript — 54 built-in features, zero dependencies",
|
|
6
10
|
"keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
|
|
@@ -48,7 +48,7 @@ export class ScssCompiler {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/** Compile all .scss files in a directory into a single CSS output file. */
|
|
51
|
-
compileScss(scssDir: string = "src/scss", output: string = "public/css/default.css", minify: boolean = false): string {
|
|
51
|
+
compileScss(scssDir: string = "src/scss", output: string = "src/public/css/default.css", minify: boolean = false): string {
|
|
52
52
|
const absDir = resolve(scssDir);
|
|
53
53
|
if (!existsSync(absDir)) return "";
|
|
54
54
|
|
|
@@ -80,11 +80,19 @@ export class ScssCompiler {
|
|
|
80
80
|
css = css.trim();
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// Write output
|
|
83
|
+
// Write output only if content changed (avoids triggering DevReload loops)
|
|
84
84
|
const absOutput = resolve(output);
|
|
85
85
|
const outDir = dirname(absOutput);
|
|
86
86
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
87
|
-
|
|
87
|
+
let existing: string | null = null;
|
|
88
|
+
try {
|
|
89
|
+
existing = existsSync(absOutput) ? readFileSync(absOutput, "utf-8") : null;
|
|
90
|
+
} catch {
|
|
91
|
+
existing = null;
|
|
92
|
+
}
|
|
93
|
+
if (existing !== css) {
|
|
94
|
+
writeFileSync(absOutput, css, "utf-8");
|
|
95
|
+
}
|
|
88
96
|
|
|
89
97
|
return css;
|
|
90
98
|
}
|
|
@@ -1085,7 +1085,20 @@ const BUILTIN_FILTERS: Record<string, FilterFn> = {
|
|
|
1085
1085
|
last: (v) => Array.isArray(v) ? v[v.length - 1] ?? null : null,
|
|
1086
1086
|
join: (v, sep) => Array.isArray(v) ? v.map(String).join(sep !== undefined ? String(sep) : ", ") : String(v),
|
|
1087
1087
|
split: (v, sep) => String(v).split(sep !== undefined ? String(sep) : " "),
|
|
1088
|
-
replace: (v
|
|
1088
|
+
replace: (v: unknown, from?: unknown, to?: unknown) => {
|
|
1089
|
+
const s = String(v);
|
|
1090
|
+
if (from !== undefined && typeof from === 'object' && from !== null && !Array.isArray(from)) {
|
|
1091
|
+
let result = s;
|
|
1092
|
+
for (const [old, newVal] of Object.entries(from as Record<string, unknown>)) {
|
|
1093
|
+
result = result.split(old).join(String(newVal));
|
|
1094
|
+
}
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
if (from !== undefined && to !== undefined) {
|
|
1098
|
+
return s.split(String(from)).join(String(to));
|
|
1099
|
+
}
|
|
1100
|
+
return s;
|
|
1101
|
+
},
|
|
1089
1102
|
default: (v, fallback) => (v !== null && v !== undefined && v !== "") ? v : (fallback !== undefined ? fallback : ""),
|
|
1090
1103
|
raw: (v) => v,
|
|
1091
1104
|
safe: (v) => v,
|
|
@@ -67,7 +67,7 @@ export class BaseModel {
|
|
|
67
67
|
* When true, auto-generates fieldMapping entries from camelCase field names
|
|
68
68
|
* to snake_case DB column names. Explicit fieldMapping entries always win.
|
|
69
69
|
*/
|
|
70
|
-
static autoMap: boolean =
|
|
70
|
+
static autoMap: boolean = true;
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Maps JS property names to database column names.
|
|
@@ -563,13 +563,15 @@ export class BaseModel {
|
|
|
563
563
|
/**
|
|
564
564
|
* Convert to plain object (dictionary).
|
|
565
565
|
* @param include Optional array of relationship names to include (supports dot notation for nesting).
|
|
566
|
+
* @param case_ Key casing: 'camel' (default, keys as-is) or 'snake' (convert via fieldMapping).
|
|
566
567
|
*/
|
|
567
|
-
toDict(include?: string[]): Record<string, unknown> {
|
|
568
|
+
toDict(include?: string[], case_: "camel" | "snake" = "camel"): Record<string, unknown> {
|
|
568
569
|
const ModelClass = this.constructor as typeof BaseModel;
|
|
569
570
|
const result: Record<string, unknown> = {};
|
|
570
571
|
for (const key of Object.keys(ModelClass.fields)) {
|
|
571
572
|
if (this[key] !== undefined) {
|
|
572
|
-
|
|
573
|
+
const outKey = case_ === "snake" ? (ModelClass.fieldMapping[key] ?? key) : key;
|
|
574
|
+
result[outKey] = this[key];
|
|
573
575
|
}
|
|
574
576
|
}
|
|
575
577
|
// Include soft delete field
|
|
@@ -604,11 +606,11 @@ export class BaseModel {
|
|
|
604
606
|
result[relName] = null;
|
|
605
607
|
} else if (Array.isArray(data)) {
|
|
606
608
|
result[relName] = (data as BaseModel[]).map((r) =>
|
|
607
|
-
r.toDict(nested.length > 0 ? nested : undefined),
|
|
609
|
+
r.toDict(nested.length > 0 ? nested : undefined, case_),
|
|
608
610
|
);
|
|
609
611
|
} else if (typeof (data as BaseModel).toDict === "function") {
|
|
610
612
|
result[relName] = (data as BaseModel).toDict(
|
|
611
|
-
nested.length > 0 ? nested : undefined,
|
|
613
|
+
nested.length > 0 ? nested : undefined, case_,
|
|
612
614
|
);
|
|
613
615
|
}
|
|
614
616
|
}
|
|
@@ -639,15 +641,15 @@ export class BaseModel {
|
|
|
639
641
|
/**
|
|
640
642
|
* Convert to an associative object (alias for toDict).
|
|
641
643
|
*/
|
|
642
|
-
toAssoc(include?: string[]): Record<string, unknown> {
|
|
643
|
-
return this.toDict(include);
|
|
644
|
+
toAssoc(include?: string[], case_: "camel" | "snake" = "camel"): Record<string, unknown> {
|
|
645
|
+
return this.toDict(include, case_);
|
|
644
646
|
}
|
|
645
647
|
|
|
646
648
|
/**
|
|
647
649
|
* Convert to a plain object (alias for toDict).
|
|
648
650
|
*/
|
|
649
|
-
toObject(): Record<string, unknown> {
|
|
650
|
-
return this.toDict();
|
|
651
|
+
toObject(case_: "camel" | "snake" = "camel"): Record<string, unknown> {
|
|
652
|
+
return this.toDict(undefined, case_);
|
|
651
653
|
}
|
|
652
654
|
|
|
653
655
|
/**
|
|
@@ -668,8 +670,8 @@ export class BaseModel {
|
|
|
668
670
|
* Convert to JSON string.
|
|
669
671
|
* @param include Optional relationship names to include.
|
|
670
672
|
*/
|
|
671
|
-
toJson(include?: string[]): string {
|
|
672
|
-
return JSON.stringify(this.toDict(include));
|
|
673
|
+
toJson(include?: string[], case_: "camel" | "snake" = "camel"): string {
|
|
674
|
+
return JSON.stringify(this.toDict(include, case_));
|
|
673
675
|
}
|
|
674
676
|
|
|
675
677
|
/**
|