@k3000/store 1.7.0 → 1.9.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.
Files changed (58) hide show
  1. package/README.md +123 -1
  2. package/architect.mjs +54 -2
  3. package/generator.mjs +67 -55
  4. package/package.json +1 -1
  5. package/test/1/buffer/683 +1 -0
  6. package/test/1/buffer/684 +1 -0
  7. package/test/1/data +0 -0
  8. package/test/1/index +0 -0
  9. package/test/2/buffer/683 +1 -0
  10. package/test/2/buffer/684 +1 -0
  11. package/test/2/data +0 -0
  12. package/test/2/index +0 -0
  13. package/test/2/index.mjs +25 -25
  14. package/test/2/type.ts +33 -33
  15. package/test/3/buffer/683 +1 -0
  16. package/test/3/buffer/684 +1 -0
  17. package/test/3/data +0 -0
  18. package/test/3/index +0 -0
  19. package/test/3/index.mjs +26 -26
  20. package/test/3/type.ts +33 -33
  21. package/test/4/buffer/683 +1 -0
  22. package/test/4/buffer/684 +1 -0
  23. package/test/4/data +0 -0
  24. package/test/4/index +0 -0
  25. package/test/4/index.mjs +26 -26
  26. package/test/4/type.ts +33 -33
  27. package/test/5/buffer/683 +1 -0
  28. package/test/5/buffer/684 +1 -0
  29. package/test/5/data +0 -0
  30. package/test/5/index +0 -0
  31. package/test/5/index.mjs +26 -26
  32. package/test/5/type.ts +33 -33
  33. package/test/6/buffer/683 +1 -0
  34. package/test/6/buffer/684 +1 -0
  35. package/test/6/data +0 -0
  36. package/test/6/index +0 -0
  37. package/test/6/index.mjs +11 -11
  38. package/test/6/type.ts +23 -23
  39. package/test/7/buffer/683 +1 -0
  40. package/test/7/buffer/684 +1 -0
  41. package/test/7/data +0 -0
  42. package/test/7/index +0 -0
  43. package/test/7/index.mjs +12 -12
  44. package/test/7/type.ts +23 -23
  45. package/test/8/buffer/683 +1 -0
  46. package/test/8/buffer/684 +1 -0
  47. package/test/8/data +0 -0
  48. package/test/8/index +0 -0
  49. package/test/8/index.mjs +12 -12
  50. package/test/8/type.ts +23 -23
  51. package/test/9/buffer/683 +1 -0
  52. package/test/9/buffer/684 +1 -0
  53. package/test/9/data +0 -0
  54. package/test/9/index +0 -0
  55. package/test/9/index.mjs +390 -0
  56. package/test/9/type.ts +174 -0
  57. package/test/index.mjs +3 -3
  58. package/test.mjs +98 -10
package/README.md CHANGED
@@ -5,6 +5,10 @@
5
5
  *2、1.5.0版本请注意page的用法。
6
6
  *3、1.6.1修改String中文存储的BUG建议更新,更新请注意第1、2条和page的用法。
7
7
  *4、1.7.0修复中文路径问题。
8
+ *5、1.8.0添加存储目录的备份与迁移的描述(无功能调整)。
9
+ *6、1.9.0让Trae builder优化代码添加了注释。
10
+ *7、反馈与讨论:19815488@qq.com
11
+
8
12
  ```
9
13
 
10
14
  **初始化和更新升级**
@@ -227,9 +231,127 @@ storage.admin.remove(...storage.admin.filter(admin => admin.usn.startsWith('test
227
231
  ```
228
232
  更多使用方法参考 `test.mjs`
229
233
 
230
- **保存:**
234
+ **保存**
231
235
 
236
+ 没3秒会自动保存,也可以手工操作
232
237
  ```
233
238
  import {close} from './test/index.mjs'
234
239
  close() // 关闭并保存
235
240
  ```
