crabatool 1.0.371 → 1.0.380

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/index.js CHANGED
@@ -4,10 +4,12 @@ var start = require('./tool/start.js');
4
4
  exports = module.exports;
5
5
  exports.utils = require('./lib/utils.js');
6
6
  exports.server = require('./lib/server.js');
7
- exports.stringBuilder = require('./lib/stringBuilder.js');
8
7
  exports.stringUtils = require('./lib/stringUtils.js');
9
8
  exports.pager = require('./lib/pager.js');
9
+ exports.cuid = require('./lib/cuid.js');
10
+ exports.JSONCRUD = require('./lib/jsoncrud.js');
10
11
  exports.DbHelper = require('./lib/db/dbHelper.js');
12
+ exports.StringBuilder = require('./lib/stringBuilder.js');
11
13
 
12
14
 
13
15
  exports.run = function(options) {
@@ -0,0 +1,254 @@
1
+ const fs = require('fs').promises
2
+ const path = require('path')
3
+ const cuid = require('./cuid.js');
4
+
5
+ class JSONCRUD {
6
+ /**
7
+ * @param {Object} config - 配置项
8
+ * @param {string} [config.storagePath] - 文件存储路径
9
+ * @param {boolean} [config.useMemory] - 是否使用内存存储
10
+ */
11
+ constructor(config = {}) {
12
+ this.config = config
13
+ this.entities = {} // 存储所有实体 { entityName: { data, relations } }
14
+ this.initialized = false
15
+
16
+ if (config.storagePath && !config.useMemory) {
17
+ this.storageFile = path.resolve(config.storagePath)
18
+ this.initFileStorage()
19
+ } else {
20
+ this.useMemory = true
21
+ this.initialized = true
22
+ }
23
+ }
24
+
25
+ // 初始化实体结构
26
+ defineEntity(entityName, relations = {}) {
27
+ if (!this.entities[entityName]) {
28
+ this.entities[entityName] = {
29
+ data: [],
30
+ relations: relations // 定义关联关系 { targetEntity: foreignKey }
31
+ }
32
+ }
33
+ }
34
+
35
+ // 保存到文件
36
+ async saveToFile() {
37
+ if (!this.useMemory) {
38
+ // 构建包含所有实体的存储对象
39
+ const dataToSave = Object.entries(this.entities).reduce((acc, [entityName, entityData]) => {
40
+ acc[entityName] = entityData.data
41
+ return acc
42
+ }, {})
43
+
44
+ // 创建存储目录(如果不存在)
45
+ await fs.mkdir(path.dirname(this.storageFile), { recursive: true })
46
+
47
+ // 原子化写入(避免写入过程中崩溃导致数据丢失)
48
+ const tmpFile = `${this.storageFile}.tmp`
49
+ await fs.writeFile(tmpFile, JSON.stringify(dataToSave, null, 2))
50
+ await fs.rename(tmpFile, this.storageFile)
51
+ }
52
+ }
53
+
54
+ // 核心CRUD方法
55
+ async create(entityName, item) {
56
+ await this.ready()
57
+ this.verifyEntityExists(entityName)
58
+
59
+ const newItem = {
60
+ ...item,
61
+ id: cuid.newCuidString(),
62
+ createdAt: new Date().toISOString()
63
+ }
64
+ this.entities[entityName].data.push(newItem)
65
+ await this.persist()
66
+ return newItem
67
+ }
68
+
69
+ async find(entityName, {
70
+ filterFunc,
71
+ page = 1,
72
+ pageSize = 10,
73
+ sortBy = 'createdAt',
74
+ sortOrder = 'desc'
75
+ } = {}) {
76
+ await this.ready()
77
+ this.verifyEntityExists(entityName)
78
+
79
+ var allData = [...this.entities[entityName].data];
80
+ if (typeof filterFunc == 'function') {
81
+ allData = allData.filter(filterFunc);
82
+ }
83
+ const sorted = allData.sort((a, b) => {
84
+ sortOrder === 'asc' ?
85
+ a[sortBy]?.localeCompare(b[sortBy]) :
86
+ b[sortBy]?.localeCompare(a[sortBy])
87
+ });
88
+
89
+ const start = (page - 1) * pageSize
90
+ const end = start + pageSize
91
+
92
+ return {
93
+ data: sorted.slice(start, end),
94
+ pageData: {
95
+ count: allData.length,
96
+ page,
97
+ pageSize,
98
+ totalPages: Math.ceil(allData.length / pageSize)
99
+ }
100
+ }
101
+ }
102
+
103
+ async findRelated(entityName, targetEntity, relationKey, targetId) {
104
+ await this.ready()
105
+ this.verifyEntityExists(entityName)
106
+ this.verifyEntityExists(targetEntity)
107
+
108
+ const relationConfig = this.entities[entityName].relations[targetEntity]
109
+ if (!relationConfig) {
110
+ throw new Error(`Relation between ${entityName} and ${targetEntity} not defined`)
111
+ }
112
+
113
+ return this.entities[entityName].data.filter(
114
+ item => item[relationConfig.foreignKey] === targetId
115
+ )
116
+ }
117
+
118
+ async update(entityName, id, updates) {
119
+ await this.ready()
120
+ this.verifyEntityExists(entityName)
121
+
122
+ const index = this.entities[entityName].data.findIndex(item => item.id === id)
123
+ if (index === -1) return null
124
+
125
+ this.entities[entityName].data[index] = {
126
+ ...this.entities[entityName].data[index],
127
+ ...updates,
128
+ updatedAt: new Date().toISOString()
129
+ }
130
+ await this.persist()
131
+ return this.entities[entityName].data[index]
132
+ }
133
+
134
+ async delete(entityName, id) {
135
+ await this.ready()
136
+ this.verifyEntityExists(entityName)
137
+
138
+ const initialLength = this.entities[entityName].data.length
139
+ this.entities[entityName].data = this.entities[entityName].data.filter(
140
+ item => item.id !== id
141
+ )
142
+ const success = this.entities[entityName].data.length < initialLength
143
+ if (success) await this.persist()
144
+ return success
145
+ }
146
+
147
+ // 辅助方法
148
+ verifyEntityExists(entityName, create) {
149
+ if (!this.entities[entityName]) {
150
+ this.defineEntity(entityName);
151
+ }
152
+ }
153
+
154
+ // 等待初始化完成
155
+ async ready() {
156
+ while (!this.initialized) {
157
+ await new Promise(resolve => setTimeout(resolve, 50))
158
+ }
159
+ }
160
+
161
+ // 初始化文件存储
162
+ async initFileStorage() {
163
+ try {
164
+ // 1. 读取存储文件内容
165
+ const fileContent = await fs.readFile(this.storageFile, 'utf-8')
166
+ const storedData = JSON.parse(fileContent || '{}')
167
+
168
+ // 2. 自动创建存储中的实体(如果尚未定义)
169
+ Object.keys(storedData).forEach(entityName => {
170
+ if (!this.entities[entityName]) {
171
+ this.defineEntity(entityName, {}) // 默认空关联关系
172
+ }
173
+ })
174
+
175
+ // 3. 合并存储数据到已定义实体
176
+ Object.entries(storedData).forEach(([entityName, items]) => {
177
+ if (this.entities[entityName]) {
178
+ // 保留现有关联关系配置
179
+ this.entities[entityName].data = items.map(item => ({
180
+ ...item,
181
+ // 确保存在时间戳字段
182
+ createdAt: item.createdAt || new Date().toISOString(),
183
+ updatedAt: item.updatedAt || null
184
+ }))
185
+ }
186
+ })
187
+
188
+ } catch (err) {
189
+ if (err.code === 'ENOENT') {
190
+ // 4. 文件不存在时初始化空文件
191
+ await this.saveToFile()
192
+ } else if (err instanceof SyntaxError) {
193
+ // 5. 处理文件内容损坏的情况
194
+ console.error('Storage file corruption detected, resetting...')
195
+ await this.saveToFile()
196
+ } else {
197
+ throw err
198
+ }
199
+ } finally {
200
+ // 6. 确保初始化完成标记
201
+ this.initialized = true
202
+ }
203
+ }
204
+
205
+ // 通用保存方法
206
+ async persist() {
207
+ if (this.useMemory) return
208
+ await this.saveToFile()
209
+ }
210
+
211
+ }
212
+ exports = module.exports = JSONCRUD;
213
+
214
+ /*
215
+ // 使用示例
216
+ async function main() {
217
+ const db = new JSONCRUD({ storagePath: './multi-data.json' })
218
+
219
+ // 1. 定义实体和关联关系
220
+ db.defineEntity('users', {
221
+ posts: { foreignKey: 'authorId' }
222
+ })
223
+
224
+ db.defineEntity('posts', {
225
+ author: { foreignKey: 'id' } // 反向关联
226
+ })
227
+
228
+ // 2. 创建数据
229
+ const user = await db.create('users', {
230
+ name: 'John',
231
+ email: 'john@example.com'
232
+ })
233
+
234
+ await db.create('posts', {
235
+ title: 'First Post',
236
+ content: 'Hello World',
237
+ authorId: user.id
238
+ })
239
+
240
+ // 3. 分页查询
241
+ const postPage = await db.find('posts', {
242
+ page: 1,
243
+ pageSize: 5,
244
+ sortBy: 'createdAt'
245
+ })
246
+ console.log('Paginated posts:', postPage)
247
+
248
+ // 4. 关联查询
249
+ const userPosts = await db.findRelated('posts', 'users', 'author', user.id)
250
+ console.log('User posts:', userPosts)
251
+ }
252
+
253
+ main()
254
+ */
package/lib/server.js CHANGED
@@ -157,6 +157,15 @@ function createServer(app, options) {
157
157
  var port = server.address().port;
158
158
  var url = 'http://127.0.0.1:' + port;
159
159
 
160
+ // 告诉业务已经启动服务了
161
+ var globaljs = path.join(webPath, '../lib/global.js');
162
+ if (fs.existsSync(globaljs)) {
163
+ var global = require(globaljs);
164
+ if (global.main) {
165
+ global.main(server);
166
+ }
167
+ }
168
+
160
169
  if (options && options.onRun) {
161
170
  options.onRun(port);
162
171
  }
package/lib/utils.js CHANGED
@@ -136,7 +136,7 @@ class Utils {
136
136
  text = text.replace('_', ''); // 第一个字符不能是下划线
137
137
  return text.replace(/[^a-zA-Z0-9_]/g, ''); // 过滤非字母、数字、下划线的其它字符
138
138
  }
139
-
139
+
140
140
  format() {
141
141
  // 字符串参数化
142
142
  var args = new Array(arguments.length);
@@ -346,6 +346,14 @@ class Utils {
346
346
  next(); // 继续处理
347
347
  }
348
348
 
349
+ isEmpty(v) {
350
+ return v === '' || this._isUndefinedOrNull(v);
351
+ }
352
+
353
+ isUndefinedOrNull(v) {
354
+ return v === null || v === undefined;
355
+ }
356
+
349
357
  // 删除文件夹,清空文件夹,删除目录,清空目录,清空整个目录,包括子目录和文件
350
358
  cleardirsSync(dirname) {
351
359
  if (!fs.existsSync(dirname)) {
@@ -543,11 +551,19 @@ class Utils {
543
551
 
544
552
  var stat = fs.statSync(filePath);
545
553
  if (stat.isDirectory()) {
546
- var options2 = {};
547
- Object.assign(options2, options);
548
- options2.source = filePath;
549
- utils.getFiles(options2, fileList);
550
- } else {
554
+ if (options.level === 0) {
555
+ return;
556
+ }
557
+
558
+ if (options.folder) {
559
+ fileList.push(filePath);
560
+ } else {
561
+ var options2 = {};
562
+ Object.assign(options2, options);
563
+ options2.source = filePath;
564
+ utils.getFiles(options2, fileList);
565
+ }
566
+ } else if (!options.folder) {
551
567
  var ename = path.extname(filePath);
552
568
  if (!ename || (exts && !exts.includes(ename))) {
553
569
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crabatool",
3
- "version": "1.0.371",
3
+ "version": "1.0.380",
4
4
  "description": "crabatool",
5
5
  "main": "index.js",
6
6
  "bin": {