@keshavsoft/kschema-cli 1.1.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/CHANGELOG.md +39 -0
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/bin/cli.js +20 -0
- package/commands/init.js +20 -0
- package/commands/test.js +36 -0
- package/dev.md +74 -0
- package/index.js +1 -0
- package/package.json +16 -0
- package/src/core/configStore.js +10 -0
- package/src/utils/pathBuilder.js +3 -0
- package/src/v1/data/deleteData.js +16 -0
- package/src/v1/data/getData.js +13 -0
- package/src/v1/data/index.js +4 -0
- package/src/v1/data/insertData.js +16 -0
- package/src/v1/data/updateData.js +20 -0
- package/src/v1/index.js +3 -0
- package/src/v2/config/getSchema.js +12 -0
- package/src/v2/data/deleteData.js +16 -0
- package/src/v2/data/getData.js +13 -0
- package/src/v2/data/index.js +4 -0
- package/src/v2/data/insertData copy.js +46 -0
- package/src/v2/data/insertData.js +52 -0
- package/src/v2/data/updateData.js +20 -0
- package/src/v2/index.js +3 -0
- package/src/v3/config/getSchema.js +12 -0
- package/src/v3/data/deleteData.js +16 -0
- package/src/v3/data/getData.js +13 -0
- package/src/v3/data/index.js +4 -0
- package/src/v3/data/insertData.js +27 -0
- package/src/v3/data/updateData.js +20 -0
- package/src/v3/helpers/fileHelper.js +11 -0
- package/src/v3/helpers/pkHelper.js +18 -0
- package/src/v3/helpers/validateHelper.js +20 -0
- package/src/v3/index.js +3 -0
- package/src/v4/config/getSchema.js +12 -0
- package/src/v4/data/deleteData.js +16 -0
- package/src/v4/data/getData.js +13 -0
- package/src/v4/data/index.js +4 -0
- package/src/v4/data/insertData.js +47 -0
- package/src/v4/data/updateData.js +20 -0
- package/src/v4/helpers/fileHelper.js +11 -0
- package/src/v4/helpers/pkHelper.js +18 -0
- package/src/v4/helpers/recordHelper.js +9 -0
- package/src/v4/helpers/validateHelper.js +15 -0
- package/src/v4/index.js +3 -0
- package/src/v5/config/getSchema.js +12 -0
- package/src/v5/data/deleteData.js +16 -0
- package/src/v5/data/getData.js +13 -0
- package/src/v5/data/index.js +8 -0
- package/src/v5/data/insertData.js +56 -0
- package/src/v5/data/insertDataStrict.js +63 -0
- package/src/v5/data/updateData.js +20 -0
- package/src/v5/helpers/fileHelper.js +11 -0
- package/src/v5/helpers/pkHelper.js +18 -0
- package/src/v5/helpers/recordHelper.js +9 -0
- package/src/v5/helpers/validateHelper.js +15 -0
- package/src/v5/index.js +3 -0
- package/src/v6/config/getSchema.js +12 -0
- package/src/v6/data/deleteData.js +16 -0
- package/src/v6/data/getData.js +13 -0
- package/src/v6/data/index.js +8 -0
- package/src/v6/data/insertData.js +56 -0
- package/src/v6/data/insertDataStrict.js +76 -0
- package/src/v6/data/updateData.js +20 -0
- package/src/v6/helpers/fileHelper.js +11 -0
- package/src/v6/helpers/pkHelper.js +18 -0
- package/src/v6/helpers/recordHelper.js +9 -0
- package/src/v6/helpers/validateHelper.js +15 -0
- package/src/v6/index.js +21 -0
- package/src/v7/config/getSchema.js +12 -0
- package/src/v7/data/deleteData.js +16 -0
- package/src/v7/data/filterByColumns.js +19 -0
- package/src/v7/data/filterByPk.js +16 -0
- package/src/v7/data/findByColumns.js +21 -0
- package/src/v7/data/findByPk.js +16 -0
- package/src/v7/data/getData.js +13 -0
- package/src/v7/data/index.js +11 -0
- package/src/v7/data/insertData.js +55 -0
- package/src/v7/data/insertDataStrict.js +73 -0
- package/src/v7/data/updateData.js +20 -0
- package/src/v7/helpers/fileHelper.js +16 -0
- package/src/v7/helpers/pkHelper.js +21 -0
- package/src/v7/helpers/recordHelper.js +15 -0
- package/src/v7/helpers/validateHelper.js +27 -0
- package/src/v7/index.js +27 -0
- package/src/v8/config/getSchema.js +12 -0
- package/src/v8/data/deleteByColumnsData.js +20 -0
- package/src/v8/data/deleteData.js +22 -0
- package/src/v8/data/filterByColumns.js +19 -0
- package/src/v8/data/filterByPk.js +16 -0
- package/src/v8/data/findByColumns.js +21 -0
- package/src/v8/data/findByPk.js +16 -0
- package/src/v8/data/getData.js +13 -0
- package/src/v8/data/index.js +12 -0
- package/src/v8/data/insertData.js +55 -0
- package/src/v8/data/insertDataStrict.js +73 -0
- package/src/v8/data/updateData.js +20 -0
- package/src/v8/helpers/fileHelper.js +16 -0
- package/src/v8/helpers/pkHelper.js +21 -0
- package/src/v8/helpers/recordHelper.js +15 -0
- package/src/v8/helpers/validateHelper.js +27 -0
- package/src/v8/index.js +43 -0
- package/template/Config/Schemas/BillsTable.json +165 -0
- package/template/Config/Schemas/ItemsTable.json +200 -0
- package/template/Config/Schemas/LedgerNames.json +45 -0
- package/template/Config/Schemas/StockItems.json +45 -0
- package/template/Config/api.json +8 -0
- package/template/Config/schema.json +8 -0
- package/template/Config/ui.json +8 -0
- package/template/config.json +4 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* insertDataStrict - Strict Orchestration Flow
|
|
3
|
+
*
|
|
4
|
+
* 1. Load config & schema
|
|
5
|
+
* 2. Resolve primary key + file path
|
|
6
|
+
* 3. Reject manual primary key input
|
|
7
|
+
* 4. Reject extra fields (only schema fields allowed)
|
|
8
|
+
* 5. Read existing data
|
|
9
|
+
* 6. Normalize record (keep only schema fields)
|
|
10
|
+
* 7. Validate (required + unique on schema columns)
|
|
11
|
+
* 8. Attach primary key (auto increment)
|
|
12
|
+
* 9. Push + persist to file
|
|
13
|
+
* 10. Return inserted record
|
|
14
|
+
*
|
|
15
|
+
* Notes:
|
|
16
|
+
* - Only schema-defined fields are allowed
|
|
17
|
+
* - Any extra field will throw an error
|
|
18
|
+
* - Primary key is system-generated only
|
|
19
|
+
*/
|
|
20
|
+
import { getConfig } from "../../core/configStore.js";
|
|
21
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
22
|
+
import { getSchema } from "../config/getSchema.js";
|
|
23
|
+
|
|
24
|
+
import { getPrimaryKey, attachPrimaryKey } from "../helpers/pkHelper.js";
|
|
25
|
+
import { readData, writeData } from "../helpers/fileHelper.js";
|
|
26
|
+
import { validateRecord } from "../helpers/validateHelper.js";
|
|
27
|
+
import { normalizeRecord } from "../helpers/recordHelper.js";
|
|
28
|
+
|
|
29
|
+
export const insertDataStrict = ({ table, record }) => {
|
|
30
|
+
try {
|
|
31
|
+
const config = getConfig();
|
|
32
|
+
const schema = getSchema(table);
|
|
33
|
+
|
|
34
|
+
const pk = getPrimaryKey(schema.columns);
|
|
35
|
+
const path = buildDataPath(config, table);
|
|
36
|
+
|
|
37
|
+
const schemaFields = schema.columns.map(c => c.field);
|
|
38
|
+
|
|
39
|
+
if (pk in record) {
|
|
40
|
+
throw new Error(`Primary key '${pk}' should not be provided`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const extraFields = Object.keys(record).filter(
|
|
44
|
+
key => !schemaFields.includes(key)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (extraFields.length) {
|
|
48
|
+
throw new Error(`Invalid fields: ${extraFields.join(", ")}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const data = readData(path);
|
|
52
|
+
|
|
53
|
+
const cleanRecord = normalizeRecord(record, schema.columns);
|
|
54
|
+
|
|
55
|
+
validateRecord(cleanRecord, schema.columns, data);
|
|
56
|
+
|
|
57
|
+
const newRecord = attachPrimaryKey(cleanRecord, pk, data);
|
|
58
|
+
|
|
59
|
+
data.push(newRecord);
|
|
60
|
+
|
|
61
|
+
writeData(path, data);
|
|
62
|
+
|
|
63
|
+
// return newRecord;
|
|
64
|
+
|
|
65
|
+
// existing logic...
|
|
66
|
+
return { success: true, data: newRecord };
|
|
67
|
+
} catch (err) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: {
|
|
71
|
+
message: err.message,
|
|
72
|
+
code: "VALIDATION_ERROR"
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getConfig } from "../../core/configStore.js";
|
|
3
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
4
|
+
|
|
5
|
+
export const updateData = ({ table, key, value, updates }) => {
|
|
6
|
+
const cfg = getConfig();
|
|
7
|
+
const path = buildDataPath(cfg, table);
|
|
8
|
+
|
|
9
|
+
const data = JSON.parse(fs.readFileSync(path));
|
|
10
|
+
|
|
11
|
+
const index = data.findIndex(item => item[key] === value);
|
|
12
|
+
|
|
13
|
+
if (index === -1) throw new Error("Record not found");
|
|
14
|
+
|
|
15
|
+
data[index] = { ...data[index], ...updates };
|
|
16
|
+
|
|
17
|
+
fs.writeFileSync(path, JSON.stringify(data, null, 2));
|
|
18
|
+
|
|
19
|
+
return data[index];
|
|
20
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// v3/helpers/fileHelper.js
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
|
|
4
|
+
export const readData = (path) => {
|
|
5
|
+
if (!fs.existsSync(path)) return [];
|
|
6
|
+
return JSON.parse(fs.readFileSync(path, "utf-8"));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const writeData = (path, data) => {
|
|
10
|
+
fs.writeFileSync(path, JSON.stringify(data, null, 2));
|
|
11
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// v3/helpers/pkHelper.js
|
|
2
|
+
|
|
3
|
+
export const getPrimaryKey = (columns) => {
|
|
4
|
+
const pkColumn = columns.find(c => c.primary);
|
|
5
|
+
if (!pkColumn) throw new Error("Primary key not defined");
|
|
6
|
+
return pkColumn.field;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const attachPrimaryKey = (record, pk, data) => {
|
|
10
|
+
const maxId = data.length
|
|
11
|
+
? Math.max(...data.map(row => row[pk] || 0))
|
|
12
|
+
: 0;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
...record,
|
|
16
|
+
[pk]: maxId + 1
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const validateRecord = (record, columns, data) => {
|
|
2
|
+
columns.forEach(col => {
|
|
3
|
+
const value = record[col.field];
|
|
4
|
+
|
|
5
|
+
if (col.required && (value === undefined || value === "")) {
|
|
6
|
+
throw new Error(`${col.field} is required`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (value === undefined) return;
|
|
10
|
+
|
|
11
|
+
if (col.unique && data.some(r => r[col.field] === value)) {
|
|
12
|
+
throw new Error(`${col.field} must be unique`);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
package/src/v6/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { loadConfig, getConfig } from "../core/configStore.js";
|
|
2
|
+
import {
|
|
3
|
+
insertData,
|
|
4
|
+
getData,
|
|
5
|
+
updateData,
|
|
6
|
+
deleteData,
|
|
7
|
+
insertDataStrict
|
|
8
|
+
} from "./data/index.js";
|
|
9
|
+
|
|
10
|
+
export const kschema = {
|
|
11
|
+
loadConfig,
|
|
12
|
+
getConfig,
|
|
13
|
+
|
|
14
|
+
table: (table) => ({
|
|
15
|
+
insert: (record) => insertData({ table, record }),
|
|
16
|
+
get: () => getData({ table }),
|
|
17
|
+
update: (record) => updateData({ table, record }),
|
|
18
|
+
delete: (id) => deleteData({ table, id }),
|
|
19
|
+
insertStrict: (record) => insertDataStrict({ table, record }),
|
|
20
|
+
})
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getConfig } from "../../core/configStore.js";
|
|
3
|
+
|
|
4
|
+
export const getSchema = (table) => {
|
|
5
|
+
const config = getConfig();
|
|
6
|
+
|
|
7
|
+
const schemaPath = `${config.SchemaPath}/${table}.json`;
|
|
8
|
+
|
|
9
|
+
const schema = fs.readFileSync(schemaPath, "utf-8");
|
|
10
|
+
|
|
11
|
+
return JSON.parse(schema);
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getConfig } from "../../core/configStore.js";
|
|
3
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
4
|
+
|
|
5
|
+
export const deleteData = ({ table, key, value }) => {
|
|
6
|
+
const cfg = getConfig();
|
|
7
|
+
const path = buildDataPath(cfg, table);
|
|
8
|
+
|
|
9
|
+
const data = JSON.parse(fs.readFileSync(path));
|
|
10
|
+
|
|
11
|
+
const newData = data.filter(item => item[key] !== value);
|
|
12
|
+
|
|
13
|
+
fs.writeFileSync(path, JSON.stringify(newData, null, 2));
|
|
14
|
+
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getSchema } from "../config/getSchema.js";
|
|
4
|
+
|
|
5
|
+
import { readData } from "../helpers/fileHelper.js";
|
|
6
|
+
import { applyFilter } from "../helpers/recordHelper.js";
|
|
7
|
+
import { validateFilterKeys } from "../helpers/validateHelper.js";
|
|
8
|
+
|
|
9
|
+
export const filterByColumnsData = ({ table, filter }) => {
|
|
10
|
+
const cfg = getConfig();
|
|
11
|
+
const schema = getSchema(table);
|
|
12
|
+
|
|
13
|
+
validateFilterKeys(filter, schema.columns);
|
|
14
|
+
|
|
15
|
+
const path = buildDataPath(cfg, table);
|
|
16
|
+
const data = readData(path);
|
|
17
|
+
|
|
18
|
+
return applyFilter(data, filter);
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getPrimaryKey } from "../helpers/pkHelper.js";
|
|
4
|
+
import { getSchema } from "../config/getSchema.js";
|
|
5
|
+
import { readData } from "../helpers/fileHelper.js";
|
|
6
|
+
|
|
7
|
+
export const filterByPkData = ({ table, id }) => {
|
|
8
|
+
const cfg = getConfig();
|
|
9
|
+
const schema = getSchema(table);
|
|
10
|
+
const pk = getPrimaryKey(schema.columns);
|
|
11
|
+
|
|
12
|
+
const path = buildDataPath(cfg, table);
|
|
13
|
+
const data = readData(path);
|
|
14
|
+
|
|
15
|
+
return data.filter(row => Number(row[pk]) === Number(id));
|
|
16
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getSchema } from "../config/getSchema.js";
|
|
4
|
+
|
|
5
|
+
import { readData } from "../helpers/fileHelper.js";
|
|
6
|
+
import { applyFilter } from "../helpers/recordHelper.js";
|
|
7
|
+
import { validateFilterKeys } from "../helpers/validateHelper.js";
|
|
8
|
+
|
|
9
|
+
export const findByColumnsData = ({ table, filter }) => {
|
|
10
|
+
const cfg = getConfig();
|
|
11
|
+
const schema = getSchema(table);
|
|
12
|
+
|
|
13
|
+
validateFilterKeys(filter, schema.columns);
|
|
14
|
+
|
|
15
|
+
const path = buildDataPath(cfg, table);
|
|
16
|
+
const data = readData(path);
|
|
17
|
+
|
|
18
|
+
return data.find(row =>
|
|
19
|
+
Object.keys(filter).every(key => row[key] === filter[key])
|
|
20
|
+
) || null;
|
|
21
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getPrimaryKey } from "../helpers/pkHelper.js";
|
|
4
|
+
import { getSchema } from "../config/getSchema.js";
|
|
5
|
+
import { readData } from "../helpers/fileHelper.js";
|
|
6
|
+
|
|
7
|
+
export const findByPkData = ({ table, id }) => {
|
|
8
|
+
const cfg = getConfig();
|
|
9
|
+
const schema = getSchema(table);
|
|
10
|
+
const pk = getPrimaryKey(schema.columns);
|
|
11
|
+
|
|
12
|
+
const path = buildDataPath(cfg, table);
|
|
13
|
+
const data = readData(path);
|
|
14
|
+
|
|
15
|
+
return data.find(row => Number(row[pk]) === Number(id)) || null;
|
|
16
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getConfig } from "../../core/configStore.js";
|
|
3
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
4
|
+
|
|
5
|
+
export const getData = ({ table }) => {
|
|
6
|
+
const cfg = getConfig();
|
|
7
|
+
|
|
8
|
+
const path = buildDataPath(cfg, table);
|
|
9
|
+
|
|
10
|
+
const data = fs.readFileSync(path, "utf-8");
|
|
11
|
+
|
|
12
|
+
return JSON.parse(data);
|
|
13
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { getData } from "./getData.js";
|
|
2
|
+
export { insertData } from "./insertData.js";
|
|
3
|
+
export { insertDataStrict } from "./insertDataStrict.js";
|
|
4
|
+
|
|
5
|
+
export { updateData } from "./updateData.js";
|
|
6
|
+
export { deleteData } from "./deleteData.js";
|
|
7
|
+
|
|
8
|
+
export { findByPkData } from "./findByPk.js";
|
|
9
|
+
export { filterByPkData } from "./filterByPk.js";
|
|
10
|
+
|
|
11
|
+
export { filterByColumnsData } from "./filterByColumns.js";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* insertData - Orchestration Flow
|
|
3
|
+
*
|
|
4
|
+
* 1. Load config & schema
|
|
5
|
+
* 2. Resolve primary key + file path
|
|
6
|
+
* 3. Read existing data
|
|
7
|
+
* 4. Prepare record (allow extra fields, validate only schema fields)
|
|
8
|
+
* 5. Validate (required + unique only on schema columns)
|
|
9
|
+
* 6. Attach primary key (auto increment)
|
|
10
|
+
* 7. Push + persist to file
|
|
11
|
+
* 8. Return inserted record
|
|
12
|
+
*
|
|
13
|
+
* Notes:
|
|
14
|
+
* - Extra fields are NOT validated but are stored
|
|
15
|
+
* - Schema drives validation, not storage
|
|
16
|
+
*/
|
|
17
|
+
import { getConfig } from "../../core/configStore.js";
|
|
18
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
19
|
+
import { getSchema } from "../config/getSchema.js";
|
|
20
|
+
|
|
21
|
+
import { getPrimaryKey, attachPrimaryKey } from "../helpers/pkHelper.js";
|
|
22
|
+
import { readData, writeData } from "../helpers/fileHelper.js";
|
|
23
|
+
import { validateRecord } from "../helpers/validateHelper.js";
|
|
24
|
+
import { normalizeRecord } from "../helpers/recordHelper.js";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Inserts a record into the data store
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} params
|
|
30
|
+
* @param {string} params.table - Table name
|
|
31
|
+
* @param {Object} params.record - Record data to insert
|
|
32
|
+
*
|
|
33
|
+
* @returns {Object} Inserted record with primary key
|
|
34
|
+
*/
|
|
35
|
+
export const insertData = ({ table, record }) => {
|
|
36
|
+
const config = getConfig();
|
|
37
|
+
const schema = getSchema(table);
|
|
38
|
+
|
|
39
|
+
const pk = getPrimaryKey(schema.columns);
|
|
40
|
+
const path = buildDataPath(config, table);
|
|
41
|
+
|
|
42
|
+
const data = readData(path);
|
|
43
|
+
|
|
44
|
+
const cleanRecord = { ...record };
|
|
45
|
+
|
|
46
|
+
validateRecord(cleanRecord, schema.columns, data);
|
|
47
|
+
|
|
48
|
+
const newRecord = attachPrimaryKey(cleanRecord, pk, data);
|
|
49
|
+
|
|
50
|
+
data.push(newRecord);
|
|
51
|
+
|
|
52
|
+
writeData(path, data);
|
|
53
|
+
|
|
54
|
+
return newRecord[pk];
|
|
55
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* insertDataStrict - Strict Orchestration Flow
|
|
3
|
+
*
|
|
4
|
+
* 1. Load config & schema
|
|
5
|
+
* 2. Resolve primary key + file path
|
|
6
|
+
* 3. Reject manual primary key input
|
|
7
|
+
* 4. Reject extra fields (only schema fields allowed)
|
|
8
|
+
* 5. Read existing data
|
|
9
|
+
* 6. Normalize record (keep only schema fields)
|
|
10
|
+
* 7. Validate (required + unique on schema columns)
|
|
11
|
+
* 8. Attach primary key (auto increment)
|
|
12
|
+
* 9. Push + persist to file
|
|
13
|
+
* 10. Return inserted record
|
|
14
|
+
*
|
|
15
|
+
* Notes:
|
|
16
|
+
* - Only schema-defined fields are allowed
|
|
17
|
+
* - Any extra field will throw an error
|
|
18
|
+
* - Primary key is system-generated only
|
|
19
|
+
*/
|
|
20
|
+
import { getConfig } from "../../core/configStore.js";
|
|
21
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
22
|
+
import { getSchema } from "../config/getSchema.js";
|
|
23
|
+
|
|
24
|
+
import { getPrimaryKey, attachPrimaryKey } from "../helpers/pkHelper.js";
|
|
25
|
+
import { readData, writeData } from "../helpers/fileHelper.js";
|
|
26
|
+
import { validateRecord } from "../helpers/validateHelper.js";
|
|
27
|
+
import { normalizeRecord } from "../helpers/recordHelper.js";
|
|
28
|
+
|
|
29
|
+
export const insertDataStrict = ({ table, record }) => {
|
|
30
|
+
try {
|
|
31
|
+
const config = getConfig();
|
|
32
|
+
const schema = getSchema(table);
|
|
33
|
+
|
|
34
|
+
const pk = getPrimaryKey(schema.columns);
|
|
35
|
+
const path = buildDataPath(config, table);
|
|
36
|
+
|
|
37
|
+
const schemaFields = schema.columns.map(c => c.field);
|
|
38
|
+
|
|
39
|
+
if (pk in record) {
|
|
40
|
+
throw new Error(`Primary key '${pk}' should not be provided`);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const extraFields = Object.keys(record).filter(
|
|
44
|
+
key => !schemaFields.includes(key)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (extraFields.length) {
|
|
48
|
+
throw new Error(`Invalid fields: ${extraFields.join(", ")}`);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const data = readData(path);
|
|
52
|
+
|
|
53
|
+
const cleanRecord = normalizeRecord(record, schema.columns);
|
|
54
|
+
|
|
55
|
+
validateRecord(cleanRecord, schema.columns, data);
|
|
56
|
+
|
|
57
|
+
const newRecord = attachPrimaryKey(cleanRecord, pk, data);
|
|
58
|
+
|
|
59
|
+
data.push(newRecord);
|
|
60
|
+
|
|
61
|
+
writeData(path, data);
|
|
62
|
+
|
|
63
|
+
return pk;
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: {
|
|
68
|
+
message: err.message,
|
|
69
|
+
code: "VALIDATION_ERROR"
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getConfig } from "../../core/configStore.js";
|
|
3
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
4
|
+
|
|
5
|
+
export const updateData = ({ table, key, value, updates }) => {
|
|
6
|
+
const cfg = getConfig();
|
|
7
|
+
const path = buildDataPath(cfg, table);
|
|
8
|
+
|
|
9
|
+
const data = JSON.parse(fs.readFileSync(path));
|
|
10
|
+
|
|
11
|
+
const index = data.findIndex(item => item[key] === value);
|
|
12
|
+
|
|
13
|
+
if (index === -1) throw new Error("Record not found");
|
|
14
|
+
|
|
15
|
+
data[index] = { ...data[index], ...updates };
|
|
16
|
+
|
|
17
|
+
fs.writeFileSync(path, JSON.stringify(data, null, 2));
|
|
18
|
+
|
|
19
|
+
return data[index];
|
|
20
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
|
|
3
|
+
export const readData = (path) => {
|
|
4
|
+
if (!fs.existsSync(path)) return [];
|
|
5
|
+
return JSON.parse(fs.readFileSync(path, "utf-8"));
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const writeData = (path, data) => {
|
|
9
|
+
fs.writeFileSync(path, JSON.stringify(data, null, 2));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const applyFilter = (data, filter) => {
|
|
13
|
+
return data.filter(row =>
|
|
14
|
+
Object.keys(filter).every(key => row[key] === filter[key])
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// v3/helpers/pkHelper.js
|
|
2
|
+
|
|
3
|
+
export const getPrimaryKey = (columns) => {
|
|
4
|
+
const pkColumn = columns.find(c => c.primary);
|
|
5
|
+
if (!pkColumn) throw new Error("Primary key not defined");
|
|
6
|
+
return pkColumn.field;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const attachPrimaryKey = (record, pk, data) => {
|
|
10
|
+
let maxId = 0;
|
|
11
|
+
|
|
12
|
+
for (const row of data) {
|
|
13
|
+
const val = Number(row[pk]) || 0;
|
|
14
|
+
if (val > maxId) maxId = val;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
...record,
|
|
19
|
+
[pk]: maxId + 1
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const normalizeRecord = (record, columns) => {
|
|
2
|
+
const clean = {};
|
|
3
|
+
columns.forEach(col => {
|
|
4
|
+
if (record[col.field] !== undefined) {
|
|
5
|
+
clean[col.field] = record[col.field];
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
return clean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const applyFilter = (data, filter) => {
|
|
12
|
+
return data.filter(row =>
|
|
13
|
+
Object.keys(filter).every(key => row[key] === filter[key])
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const validateRecord = (record, columns, data) => {
|
|
2
|
+
columns.forEach(col => {
|
|
3
|
+
const value = record[col.field];
|
|
4
|
+
|
|
5
|
+
if (col.required && (value === undefined || value === null || value === "")) {
|
|
6
|
+
throw new Error(`${col.field} is required`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (value === undefined) return;
|
|
10
|
+
|
|
11
|
+
if (col.unique && data.some(r => r[col.field] === value)) {
|
|
12
|
+
throw new Error(`${col.field} must be unique`);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const validateFilterKeys = (filter, columns) => {
|
|
18
|
+
const validColumns = columns.map(col => col.field);
|
|
19
|
+
|
|
20
|
+
const invalidKeys = Object.keys(filter).filter(
|
|
21
|
+
key => !validColumns.includes(key)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (invalidKeys.length > 0) {
|
|
25
|
+
throw new Error(`Invalid columns: ${invalidKeys.join(", ")}`);
|
|
26
|
+
};
|
|
27
|
+
};
|
package/src/v7/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { loadConfig, getConfig } from "../core/configStore.js";
|
|
2
|
+
import {
|
|
3
|
+
insertData,
|
|
4
|
+
getData,
|
|
5
|
+
updateData,
|
|
6
|
+
deleteData,
|
|
7
|
+
insertDataStrict,
|
|
8
|
+
findByPkData,
|
|
9
|
+
filterByPkData,
|
|
10
|
+
filterByColumnsData
|
|
11
|
+
} from "./data/index.js";
|
|
12
|
+
|
|
13
|
+
export const kschema = {
|
|
14
|
+
loadConfig,
|
|
15
|
+
getConfig,
|
|
16
|
+
|
|
17
|
+
table: (table) => ({
|
|
18
|
+
insert: (record) => insertData({ table, record }),
|
|
19
|
+
get: () => getData({ table }),
|
|
20
|
+
update: (record) => updateData({ table, record }),
|
|
21
|
+
delete: (id) => deleteData({ table, id }),
|
|
22
|
+
insertStrict: (record) => insertDataStrict({ table, record }),
|
|
23
|
+
findByPk: (id) => findByPkData({ table, id }),
|
|
24
|
+
filterByPk: (id) => filterByPkData({ table, id }),
|
|
25
|
+
filterByColumns: (filter) => filterByColumnsData({ table, filter }),
|
|
26
|
+
})
|
|
27
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { getConfig } from "../../core/configStore.js";
|
|
3
|
+
|
|
4
|
+
export const getSchema = (table) => {
|
|
5
|
+
const config = getConfig();
|
|
6
|
+
|
|
7
|
+
const schemaPath = `${config.SchemaPath}/${table}.json`;
|
|
8
|
+
|
|
9
|
+
const schema = fs.readFileSync(schemaPath, "utf-8");
|
|
10
|
+
|
|
11
|
+
return JSON.parse(schema);
|
|
12
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { readData, writeData } from "../helpers/fileHelper.js";
|
|
4
|
+
import { applyFilter } from "../helpers/recordHelper.js";
|
|
5
|
+
|
|
6
|
+
const deleteByColumnsData = ({ table, filter }) => {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const path = buildDataPath(config, table);
|
|
9
|
+
const data = readData(path);
|
|
10
|
+
|
|
11
|
+
const toDelete = applyFilter(data, filter);
|
|
12
|
+
if (toDelete.length === 0) throw new Error(`No records match filter`);
|
|
13
|
+
|
|
14
|
+
const newData = data.filter(item => !toDelete.includes(item));
|
|
15
|
+
writeData(path, newData);
|
|
16
|
+
|
|
17
|
+
return toDelete.length;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { deleteByColumnsData };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getSchema } from "../config/getSchema.js";
|
|
4
|
+
import { getPrimaryKey } from "../helpers/pkHelper.js";
|
|
5
|
+
import { readData, writeData } from "../helpers/fileHelper.js";
|
|
6
|
+
|
|
7
|
+
const deleteData = ({ table, id }) => {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const path = buildDataPath(config, table);
|
|
10
|
+
|
|
11
|
+
const pk = getPrimaryKey(getSchema(table).columns);
|
|
12
|
+
|
|
13
|
+
const data = readData(path);
|
|
14
|
+
|
|
15
|
+
if (!data.some(item => item[pk] === id)) throw new Error(`${pk}: ${id} not found`);
|
|
16
|
+
|
|
17
|
+
writeData(path, data.filter(item => item[pk] !== id));
|
|
18
|
+
|
|
19
|
+
return true;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { deleteData };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getSchema } from "../config/getSchema.js";
|
|
4
|
+
|
|
5
|
+
import { readData } from "../helpers/fileHelper.js";
|
|
6
|
+
import { applyFilter } from "../helpers/recordHelper.js";
|
|
7
|
+
import { validateFilterKeys } from "../helpers/validateHelper.js";
|
|
8
|
+
|
|
9
|
+
export const filterByColumnsData = ({ table, filter }) => {
|
|
10
|
+
const cfg = getConfig();
|
|
11
|
+
const schema = getSchema(table);
|
|
12
|
+
|
|
13
|
+
validateFilterKeys(filter, schema.columns);
|
|
14
|
+
|
|
15
|
+
const path = buildDataPath(cfg, table);
|
|
16
|
+
const data = readData(path);
|
|
17
|
+
|
|
18
|
+
return applyFilter(data, filter);
|
|
19
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getConfig } from "../../core/configStore.js";
|
|
2
|
+
import { buildDataPath } from "../../utils/pathBuilder.js";
|
|
3
|
+
import { getPrimaryKey } from "../helpers/pkHelper.js";
|
|
4
|
+
import { getSchema } from "../config/getSchema.js";
|
|
5
|
+
import { readData } from "../helpers/fileHelper.js";
|
|
6
|
+
|
|
7
|
+
export const filterByPkData = ({ table, id }) => {
|
|
8
|
+
const cfg = getConfig();
|
|
9
|
+
const schema = getSchema(table);
|
|
10
|
+
const pk = getPrimaryKey(schema.columns);
|
|
11
|
+
|
|
12
|
+
const path = buildDataPath(cfg, table);
|
|
13
|
+
const data = readData(path);
|
|
14
|
+
|
|
15
|
+
return data.filter(row => Number(row[pk]) === Number(id));
|
|
16
|
+
};
|