jsql-neo 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ // © Vexify 2026 All Rights Reserved.
2
+ /**
3
+ * JSQL Date Types — DATE / DATETIME / TIMESTAMP 类型处理
4
+ */
5
+
6
+ const { createError } = require('./errors');
7
+
8
+ /**
9
+ * 验证并规范化日期类型值
10
+ * @param {*} value - 输入值
11
+ * @param {string} type - 'date' | 'datetime' | 'timestamp'
12
+ * @param {string} fieldName - 字段名(用于错误提示)
13
+ * @returns {*} 规范化后的值
14
+ */
15
+ function validateDateType(value, type, fieldName) {
16
+ if (value === null || value === undefined) return value;
17
+
18
+ switch (type) {
19
+ case 'date':
20
+ return validateDate(value, fieldName);
21
+ case 'datetime':
22
+ return validateDateTime(value, fieldName);
23
+ case 'timestamp':
24
+ return validateTimestamp(value, fieldName);
25
+ case 'time':
26
+ return validateTime(value, fieldName);
27
+ default:
28
+ return value;
29
+ }
30
+ }
31
+
32
+ // ============================================================
33
+ // DATE: YYYY-MM-DD
34
+ // ============================================================
35
+
36
+ function validateDate(value, fieldName) {
37
+ if (value instanceof Date) {
38
+ return value.toISOString().slice(0, 10);
39
+ }
40
+ if (typeof value === 'number') {
41
+ // Unix timestamp → date
42
+ return new Date(value * 1000).toISOString().slice(0, 10);
43
+ }
44
+ if (typeof value === 'string') {
45
+ const d = parseDateString(value);
46
+ if (d) return d.toISOString().slice(0, 10);
47
+ }
48
+ throw createError('ER_INVALID_DATE', fieldName, String(value));
49
+ }
50
+
51
+ function parseDateString(str) {
52
+ // YYYY-MM-DD
53
+ let m = str.match(/^(\d{4})-(\d{2})-(\d{2})$/);
54
+ if (m) {
55
+ const d = new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));
56
+ if (d.getFullYear() === parseInt(m[1])) return d;
57
+ }
58
+ // YYYY/MM/DD
59
+ m = str.match(/^(\d{4})\/(\d{2})\/(\d{2})$/);
60
+ if (m) {
61
+ const d = new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3]));
62
+ if (d.getFullYear() === parseInt(m[1])) return d;
63
+ }
64
+ // MM/DD/YYYY
65
+ m = str.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
66
+ if (m) {
67
+ const d = new Date(parseInt(m[3]), parseInt(m[1]) - 1, parseInt(m[2]));
68
+ if (d.getFullYear() === parseInt(m[3])) return d;
69
+ }
70
+ // Try native parse
71
+ const d = new Date(str);
72
+ if (!isNaN(d.getTime())) return d;
73
+ return null;
74
+ }
75
+
76
+ // ============================================================
77
+ // DATETIME: YYYY-MM-DD HH:MM:SS
78
+ // ============================================================
79
+
80
+ function validateDateTime(value, fieldName) {
81
+ if (value instanceof Date) {
82
+ return value.toISOString().replace('T', ' ').slice(0, 19);
83
+ }
84
+ if (typeof value === 'number') {
85
+ return new Date(value * 1000).toISOString().replace('T', ' ').slice(0, 19);
86
+ }
87
+ if (typeof value === 'string') {
88
+ // ISO format: 2024-01-15T10:30:00.000Z
89
+ const isoMatch = value.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})/);
90
+ if (isoMatch) {
91
+ return isoMatch[1] + ' ' + isoMatch[2];
92
+ }
93
+ // MySQL format: YYYY-MM-DD HH:MM:SS
94
+ const myMatch = value.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})$/);
95
+ if (myMatch) {
96
+ const d = new Date(value.replace(' ', 'T') + 'Z');
97
+ if (!isNaN(d.getTime())) return value;
98
+ }
99
+ const d = new Date(value);
100
+ if (!isNaN(d.getTime())) {
101
+ return d.toISOString().replace('T', ' ').slice(0, 19);
102
+ }
103
+ }
104
+ throw createError('ER_INVALID_DATE', fieldName, String(value));
105
+ }
106
+
107
+ // ============================================================
108
+ // TIMESTAMP: Unix 时间戳(秒)
109
+ // ============================================================
110
+
111
+ function validateTimestamp(value, fieldName) {
112
+ if (value instanceof Date) {
113
+ return Math.floor(value.getTime() / 1000);
114
+ }
115
+ if (typeof value === 'number') {
116
+ // 自动检测毫秒时间戳
117
+ if (value > 1e12) return Math.floor(value / 1000);
118
+ return value;
119
+ }
120
+ if (typeof value === 'string') {
121
+ const d = new Date(value);
122
+ if (!isNaN(d.getTime())) {
123
+ return Math.floor(d.getTime() / 1000);
124
+ }
125
+ const n = parseInt(value, 10);
126
+ if (!isNaN(n)) {
127
+ if (n > 1e12) return Math.floor(n / 1000);
128
+ return n;
129
+ }
130
+ }
131
+ throw createError('ER_INVALID_DATE', fieldName, String(value));
132
+ }
133
+
134
+ // ============================================================
135
+ // TIME: HH:MM:SS
136
+ // ============================================================
137
+
138
+ function validateTime(value, fieldName) {
139
+ if (typeof value === 'string') {
140
+ const m = value.match(/^(\d{2}):(\d{2}):(\d{2})$/);
141
+ if (m) {
142
+ const h = parseInt(m[1]), min = parseInt(m[2]), sec = parseInt(m[3]);
143
+ if (h >= 0 && h < 24 && min >= 0 && min < 60 && sec >= 0 && sec < 60) {
144
+ return value;
145
+ }
146
+ }
147
+ // 负时间: -HH:MM:SS
148
+ const neg = value.match(/^-(\d{2}):(\d{2}):(\d{2})$/);
149
+ if (neg) return value;
150
+ }
151
+ throw createError('ER_INVALID_DATE', fieldName, String(value));
152
+ }
153
+
154
+ // ============================================================
155
+ // 类型转换辅助
156
+ // ============================================================
157
+
158
+ function now(type = 'datetime') {
159
+ const d = new Date();
160
+ switch (type) {
161
+ case 'date': return d.toISOString().slice(0, 10);
162
+ case 'timestamp': return Math.floor(d.getTime() / 1000);
163
+ case 'time': return d.toISOString().slice(11, 19);
164
+ default: return d.toISOString().replace('T', ' ').slice(0, 19);
165
+ }
166
+ }
167
+
168
+ function formatDate(value, format = 'YYYY-MM-DD') {
169
+ if (!value) return value;
170
+ const d = value instanceof Date ? value : new Date(value);
171
+ if (isNaN(d.getTime())) return String(value);
172
+
173
+ const pad = n => String(n).padStart(2, '0');
174
+ return format
175
+ .replace('YYYY', d.getFullYear())
176
+ .replace('MM', pad(d.getMonth() + 1))
177
+ .replace('DD', pad(d.getDate()))
178
+ .replace('HH', pad(d.getHours()))
179
+ .replace('mm', pad(d.getMinutes()))
180
+ .replace('ss', pad(d.getSeconds()));
181
+ }
182
+
183
+ module.exports = { validateDateType, now, formatDate };
package/lib/errors.js ADDED
@@ -0,0 +1,84 @@
1
+ // © Vexify 2026 All Rights Reserved.
2
+ /**
3
+ * JSQL Error Codes — MySQL 风格错误码体系
4
+ */
5
+
6
+ class JSQL_Error extends Error {
7
+ constructor(code, message, details = {}) {
8
+ super(message);
9
+ this.name = 'JSQL_Error';
10
+ this.code = code;
11
+ this.details = details;
12
+ this.timestamp = new Date().toISOString();
13
+ }
14
+
15
+ toJSON() {
16
+ return {
17
+ error: true,
18
+ code: this.code,
19
+ message: this.message,
20
+ details: this.details,
21
+ timestamp: this.timestamp
22
+ };
23
+ }
24
+ }
25
+
26
+ // MySQL 兼容错误码
27
+ const ErrorCodes = {
28
+ // 表/库操作
29
+ ER_TABLE_EXISTS: { code: 1050, msg: "Table '%s' already exists" },
30
+ ER_NO_SUCH_TABLE: { code: 1146, msg: "Table '%s' doesn't exist" },
31
+ ER_DB_CREATE_EXISTS: { code: 1007, msg: "Can't create database '%s'; database exists" },
32
+
33
+ // 列操作
34
+ ER_DUP_FIELDNAME: { code: 1060, msg: "Duplicate column name '%s'" },
35
+ ER_BAD_FIELD_ERROR: { code: 1054, msg: "Unknown column '%s' in '%s'" },
36
+ ER_CANT_DROP_FIELD: { code: 1091, msg: "Can't DROP '%s'; check that column exists" },
37
+
38
+ // 约束
39
+ ER_DUP_ENTRY: { code: 1062, msg: "Duplicate entry '%s' for key '%s'" },
40
+ ER_NO_DEFAULT_FOR_FIELD: { code: 1364, msg: "Field '%s' doesn't have a default value" },
41
+ ER_BAD_NULL_ERROR: { code: 1048, msg: "Column '%s' cannot be null" },
42
+ ER_CHECK_CONSTRAINT: { code: 3819, msg: "Check constraint '%s' is violated" },
43
+ ER_NO_REFERENCED_ROW: { code: 1216, msg: "Cannot add or update: foreign key constraint fails" },
44
+ ER_ROW_IS_REFERENCED: { code: 1217, msg: "Cannot delete or update: a foreign key constraint fails" },
45
+
46
+ // 数据类型
47
+ ER_TRUNCATED_WRONG_VALUE: { code: 1292, msg: "Truncated incorrect %s value: '%s'" },
48
+ ER_INVALID_DATE: { code: 1292, msg: "Incorrect date value: '%s'" },
49
+ ER_DATA_TOO_LONG: { code: 1406, msg: "Data too long for column '%s'" },
50
+ ER_OUT_OF_RANGE: { code: 1264, msg: "Out of range value for column '%s'" },
51
+
52
+ // 事务
53
+ ER_TRANSACTION_ACTIVE: { code: 1568, msg: "Transaction already in progress" },
54
+ ER_NO_TRANSACTION: { code: 1569, msg: "No transaction in progress" },
55
+
56
+ // 其他
57
+ ER_NOT_SUPPORTED: { code: 1235, msg: "This version doesn't yet support '%s'" },
58
+ ER_PARSE_ERROR: { code: 1064, msg: "You have an error in your syntax: %s" },
59
+ ER_LOCK_WAIT_TIMEOUT: { code: 1205, msg: "Lock wait timeout exceeded; try restarting transaction" },
60
+ ER_FILE_NOT_FOUND: { code: 1017, msg: "Can't find file: '%s'" },
61
+ ER_VIEW_EXISTS: { code: 1050, msg: "View '%s' already exists" },
62
+ ER_TRIGGER_EXISTS: { code: 1359, msg: "Trigger '%s' already exists" },
63
+ ER_TRIGGER_NOT_FOUND: { code: 1360, msg: "Trigger '%s' does not exist" },
64
+ ER_PLUGIN_ERR: { code: 1125, msg: "Plugin error: %s" },
65
+ };
66
+
67
+ /**
68
+ * 创建错误
69
+ */
70
+ function createError(codeKey, ...args) {
71
+ const template = ErrorCodes[codeKey];
72
+ if (!template) {
73
+ return new JSQL_Error('UNKNOWN', `Unknown error: ${codeKey}`, { codeKey, args });
74
+ }
75
+
76
+ let msg = template.msg;
77
+ for (let i = 0; i < args.length; i++) {
78
+ msg = msg.replace('%s', String(args[i]));
79
+ }
80
+
81
+ return new JSQL_Error(template.code, msg, { args });
82
+ }
83
+
84
+ module.exports = { JSQL_Error, ErrorCodes, createError };