241
+ **备份与迁移**
242
+
243
+ 当使用`upgrade`升级的时候会按照版本号备份历史版本。
244
+
245
+ 也能直接将整个存储目录保存,复制妥存。
246
+
247
+ 同样道理,迁移就是将存储目录拷贝到相应项目目录,使用相应版本的库运行即可。
248
+
249
+ ---
250
+
251
+ **概述**
252
+
253
+ - 本工具提供一种轻量的、可版本化的本地结构化存储方案,面向 Node.js 项目;以“集合/列”定义模式,生成访问层代码,数据落盘到二进制文件并配合加密索引文件。
254
+ - 适合嵌入式、桌面端或需要无需外部数据库的应用,支持索引查询、范围查询、分页与联合查询,且提供平滑的结构升级与密码迁移能力。
255
+
256
+ **核心概念**
257
+
258
+ - `Storage`:底层存储运行时,负责数据读写、缓存、加解密、结构与记录的持久化以及并发打开检查。
259
+ - `Entity`:单条记录对象,字段访问器封装了二进制读写与索引维护;支持 Buffer/Text 外置文件字段。
260
+ - `Entities`:集合的代理,提供 `push/pop/unshift/splice/remove/page` 等数组语义与 `indexBy<Field>` 查询。
261
+ - 集合/列定义:通过类型 DSL(`Id/Uint/Int/BigUint/Bigint/Time/Float/String/Buffer/Text`)声明列类型、索引、默认值、长度、步进。
262
+
263
+ **目录结构详解**
264
+
265
+ - `index`:二进制索引文件,包含两段加密 JSON:结构(struct)与记录(record)。头部额外包含一个 4 字节整数作为“打开计数”,用于并发打开检测。
266
+ - `data`:二进制数据文件,按集合总长度定长布局存储;每条记录按列 `position/length` 定位读取。
267
+ - `buffer/`:外置文件目录,存放 `Buffer/Text` 字段实际内容;字段本体存储一个字节作为标志位,值为 1 表示存在外置内容。
268
+ - `<version>/index.mjs`:访问层代码,按结构自动生成集合实体类与集合代理;顶层 `index.mjs` 始终重导出最新版本。
269
+
270
+ **加密与密码**
271
+
272
+ - 结构与记录的加解密使用 `AES-192-CBC`,密钥由 `md5(password)+salt` 派生,IV 来自 MD5 的中间字节;密码来源于 `Storage(import.meta.url)` 的查询参数或 `upgrade` 传参。
273
+ - 密码迁移:通过 `upgrade('name', { newPwd, password })` 在提交时重新加密结构与记录;若密码不匹配或存储文件损坏会抛出错误。
274
+
275
+ **版本与升级流程**
276
+
277
+ - `upgrade(name, options)` 会:
278
+ - 计算最新版本 `v` 并将其目录完整复制到新版本目录 `version`;
279
+ - 打开旧/新 `Storage`(支持密码切换),准备生成路径(`index.mjs/type.ts`);
280
+ - 返回结构变更 API;`submit()` 会写入加密结构与记录、关闭句柄、生成访问代码,并在集合结构有更新时把旧版数据迁移到新版集合。
281
+
282
+ **索引与查询**
283
+
284
+ - 为列开启索引可使用 `updateIndex('set', { col: true })` 或在 DSL 中 `String().index(true)` 等;索引是按字段二进制值排序的记录位置列表。
285
+ - 字段写入使用两种模式:索引列通过 `set2` 维护有序索引(二分定位插入),普通列使用 `set` 直接写入。
286
+ - 查询方法:
287
+ - 值查询:`indexBy<Field>(value)` 返回与二进制值相等的所有记录;
288
+ - 范围查询:`indexBy<Field>(value1, value2)` 或针对 `Time` 用 `indexByTime({ after, before })`;
289
+ - 普通数组查询:`filter/find/some/findIndex` 等对集合进行内存遍历。
290
+
291
+ **分页与缓存**
292
+
293
+ - `page(predicate, index, size, params)` 支持两种模式:
294
+ - 简单分页:`page(fn, 1, 10)` 返回第 1 页、大小 10 的结果(按记录顺序);
295
+ - 缓存分页:`page(fn, 'index', 'size', params)` 将 `params` 作为缓存键,命中时不重复计算;内部每轮(3 秒)标记过期,下一轮未访问的缓存将被移除。
296
+
297
+ **大字段(Buffer/Text)**
298
+
299
+ - `Buffer`/`Text` 字段内容存储在 `buffer/` 目录,以记录起始位置+列偏移作为文件名;字段本体只存放 1 字节标记位。
300
+ - 删除或替换记录时,会自动清理关联的外置文件;读取时通过 `readFileSync` 返回二进制或文本内容。
301
+
302
+ **性能与注意事项**
303
+
304
+ - 自动保存与清理:每 3 秒写回需要保存的值、刷新记录并清理过期缓存;
305
+ - 缓存上限:Value/Entity/Cache 的 Map 最多各 99999 条;超过上限的写入会直接落盘而不进入缓存;
306
+ - 并发打开:打开计数变化会抛错提示“已在其他地方打开”,防止同目录并发写入;
307
+ - 路径解析:`Storage(import.meta.url)` 在 file URL 情况下使用 `process.cwd()` 作为目录基础,请在正确工作目录下运行或传显式路径;
308
+ - 时间编码:`Time` 字段以定长十六进制 Buffer 表示,使用 `d2b/b2d` 辅助范围比较;最大时间常量为 `MaxTime`。
309
+
310
+ **API 参考(结构变更)**
311
+
312
+ ```
313
+ const gen = upgrade(name, { version, password, newPwd, store })
314
+ gen.appendSet(name, set, remark) // 新增集合
315
+ gen.updateCol(name, set) // 更新集合列
316
+ gen.renameSet(name, newName) // 集合更名
317
+ gen.renameCol(name, { old: new }) // 列更名
318
+ gen.deleteSet(name) // 删除集合
319
+ gen.deleteCol(name, colName) // 删除列
320
+ gen.updateSetRemark(name, remark) // 更新集合备注
321
+ gen.updateIndex(name, { col: true }) // 索引开关
322
+ gen.submit() // 提交生成与数据迁移
323
+ ```
324
+
325
+ **最小示例**
326
+
327
+ ```
328
+ import upgrade, { Id, String, Time, Uint } from '@k3000/store/generator.mjs'
329
+
330
+ const { appendSet, submit } = upgrade('myStore')
331
+
332
+ appendSet('user', {
333
+ id: Id,
334
+ uid: String('账号').index(true),
335
+ pwd: String('密码').length(64),
336
+ createdAt: Time('创建时间').index(true).value(Time.now),
337
+ status: Uint(1).remark('状态位')
338
+ }, '用户表')
339
+
340
+ await submit()
341
+
342
+ // 使用
343
+ import storage from './myStore/index.mjs'
344
+ storage.user.push({ uid: 'alice', pwd: '***', status: 1 })
345
+ const recent = storage.user.indexByCreatedAt({ after: new Date('2024-01-01') })
346
+ ```
347
+
348
+ **常见问题(FAQ)**
349
+
350
+ - `Error: ENOENT: no such file or directory, open '.../data'`:当前工作目录与版本目录不匹配或目录未初始化。请先执行 `upgrade` 生成版本,并在该目录下运行。
351
+ - `密码不匹配或者存储文件不正确`:传入密码与存储目录加密信息不一致,或索引文件损坏。请确认密码或重新初始化。
352
+ - `索引查询无结果`:检查是否为该列开启索引(定义或 `updateIndex`),以及值/范围是否与列类型与长度匹配。
353
+ - `page 返回空`:检查 `size` 是否为正数,`predicate` 是否正确,或缓存参数键名是否与 `'index'/'size'` 一致。
354
+
355
+ **备份与迁移补充**
356
+
357
+ - 每次 `upgrade` 会将旧版本目录完整复制为备份;迁移仅需复制整个存储目录到目标项目,并确保使用兼容版本的库与正确密码即可。
package/architect.mjs CHANGED
@@ -1,5 +1,14 @@
1
- import {openSync, closeSync, readFileSync, writeFileSync, readSync, writeSync, unlinkSync} from "node:fs"
1
+ import {openSync, closeSync, readFileSync, writeFileSync, readSync, writeSync, unlinkSync, mkdirSync, existsSync} from "node:fs"
2
2
  import {createCipheriv, createDecipheriv, createHash, scryptSync} from "node:crypto"
