elit 3.1.8 → 3.1.9
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/cli.js +291 -2
- package/dist/database.d.mts +10 -1
- package/dist/database.d.ts +9 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +293 -2
- package/dist/database.mjs +291 -1
- package/dist/server.js +290 -1
- package/dist/server.mjs +290 -1
- package/package.json +1 -1
- package/src/database.ts +364 -2
package/dist/server.mjs
CHANGED
|
@@ -2723,8 +2723,16 @@ var Database = class {
|
|
|
2723
2723
|
acc[type] = (...args) => logs.push({ type, args });
|
|
2724
2724
|
return acc;
|
|
2725
2725
|
}, {});
|
|
2726
|
+
const systemBase = {
|
|
2727
|
+
update,
|
|
2728
|
+
remove,
|
|
2729
|
+
rename: rename2,
|
|
2730
|
+
read,
|
|
2731
|
+
create,
|
|
2732
|
+
save
|
|
2733
|
+
};
|
|
2726
2734
|
this.register({
|
|
2727
|
-
|
|
2735
|
+
dbConsole: { ...customConsole, ...systemBase }
|
|
2728
2736
|
});
|
|
2729
2737
|
let stringCode;
|
|
2730
2738
|
if (typeof code === "function") {
|
|
@@ -2819,6 +2827,287 @@ var Database = class {
|
|
|
2819
2827
|
return await this.vmRun(code, options);
|
|
2820
2828
|
}
|
|
2821
2829
|
};
|
|
2830
|
+
function create(dbName, code) {
|
|
2831
|
+
const DIR = "databases";
|
|
2832
|
+
const basePath = process.cwd();
|
|
2833
|
+
const baseDir = path.resolve(basePath, DIR);
|
|
2834
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
2835
|
+
fs2.appendFileSync(dbPath, code.toString(), "utf8");
|
|
2836
|
+
}
|
|
2837
|
+
function read(dbName) {
|
|
2838
|
+
const DIR = "databases";
|
|
2839
|
+
const basePath = process.cwd();
|
|
2840
|
+
const baseDir = path.resolve(basePath, DIR);
|
|
2841
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
2842
|
+
if (!fs2.existsSync(dbPath)) {
|
|
2843
|
+
throw new Error(`Database '${dbName}' not found`);
|
|
2844
|
+
}
|
|
2845
|
+
return fs2.readFileSync(dbPath, "utf8");
|
|
2846
|
+
}
|
|
2847
|
+
function remove(dbName, fnName) {
|
|
2848
|
+
const DIR = "databases";
|
|
2849
|
+
const basePath = process.cwd();
|
|
2850
|
+
const baseDir = path.resolve(basePath, DIR);
|
|
2851
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
2852
|
+
if (!fs2.existsSync(dbPath)) return false;
|
|
2853
|
+
if (!fnName) {
|
|
2854
|
+
const bak2 = `${dbPath}.bak`;
|
|
2855
|
+
try {
|
|
2856
|
+
fs2.copyFileSync(dbPath, bak2);
|
|
2857
|
+
} catch (e) {
|
|
2858
|
+
}
|
|
2859
|
+
try {
|
|
2860
|
+
fs2.unlinkSync(dbPath);
|
|
2861
|
+
return "Removed successfully";
|
|
2862
|
+
} catch (e) {
|
|
2863
|
+
return "Removed failed";
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
const bak = `${dbPath}.bak`;
|
|
2867
|
+
try {
|
|
2868
|
+
fs2.copyFileSync(dbPath, bak);
|
|
2869
|
+
} catch (e) {
|
|
2870
|
+
}
|
|
2871
|
+
let src = fs2.readFileSync(dbPath, "utf8");
|
|
2872
|
+
const escaped = fnName.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
2873
|
+
const startRe = new RegExp(
|
|
2874
|
+
`function\\s+${escaped}\\s*\\(|\\bclass\\s+${escaped}\\b|\\b(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:function\\b|class\\b|\\(|\\{|\\[)`,
|
|
2875
|
+
"m"
|
|
2876
|
+
);
|
|
2877
|
+
const startMatch = src.match(startRe);
|
|
2878
|
+
if (startMatch) {
|
|
2879
|
+
const startIdx = startMatch.index;
|
|
2880
|
+
const len = src.length;
|
|
2881
|
+
const idxCurly = src.indexOf("{", startIdx);
|
|
2882
|
+
const idxBracket = src.indexOf("[", startIdx);
|
|
2883
|
+
let braceOpen = -1;
|
|
2884
|
+
if (idxCurly === -1) braceOpen = idxBracket;
|
|
2885
|
+
else if (idxBracket === -1) braceOpen = idxCurly;
|
|
2886
|
+
else braceOpen = Math.min(idxCurly, idxBracket);
|
|
2887
|
+
if (braceOpen !== -1) {
|
|
2888
|
+
const openingChar = src[braceOpen];
|
|
2889
|
+
const closingChar = openingChar === "[" ? "]" : "}";
|
|
2890
|
+
let i = braceOpen + 1;
|
|
2891
|
+
let depth = 1;
|
|
2892
|
+
while (i < len && depth > 0) {
|
|
2893
|
+
const ch = src[i];
|
|
2894
|
+
if (ch === openingChar) depth++;
|
|
2895
|
+
else if (ch === closingChar) depth--;
|
|
2896
|
+
i++;
|
|
2897
|
+
}
|
|
2898
|
+
let braceClose = i;
|
|
2899
|
+
let endIdx = braceClose;
|
|
2900
|
+
if (src.slice(braceClose, braceClose + 1) === ";")
|
|
2901
|
+
endIdx = braceClose + 1;
|
|
2902
|
+
const before = src.slice(0, startIdx);
|
|
2903
|
+
const after = src.slice(endIdx);
|
|
2904
|
+
src = before + after;
|
|
2905
|
+
} else {
|
|
2906
|
+
const semi = src.indexOf(";", startIdx);
|
|
2907
|
+
let endIdx = semi !== -1 ? semi + 1 : src.indexOf("\n\n", startIdx);
|
|
2908
|
+
if (endIdx === -1) endIdx = len;
|
|
2909
|
+
src = src.slice(0, startIdx) + src.slice(endIdx);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
const exportRe = new RegExp(
|
|
2913
|
+
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
2914
|
+
"g"
|
|
2915
|
+
);
|
|
2916
|
+
src = src.replace(exportRe, "");
|
|
2917
|
+
src = src.replace(/\n{3,}/g, "\n\n");
|
|
2918
|
+
fs2.writeFileSync(dbPath, src, "utf8");
|
|
2919
|
+
return `Removed ${fnName} from database ${dbName}.`;
|
|
2920
|
+
}
|
|
2921
|
+
function rename2(oldName, newName) {
|
|
2922
|
+
const DIR = "databases";
|
|
2923
|
+
const basePath = process.cwd();
|
|
2924
|
+
const baseDir = path.resolve(basePath, DIR);
|
|
2925
|
+
const oldPath = path.join(baseDir, `${oldName}.ts`);
|
|
2926
|
+
const newPath = path.join(baseDir, `${newName}.ts`);
|
|
2927
|
+
if (!fs2.existsSync(oldPath)) {
|
|
2928
|
+
return `Error: File '${oldName}.ts' does not exist in the database`;
|
|
2929
|
+
}
|
|
2930
|
+
if (fs2.existsSync(newPath)) {
|
|
2931
|
+
return `Error: File '${newName}.ts' already exists in the database`;
|
|
2932
|
+
}
|
|
2933
|
+
try {
|
|
2934
|
+
fs2.renameSync(oldPath, newPath);
|
|
2935
|
+
return `Successfully renamed '${oldName}.ts' to '${newName}.ts'`;
|
|
2936
|
+
} catch (error) {
|
|
2937
|
+
return `Error renaming file: ${error instanceof Error ? error.message : String(error)}`;
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
function save(dbName, code) {
|
|
2941
|
+
const DIR = "databases";
|
|
2942
|
+
const basePath = process.cwd();
|
|
2943
|
+
const baseDir = path.resolve(basePath, DIR);
|
|
2944
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
2945
|
+
let fileContent = typeof code === "function" ? code.toString() : code;
|
|
2946
|
+
fs2.writeFileSync(dbPath, fileContent, "utf8");
|
|
2947
|
+
}
|
|
2948
|
+
function update(dbName, fnName, code) {
|
|
2949
|
+
const DIR = "databases";
|
|
2950
|
+
const basePath = process.cwd();
|
|
2951
|
+
const baseDir = path.resolve(basePath, DIR);
|
|
2952
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
2953
|
+
let src;
|
|
2954
|
+
if (!fs2.existsSync(dbPath)) {
|
|
2955
|
+
try {
|
|
2956
|
+
fs2.writeFileSync(dbPath, "", "utf8");
|
|
2957
|
+
return `Created new database file: ${dbPath}`;
|
|
2958
|
+
} catch (e) {
|
|
2959
|
+
return `Failed to create dbPath file: ${dbPath}`;
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
src = fs2.readFileSync(dbPath, "utf8");
|
|
2963
|
+
const originalSrc = src;
|
|
2964
|
+
const escaped = fnName.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
2965
|
+
const startRe = new RegExp(
|
|
2966
|
+
`function\\s+${escaped}\\s*\\(|\\bclass\\s+${escaped}\\b|\\b(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:function\\b|class\\b|\\(|\\{|\\[)`,
|
|
2967
|
+
"m"
|
|
2968
|
+
);
|
|
2969
|
+
const startMatch = src.match(startRe);
|
|
2970
|
+
let declKind = null;
|
|
2971
|
+
if (startMatch) {
|
|
2972
|
+
let startIdx = startMatch.index;
|
|
2973
|
+
const snippet = src.slice(startIdx, startIdx + 80);
|
|
2974
|
+
if (/^function\b/.test(snippet)) declKind = "functionDecl";
|
|
2975
|
+
else if (/^class\b/.test(snippet)) declKind = "classDecl";
|
|
2976
|
+
else if (/^\b(?:const|let|var)\b/.test(snippet)) declKind = "varAssign";
|
|
2977
|
+
}
|
|
2978
|
+
let newCode;
|
|
2979
|
+
if (typeof code === "function") {
|
|
2980
|
+
const fnStr = code.toString();
|
|
2981
|
+
if (declKind === "functionDecl") {
|
|
2982
|
+
if (/^function\s+\w+/.test(fnStr)) newCode = fnStr;
|
|
2983
|
+
else
|
|
2984
|
+
newCode = `function ${fnName}${fnStr.replace(
|
|
2985
|
+
/^function\s*\(/,
|
|
2986
|
+
"("
|
|
2987
|
+
)}`;
|
|
2988
|
+
} else if (declKind === "classDecl") {
|
|
2989
|
+
if (/^class\s+\w+/.test(fnStr)) newCode = fnStr;
|
|
2990
|
+
else if (/^class\s*\{/.test(fnStr))
|
|
2991
|
+
newCode = fnStr.replace(/^class\s*\{/, `class ${fnName} {`);
|
|
2992
|
+
else newCode = `const ${fnName} = ${fnStr};`;
|
|
2993
|
+
} else {
|
|
2994
|
+
newCode = `const ${fnName} = ${fnStr};`;
|
|
2995
|
+
}
|
|
2996
|
+
} else {
|
|
2997
|
+
newCode = `const ${fnName} = ${valueToCode(code, 0)};`;
|
|
2998
|
+
}
|
|
2999
|
+
if (startMatch) {
|
|
3000
|
+
const startIdx = startMatch.index;
|
|
3001
|
+
const idxCurly = src.indexOf("{", startIdx);
|
|
3002
|
+
const idxBracket = src.indexOf("[", startIdx);
|
|
3003
|
+
let braceOpen = -1;
|
|
3004
|
+
if (idxCurly === -1) braceOpen = idxBracket;
|
|
3005
|
+
else if (idxBracket === -1) braceOpen = idxCurly;
|
|
3006
|
+
else braceOpen = Math.min(idxCurly, idxBracket);
|
|
3007
|
+
if (braceOpen === -1) {
|
|
3008
|
+
const exportRe = new RegExp(
|
|
3009
|
+
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
3010
|
+
"m"
|
|
3011
|
+
);
|
|
3012
|
+
if (exportRe.test(src)) {
|
|
3013
|
+
src = src.replace(
|
|
3014
|
+
exportRe,
|
|
3015
|
+
`${newCode}
|
|
3016
|
+
|
|
3017
|
+
export const ${fnName}: any = ${fnName};`
|
|
3018
|
+
);
|
|
3019
|
+
} else {
|
|
3020
|
+
src = src + `
|
|
3021
|
+
|
|
3022
|
+
${newCode}
|
|
3023
|
+
|
|
3024
|
+
export const ${fnName}: any = ${fnName};`;
|
|
3025
|
+
}
|
|
3026
|
+
} else {
|
|
3027
|
+
const openingChar = src[braceOpen];
|
|
3028
|
+
const closingChar = openingChar === "[" ? "]" : "}";
|
|
3029
|
+
let i = braceOpen + 1;
|
|
3030
|
+
let depth = 1;
|
|
3031
|
+
const len = src.length;
|
|
3032
|
+
while (i < len && depth > 0) {
|
|
3033
|
+
const ch = src[i];
|
|
3034
|
+
if (ch === openingChar) depth++;
|
|
3035
|
+
else if (ch === closingChar) depth--;
|
|
3036
|
+
i++;
|
|
3037
|
+
}
|
|
3038
|
+
let braceClose = i;
|
|
3039
|
+
let endIdx = braceClose;
|
|
3040
|
+
if (src.slice(braceClose, braceClose + 1) === ";")
|
|
3041
|
+
endIdx = braceClose + 1;
|
|
3042
|
+
const before = src.slice(0, startIdx);
|
|
3043
|
+
const after = src.slice(endIdx);
|
|
3044
|
+
src = before + newCode + after;
|
|
3045
|
+
}
|
|
3046
|
+
} else {
|
|
3047
|
+
const exportRe = new RegExp(
|
|
3048
|
+
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
3049
|
+
"m"
|
|
3050
|
+
);
|
|
3051
|
+
if (exportRe.test(src)) {
|
|
3052
|
+
src = src.replace(
|
|
3053
|
+
exportRe,
|
|
3054
|
+
`${newCode}
|
|
3055
|
+
|
|
3056
|
+
export const ${fnName}: any = ${fnName};`
|
|
3057
|
+
);
|
|
3058
|
+
} else {
|
|
3059
|
+
src = src + `
|
|
3060
|
+
|
|
3061
|
+
${newCode}
|
|
3062
|
+
|
|
3063
|
+
export const ${fnName}: any = ${fnName};`;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
fs2.writeFileSync(dbPath, src, "utf8");
|
|
3067
|
+
if (src === originalSrc) {
|
|
3068
|
+
return `Saved ${fnName} to database ${dbName}.`;
|
|
3069
|
+
} else {
|
|
3070
|
+
return `Updated ${dbName} with ${fnName}.`;
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
function valueToCode(val, depth = 0) {
|
|
3074
|
+
const indentUnit = " ";
|
|
3075
|
+
const indent = indentUnit.repeat(depth);
|
|
3076
|
+
const indentInner = indentUnit.repeat(depth + 1);
|
|
3077
|
+
if (val === null) return "null";
|
|
3078
|
+
const t = typeof val;
|
|
3079
|
+
if (t === "string") return JSON.stringify(val);
|
|
3080
|
+
if (t === "number" || t === "boolean") return String(val);
|
|
3081
|
+
if (t === "function") return val.toString();
|
|
3082
|
+
if (Array.isArray(val)) {
|
|
3083
|
+
if (val.length === 0) return "[]";
|
|
3084
|
+
const items = val.map((v) => valueToCode(v, depth + 1));
|
|
3085
|
+
return "[\n" + items.map((it) => indentInner + it).join(",\n") + "\n" + indent + "]";
|
|
3086
|
+
}
|
|
3087
|
+
if (t === "object") {
|
|
3088
|
+
const keys = Object.keys(val);
|
|
3089
|
+
if (keys.length === 0) return "{}";
|
|
3090
|
+
const entries = keys.map((k) => {
|
|
3091
|
+
const keyPart = isIdentifier(k) ? k : JSON.stringify(k);
|
|
3092
|
+
const v = valueToCode(val[k], depth + 1);
|
|
3093
|
+
return indentInner + keyPart + ": " + v;
|
|
3094
|
+
});
|
|
3095
|
+
return "{\n" + entries.join(",\n") + "\n" + indent + "}";
|
|
3096
|
+
}
|
|
3097
|
+
return String(val);
|
|
3098
|
+
}
|
|
3099
|
+
function isIdentifier(key) {
|
|
3100
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
|
|
3101
|
+
}
|
|
3102
|
+
var dbConsole = {
|
|
3103
|
+
create,
|
|
3104
|
+
read,
|
|
3105
|
+
remove,
|
|
3106
|
+
rename: rename2,
|
|
3107
|
+
save,
|
|
3108
|
+
update,
|
|
3109
|
+
...console
|
|
3110
|
+
};
|
|
2822
3111
|
|
|
2823
3112
|
// src/server.ts
|
|
2824
3113
|
var ServerDatabase = class {
|
package/package.json
CHANGED
package/src/database.ts
CHANGED
|
@@ -90,7 +90,6 @@ export class Database {
|
|
|
90
90
|
throw new Error(`Module ${specifier} is not allowed or not found.`);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
93
|
private async vmRun(code: string | Function, _options?: vm.RunningCodeOptions | string) {
|
|
95
94
|
const logs: any[] = [];
|
|
96
95
|
|
|
@@ -99,8 +98,19 @@ export class Database {
|
|
|
99
98
|
return acc;
|
|
100
99
|
}, {});
|
|
101
100
|
|
|
101
|
+
const systemBase = {
|
|
102
|
+
update: update,
|
|
103
|
+
remove: remove,
|
|
104
|
+
rename: rename,
|
|
105
|
+
read: read,
|
|
106
|
+
create: create,
|
|
107
|
+
save: save
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
102
112
|
this.register({
|
|
103
|
-
|
|
113
|
+
dbConsole: { ...customConsole, ...systemBase }
|
|
104
114
|
});
|
|
105
115
|
|
|
106
116
|
let stringCode: string;
|
|
@@ -235,6 +245,340 @@ export class Database {
|
|
|
235
245
|
|
|
236
246
|
}
|
|
237
247
|
|
|
248
|
+
function create(dbName: string, code: string | Function): void {
|
|
249
|
+
const DIR = "databases";
|
|
250
|
+
const basePath = process.cwd();
|
|
251
|
+
const baseDir: string = path.resolve(basePath, DIR);
|
|
252
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
253
|
+
// Prepare the export line
|
|
254
|
+
fs.appendFileSync(dbPath, code.toString(), 'utf8');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function read(dbName: string): string {
|
|
258
|
+
const DIR = "databases";
|
|
259
|
+
const basePath = process.cwd();
|
|
260
|
+
const baseDir: string = path.resolve(basePath, DIR);
|
|
261
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
262
|
+
|
|
263
|
+
if (!fs.existsSync(dbPath)) {
|
|
264
|
+
throw new Error(`Database '${dbName}' not found`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return fs.readFileSync(dbPath, 'utf8');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function remove(dbName: string, fnName: string) {
|
|
271
|
+
const DIR = "databases";
|
|
272
|
+
const basePath = process.cwd();
|
|
273
|
+
const baseDir: string = path.resolve(basePath, DIR); // โฟลเดอร์ที่อนุญาต
|
|
274
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
275
|
+
if (!fs.existsSync(dbPath)) return false;
|
|
276
|
+
|
|
277
|
+
// if no functionName provided -> remove the whole file (after backup)
|
|
278
|
+
if (!fnName) {
|
|
279
|
+
const bak = `${dbPath}.bak`;
|
|
280
|
+
try {
|
|
281
|
+
fs.copyFileSync(dbPath, bak);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
// ignore backup errors
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
fs.unlinkSync(dbPath);
|
|
287
|
+
return "Removed successfully";
|
|
288
|
+
} catch (e) {
|
|
289
|
+
return "Removed failed";
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// create a backup before editing the file in-place
|
|
294
|
+
const bak = `${dbPath}.bak`;
|
|
295
|
+
try {
|
|
296
|
+
fs.copyFileSync(dbPath, bak);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
// ignore backup errors but continue carefully
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let src = fs.readFileSync(dbPath, "utf8");
|
|
302
|
+
const escaped = fnName.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
303
|
+
|
|
304
|
+
// regex to find a declaration of the named symbol (function, class, or var/const/let assignment)
|
|
305
|
+
const startRe = new RegExp(
|
|
306
|
+
`function\\s+${escaped}\\s*\\(|\\bclass\\s+${escaped}\\b|\\b(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:function\\b|class\\b|\\(|\\{|\\[)`,
|
|
307
|
+
"m"
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const startMatch = src.match(startRe);
|
|
311
|
+
|
|
312
|
+
if (startMatch) {
|
|
313
|
+
const startIdx = startMatch.index;
|
|
314
|
+
|
|
315
|
+
// find the first meaningful character after startIdx: {, [, or ; or newline
|
|
316
|
+
const len = src.length;
|
|
317
|
+
const idxCurly = src.indexOf("{", startIdx);
|
|
318
|
+
const idxBracket = src.indexOf("[", startIdx);
|
|
319
|
+
let braceOpen = -1;
|
|
320
|
+
if (idxCurly === -1) braceOpen = idxBracket;
|
|
321
|
+
else if (idxBracket === -1) braceOpen = idxCurly;
|
|
322
|
+
else braceOpen = Math.min(idxCurly, idxBracket);
|
|
323
|
+
|
|
324
|
+
if (braceOpen !== -1) {
|
|
325
|
+
const openingChar = src[braceOpen];
|
|
326
|
+
const closingChar = openingChar === "[" ? "]" : "}";
|
|
327
|
+
let i = braceOpen + 1;
|
|
328
|
+
let depth = 1;
|
|
329
|
+
while (i < len && depth > 0) {
|
|
330
|
+
const ch = src[i];
|
|
331
|
+
if (ch === openingChar) depth++;
|
|
332
|
+
else if (ch === closingChar) depth--;
|
|
333
|
+
i++;
|
|
334
|
+
}
|
|
335
|
+
let braceClose = i;
|
|
336
|
+
let endIdx = braceClose;
|
|
337
|
+
if (src.slice(braceClose, braceClose + 1) === ";")
|
|
338
|
+
endIdx = braceClose + 1;
|
|
339
|
+
|
|
340
|
+
const before = src.slice(0, startIdx);
|
|
341
|
+
const after = src.slice(endIdx);
|
|
342
|
+
src = before + after;
|
|
343
|
+
} else {
|
|
344
|
+
// fallback: remove until next semicolon or a blank line
|
|
345
|
+
const semi = src.indexOf(";", startIdx);
|
|
346
|
+
let endIdx = semi !== -1 ? semi + 1 : src.indexOf("\n\n", startIdx);
|
|
347
|
+
if (endIdx === -1) endIdx = len;
|
|
348
|
+
src = src.slice(0, startIdx) + src.slice(endIdx);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// remove any export const <name>: any = <name>; lines
|
|
353
|
+
const exportRe = new RegExp(
|
|
354
|
+
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
355
|
+
"g"
|
|
356
|
+
);
|
|
357
|
+
src = src.replace(exportRe, "");
|
|
358
|
+
|
|
359
|
+
// tidy up multiple blank lines
|
|
360
|
+
src = src.replace(/\n{3,}/g, "\n\n");
|
|
361
|
+
|
|
362
|
+
fs.writeFileSync(dbPath, src, "utf8");
|
|
363
|
+
|
|
364
|
+
return `Removed ${fnName} from database ${dbName}.`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function rename(oldName: string, newName: string): string {
|
|
368
|
+
const DIR = "databases";
|
|
369
|
+
const basePath = process.cwd();
|
|
370
|
+
const baseDir: string = path.resolve(basePath, DIR); // โฟลเดอร์ที่อนุญาต
|
|
371
|
+
const oldPath = path.join(baseDir, `${oldName}.ts`);
|
|
372
|
+
const newPath = path.join(baseDir, `${newName}.ts`);
|
|
373
|
+
|
|
374
|
+
// Check if the source file exists
|
|
375
|
+
if (!fs.existsSync(oldPath)) {
|
|
376
|
+
return `Error: File '${oldName}.ts' does not exist in the database`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Check if the destination file already exists
|
|
380
|
+
if (fs.existsSync(newPath)) {
|
|
381
|
+
return `Error: File '${newName}.ts' already exists in the database`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
// Rename the file
|
|
386
|
+
fs.renameSync(oldPath, newPath);
|
|
387
|
+
return `Successfully renamed '${oldName}.ts' to '${newName}.ts'`;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return `Error renaming file: ${error instanceof Error ? error.message : String(error)}`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function save(dbName: string, code: string | Function | any): void {
|
|
394
|
+
const DIR = "databases";
|
|
395
|
+
const basePath = process.cwd();
|
|
396
|
+
const baseDir: string = path.resolve(basePath, DIR); // โฟลเดอร์ที่อนุญาต
|
|
397
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
398
|
+
|
|
399
|
+
let fileContent = typeof code === 'function' ? code.toString() : code;
|
|
400
|
+
|
|
401
|
+
fs.writeFileSync(dbPath, fileContent, 'utf8');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function update(dbName: string, fnName: string, code: string | Function) {
|
|
405
|
+
const DIR = "databases";
|
|
406
|
+
const basePath = process.cwd();
|
|
407
|
+
const baseDir: string = path.resolve(basePath, DIR); // โฟลเดอร์ที่อนุญาต
|
|
408
|
+
const dbPath = path.join(baseDir, `${dbName}.ts`);
|
|
409
|
+
|
|
410
|
+
let src;
|
|
411
|
+
|
|
412
|
+
if (!fs.existsSync(dbPath)) {
|
|
413
|
+
// If dbPath doesn't exist, create an empty module file so we can insert into it.
|
|
414
|
+
try {
|
|
415
|
+
fs.writeFileSync(dbPath, "", "utf8");
|
|
416
|
+
// console.log("Created new database file:", dbPath);
|
|
417
|
+
return `Created new database file: ${dbPath}`;
|
|
418
|
+
} catch (e) {
|
|
419
|
+
// console.error("Failed to create dbPath file:", dbPath, e && e.message ? e.message : e);
|
|
420
|
+
return `Failed to create dbPath file: ${dbPath}`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
src = fs.readFileSync(dbPath, "utf8");
|
|
425
|
+
|
|
426
|
+
const originalSrc = src;
|
|
427
|
+
|
|
428
|
+
const escaped = fnName.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
429
|
+
const startRe = new RegExp(
|
|
430
|
+
`function\\s+${escaped}\\s*\\(|\\bclass\\s+${escaped}\\b|\\b(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:function\\b|class\\b|\\(|\\{|\\[)`,
|
|
431
|
+
"m"
|
|
432
|
+
);
|
|
433
|
+
const startMatch = src.match(startRe);
|
|
434
|
+
|
|
435
|
+
// determine declKind in the current file (if present)
|
|
436
|
+
let declKind = null;
|
|
437
|
+
|
|
438
|
+
if (startMatch) {
|
|
439
|
+
let startIdx: any = startMatch.index;
|
|
440
|
+
const snippet = src.slice(startIdx, startIdx + 80);
|
|
441
|
+
if (/^function\b/.test(snippet)) declKind = "functionDecl";
|
|
442
|
+
else if (/^class\b/.test(snippet)) declKind = "classDecl";
|
|
443
|
+
else if (/^\b(?:const|let|var)\b/.test(snippet)) declKind = "varAssign";
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// build replacement code for this value
|
|
447
|
+
let newCode;
|
|
448
|
+
if (typeof code === "function") {
|
|
449
|
+
const fnStr = code.toString();
|
|
450
|
+
// prefer const assignment for anonymous functions/classes or arrow functions
|
|
451
|
+
if (declKind === "functionDecl") {
|
|
452
|
+
if (/^function\s+\w+/.test(fnStr)) newCode = fnStr;
|
|
453
|
+
else
|
|
454
|
+
newCode = `function ${fnName}${fnStr.replace(
|
|
455
|
+
/^function\s*\(/,
|
|
456
|
+
"("
|
|
457
|
+
)}`;
|
|
458
|
+
} else if (declKind === "classDecl") {
|
|
459
|
+
if (/^class\s+\w+/.test(fnStr)) newCode = fnStr;
|
|
460
|
+
else if (/^class\s*\{/.test(fnStr))
|
|
461
|
+
newCode = fnStr.replace(/^class\s*\{/, `class ${fnName} {`);
|
|
462
|
+
else newCode = `const ${fnName} = ${fnStr};`;
|
|
463
|
+
} else {
|
|
464
|
+
newCode = `const ${fnName} = ${fnStr};`;
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
newCode = `const ${fnName} = ${valueToCode(code, 0)};`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// replacement: if found startMatch, find block and replace between startIdx and endIdx
|
|
471
|
+
if (startMatch) {
|
|
472
|
+
const startIdx = startMatch.index;
|
|
473
|
+
// find first '{' or '[' after startIdx
|
|
474
|
+
const idxCurly = src.indexOf("{", startIdx);
|
|
475
|
+
const idxBracket = src.indexOf("[", startIdx);
|
|
476
|
+
let braceOpen = -1;
|
|
477
|
+
if (idxCurly === -1) braceOpen = idxBracket;
|
|
478
|
+
else if (idxBracket === -1) braceOpen = idxCurly;
|
|
479
|
+
else braceOpen = Math.min(idxCurly, idxBracket);
|
|
480
|
+
|
|
481
|
+
if (braceOpen === -1) {
|
|
482
|
+
// no block — fallback: replace export or append
|
|
483
|
+
const exportRe = new RegExp(
|
|
484
|
+
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
485
|
+
"m"
|
|
486
|
+
);
|
|
487
|
+
if (exportRe.test(src)) {
|
|
488
|
+
src = src.replace(
|
|
489
|
+
exportRe,
|
|
490
|
+
`${newCode}\n\nexport const ${fnName}: any = ${fnName};`
|
|
491
|
+
);
|
|
492
|
+
} else {
|
|
493
|
+
src =
|
|
494
|
+
src + `\n\n${newCode}\n\nexport const ${fnName}: any = ${fnName};`;
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
const openingChar = src[braceOpen];
|
|
498
|
+
const closingChar = openingChar === "[" ? "]" : "}";
|
|
499
|
+
let i = braceOpen + 1;
|
|
500
|
+
let depth = 1;
|
|
501
|
+
const len = src.length;
|
|
502
|
+
while (i < len && depth > 0) {
|
|
503
|
+
const ch = src[i];
|
|
504
|
+
if (ch === openingChar) depth++;
|
|
505
|
+
else if (ch === closingChar) depth--;
|
|
506
|
+
i++;
|
|
507
|
+
}
|
|
508
|
+
let braceClose = i;
|
|
509
|
+
let endIdx = braceClose;
|
|
510
|
+
if (src.slice(braceClose, braceClose + 1) === ";")
|
|
511
|
+
endIdx = braceClose + 1;
|
|
512
|
+
|
|
513
|
+
const before = src.slice(0, startIdx);
|
|
514
|
+
const after = src.slice(endIdx);
|
|
515
|
+
src = before + newCode + after;
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
// not found — try to insert before existing export or append
|
|
519
|
+
const exportRe = new RegExp(
|
|
520
|
+
`export\\s+const\\s+${escaped}\\s*:\\s*any\\s*=\\s*${escaped}\\s*;?`,
|
|
521
|
+
"m"
|
|
522
|
+
);
|
|
523
|
+
if (exportRe.test(src)) {
|
|
524
|
+
src = src.replace(
|
|
525
|
+
exportRe,
|
|
526
|
+
`${newCode}\n\nexport const ${fnName}: any = ${fnName};`
|
|
527
|
+
);
|
|
528
|
+
} else {
|
|
529
|
+
src =
|
|
530
|
+
src + `\n\n${newCode}\n\nexport const ${fnName}: any = ${fnName};`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
fs.writeFileSync(dbPath, src, "utf8");
|
|
535
|
+
|
|
536
|
+
if (src === originalSrc) {
|
|
537
|
+
return `Saved ${fnName} to database ${dbName}.`;
|
|
538
|
+
} else {
|
|
539
|
+
return `Updated ${dbName} with ${fnName}.`;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function valueToCode(val: any, depth: number = 0): string {
|
|
544
|
+
const indentUnit = " ";
|
|
545
|
+
const indent = indentUnit.repeat(depth);
|
|
546
|
+
const indentInner = indentUnit.repeat(depth + 1);
|
|
547
|
+
|
|
548
|
+
if (val === null) return "null";
|
|
549
|
+
const t = typeof val;
|
|
550
|
+
if (t === "string") return JSON.stringify(val);
|
|
551
|
+
if (t === "number" || t === "boolean") return String(val);
|
|
552
|
+
if (t === "function") return val.toString();
|
|
553
|
+
if (Array.isArray(val)) {
|
|
554
|
+
if (val.length === 0) return "[]";
|
|
555
|
+
const items = val.map((v) => valueToCode(v, depth + 1));
|
|
556
|
+
return (
|
|
557
|
+
"[\n" +
|
|
558
|
+
items.map((it) => indentInner + it).join(",\n") +
|
|
559
|
+
"\n" +
|
|
560
|
+
indent +
|
|
561
|
+
"]"
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
if (t === "object") {
|
|
565
|
+
const keys = Object.keys(val);
|
|
566
|
+
if (keys.length === 0) return "{}";
|
|
567
|
+
const entries = keys.map((k) => {
|
|
568
|
+
const keyPart = isIdentifier(k) ? k : JSON.stringify(k);
|
|
569
|
+
const v = valueToCode(val[k], depth + 1);
|
|
570
|
+
return indentInner + keyPart + ": " + v;
|
|
571
|
+
});
|
|
572
|
+
return "{\n" + entries.join(",\n") + "\n" + indent + "}";
|
|
573
|
+
}
|
|
574
|
+
return String(val);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function isIdentifier(key: any) {
|
|
578
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
|
|
238
582
|
// Default database instance
|
|
239
583
|
|
|
240
584
|
export function database() {
|
|
@@ -243,4 +587,22 @@ export function database() {
|
|
|
243
587
|
});
|
|
244
588
|
}
|
|
245
589
|
|
|
590
|
+
export interface DatabaseConsole extends Console {
|
|
591
|
+
create?(dbName: string, code: string | Function): void;
|
|
592
|
+
read(dbName: string): string;
|
|
593
|
+
remove(dbName: string, fnName: string): any;
|
|
594
|
+
rename(oldName: string, newName: string): string;
|
|
595
|
+
save(dbName: string, code: string | Function | any): void;
|
|
596
|
+
update(dbName: string, fnName: string, code: string | Function): any;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export const dbConsole: DatabaseConsole = {
|
|
600
|
+
create: create,
|
|
601
|
+
read: read,
|
|
602
|
+
remove: remove,
|
|
603
|
+
rename: rename,
|
|
604
|
+
save: save,
|
|
605
|
+
update: update,
|
|
606
|
+
...console
|
|
607
|
+
}
|
|
246
608
|
export default database;
|