@cicctencent/json-orm 0.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/README.md +154 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +66 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/json-backend-fs.d.ts +26 -0
- package/dist/cjs/json-backend-fs.d.ts.map +1 -0
- package/dist/cjs/json-backend-fs.js +278 -0
- package/dist/cjs/json-backend-fs.js.map +1 -0
- package/dist/cjs/json-collection.d.ts +70 -0
- package/dist/cjs/json-collection.d.ts.map +1 -0
- package/dist/cjs/json-collection.js +385 -0
- package/dist/cjs/json-collection.js.map +1 -0
- package/dist/cjs/json-config.d.ts +87 -0
- package/dist/cjs/json-config.d.ts.map +1 -0
- package/dist/cjs/json-config.js +77 -0
- package/dist/cjs/json-config.js.map +1 -0
- package/dist/cjs/json-datasource.d.ts +35 -0
- package/dist/cjs/json-datasource.d.ts.map +1 -0
- package/dist/cjs/json-datasource.js +131 -0
- package/dist/cjs/json-datasource.js.map +1 -0
- package/dist/cjs/json-entity-meta.d.ts +42 -0
- package/dist/cjs/json-entity-meta.d.ts.map +1 -0
- package/dist/cjs/json-entity-meta.js +128 -0
- package/dist/cjs/json-entity-meta.js.map +1 -0
- package/dist/cjs/json-query-builder.d.ts +83 -0
- package/dist/cjs/json-query-builder.d.ts.map +1 -0
- package/dist/cjs/json-query-builder.js +477 -0
- package/dist/cjs/json-query-builder.js.map +1 -0
- package/dist/cjs/json-repository.d.ts +59 -0
- package/dist/cjs/json-repository.d.ts.map +1 -0
- package/dist/cjs/json-repository.js +309 -0
- package/dist/cjs/json-repository.js.map +1 -0
- package/dist/cjs/json-sql-expr.d.ts +107 -0
- package/dist/cjs/json-sql-expr.d.ts.map +1 -0
- package/dist/cjs/json-sql-expr.js +857 -0
- package/dist/cjs/json-sql-expr.js.map +1 -0
- package/dist/cjs/json-sql-select.d.ts +9 -0
- package/dist/cjs/json-sql-select.d.ts.map +1 -0
- package/dist/cjs/json-sql-select.js +434 -0
- package/dist/cjs/json-sql-select.js.map +1 -0
- package/dist/cjs/json-sql-shared.d.ts +16 -0
- package/dist/cjs/json-sql-shared.d.ts.map +1 -0
- package/dist/cjs/json-sql-shared.js +12 -0
- package/dist/cjs/json-sql-shared.js.map +1 -0
- package/dist/cjs/json-sql.d.ts +8 -0
- package/dist/cjs/json-sql.d.ts.map +1 -0
- package/dist/cjs/json-sql.js +322 -0
- package/dist/cjs/json-sql.js.map +1 -0
- package/dist/cjs/json-where.d.ts +54 -0
- package/dist/cjs/json-where.d.ts.map +1 -0
- package/dist/cjs/json-where.js +352 -0
- package/dist/cjs/json-where.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/index.d.ts +25 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +26 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/json-backend-fs.d.ts +26 -0
- package/dist/esm/json-backend-fs.d.ts.map +1 -0
- package/dist/esm/json-backend-fs.js +241 -0
- package/dist/esm/json-backend-fs.js.map +1 -0
- package/dist/esm/json-collection.d.ts +70 -0
- package/dist/esm/json-collection.d.ts.map +1 -0
- package/dist/esm/json-collection.js +346 -0
- package/dist/esm/json-collection.js.map +1 -0
- package/dist/esm/json-config.d.ts +87 -0
- package/dist/esm/json-config.d.ts.map +1 -0
- package/dist/esm/json-config.js +69 -0
- package/dist/esm/json-config.js.map +1 -0
- package/dist/esm/json-datasource.d.ts +35 -0
- package/dist/esm/json-datasource.d.ts.map +1 -0
- package/dist/esm/json-datasource.js +123 -0
- package/dist/esm/json-datasource.js.map +1 -0
- package/dist/esm/json-entity-meta.d.ts +42 -0
- package/dist/esm/json-entity-meta.d.ts.map +1 -0
- package/dist/esm/json-entity-meta.js +120 -0
- package/dist/esm/json-entity-meta.js.map +1 -0
- package/dist/esm/json-query-builder.d.ts +83 -0
- package/dist/esm/json-query-builder.d.ts.map +1 -0
- package/dist/esm/json-query-builder.js +473 -0
- package/dist/esm/json-query-builder.js.map +1 -0
- package/dist/esm/json-repository.d.ts +59 -0
- package/dist/esm/json-repository.d.ts.map +1 -0
- package/dist/esm/json-repository.js +304 -0
- package/dist/esm/json-repository.js.map +1 -0
- package/dist/esm/json-sql-expr.d.ts +107 -0
- package/dist/esm/json-sql-expr.d.ts.map +1 -0
- package/dist/esm/json-sql-expr.js +847 -0
- package/dist/esm/json-sql-expr.js.map +1 -0
- package/dist/esm/json-sql-select.d.ts +9 -0
- package/dist/esm/json-sql-select.d.ts.map +1 -0
- package/dist/esm/json-sql-select.js +429 -0
- package/dist/esm/json-sql-select.js.map +1 -0
- package/dist/esm/json-sql-shared.d.ts +16 -0
- package/dist/esm/json-sql-shared.d.ts.map +1 -0
- package/dist/esm/json-sql-shared.js +9 -0
- package/dist/esm/json-sql-shared.js.map +1 -0
- package/dist/esm/json-sql.d.ts +8 -0
- package/dist/esm/json-sql.d.ts.map +1 -0
- package/dist/esm/json-sql.js +319 -0
- package/dist/esm/json-sql.js.map +1 -0
- package/dist/esm/json-where.d.ts +54 -0
- package/dist/esm/json-where.d.ts.map +1 -0
- package/dist/esm/json-where.js +342 -0
- package/dist/esm/json-where.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/examples/01-basic-entity.ts +89 -0
- package/examples/02-query-builder.ts +114 -0
- package/examples/03-raw-sql.ts +105 -0
- package/examples/04-non-entity-table.ts +70 -0
- package/examples/05-custom-backend.ts +145 -0
- package/examples/06-find-operators.ts +75 -0
- package/examples/README.md +73 -0
- package/examples/tsconfig.json +20 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @cicctencent/json-orm
|
|
2
|
+
|
|
3
|
+
TypeORM 兼容的 JSON 文件存储引擎(**框架无关、业务解耦**的公共包):以 JSON 文件承载
|
|
4
|
+
`data/<表名>/<主键>.json` 的落盘存储,提供 TypeORM `DataSource` / `Repository` 子集、
|
|
5
|
+
`QueryBuilder` 与内存 SQL 求值器,作为无数据库场景下的透明持久化回退层。
|
|
6
|
+
|
|
7
|
+
> 本包为独立公共包,不依赖 `@midwayjs/*`、`@cicctencent/*` 或任何应用内部模块;
|
|
8
|
+
> 承载其运行所需的目录解析、非实体表注册等应用决策,由接入方以配置注入。
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
作为 pnpm workspace 包接入(推荐):
|
|
13
|
+
|
|
14
|
+
```jsonc
|
|
15
|
+
// app/work-space/package.json
|
|
16
|
+
{
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@cicctencent/json-orm": "workspace:*"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
运行时依赖(peerDependencies,宿主需已安装):`typeorm@^0.3.31`、`reflect-metadata@^0.2.2`。
|
|
28
|
+
|
|
29
|
+
## 配置注入(使用前必做)
|
|
30
|
+
|
|
31
|
+
引擎不内置任何存储根目录或非实体表注册策略;**首次访问前**必须调用
|
|
32
|
+
`configureJsonStorage()` 注入:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import {
|
|
36
|
+
configureJsonStorage,
|
|
37
|
+
getJsonDataSource,
|
|
38
|
+
createJsonRepository,
|
|
39
|
+
sqlTableFor,
|
|
40
|
+
FsStorageBackend,
|
|
41
|
+
type JsonStorageBackend,
|
|
42
|
+
} from '@cicctencent/json-orm';
|
|
43
|
+
|
|
44
|
+
// 存储根目录(如 <data>/db)与非实体表注册(仅经原始 SQL 访问、无 @Entity 定义的表)
|
|
45
|
+
configureJsonStorage({
|
|
46
|
+
dbDir: '/path/to/data/db',
|
|
47
|
+
nonEntityTables: {
|
|
48
|
+
some_raw_table: { primaryColumn: 'id' },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- `dbDir` 未注入而触发集合落盘时,引擎抛出明确错误并指引先调用
|
|
54
|
+
`configureJsonStorage({ dbDir })`,**不会**静默回退到隐式目录。
|
|
55
|
+
- `nonEntityTables` 可选;注入后 `isNonEntityTable` / `getNonEntityPrimaryColumn` /
|
|
56
|
+
`getNonEntityUniqueColumns` / `listNonEntityTables` 按注入数据返回,原始 SQL 的幂等
|
|
57
|
+
upsert 按注入主键/唯一列判重。
|
|
58
|
+
- 配置支持分次部分注入、后写覆盖;测试可调用 `resetJsonStorageForTest()` 隔离。
|
|
59
|
+
|
|
60
|
+
## 存储后端(可注入、异步)
|
|
61
|
+
|
|
62
|
+
引擎全部持久化 I/O 经可注入的 `JsonStorageBackend`(异步接口)完成,**不直接依赖 `fs`**:
|
|
63
|
+
`readFile` / `writeFileAtomic`(内部 tmp+rename 原子写)/ `readDir` / `deleteFile` /
|
|
64
|
+
`mkdir` / `stat`(跨进程缓存失效检测)/ `withLock`(跨进程临界区互斥)/ `nextId`
|
|
65
|
+
(后端级原子自增)/ `ensureSeqAtLeast`(显式大主键抬升序列器)。
|
|
66
|
+
|
|
67
|
+
默认后端为 `FsStorageBackend`(`fs/promises` 实现,等价于改造前的本地 JSON 落盘)。
|
|
68
|
+
注入方式:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// 方式 A:仅注入 dbDir(自动构造 FsStorageBackend,向后兼容)
|
|
72
|
+
configureJsonStorage({ dbDir: '/path/to/data/db' });
|
|
73
|
+
|
|
74
|
+
// 方式 B:注入自定义后端(如对象存储 COS/S3),优先于 dbDir
|
|
75
|
+
const cosBackend: JsonStorageBackend = createCosBackend(/* ... */);
|
|
76
|
+
configureJsonStorage({ backend: cosBackend });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
所有数据访问方法(`find` / `save` / `update` / `delete` / `upsert` / `query`、
|
|
80
|
+
原生 SQL 的 `SELECT`/`INSERT`/`UPDATE`/`DELETE` 求值、集合读写、序列分配)**全程异步**,
|
|
81
|
+
数据路径无阻塞式同步 I/O。调用方对接 `DataSource` / `Repository` / `EntityManager`
|
|
82
|
+
公共 API 时无需改动(这些方法本就返回 `Promise`)。
|
|
83
|
+
|
|
84
|
+
### 多进程安全
|
|
85
|
+
|
|
86
|
+
- **序列器唯一**:`nextId()` 的分配在后端 `withLock` 临界区内完成读-改-写,跨进程严格
|
|
87
|
+
递增且唯一;显式写入大于当前序列器的主键时经 `ensureSeqAtLeast` 抬升,避免后续撞车。
|
|
88
|
+
- **跨进程缓存失效**:每个进程在内存保留整表快照以支撑 SQL 扫描性能;读取前经
|
|
89
|
+
`backend.stat(表目录)` 检测变更(**`etag` 优先**,两侧均无 etag 时以 `mtimeMs` 增大
|
|
90
|
+
为准),若他进程已变更则重载。节流由 `reloadTtlMs`
|
|
91
|
+
(`configureJsonStorage({ reloadTtlMs })`,默认 `0` 即每次访问校验)调控;`>0` 时
|
|
92
|
+
在 TTL 窗口内可复用缓存(权衡:可能短暂读到他进程写入前的快照)。
|
|
93
|
+
- **写路径强制校验**:`write` / `delete` / `nextId` 不受 `reloadTtlMs` 节流,写入前
|
|
94
|
+
必定先做一次失效校验,保证写入后记录的水位与内存快照一致,不掩蔽他进程写入。
|
|
95
|
+
- 单行写仍用 tmp+rename 文件级原子(临时文件名含 pid 与随机量,并发写同一行不产生
|
|
96
|
+
交错内容,也不抛 `ENOENT`),不引入整表写锁。
|
|
97
|
+
- **锁归属校验**:`withLock` 以令牌标识持有者,释放前校验令牌;锁被判定为孤儿并由
|
|
98
|
+
他人取得后,原持有者不会误删他人锁(避免两进程同时进入临界区)。
|
|
99
|
+
|
|
100
|
+
## 公开 API 概览
|
|
101
|
+
|
|
102
|
+
| 类别 | 导出 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| 配置 | `configureJsonStorage` / `resetJsonStorageForTest` / `JsonStorageOptions` / `NonEntityTableInfo` / `JsonStorageBackend` / `JsonStorageFileStat` / `FsStorageBackend` |
|
|
105
|
+
| 数据源 | `JsonDataSource` / `JsonEntityManager` / `getJsonDataSource` / `resetJsonDataSourceForTest` / `sqlTableFor` |
|
|
106
|
+
| 仓库 | `JsonRepository` / `createJsonRepository` / `FindOptionsLoose` |
|
|
107
|
+
| 查询/SQL | `JsonQueryBuilder` / `executeRawQuery` / `SqlTable` / `GetSqlTable` / `toParams` |
|
|
108
|
+
| 集合 | `JsonCollection` / `getCollection` / `resetCollectionsForTest` / `JsonRow` |
|
|
109
|
+
| 实体元数据 | `resolveEntityMeta` / `resolveEntityMetaByTable` / `JsonEntityMeta` / `JsonEntityColumn` 及非实体表四函数 |
|
|
110
|
+
| SQL 词法/表达式 | `tokenizeSql` / `TokenCursor` / `matchesWhere` / `looseEqual` / `compareValues` / `likeMatch` / `parseExpr` / `evalExpr` / `truthy` / `hasAggregate` / `AGG_FUNCS` / `Expr` / `EvalContext` / `AliasInfo` / `jsonPathGet` / `jsonUnquote` / `toDate` / `mysqlDatetime` |
|
|
111
|
+
|
|
112
|
+
完整类型与行为见本仓库 `openspec/specs/json-orm-storage-engine/spec.md`。
|
|
113
|
+
|
|
114
|
+
> **写操作返回**:`UPDATE` / `DELETE` 返回 `{ affected, raw }`;原生 SQL 的 `UPDATE`/`DELETE`
|
|
115
|
+
> 返回 OkPacket 形态,其中 `affectedRows` 为**匹配行数**、`changedRows` 为**实际变更行数**
|
|
116
|
+
> (与 MySQL `info` 的 Rows matched / Changed 对应)。据此判断「是否真的改到了值」时
|
|
117
|
+
> 应使用 `changedRows`。
|
|
118
|
+
>
|
|
119
|
+
> **DDL 垫片**:`createQueryRunner()` 仅用于 schema 探测,`hasTable` 恒返回 `true`
|
|
120
|
+
> (即「断言表存在」)。因此 `if (!(await hasTable(t))) await createTable(t)` 这类
|
|
121
|
+
> 条件建表代码**不会执行建表**——JSON 模式本就无需建表,但调用方需知悉该差异。
|
|
122
|
+
> `transaction` 系列方法均为 no-op,不提供跨表/跨语句原子性与回滚。
|
|
123
|
+
|
|
124
|
+
## 示例
|
|
125
|
+
|
|
126
|
+
`examples/` 目录提供了可直接运行的完整示例(需先 `pnpm build` 产出 `dist`,再用
|
|
127
|
+
`npx tsx examples/<file>.ts` 运行),覆盖最常见接入场景:
|
|
128
|
+
|
|
129
|
+
| 示例 | 场景 |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `01-basic-entity.ts` | 定义实体 + Repository 的 save/find/findOneBy/findAndCount/update/delete |
|
|
132
|
+
| `02-query-builder.ts` | QueryBuilder:字符串/对象条件、LEFT JOIN、GROUP BY 聚合、分页计数 |
|
|
133
|
+
| `03-raw-sql.ts` | `query`:SELECT/INSERT/UPDATE/DELETE、ODKU upsert、TRUNCATE;位置/命名参数 |
|
|
134
|
+
| `04-non-entity-table.ts` | 非实体表(仅原始 SQL 访问)+ `nonEntityTables` 注册 |
|
|
135
|
+
| `05-custom-backend.ts` | 实现 `JsonStorageBackend` 注入自定义(内存)后端 |
|
|
136
|
+
| `06-find-operators.ts` | Repository `where` 的 FindOperator:In/Like/Not/IsNull/MoreThan/Between |
|
|
137
|
+
|
|
138
|
+
关键约定(两套命名空间、合并写语义、多进程安全等)见 `examples/README.md`。
|
|
139
|
+
|
|
140
|
+
## 构建与测试
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pnpm build # 产出 dist/esm(NodeNext)与 dist/cjs,并经 fixup-cjs 修复 .d.ts
|
|
144
|
+
pnpm typecheck # 仅类型检查(ESM 配置)
|
|
145
|
+
pnpm test # vitest 运行 src/__tests__ 下引擎测试(含 109 用例)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
> **接入方注意**:本包以 `workspace:*` 形式被应用消费,应用生产构建/本地开发前
|
|
149
|
+
> 须先产出本包 `dist`(即先执行 `pnpm --filter @cicctencent/json-orm build`)。
|
|
150
|
+
> 应用侧 `bin/dev.sh` 的共享包构建段已注释,沿用「本地预构建 dist」的既有约定。
|
|
151
|
+
|
|
152
|
+
## 许可证
|
|
153
|
+
|
|
154
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @cicctencent/json-orm 公开 API
|
|
3
|
+
*
|
|
4
|
+
* TypeORM 兼容的 JSON 文件存储引擎(框架无关、业务解耦):
|
|
5
|
+
* 使用前须先经 `configureJsonStorage()` 注入存储根目录(与非实体表注册),
|
|
6
|
+
* 详见 README。
|
|
7
|
+
*/
|
|
8
|
+
export { configureJsonStorage, resetJsonStorageForTest } from './json-config.js';
|
|
9
|
+
export type { JsonStorageOptions, NonEntityTableInfo, JsonStorageBackend, JsonStorageFileStat } from './json-config.js';
|
|
10
|
+
export { FsStorageBackend } from './json-backend-fs.js';
|
|
11
|
+
export { JsonDataSource, JsonEntityManager, getJsonDataSource, resetJsonDataSourceForTest, sqlTableFor, } from './json-datasource.js';
|
|
12
|
+
export { JsonRepository, createJsonRepository } from './json-repository.js';
|
|
13
|
+
export type { FindOptionsLoose } from './json-repository.js';
|
|
14
|
+
export { JsonQueryBuilder } from './json-query-builder.js';
|
|
15
|
+
export { executeRawQuery } from './json-sql.js';
|
|
16
|
+
export { toParams } from './json-sql-shared.js';
|
|
17
|
+
export type { SqlTable, GetSqlTable } from './json-sql-shared.js';
|
|
18
|
+
export { JsonCollection, getCollection, resetCollectionsForTest } from './json-collection.js';
|
|
19
|
+
export type { JsonRow } from './json-collection.js';
|
|
20
|
+
export { resolveEntityMeta, resolveEntityMetaByTable, isNonEntityTable, getNonEntityPrimaryColumn, getNonEntityUniqueColumns, listNonEntityTables, } from './json-entity-meta.js';
|
|
21
|
+
export type { JsonEntityMeta, JsonEntityColumn } from './json-entity-meta.js';
|
|
22
|
+
export { tokenizeSql, TokenCursor, matchesWhere, looseEqual, compareValues, likeMatch, } from './json-where.js';
|
|
23
|
+
export { parseExpr, evalExpr, truthy, hasAggregate, AGG_FUNCS, jsonPathGet, jsonUnquote, toDate, mysqlDatetime, } from './json-sql-expr.js';
|
|
24
|
+
export type { Expr, EvalContext, AliasInfo } from './json-sql-expr.js';
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AACjF,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACxH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAGxD,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,iBAAiB,EACjB,0BAA0B,EAC1B,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,EAChB,yBAAyB,EACzB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9E,OAAO,EACL,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,aAAa,EACb,SAAS,GACV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,SAAS,EACT,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,SAAS,EACT,WAAW,EACX,WAAW,EACX,MAAM,EACN,aAAa,GACd,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @cicctencent/json-orm 公开 API
|
|
4
|
+
*
|
|
5
|
+
* TypeORM 兼容的 JSON 文件存储引擎(框架无关、业务解耦):
|
|
6
|
+
* 使用前须先经 `configureJsonStorage()` 注入存储根目录(与非实体表注册),
|
|
7
|
+
* 详见 README。
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.mysqlDatetime = exports.toDate = exports.jsonUnquote = exports.jsonPathGet = exports.AGG_FUNCS = exports.hasAggregate = exports.truthy = exports.evalExpr = exports.parseExpr = exports.likeMatch = exports.compareValues = exports.looseEqual = exports.matchesWhere = exports.TokenCursor = exports.tokenizeSql = exports.listNonEntityTables = exports.getNonEntityUniqueColumns = exports.getNonEntityPrimaryColumn = exports.isNonEntityTable = exports.resolveEntityMetaByTable = exports.resolveEntityMeta = exports.resetCollectionsForTest = exports.getCollection = exports.JsonCollection = exports.toParams = exports.executeRawQuery = exports.JsonQueryBuilder = exports.createJsonRepository = exports.JsonRepository = exports.sqlTableFor = exports.resetJsonDataSourceForTest = exports.getJsonDataSource = exports.JsonEntityManager = exports.JsonDataSource = exports.FsStorageBackend = exports.resetJsonStorageForTest = exports.configureJsonStorage = void 0;
|
|
11
|
+
// 配置注入
|
|
12
|
+
var json_config_ts_1 = require("./json-config.js");
|
|
13
|
+
Object.defineProperty(exports, "configureJsonStorage", { enumerable: true, get: function () { return json_config_ts_1.configureJsonStorage; } });
|
|
14
|
+
Object.defineProperty(exports, "resetJsonStorageForTest", { enumerable: true, get: function () { return json_config_ts_1.resetJsonStorageForTest; } });
|
|
15
|
+
var json_backend_fs_ts_1 = require("./json-backend-fs.js");
|
|
16
|
+
Object.defineProperty(exports, "FsStorageBackend", { enumerable: true, get: function () { return json_backend_fs_ts_1.FsStorageBackend; } });
|
|
17
|
+
// 数据源
|
|
18
|
+
var json_datasource_ts_1 = require("./json-datasource.js");
|
|
19
|
+
Object.defineProperty(exports, "JsonDataSource", { enumerable: true, get: function () { return json_datasource_ts_1.JsonDataSource; } });
|
|
20
|
+
Object.defineProperty(exports, "JsonEntityManager", { enumerable: true, get: function () { return json_datasource_ts_1.JsonEntityManager; } });
|
|
21
|
+
Object.defineProperty(exports, "getJsonDataSource", { enumerable: true, get: function () { return json_datasource_ts_1.getJsonDataSource; } });
|
|
22
|
+
Object.defineProperty(exports, "resetJsonDataSourceForTest", { enumerable: true, get: function () { return json_datasource_ts_1.resetJsonDataSourceForTest; } });
|
|
23
|
+
Object.defineProperty(exports, "sqlTableFor", { enumerable: true, get: function () { return json_datasource_ts_1.sqlTableFor; } });
|
|
24
|
+
// 仓库
|
|
25
|
+
var json_repository_ts_1 = require("./json-repository.js");
|
|
26
|
+
Object.defineProperty(exports, "JsonRepository", { enumerable: true, get: function () { return json_repository_ts_1.JsonRepository; } });
|
|
27
|
+
Object.defineProperty(exports, "createJsonRepository", { enumerable: true, get: function () { return json_repository_ts_1.createJsonRepository; } });
|
|
28
|
+
// 查询构造与原始 SQL
|
|
29
|
+
var json_query_builder_ts_1 = require("./json-query-builder.js");
|
|
30
|
+
Object.defineProperty(exports, "JsonQueryBuilder", { enumerable: true, get: function () { return json_query_builder_ts_1.JsonQueryBuilder; } });
|
|
31
|
+
var json_sql_ts_1 = require("./json-sql.js");
|
|
32
|
+
Object.defineProperty(exports, "executeRawQuery", { enumerable: true, get: function () { return json_sql_ts_1.executeRawQuery; } });
|
|
33
|
+
var json_sql_shared_ts_1 = require("./json-sql-shared.js");
|
|
34
|
+
Object.defineProperty(exports, "toParams", { enumerable: true, get: function () { return json_sql_shared_ts_1.toParams; } });
|
|
35
|
+
// 集合
|
|
36
|
+
var json_collection_ts_1 = require("./json-collection.js");
|
|
37
|
+
Object.defineProperty(exports, "JsonCollection", { enumerable: true, get: function () { return json_collection_ts_1.JsonCollection; } });
|
|
38
|
+
Object.defineProperty(exports, "getCollection", { enumerable: true, get: function () { return json_collection_ts_1.getCollection; } });
|
|
39
|
+
Object.defineProperty(exports, "resetCollectionsForTest", { enumerable: true, get: function () { return json_collection_ts_1.resetCollectionsForTest; } });
|
|
40
|
+
// 实体元数据
|
|
41
|
+
var json_entity_meta_ts_1 = require("./json-entity-meta.js");
|
|
42
|
+
Object.defineProperty(exports, "resolveEntityMeta", { enumerable: true, get: function () { return json_entity_meta_ts_1.resolveEntityMeta; } });
|
|
43
|
+
Object.defineProperty(exports, "resolveEntityMetaByTable", { enumerable: true, get: function () { return json_entity_meta_ts_1.resolveEntityMetaByTable; } });
|
|
44
|
+
Object.defineProperty(exports, "isNonEntityTable", { enumerable: true, get: function () { return json_entity_meta_ts_1.isNonEntityTable; } });
|
|
45
|
+
Object.defineProperty(exports, "getNonEntityPrimaryColumn", { enumerable: true, get: function () { return json_entity_meta_ts_1.getNonEntityPrimaryColumn; } });
|
|
46
|
+
Object.defineProperty(exports, "getNonEntityUniqueColumns", { enumerable: true, get: function () { return json_entity_meta_ts_1.getNonEntityUniqueColumns; } });
|
|
47
|
+
Object.defineProperty(exports, "listNonEntityTables", { enumerable: true, get: function () { return json_entity_meta_ts_1.listNonEntityTables; } });
|
|
48
|
+
// SQL 词法与表达式工具
|
|
49
|
+
var json_where_ts_1 = require("./json-where.js");
|
|
50
|
+
Object.defineProperty(exports, "tokenizeSql", { enumerable: true, get: function () { return json_where_ts_1.tokenizeSql; } });
|
|
51
|
+
Object.defineProperty(exports, "TokenCursor", { enumerable: true, get: function () { return json_where_ts_1.TokenCursor; } });
|
|
52
|
+
Object.defineProperty(exports, "matchesWhere", { enumerable: true, get: function () { return json_where_ts_1.matchesWhere; } });
|
|
53
|
+
Object.defineProperty(exports, "looseEqual", { enumerable: true, get: function () { return json_where_ts_1.looseEqual; } });
|
|
54
|
+
Object.defineProperty(exports, "compareValues", { enumerable: true, get: function () { return json_where_ts_1.compareValues; } });
|
|
55
|
+
Object.defineProperty(exports, "likeMatch", { enumerable: true, get: function () { return json_where_ts_1.likeMatch; } });
|
|
56
|
+
var json_sql_expr_ts_1 = require("./json-sql-expr.js");
|
|
57
|
+
Object.defineProperty(exports, "parseExpr", { enumerable: true, get: function () { return json_sql_expr_ts_1.parseExpr; } });
|
|
58
|
+
Object.defineProperty(exports, "evalExpr", { enumerable: true, get: function () { return json_sql_expr_ts_1.evalExpr; } });
|
|
59
|
+
Object.defineProperty(exports, "truthy", { enumerable: true, get: function () { return json_sql_expr_ts_1.truthy; } });
|
|
60
|
+
Object.defineProperty(exports, "hasAggregate", { enumerable: true, get: function () { return json_sql_expr_ts_1.hasAggregate; } });
|
|
61
|
+
Object.defineProperty(exports, "AGG_FUNCS", { enumerable: true, get: function () { return json_sql_expr_ts_1.AGG_FUNCS; } });
|
|
62
|
+
Object.defineProperty(exports, "jsonPathGet", { enumerable: true, get: function () { return json_sql_expr_ts_1.jsonPathGet; } });
|
|
63
|
+
Object.defineProperty(exports, "jsonUnquote", { enumerable: true, get: function () { return json_sql_expr_ts_1.jsonUnquote; } });
|
|
64
|
+
Object.defineProperty(exports, "toDate", { enumerable: true, get: function () { return json_sql_expr_ts_1.toDate; } });
|
|
65
|
+
Object.defineProperty(exports, "mysqlDatetime", { enumerable: true, get: function () { return json_sql_expr_ts_1.mysqlDatetime; } });
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAEH,OAAO;AACP,mDAAiF;AAAxE,sHAAA,oBAAoB,OAAA;AAAE,yHAAA,uBAAuB,OAAA;AAEtD,2DAAwD;AAA/C,sHAAA,gBAAgB,OAAA;AAEzB,MAAM;AACN,2DAM8B;AAL5B,oHAAA,cAAc,OAAA;AACd,uHAAA,iBAAiB,OAAA;AACjB,uHAAA,iBAAiB,OAAA;AACjB,gIAAA,0BAA0B,OAAA;AAC1B,iHAAA,WAAW,OAAA;AAGb,KAAK;AACL,2DAA4E;AAAnE,oHAAA,cAAc,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAG7C,cAAc;AACd,iEAA2D;AAAlD,yHAAA,gBAAgB,OAAA;AACzB,6CAAgD;AAAvC,8GAAA,eAAe,OAAA;AACxB,2DAAgD;AAAvC,8GAAA,QAAQ,OAAA;AAGjB,KAAK;AACL,2DAA8F;AAArF,oHAAA,cAAc,OAAA;AAAE,mHAAA,aAAa,OAAA;AAAE,6HAAA,uBAAuB,OAAA;AAG/D,QAAQ;AACR,6DAO+B;AAN7B,wHAAA,iBAAiB,OAAA;AACjB,+HAAA,wBAAwB,OAAA;AACxB,uHAAA,gBAAgB,OAAA;AAChB,gIAAA,yBAAyB,OAAA;AACzB,gIAAA,yBAAyB,OAAA;AACzB,0HAAA,mBAAmB,OAAA;AAIrB,eAAe;AACf,iDAOyB;AANvB,4GAAA,WAAW,OAAA;AACX,4GAAA,WAAW,OAAA;AACX,6GAAA,YAAY,OAAA;AACZ,2GAAA,UAAU,OAAA;AACV,8GAAA,aAAa,OAAA;AACb,0GAAA,SAAS,OAAA;AAEX,uDAU4B;AAT1B,6GAAA,SAAS,OAAA;AACT,4GAAA,QAAQ,OAAA;AACR,0GAAA,MAAM,OAAA;AACN,gHAAA,YAAY,OAAA;AACZ,6GAAA,SAAS,OAAA;AACT,+GAAA,WAAW,OAAA;AACX,+GAAA,WAAW,OAAA;AACX,0GAAA,MAAM,OAAA;AACN,iHAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { JsonStorageBackend, JsonStorageFileStat } from './json-config.js';
|
|
2
|
+
export interface FsStorageBackendOptions {
|
|
3
|
+
/** 获取锁的总等待超时(ms),超时抛错;默认 5000 */
|
|
4
|
+
lockTimeoutMs?: number;
|
|
5
|
+
/** 锁文件 mtime 超过该阈值(ms)视为孤儿锁(持有者崩溃),强制回收;默认 30000 */
|
|
6
|
+
lockStaleMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class FsStorageBackend implements JsonStorageBackend {
|
|
9
|
+
private readonly rootDir;
|
|
10
|
+
private readonly lockTimeoutMs;
|
|
11
|
+
private readonly lockStaleMs;
|
|
12
|
+
constructor(rootDir: string, options?: FsStorageBackendOptions);
|
|
13
|
+
private resolve;
|
|
14
|
+
readFile(relPath: string): Promise<string | null>;
|
|
15
|
+
writeFileAtomic(relPath: string, data: string): Promise<void>;
|
|
16
|
+
readDir(relPath: string): Promise<string[]>;
|
|
17
|
+
deleteFile(relPath: string): Promise<void>;
|
|
18
|
+
mkdir(relPath: string): Promise<void>;
|
|
19
|
+
stat(relPath: string): Promise<JsonStorageFileStat | null>;
|
|
20
|
+
withLock<T>(relPath: string, fn: () => Promise<T>): Promise<T>;
|
|
21
|
+
/** 释放锁:仅当锁内容中的令牌与本进程一致时删除(否则锁已被回收并被他人持有) */
|
|
22
|
+
private releaseLock;
|
|
23
|
+
nextId(relPath: string, key: string): Promise<number>;
|
|
24
|
+
ensureSeqAtLeast(relPath: string, key: string, value: number): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=json-backend-fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-backend-fs.d.ts","sourceRoot":"","sources":["../../src/json-backend-fs.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEhF,MAAM,WAAW,uBAAuB;IACtC,iCAAiC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgBD,qBAAa,gBAAiB,YAAW,kBAAkB;IAI7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAER,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB;IAK/E,OAAO,CAAC,OAAO;IAOT,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASjD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B7D,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS3C,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB1C,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAU1D,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAsDpE,4CAA4C;YAC9B,WAAW;IAgBnB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBrD,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkBnF"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FsStorageBackend = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* 默认存储后端:本地 JSON 文件(fs/promises)
|
|
39
|
+
*
|
|
40
|
+
* 语义等同改造前的本地落盘:`<根>/<表>/<主键>.json`,tmp+rename 原子写;
|
|
41
|
+
* 序列器与锁文件位于 `<根>/_meta/<表>.json`(迁出表目录,避免序列器写入
|
|
42
|
+
* 刷表目录 mtime,多进程下触发对等进程整表重载的失效风暴);
|
|
43
|
+
* 跨进程互斥经 `.lock` 文件(`O_EXCL` 原子创建 + 退避重试 + 孤儿锁回收)。
|
|
44
|
+
* 无第三方依赖,跨平台。
|
|
45
|
+
*/
|
|
46
|
+
const fsp = __importStar(require("fs/promises"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const META_SUFFIX = '/_meta.json';
|
|
49
|
+
/**
|
|
50
|
+
* 元信息路径判定(与平台分隔符无关):`<表>/_meta.json` → 表名,否则 null。
|
|
51
|
+
* Windows 下 `path.join(表, '_meta.json')` 产出 `表\_meta.json`,仅按 POSIX 分隔符
|
|
52
|
+
* 判断会把序列器写回表目录,使「迁出表目录」的设计失效。
|
|
53
|
+
*/
|
|
54
|
+
function metaTableOf(relPath) {
|
|
55
|
+
const norm = relPath.replace(/\\/g, '/');
|
|
56
|
+
if (!norm.endsWith(META_SUFFIX))
|
|
57
|
+
return null;
|
|
58
|
+
const table = norm.slice(0, -META_SUFFIX.length);
|
|
59
|
+
return table && !table.includes('/') ? table : null;
|
|
60
|
+
}
|
|
61
|
+
class FsStorageBackend {
|
|
62
|
+
rootDir;
|
|
63
|
+
lockTimeoutMs;
|
|
64
|
+
lockStaleMs;
|
|
65
|
+
constructor(rootDir, options) {
|
|
66
|
+
this.rootDir = rootDir;
|
|
67
|
+
this.lockTimeoutMs = options?.lockTimeoutMs ?? 5000;
|
|
68
|
+
this.lockStaleMs = options?.lockStaleMs ?? 30000;
|
|
69
|
+
}
|
|
70
|
+
resolve(relPath) {
|
|
71
|
+
// 序列器/元信息迁出表目录:`<表>/_meta.json` → `<根>/_meta/<表>.json`
|
|
72
|
+
const table = metaTableOf(relPath);
|
|
73
|
+
if (table)
|
|
74
|
+
return path.join(this.rootDir, '_meta', `${table}.json`);
|
|
75
|
+
return path.join(this.rootDir, relPath);
|
|
76
|
+
}
|
|
77
|
+
async readFile(relPath) {
|
|
78
|
+
try {
|
|
79
|
+
return await fsp.readFile(this.resolve(relPath), 'utf8');
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err?.code === 'ENOENT')
|
|
83
|
+
return null;
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async writeFileAtomic(relPath, data) {
|
|
88
|
+
const file = this.resolve(relPath);
|
|
89
|
+
await fsp.mkdir(path.dirname(file), { recursive: true });
|
|
90
|
+
// 临时文件名含 pid + 随机量:并发写同一目标时各写独立 tmp,
|
|
91
|
+
// 避免内容交错、以及他人已 rename 走 tmp 后本进程 rename 抛 ENOENT
|
|
92
|
+
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
93
|
+
try {
|
|
94
|
+
await fsp.writeFile(tmp, data, 'utf8');
|
|
95
|
+
await fsp.rename(tmp, file);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
try {
|
|
99
|
+
await fsp.rm(tmp, { force: true });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* 清理自身临时文件失败不影响原始错误 */
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
if (metaTableOf(relPath) !== null) {
|
|
107
|
+
// 旧布局迁移:尽力清理 `<表>/_meta.json`;读路径不读旧位置,
|
|
108
|
+
// 序列连续性由集合层水位下限保证(清理失败无害,旧文件不再被读取)
|
|
109
|
+
try {
|
|
110
|
+
await fsp.rm(path.join(this.rootDir, relPath), { force: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* ignore */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async readDir(relPath) {
|
|
118
|
+
try {
|
|
119
|
+
return await fsp.readdir(this.resolve(relPath));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (err?.code === 'ENOENT')
|
|
123
|
+
return [];
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async deleteFile(relPath) {
|
|
128
|
+
try {
|
|
129
|
+
await fsp.rm(this.resolve(relPath), { force: true });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* 文件已不存在则忽略 */
|
|
133
|
+
}
|
|
134
|
+
if (metaTableOf(relPath) !== null) {
|
|
135
|
+
/* 布局迁移:一并清理遗留的 <表>/_meta.json(幂等) */
|
|
136
|
+
try {
|
|
137
|
+
await fsp.rm(path.join(this.rootDir, relPath), { force: true });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
/* 同上 */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async mkdir(relPath) {
|
|
145
|
+
await fsp.mkdir(this.resolve(relPath), { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
async stat(relPath) {
|
|
148
|
+
try {
|
|
149
|
+
const s = await fsp.stat(this.resolve(relPath));
|
|
150
|
+
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
if (err?.code === 'ENOENT')
|
|
154
|
+
return null;
|
|
155
|
+
throw err;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async withLock(relPath, fn) {
|
|
159
|
+
const lockFile = `${this.resolve(relPath)}.lock`;
|
|
160
|
+
await fsp.mkdir(path.dirname(lockFile), { recursive: true });
|
|
161
|
+
const start = Date.now();
|
|
162
|
+
// 归属令牌:释放前校验,避免删除「已被回收、现由他人持有」的锁
|
|
163
|
+
const token = `${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
164
|
+
for (;;) {
|
|
165
|
+
// 超时判定置于循环开头:孤儿回收重试与 stat 失败重试分支同样受约束,避免热自旋
|
|
166
|
+
if (Date.now() - start > this.lockTimeoutMs) {
|
|
167
|
+
throw new Error(`[json-storage] 获取存储锁超时:${relPath}`);
|
|
168
|
+
}
|
|
169
|
+
let fd = null;
|
|
170
|
+
try {
|
|
171
|
+
fd = await fsp.open(lockFile, 'wx');
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (err?.code !== 'EEXIST')
|
|
175
|
+
throw err;
|
|
176
|
+
// 孤儿锁回收:mtime 超阈值视为持有者已崩溃,强制删除后重新竞争
|
|
177
|
+
// (临界区仅包裹毫秒级元数据操作,30s 余量充足;两进程同时回收时 O_EXCL 保证唯一胜者)
|
|
178
|
+
try {
|
|
179
|
+
const st = await fsp.stat(lockFile);
|
|
180
|
+
if (Date.now() - st.mtimeMs > this.lockStaleMs) {
|
|
181
|
+
await fsp.rm(lockFile, { force: true });
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
continue; // 锁已在间隙被释放,直接重试
|
|
187
|
+
}
|
|
188
|
+
await new Promise((r) => setTimeout(r, 10 + Math.random() * 40));
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
// 写入持有者信息(token/PID/时间)便于孤儿锁诊断与归属校验
|
|
193
|
+
await fd.write(JSON.stringify({ token, pid: process.pid, at: new Date().toISOString() }));
|
|
194
|
+
await fd.close();
|
|
195
|
+
fd = null;
|
|
196
|
+
try {
|
|
197
|
+
return await fn();
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
await this.releaseLock(lockFile, token);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
if (fd) {
|
|
205
|
+
try {
|
|
206
|
+
await fd.close();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
/* ignore */
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
await this.releaseLock(lockFile, token);
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/** 释放锁:仅当锁内容中的令牌与本进程一致时删除(否则锁已被回收并被他人持有) */
|
|
218
|
+
async releaseLock(lockFile, token) {
|
|
219
|
+
try {
|
|
220
|
+
const raw = await fsp.readFile(lockFile, 'utf8');
|
|
221
|
+
const holder = JSON.parse(raw);
|
|
222
|
+
if (holder?.token !== token)
|
|
223
|
+
return; // 他人持有:保留,交由回收阈值兜底
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
/* 锁文件已不存在或不可解析:无需释放 */
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
await fsp.rm(lockFile, { force: true });
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
/* 释放失败仅留孤儿锁文件,超阈值后会被回收;不阻断 */
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async nextId(relPath, key) {
|
|
237
|
+
return this.withLock(relPath, async () => {
|
|
238
|
+
let seq = 0;
|
|
239
|
+
const raw = await this.readFile(relPath);
|
|
240
|
+
if (raw) {
|
|
241
|
+
try {
|
|
242
|
+
const obj = JSON.parse(raw);
|
|
243
|
+
const v = obj[key];
|
|
244
|
+
if (typeof v === 'number')
|
|
245
|
+
seq = v;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
/* 损坏的 _meta.json → 从 0 起 */
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
seq += 1;
|
|
252
|
+
await this.writeFileAtomic(relPath, JSON.stringify({ [key]: seq }));
|
|
253
|
+
return seq;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
async ensureSeqAtLeast(relPath, key, value) {
|
|
257
|
+
await this.withLock(relPath, async () => {
|
|
258
|
+
let seq = 0;
|
|
259
|
+
const raw = await this.readFile(relPath);
|
|
260
|
+
if (raw) {
|
|
261
|
+
try {
|
|
262
|
+
const obj = JSON.parse(raw);
|
|
263
|
+
const v = obj[key];
|
|
264
|
+
if (typeof v === 'number')
|
|
265
|
+
seq = v;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
/* 损坏的 _meta.json → 以传入 value 为准 */
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (value > seq) {
|
|
272
|
+
await this.writeFileAtomic(relPath, JSON.stringify({ [key]: value }));
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
exports.FsStorageBackend = FsStorageBackend;
|
|
278
|
+
//# sourceMappingURL=json-backend-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-backend-fs.js","sourceRoot":"","sources":["../../src/json-backend-fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type JsonStorageBackend } from './json-config.js';
|
|
2
|
+
import type { JsonEntityMeta } from './json-entity-meta.js';
|
|
3
|
+
/** 行对象:属性名 → 值(实体表)或原始列名 → 值(非实体表) */
|
|
4
|
+
export type JsonRow = Record<string, unknown>;
|
|
5
|
+
export declare class JsonCollection {
|
|
6
|
+
private meta;
|
|
7
|
+
readonly tableName: string;
|
|
8
|
+
/** 主键属性(实体表)或主键列(非实体表);未知时为 undefined */
|
|
9
|
+
readonly primaryProperty?: string;
|
|
10
|
+
/** 当前集合使用的后端实例(供 getCollection 检测运行时重注后重建) */
|
|
11
|
+
readonly backend: JsonStorageBackend;
|
|
12
|
+
private readonly metaRelPath;
|
|
13
|
+
private readonly reloadTtlMs;
|
|
14
|
+
private rows;
|
|
15
|
+
private loaded;
|
|
16
|
+
private seq;
|
|
17
|
+
/** 已知后备存储(表目录)mtime;外部变更触发重载(etag 优先判定) */
|
|
18
|
+
private cacheMtime;
|
|
19
|
+
/** 已知后备存储(表目录)etag;后端提供时优先于 mtime 判定外部变更 */
|
|
20
|
+
private cacheEtag;
|
|
21
|
+
/** 上次失效校验时间戳(ms),受 reloadTtlMs 节流 */
|
|
22
|
+
private lastCheckedAt;
|
|
23
|
+
/** 进行中的加载 Promise(single-flight:并发首载/重载共享,不重复整表 I/O) */
|
|
24
|
+
private loadPromise;
|
|
25
|
+
constructor(tableName: string, meta: JsonEntityMeta | null);
|
|
26
|
+
/**
|
|
27
|
+
* 表内全部行(惰性加载后返回**深拷贝**,调用方原地修改不会污染内存集合或静默分叉磁盘)。
|
|
28
|
+
* 与 TypeORM 一致:返回的行不会因未 save 而落库。
|
|
29
|
+
*/
|
|
30
|
+
all(): Promise<JsonRow[]>;
|
|
31
|
+
/** 按主键取行(返回深拷贝,调用方原地修改不影响内存集合与磁盘) */
|
|
32
|
+
get(pk: unknown): Promise<JsonRow | undefined>;
|
|
33
|
+
/** 按谓词查找(返回深拷贝,调用方原地修改不影响内存集合与磁盘) */
|
|
34
|
+
findWhere(pred: (row: JsonRow) => boolean): Promise<JsonRow[]>;
|
|
35
|
+
/** 写入(新增或整行覆盖),返回落盘后的行 */
|
|
36
|
+
write(row: JsonRow): Promise<JsonRow>;
|
|
37
|
+
/** 删除行,返回是否删除 */
|
|
38
|
+
delete(pk: unknown): Promise<boolean>;
|
|
39
|
+
/** 分配下一个自增主键(后端级原子序列器,跨进程唯一;不低于集合已知水位) */
|
|
40
|
+
nextId(): Promise<number>;
|
|
41
|
+
/** 清空内存缓存并删除落盘数据(TRUNCATE 与测试隔离使用) */
|
|
42
|
+
clear(): Promise<void>;
|
|
43
|
+
/** @deprecated 兼容别名,请使用 clear() */
|
|
44
|
+
clearForTest(): Promise<void>;
|
|
45
|
+
/** 读取前确保集合已加载,并按 TTL 节流做跨进程失效校验 */
|
|
46
|
+
private ensureLoaded;
|
|
47
|
+
/** 失效判定:任一侧提供 etag 时以 etag 不等为变更,否则以 mtime 增大为变更 */
|
|
48
|
+
private isExternalChange;
|
|
49
|
+
/** 记录一次已确认的后备状态(etag + mtime) */
|
|
50
|
+
private rememberStat;
|
|
51
|
+
/** 经后端把表目录读入内存(single-flight:并发加载共享同一 Promise) */
|
|
52
|
+
private loadFromBackend;
|
|
53
|
+
/** 加载体:局部构建后整体交换;加载前后 stat 不一致(窗口内并发写入)则有界重试 */
|
|
54
|
+
private doLoadFromBackend;
|
|
55
|
+
/** 写/删后同步已知后备状态,使下次失效校验以本进程写入为基准 */
|
|
56
|
+
private refreshCacheMtime;
|
|
57
|
+
private resolveKey;
|
|
58
|
+
private rowKey;
|
|
59
|
+
private rowFileRel;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 获取(或创建)表的集合单例。
|
|
63
|
+
* 运行时重注后端(configureJsonStorage)后,旧集合的内存缓存属于旧数据源,
|
|
64
|
+
* 检测到后端实例变更即丢弃重建(已外传的旧 Repository/Collection 引用不追踪:
|
|
65
|
+
* 重注应在首次访问前完成,测试中配合 resetCollectionsForTest)。
|
|
66
|
+
*/
|
|
67
|
+
export declare function getCollection(tableName: string, meta: JsonEntityMeta | null): JsonCollection;
|
|
68
|
+
/** 仅供测试:清空全部集合缓存 */
|
|
69
|
+
export declare function resetCollectionsForTest(): void;
|
|
70
|
+
//# sourceMappingURL=json-collection.d.ts.map
|