3
+ import {fileURLToPath} from 'node:url'
4
+ import {dirname} from 'node:path'
5
+ /**
6
+ * 结构化存储运行时核心模块
7
+ * - 定义数据读写协议(定长 Buffer 字段、外置文件字段)
8
+ * - 提供实体(Entity)与集合(Entities)抽象,支持索引查询、范围查询与分页
9
+ * - 管理结构/记录的持久化与加解密、变更缓存与周期性保存
10
+ * - 为生成器输出的访问层(index.mjs)提供底层能力
11
+ */
3
12
 
4
13
  /**
5
14
  *
@@ -10,6 +19,8 @@ import {createCipheriv, createDecipheriv, createHash, scryptSync} from "node:cry
10
19
  */
11
20
  Array.prototype.eachFlat = function (array, predicate, map) {
12
21
 
22
+ // eachFlat:类似 LEFT JOIN,将本数组与目标数组按谓词进行展开合并
23
+
13
24
  if (typeof predicate === "string") {
14
25
 
15
26
  const key = predicate
@@ -52,6 +63,8 @@ Array.prototype.eachFlat = function (array, predicate, map) {
52
63
  */
53
64
  Array.prototype.filterFlat = function (array, predicate, map) {
54
65
 
66
+ // filterFlat:类似 INNER JOIN,仅保留满足谓词的组合项
67
+
55
68
  if (typeof predicate === "string") {
56
69
 
57
70
  const key = predicate
@@ -75,16 +88,19 @@ Array.prototype.filterFlat = function (array, predicate, map) {
75
88
  }
76
89
  }
77
90
 
91
+ // 缓存容量上限(value/entity/cache 三类 Map 的最大条目数)
78
92
  const MaxSize = 99999
79
93
  /**
80
94
  * 时间筛选的最大值
81
95
  * @type {number}
82
96
  */
97
+ // 时间筛选允许的最大毫秒值(用于范围查询边界)
83
98
  export const MaxTime = 99999999999999
84
99
  /**
85
100
  * 用于获取存储的位置
86
101
  * @type {symbol}
87
102
  */
103
+ // 实体在数据文件中的起始位置标识,用于删除/回收与定位
88
104
  export const position = Symbol('position')
89
105
 
90
106
  export const TypeLen = {
@@ -233,6 +249,7 @@ export class Entity {
233
249
 
234
250
  set2(name, buffer) {
235
251
 
252
+ // 维护索引列:写入值并按二分/边界插入保持 index[name] 有序
236
253
  const position = this.#position + this.#positionSet[name]
237
254
  const index = this.#indexSet[name]
238
255
  const length = this.#lengthSet[name]
@@ -289,6 +306,7 @@ export class Entity {
289
306
 
290
307
  readFileSync(name) {
291
308
 
309
+ // 读取外置 buffer/text 文件内容(字段本体只存标志位与位置)
292
310
  return readFileSync(this.#storage.bufferDir + (this.#position + this.#positionSet[name]))
293
311
  }
294
312
 
@@ -298,6 +316,9 @@ export class Entity {
298
316
 
299
317
  if (value instanceof Buffer) {
300
318
 
319
+ if (!existsSync(this.#storage.bufferDir)) {
320
+ mkdirSync(this.#storage.bufferDir)
321
+ }
301
322
  writeFileSync(this.#storage.bufferDir + (this.#position + this.#positionSet[name]), value)
302
323
 
303
324
  buffer[0] = 1
@@ -362,6 +383,9 @@ export class Entity {
362
383
 
363
384
  if (value) {
364
385
 
386
+ if (!existsSync(this.#storage.bufferDir)) {
387
+ mkdirSync(this.#storage.bufferDir)
388
+ }
365
389
  writeFileSync(this.#storage.bufferDir + (this.#position + this.#positionSet[name]),
366
390
  typeof value === "string" ? value : String(value))
367
391
 
@@ -444,6 +468,7 @@ export class Entities extends Array {
444
468
 
445
469
  if (index >= record.length || index < 0) return null
446
470
 
471
+ // 索引访问返回实体代理(含字段 getter/setter)
447
472
  return storage.getEntity(record[p], classify, this.#index, this.#lengthSet, this.#positionSet)
448
473
  }
449
474
 
@@ -453,6 +478,7 @@ export class Entities extends Array {
453
478
 
454
479
  if (typeof result !== "function") return result
455
480
 
481
+ // 将原生数组方法委托到实体对象数组
456
482
  return (...arg) => result.call(record.map(position => storage.getEntity(position, classify,
457
483
  this.#index, this.#lengthSet, this.#positionSet)), ...arg)
458
484
  }
@@ -496,6 +522,7 @@ export class Entities extends Array {
496
522
 
497
523
  push(...items) {
498
524
 
525
+ // 追加:复用空闲位或推进指针,返回实体对象(已应用默认值创建)
499
526
  if (this.#storage.needSave === false) {
500
527
 
501
528
  this.#storage.needSave = true
@@ -524,6 +551,7 @@ export class Entities extends Array {
524
551
 
525
552
  pop() {
526
553
 
554
+ // 弹出最后一条:回收索引与外置文件,位置进入空闲池
527
555
  if (this.#storage.needSave === false) {
528
556
 
529
557
  this.#storage.needSave = true
@@ -540,6 +568,7 @@ export class Entities extends Array {
540
568
 
541
569
  unshift(...items) {
542
570
 
571
+ // 头部插入:同 push 但写入到记录头部
543
572
  if (this.#storage.needSave === false) {
544
573
 
545
574
  this.#storage.needSave = true
@@ -568,6 +597,7 @@ export class Entities extends Array {
568
597
 
569
598
  shift() {
570
599
 
600
+ // 移除首条:与 pop 相似
571
601
  if (this.#storage.needSave === false) {
572
602
 
573
603
  this.#storage.needSave = true
@@ -583,6 +613,7 @@ export class Entities extends Array {
583
613
 
584
614
  splice(start, deleteCount, ...items) {
585
615
 
616
+ // 区间替换:批量分配位置并回收被删除位置
586
617
  if (this.#storage.needSave === false) {
587
618
 
588
619
  this.#storage.needSave = true
@@ -642,6 +673,7 @@ export class Entities extends Array {
642
673
  */
643
674
  page(predicate, page, size, params) {
644
675
 
676
+ // 分页:支持带缓存键的分页与简单分页两种模式
645
677
  if (typeof page === "string" && typeof size === "string" && typeof params === "object") {
646
678
 
647
679
  return this.#page(predicate, params[page], params[size], this.#name + ' ' +
@@ -681,6 +713,7 @@ export class Entities extends Array {
681
713
 
682
714
  remove(...items) {
683
715
 
716
+ // 从集合中移除实体(按位置比对),触发回收与空闲入池
684
717
  let index
685
718
 
686
719
  if (Array.isArray(items[0])) {
@@ -964,7 +997,17 @@ export class Storage {
964
997
 
965
998
  constructor(url, password, newPwd) {
966
999
 
967
- let dir = url.startsWith('file:///') ? process.cwd() : url
1000
+ let dir
1001
+
1002
+ if (url.startsWith('file:///')) {
1003
+
1004
+ const filePath = fileURLToPath(url)
1005
+ dir = dirname(filePath)
1006
+
1007
+ } else {
1008
+
1009
+ dir = url
1010
+ }
968
1011
 
969
1012
  dir = dir.replace(/\\/g, '/')
970
1013
 
@@ -1022,6 +1065,7 @@ export class Storage {
1022
1065
 
1023
1066
  #save() {
1024
1067
 
1068
+ // 将需要保存的缓存值写回数据文件;如记录有结构改动,刷新记录块
1025
1069
  for (const [position, item] of this.#values.entries()) {
1026
1070
 
1027
1071
  if (item.needsSave) {
@@ -1047,6 +1091,7 @@ export class Storage {
1047
1091
 
1048
1092
  #check(...arg) {
1049
1093
 
1094
+ // 简单的每轮过期机制:第二次巡检仍为 expired=true 的条目会被移除
1050
1095
  for (const map of arg) {
1051
1096
 
1052
1097
  for (const [key, value] of map) {
@@ -1065,6 +1110,7 @@ export class Storage {
1065
1110
 
1066
1111
  #writeNumber(number, position) {
1067
1112
 
1113
+ // 向索引文件写入 4 字节无符号整数(版本/长度/指针等)
1068
1114
  const buffer = Buffer.alloc(4)
1069
1115
 
1070
1116
  buffer.writeUint32BE(number, 0)
@@ -1089,6 +1135,7 @@ export class Storage {
1089
1135
  extra: {},
1090
1136
  }) {
1091
1137
 
1138
+ // 更新结构并写入索引文件(加密 JSON)
1092
1139
  this.#struct = struct
1093
1140
 
1094
1141
  this.#update(struct)
@@ -1102,6 +1149,7 @@ export class Storage {
1102
1149
  pointer: 0,
1103
1150
  }, length = 4 + this.#getLength()) {
1104
1151
 
1152
+ // 更新记录并写入索引文件(结构长度之后的区域)
1105
1153
  this.#record = record
1106
1154
 
1107
1155
  this.#update(record, length)
@@ -1228,6 +1276,7 @@ export class Storage {
1228
1276
  */
1229
1277
  getValue(position, length) {
1230
1278
 
1279
+ // 读取数据文件指定位置的定长 Buffer,配合 LRU 式过期标记缓存
1231
1280
  if (this.#values.has(position)) {
1232
1281
 
1233
1282
  const item = this.#values.get(position)
@@ -1267,6 +1316,7 @@ export class Storage {
1267
1316
 
1268
1317
  setValue(position, length, value) {
1269
1318
 
1319
+ // 设置缓存并标记是否需要保存(或直接写盘,当缓存已满)
1270
1320
  if (this.#values.has(position)) {
1271
1321
 
1272
1322
  const item = this.#values.get(position)
@@ -1326,6 +1376,7 @@ export class Storage {
1326
1376
 
1327
1377
  getEntity(position, classify, ...arg) {
1328
1378
 
1379
+ // 获取实体代理对象,带过期标记以配合周期性清理
1329
1380
  if (this.#entities.has(position)) {
1330
1381
 
1331
1382
  const item = this.#entities.get(position)
@@ -1387,6 +1438,7 @@ export class Storage {
1387
1438
 
1388
1439
  updateValue(value, name, key) {
1389
1440
 
1441
+ // 维护有步进的列(如自增 Id):当未提供值时按步进递增并返回
1390
1442
  if (value === undefined) return this.#record.value[name][key] += this.#struct.base[name][key].step
1391
1443
 
1392
1444
  if (Number.isNaN(Number.parseFloat(value))) {
package/generator.mjs CHANGED
@@ -1,6 +1,12 @@
1
1
  import {copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync} from "node:fs";
2
- import {buffer, string, Storage, TypeLen, readInt24BE, readUint24BE} from './architect.mjs'
2
+ import {buffer, string, Storage, TypeLen, readInt24BE, readUint24BE, b2d} from './architect.mjs'
3
3
  import {resolve} from 'node:path'
4
+ /**
5
+ * 代码生成与结构升级模块
6
+ * - 提供类型 DSL(Id/Uint/Int/...)定义集合结构、索引、默认值、长度与步进
7
+ * - 生成运行时访问代码(index.mjs)与类型声明(type.ts)
8
+ * - 管理版本目录复制、密码迁移与数据迁移
9
+ */
4
10
 
5
11
  const modulePath = import.meta.url.endsWith('?test=true') ? '../../architect.mjs' : '@k3000/store/architect.mjs'
6
12
  const modulePath2 = import.meta.url.endsWith('?test=true') ? '../../generator.mjs' : '@k3000/store'
@@ -49,6 +55,7 @@ const TimeAttr = {
49
55
  }
50
56
 
51
57
  const SCOPE = {}
58
+ // SCOPE:内部哨兵对象,限制链式配置仅由类型构造器内部调用生效
52
59
 
53
60
  export const bigintSerialize = (_, value) => typeof value === "bigint" ? value.toString() : value
54
61
 
@@ -495,6 +502,7 @@ export const Text = remark => new class Text extends Type {
495
502
 
496
503
  export const struct = (storage, remark, name) => {
497
504
 
505
+ // 将底层结构反射为可读的元信息(类型、备注、索引、默认值、长度等)
498
506
  const struct = {}, base = storage.struct.base
499
507
 
500
508
  for (const [key, value] of Object.entries(storage.struct.extra)) {
@@ -545,6 +553,7 @@ export const struct = (storage, remark, name) => {
545
553
 
546
554
  const updateColPos = (base, extra) => {
547
555
 
556
+ // 重新计算列起始位置与集合总长度(按顺序累加定长字段)
548
557
  let position = 0
549
558
 
550
559
  for (const value of Object.values(base)) {
@@ -559,6 +568,7 @@ const updateColPos = (base, extra) => {
559
568
 
560
569
  function appendSet(name, set, remark = '') {
561
570
 
571
+ // 新增集合:初始化结构/记录占位,并根据列定义填充索引/步进默认值
562
572
  if (!Reflect.has(this.curr.struct.base, name)) {
563
573
 
564
574
  console.log('append set:', name)
@@ -619,6 +629,7 @@ function appendSet(name, set, remark = '') {
619
629
 
620
630
  function updateCol(name, set) {
621
631
 
632
+ // 更新集合列:覆盖或新增列定义,维护索引与步进默认值
622
633
  if (Reflect.has(this.curr.struct.base, name)) {
623
634
 
624
635
  console.log('current set:', name)
@@ -674,6 +685,7 @@ function updateCol(name, set) {
674
685
 
675
686
  function renameSet(name, newName) {
676
687
 
688
+ // 集合更名:同步结构/备注/记录各处键名
677
689
  if (typeof newName === "string"
678
690
  && Reflect.has(this.curr.struct.base, name)
679
691
  && !Reflect.has(this.curr.struct.base, newName)) {
@@ -708,6 +720,7 @@ function renameSet(name, newName) {
708
720
 
709
721
  function renameCol(name, set) {
710
722
 
723
+ // 列更名:同步结构/备注/记录各处键名与对应索引容器
711
724
  if (Reflect.has(this.curr.struct.base, name)) {
712
725
 
713
726
  for (let [key, newKey] of Object.entries(set)) {
@@ -748,6 +761,7 @@ function renameCol(name, set) {
748
761
 
749
762
  function deleteSet(name) {
750
763
 
764
+ // 删除集合:移除结构与记录的全部条目
751
765
  if (Reflect.has(this.curr.struct.base, name)) {
752
766
 
753
767
  console.log(`delete set: ${name}`)
@@ -763,6 +777,7 @@ function deleteSet(name) {
763
777
 
764
778
  function deleteCol(name, col) {
765
779
 
780
+ // 删除列:移除结构与记录条目并重算位置
766
781
  if (Reflect.has(this.curr.struct.base, name)
767
782
  && Reflect.has(this.curr.struct.base[name], col)) {
768
783
 
@@ -783,6 +798,7 @@ function deleteCol(name, col) {
783
798
 
784
799
  function updateSetRemark(name, remark) {
785
800
 
801
+ // 更新集合备注
786
802
  if (Reflect.has(this.curr.struct.base, name)) {
787
803
 
788
804
  console.log(`update ${name} remark:`, remark)
@@ -793,93 +809,74 @@ function updateSetRemark(name, remark) {
793
809
 
794
810
  const updateIndex2 = function (store, record, struct) {
795
811
 
796
- record = record.map(position => position + struct.position)
812
+ const positions = record.map(position => position + struct.position)
813
+ const length = struct.length
797
814
 
798
815
  switch (struct.type) {
799
816
 
800
817
  case typeMap.id:
801
-
802
- return record.sort((a, b) => store.getValue(a, struct.length).readUInt32BE()
803
- - store.getValue(b, struct.length).readUInt32BE())
818
+ return positions.map(p => [p, store.getValue(p, length).readUInt32BE(0)])
819
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
804
820
 
805
821
  case typeMap.uint:
806
-
807
- switch (struct.length) {
808
-
822
+ switch (length) {
809
823
  case 1:
810
-
811
- return record.sort((a, b) => store.getValue(a, struct.length).readUInt8()
812
- - store.getValue(b, struct.length).readUInt8())
813
-
824
+ return positions.map(p => [p, store.getValue(p, length).readUInt8(0)])
825
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
814
826
  case 2:
815
-
816
- return record.sort((a, b) => store.getValue(a, struct.length).readUInt16BE()
817
- - store.getValue(b, struct.length).readUInt16BE())
818
-
827
+ return positions.map(p => [p, store.getValue(p, length).readUInt16BE(0)])
828
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
819
829
  case 3:
820
-
821
- return record.sort((a, b) => readUint24BE(store.getValue(a, struct.length))
822
- - readUint24BE(store.getValue(b, struct.length)))
823
-
830
+ return positions.map(p => [p, readUint24BE(store.getValue(p, length))])
831
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
824
832
  case 4:
825
-
826
- return record.sort((a, b) => store.getValue(a, struct.length).readUInt32BE()
827
- - store.getValue(b, struct.length).readUInt32BE())
833
+ return positions.map(p => [p, store.getValue(p, length).readUInt32BE(0)])
834
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
828
835
  }
836
+ break
829
837
 
830
838
  case typeMap.int:
831
-
832
- switch (struct.length) {
833
-
839
+ switch (length) {
834
840
  case 1:
835
-
836
- return record.sort((a, b) => store.getValue(a, struct.length).readInt8()
837
- - store.getValue(b, struct.length).readInt8())
838
-
841
+ return positions.map(p => [p, store.getValue(p, length).readInt8(0)])
842
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
839
843
  case 2:
840
-
841
- return record.sort((a, b) => store.getValue(a, struct.length).readInt16BE()
842
- - store.getValue(b, struct.length).readInt16BE())
843
-
844
+ return positions.map(p => [p, store.getValue(p, length).readInt16BE(0)])
845
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
844
846
  case 3:
845
-
846
- return record.sort((a, b) => readInt24BE(store.getValue(a, struct.length))
847
- - readInt24BE(store.getValue(b, struct.length)))
848
-
847
+ return positions.map(p => [p, readInt24BE(store.getValue(p, length))])
848
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
849
849
  case 4:
850
-
851
- return record.sort((a, b) => store.getValue(a, struct.length).readInt32BE()
852
- - store.getValue(b, struct.length).readInt32BE())
850
+ return positions.map(p => [p, store.getValue(p, length).readInt32BE(0)])
851
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
853
852
  }
853
+ break
854
854
 
855
855
  case typeMap.bigUint:
856
-
857
- return record.sort((a, b) => store.getValue(a, struct.length).readBigUInt64BE()
858
- - store.getValue(b, struct.length).readBigUInt64BE())
856
+ return positions.map(p => [p, store.getValue(p, length).readBigUInt64BE(0)])
857
+ .sort((a, b) => (a[1] - b[1])).map(([p]) => p)
859
858
 
860
859
  case typeMap.bigint:
861
-
862
- return record.sort((a, b) => store.getValue(a, struct.length).readBigInt64BE()
863
- - store.getValue(b, struct.length).readBigInt64BE())
860
+ return positions.map(p => [p, store.getValue(p, length).readBigInt64BE(0)])
861
+ .sort((a, b) => (a[1] - b[1])).map(([p]) => p)
864
862
 
865
863
  case typeMap.time:
866
-
867
- return record.sort((a, b) => b2d(store.getValue(a, struct.length))
868
- - b2d(store.getValue(b, struct.length)))
864
+ return positions.map(p => [p, b2d(store.getValue(p, length))])
865
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
869
866
 
870
867
  case typeMap.float:
871
-
872
- return record.sort((a, b) => store.getValue(a, struct.length).readDoubleBE()
873
- - store.getValue(b, struct.length).readDoubleBE())
868
+ return positions.map(p => [p, store.getValue(p, length).readDoubleBE(0)])
869
+ .sort((a, b) => a[1] - b[1]).map(([p]) => p)
874
870
 
875
871
  case typeMap.string:
876
-
877
- return record.sort((a, b) => store.getValue(a, struct.length).compare(store.getValue(b, struct.length)))
872
+ return positions.map(p => [p, store.getValue(p, length)])
873
+ .sort((a, b) => a[1].compare(b[1])).map(([p]) => p)
878
874
  }
879
875
  }
880
876
 
881
877
  const updateIndex = function (name, set) {
882
878
 
879
+ // 开启/关闭指定列索引:重建或清理索引映射
883
880
  const struct = this.curr.struct.base[name]
884
881
  const index = this.curr.record.index[name]
885
882
 
@@ -918,6 +915,11 @@ const commentRemark = (remark = '') => remark.replace(/\n/g, '\n\t * ')
918
915
 
919
916
  function submit() {
920
917
 
918
+ // 提交:
919
+ // 1) 清理更新集合的运行时记录
920
+ // 2) 写入结构/记录(加密)并关闭旧/新存储句柄
921
+ // 3) 生成类型声明与访问层代码
922
+ // 4) 将旧版本数据迁移到新版本(当集合结构有更新)
921
923
  for (const name of this.updateSet.values()) {
922
924
 
923
925
  this.curr.record.idle[name] = []
@@ -986,6 +988,7 @@ export default storage
986
988
 
987
989
  const initDir = (name, dir) => {
988
990
 
991
+ // 初始化版本目录:创建 index.mjs/index/data/buffer 与 Storage 实例
989
992
  const existsDir = existsSync(name) && statSync(name).isDirectory()
990
993
 
991
994
  if (!existsDir) {
@@ -1042,6 +1045,7 @@ export default new class extends Store {
1042
1045
 
1043
1046
  const copyDir = (src, dest) => {
1044
1047
 
1048
+ // 复制目录(用于版本演进的目录克隆)
1045
1049
  mkdirSync(dest)
1046
1050
 
1047
1051
  for (const name of readdirSync(src)) {
@@ -1125,6 +1129,7 @@ const gen = {
1125
1129
 
1126
1130
  const clipStatic = (name, [key, value]) => {
1127
1131
 
1132
+ // 生成实体创建阶段的默认值赋值代码(含步进值与特殊时间默认)
1128
1133
  if (!Reflect.has(value, 'value')) return ''
1129
1134
 
1130
1135
  let valueStr = ''
@@ -1166,6 +1171,7 @@ const clipStatic = (name, [key, value]) => {
1166
1171
 
1167
1172
  const clipIndex = (name, key, k, v) => {
1168
1173
 
1174
+ // 生成字段 setter 写入语句:区分索引列与普通列的写入方式
1169
1175
  let main = '0'
1170
1176
 
1171
1177
  if (v.type === typeMap.int) {
@@ -1215,6 +1221,7 @@ const sort2 = ([a], [b]) => a.localeCompare(b)
1215
1221
 
1216
1222
  const clipEntity = ([key, value]) => {
1217
1223
 
1224
+ // 生成实体类:包含静态 create 默认值、字段属性访问器与自动更新时间
1218
1225
  const update = Object.entries(value).find(([_, v]) => v.type === typeMap.time && v.value === TimeAttr.update)
1219
1226
 
1220
1227
  const name = key[0].toUpperCase() + key.substring(1)
@@ -1253,6 +1260,7 @@ ${update ? `
1253
1260
 
1254
1261
  const clipEntities = ([key, value]) => {
1255
1262
 
1263
+ // 生成集合代理类:屏蔽内部方法、提供索引查询方法
1256
1264
  const name = key[0].toUpperCase() + key.substring(1)
1257
1265
 
1258
1266
  return `
@@ -1438,6 +1446,10 @@ export default function upgrade(name, {
1438
1446
  store,
1439
1447
  } = {}) {
1440
1448
 
1449
+ // 升级流程:
1450
+ // - 计算最新版本 v,克隆 v 到新版本目录
1451
+ // - 打开旧/新 Storage(支持密码切换),准备代码生成路径
1452
+ // - 返回结构变更操作器,最终由 submit() 完成落盘与代码生成
1441
1453
  const dir = name[name.length - 1] === '/' ? name : name + '/'
1442
1454
 
1443
1455
  if (!existsSync(dir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k3000/store",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "storage",
5
5
  "main": "generator.mjs",
6
6
  "scripts": {
@@ -0,0 +1 @@
1
+ abc