@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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 示例 04:非实体表(仅经原生 SQL 访问)
|
|
3
|
+
*
|
|
4
|
+
* 演示:
|
|
5
|
+
* - configureJsonStorage({ nonEntityTables }) 注册「无 @Entity 定义、仅原始 SQL 访问」的表
|
|
6
|
+
* - 这类表没有实体元数据,只能用 ds.query 读写(无 Repository 投影/属性映射)
|
|
7
|
+
* - 列名即数据库列名(无属性名映射层)
|
|
8
|
+
* - 配套的 isNonEntityTable / listNonEntityTables 等查询函数
|
|
9
|
+
*
|
|
10
|
+
* 运行:
|
|
11
|
+
* npx tsx examples/04-non-entity-table.ts
|
|
12
|
+
*/
|
|
13
|
+
import * as os from 'os';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import {
|
|
16
|
+
configureJsonStorage,
|
|
17
|
+
getJsonDataSource,
|
|
18
|
+
isNonEntityTable,
|
|
19
|
+
listNonEntityTables,
|
|
20
|
+
getNonEntityPrimaryColumn,
|
|
21
|
+
} from '@cicctencent/json-orm';
|
|
22
|
+
|
|
23
|
+
async function main(): Promise<void> {
|
|
24
|
+
// 注册非实体表:表名 → 主键列(与可选唯一列组)
|
|
25
|
+
configureJsonStorage({
|
|
26
|
+
dbDir: path.join(os.tmpdir(), 'json-orm-example-04'),
|
|
27
|
+
nonEntityTables: {
|
|
28
|
+
t_kv_store: {
|
|
29
|
+
primaryColumn: 'k',
|
|
30
|
+
uniqueColumns: [['k']],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 注入后可经工具函数查询注册信息
|
|
36
|
+
console.log('is t_kv_store non-entity:', isNonEntityTable('t_kv_store'));
|
|
37
|
+
console.log('registered tables:', listNonEntityTables());
|
|
38
|
+
console.log('primary column:', getNonEntityPrimaryColumn('t_kv_store'));
|
|
39
|
+
|
|
40
|
+
const ds = getJsonDataSource();
|
|
41
|
+
|
|
42
|
+
// 写入(列名即落盘键)
|
|
43
|
+
await ds.query('INSERT INTO t_kv_store (k, v, expire_at) VALUES (?, ?, ?)', [
|
|
44
|
+
'token:1',
|
|
45
|
+
'secret',
|
|
46
|
+
'2099-01-01 00:00:00',
|
|
47
|
+
]);
|
|
48
|
+
await ds.query('INSERT INTO t_kv_store (k, v) VALUES (?, ?)', ['config:theme', 'dark']);
|
|
49
|
+
|
|
50
|
+
// 读取
|
|
51
|
+
const rows = await ds.query('SELECT * FROM t_kv_store WHERE k LIKE ?', ['token:%']);
|
|
52
|
+
console.log('token rows:', rows);
|
|
53
|
+
|
|
54
|
+
// 按唯一列 upsert(ODKU)
|
|
55
|
+
await ds.query(
|
|
56
|
+
`INSERT INTO t_kv_store (k, v) VALUES (?, ?)
|
|
57
|
+
ON DUPLICATE KEY UPDATE v = VALUES(v)`,
|
|
58
|
+
['config:theme', 'light'],
|
|
59
|
+
);
|
|
60
|
+
console.log('after upsert:', await ds.query('SELECT * FROM t_kv_store WHERE k = ?', ['config:theme']));
|
|
61
|
+
|
|
62
|
+
// 删除
|
|
63
|
+
await ds.query('DELETE FROM t_kv_store WHERE k = ?', ['token:1']);
|
|
64
|
+
console.log('count after delete:', (await ds.query('SELECT COUNT(*) AS c FROM t_kv_store'))[0].c);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((e) => {
|
|
68
|
+
console.error(e);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 示例 05:注入自定义存储后端( JsonStorageBackend 接口)
|
|
3
|
+
*
|
|
4
|
+
* 演示:
|
|
5
|
+
* - 引擎全部持久化 I/O 经可注入的 JsonStorageBackend 完成,不直接依赖 fs
|
|
6
|
+
* - 实现该异步接口即可把存储指向对象存储(COS/S3)、内存或任意远程后端
|
|
7
|
+
* - 注入方式:configureJsonStorage({ backend })(优先于 dbDir)
|
|
8
|
+
*
|
|
9
|
+
* 下面给出一个最小可用的「内存后端」实现,便于测试/嵌入与理解接口契约。
|
|
10
|
+
*
|
|
11
|
+
* 运行:
|
|
12
|
+
* npx tsx examples/05-custom-backend.ts
|
|
13
|
+
*/
|
|
14
|
+
import 'reflect-metadata';
|
|
15
|
+
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
|
16
|
+
import {
|
|
17
|
+
configureJsonStorage,
|
|
18
|
+
getJsonDataSource,
|
|
19
|
+
type JsonStorageBackend,
|
|
20
|
+
type JsonStorageFileStat,
|
|
21
|
+
} from '@cicctencent/json-orm';
|
|
22
|
+
|
|
23
|
+
/** 最小可用的内存后端:文件存于进程内 Map,序列器独立维护 */
|
|
24
|
+
class MemoryStorageBackend implements JsonStorageBackend {
|
|
25
|
+
private files = new Map<string, string>();
|
|
26
|
+
private seq = new Map<string, number>();
|
|
27
|
+
private chains = new Map<string, Promise<unknown>>();
|
|
28
|
+
|
|
29
|
+
async readFile(relPath: string): Promise<string | null> {
|
|
30
|
+
return this.files.get(relPath) ?? null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async writeFileAtomic(relPath: string, data: string): Promise<void> {
|
|
34
|
+
this.files.set(relPath, data);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async readDir(relPath: string): Promise<string[]> {
|
|
38
|
+
const prefix = relPath === '' ? '' : relPath.replace(/\/$/, '') + '/';
|
|
39
|
+
const out = new Set<string>();
|
|
40
|
+
for (const f of this.files.keys()) {
|
|
41
|
+
if (prefix && !f.startsWith(prefix)) continue;
|
|
42
|
+
const rest = f.slice(prefix.length);
|
|
43
|
+
const slash = rest.indexOf('/');
|
|
44
|
+
out.add(slash >= 0 ? rest.slice(0, slash) : rest);
|
|
45
|
+
}
|
|
46
|
+
return [...out];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async deleteFile(relPath: string): Promise<void> {
|
|
50
|
+
this.files.delete(relPath);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async mkdir(_relPath: string): Promise<void> {
|
|
54
|
+
/* 内存后端无需真实建目录 */
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async stat(relPath: string): Promise<JsonStorageFileStat | null> {
|
|
58
|
+
const prefix = relPath === '' ? '' : relPath.replace(/\/$/, '') + '/';
|
|
59
|
+
let found = prefix === '';
|
|
60
|
+
if (!found) {
|
|
61
|
+
for (const f of this.files.keys()) {
|
|
62
|
+
if (f.startsWith(prefix)) {
|
|
63
|
+
found = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 表目录不存在返回 null:引擎视为无变更、不触发重载。
|
|
69
|
+
// 这里按内容合成 etag 而非返回 Date.now()——后者每次都变,会让引擎
|
|
70
|
+
// 在每次访问时判定「外部已变更」并整表重载(等于放弃了缓存)
|
|
71
|
+
return found ? { mtimeMs: Date.now(), size: 0, etag: this.contentEtag(prefix) } : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 按目录内容合成版本标识(内容不变则 etag 不变) */
|
|
75
|
+
private contentEtag(prefix: string): string {
|
|
76
|
+
let h = 0;
|
|
77
|
+
for (const [k, v] of [...this.files.entries()].sort(([a], [b]) => (a < b ? -1 : 1))) {
|
|
78
|
+
if (prefix && !k.startsWith(prefix)) continue;
|
|
79
|
+
for (let i = 0; i < v.length; i += 1) {
|
|
80
|
+
h = (h * 31 + v.charCodeAt(i)) | 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return `mem-${h}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 跨进程临界区:这里用单进程互斥链近似;真实远程后端应改用对象存储的原子锁 */
|
|
87
|
+
async withLock<T>(relPath: string, fn: () => Promise<T>): Promise<T> {
|
|
88
|
+
const prev = this.chains.get(relPath) ?? Promise.resolve();
|
|
89
|
+
let release!: () => void;
|
|
90
|
+
const next = new Promise<void>((res) => {
|
|
91
|
+
release = res;
|
|
92
|
+
});
|
|
93
|
+
this.chains.set(relPath, prev.then(() => next));
|
|
94
|
+
await prev;
|
|
95
|
+
try {
|
|
96
|
+
return await fn();
|
|
97
|
+
} finally {
|
|
98
|
+
release();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 后端级原子自增(跨进程唯一主键) */
|
|
103
|
+
async nextId(relPath: string, key: string): Promise<number> {
|
|
104
|
+
const k = `${relPath}#${key}`;
|
|
105
|
+
const cur = this.seq.get(k) ?? 0;
|
|
106
|
+
const v = cur + 1;
|
|
107
|
+
this.seq.set(k, v);
|
|
108
|
+
return v;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async ensureSeqAtLeast(relPath: string, key: string, value: number): Promise<void> {
|
|
112
|
+
const k = `${relPath}#${key}`;
|
|
113
|
+
const cur = this.seq.get(k) ?? 0;
|
|
114
|
+
if (value > cur) this.seq.set(k, value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Entity('t_memory_user')
|
|
119
|
+
class MemoryUser {
|
|
120
|
+
@PrimaryGeneratedColumn()
|
|
121
|
+
id!: number;
|
|
122
|
+
|
|
123
|
+
@Column({ name: 'Fname', type: 'varchar' })
|
|
124
|
+
name!: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function main(): Promise<void> {
|
|
128
|
+
// 注入自定义后端(优先于 dbDir);同进程内其余代码无感知
|
|
129
|
+
configureJsonStorage({ backend: new MemoryStorageBackend() });
|
|
130
|
+
|
|
131
|
+
const ds = getJsonDataSource();
|
|
132
|
+
const repo = ds.getRepository(MemoryUser);
|
|
133
|
+
|
|
134
|
+
const a = await repo.save({ name: 'Alice' });
|
|
135
|
+
const b = await repo.save({ name: 'Bob' });
|
|
136
|
+
console.log('saved ids (from memory backend):', a.id, b.id);
|
|
137
|
+
|
|
138
|
+
console.log('all:', await repo.find());
|
|
139
|
+
console.log('count:', await repo.count());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main().catch((e) => {
|
|
143
|
+
console.error(e);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 示例 06:Repository 查询条件(FindOperator)
|
|
3
|
+
*
|
|
4
|
+
* 演示 where 条件支持的对象语法(与 TypeORM FindOptionsWhere 一致):
|
|
5
|
+
* - 数组 = OR
|
|
6
|
+
* - In / Like / Not / IsNull / MoreThan / MoreThanOrEqual / LessThan / Between
|
|
7
|
+
* 属性名即实体字段名(非数据库列名)。
|
|
8
|
+
*
|
|
9
|
+
* 运行:
|
|
10
|
+
* npx tsx examples/06-find-operators.ts
|
|
11
|
+
*/
|
|
12
|
+
import 'reflect-metadata';
|
|
13
|
+
import {
|
|
14
|
+
Entity,
|
|
15
|
+
PrimaryGeneratedColumn,
|
|
16
|
+
Column,
|
|
17
|
+
In,
|
|
18
|
+
Like,
|
|
19
|
+
Not,
|
|
20
|
+
IsNull,
|
|
21
|
+
MoreThan,
|
|
22
|
+
Between,
|
|
23
|
+
} from 'typeorm';
|
|
24
|
+
import * as os from 'os';
|
|
25
|
+
import * as path from 'path';
|
|
26
|
+
import { configureJsonStorage, getJsonDataSource } from '@cicctencent/json-orm';
|
|
27
|
+
|
|
28
|
+
@Entity('t_city')
|
|
29
|
+
class City {
|
|
30
|
+
@PrimaryGeneratedColumn()
|
|
31
|
+
id!: number;
|
|
32
|
+
|
|
33
|
+
@Column({ name: 'Fname', type: 'varchar' })
|
|
34
|
+
name!: string;
|
|
35
|
+
|
|
36
|
+
@Column({ name: 'Fcountry', type: 'varchar' })
|
|
37
|
+
country!: string;
|
|
38
|
+
|
|
39
|
+
@Column({ name: 'Fpopulation', type: 'int' })
|
|
40
|
+
population!: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main(): Promise<void> {
|
|
44
|
+
configureJsonStorage({ dbDir: path.join(os.tmpdir(), 'json-orm-example-06') });
|
|
45
|
+
const repo = getJsonDataSource().getRepository(City);
|
|
46
|
+
|
|
47
|
+
await repo.save([
|
|
48
|
+
{ name: 'Beijing', country: 'CN', population: 21_000_000 },
|
|
49
|
+
{ name: 'Shanghai', country: 'CN', population: 24_000_000 },
|
|
50
|
+
{ name: 'Tokyo', country: 'JP', population: 37_000_000 },
|
|
51
|
+
{ name: 'Paris', country: 'FR', population: 11_000_000 },
|
|
52
|
+
{ name: 'Unknown', country: null as unknown as string, population: 0 },
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
console.log('国家为 CN 或 JP:', await repo.find({ where: { country: In(['CN', 'JP']) } }));
|
|
56
|
+
|
|
57
|
+
console.log('名字含 "a"(Like 透传 % 通配给求值器):',
|
|
58
|
+
await repo.find({ where: { name: Like('%a%') } }));
|
|
59
|
+
|
|
60
|
+
console.log('人口 > 20,000,000:', await repo.find({ where: { population: MoreThan(20_000_000) } }));
|
|
61
|
+
|
|
62
|
+
console.log('人口在 10M~25M 区间:', await repo.find({ where: { population: Between(10_000_000, 25_000_000) } }));
|
|
63
|
+
|
|
64
|
+
console.log('country 为空(IsNull):', await repo.find({ where: { country: IsNull() } }));
|
|
65
|
+
|
|
66
|
+
console.log('country 不为空(Not + IsNull):', await repo.find({ where: { country: Not(IsNull()) } }));
|
|
67
|
+
|
|
68
|
+
console.log('数组 OR:name=Beijing 或 population<5M:',
|
|
69
|
+
await repo.find({ where: [{ name: 'Beijing' }, { population: MoreThan(5_000_000) }] }));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
main().catch((e) => {
|
|
73
|
+
console.error(e);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @cicctencent/json-orm 使用示例
|
|
2
|
+
|
|
3
|
+
本目录是可直接运行的示例,覆盖最常见的接入场景。所有示例都从消费方视角使用
|
|
4
|
+
`@cicctencent/json-orm`(与 README 一致);在**本仓库内**运行示例前需先产出 `dist`:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pnpm --filter @cicctencent/json-orm build
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
然后用 [tsx](https://github.com/tsx-doten/tsx)(或任意 TS 运行器)执行:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx tsx examples/01-basic-entity.ts
|
|
14
|
+
npx tsx examples/02-query-builder.ts
|
|
15
|
+
npx tsx examples/03-raw-sql.ts
|
|
16
|
+
npx tsx examples/04-non-entity-table.ts
|
|
17
|
+
npx tsx examples/05-custom-backend.ts
|
|
18
|
+
npx tsx examples/06-find-operators.ts
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> 若示例改在本仓库内以源码方式运行,把示例顶部的
|
|
22
|
+
> `import { ... } from '@cicctencent/json-orm'` 换成相对源码路径
|
|
23
|
+
> `from '../src/index.ts'` 即可(无需先 `build`)。
|
|
24
|
+
|
|
25
|
+
## 关键约定(务必先读)
|
|
26
|
+
|
|
27
|
+
1. **首次访问前必须注入存储配置**:调用 `configureJsonStorage({ dbDir })`
|
|
28
|
+
或 `configureJsonStorage({ backend })`。未注入时引擎抛明确错误,不静默回退。
|
|
29
|
+
2. **实体用 TypeORM 装饰器定义**:`@Entity('t_xxx')`、`@PrimaryGeneratedColumn()`、
|
|
30
|
+
`@Column({ name: 'Fxxx' })`、`@CreateDateColumn` / `@UpdateDateColumn`。
|
|
31
|
+
别忘了在文件顶部 `import 'reflect-metadata'`(装饰器元数据来源)。
|
|
32
|
+
3. **两套命名空间**:
|
|
33
|
+
- Repository / QueryBuilder **对象条件**用**实体属性名**(如 `city`)。
|
|
34
|
+
- **原生 SQL 字符串**(`ds.query`)的列名用**数据库列名**(如 `Fcity`),
|
|
35
|
+
与 `@Column({ name })` 对应。
|
|
36
|
+
4. **合并写语义**:`save` / `update` / `upsert` 的 `partial` 中值为 `undefined`
|
|
37
|
+
的字段表示「不修改」,与 TypeORM 一致,不会把存量列擦除。
|
|
38
|
+
5. **多进程安全**:序列器与锁写入位于 `<根>/_meta/<表>.json`,不触发表目录失效信号;
|
|
39
|
+
默认后端 `FsStorageBackend` 已实现孤儿锁回收与序列器水位下限。
|
|
40
|
+
6. **原生 SQL 的位置参数(`?`)按 SQL 文本顺序绑定**(与 MySQL 一致):解析期为每个
|
|
41
|
+
`?` 分配语句内序号,绑定结果不受求值顺序/求值次数影响
|
|
42
|
+
(如 `SET f = f + ? WHERE k = ?` 传入 `[增量, 键]`)。也支持命名参数(`:name`),
|
|
43
|
+
见 `03-raw-sql.ts`。
|
|
44
|
+
|
|
45
|
+
## 示例清单
|
|
46
|
+
|
|
47
|
+
| 文件 | 场景 |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| `01-basic-entity.ts` | 定义实体 + Repository 的 save/find/findOneBy/findAndCount/update/delete |
|
|
50
|
+
| `02-query-builder.ts` | QueryBuilder:字符串/对象条件、LEFT JOIN、GROUP BY 聚合、分页计数 |
|
|
51
|
+
| `03-raw-sql.ts` | `ds.query`:SELECT/INSERT/UPDATE/DELETE、ODKU upsert、TRUNCATE;位置/命名参数 |
|
|
52
|
+
| `04-non-entity-table.ts` | 非实体表(仅原始 SQL 访问)+ `nonEntityTables` 注册与查询函数 |
|
|
53
|
+
| `05-custom-backend.ts` | 实现 `JsonStorageBackend` 注入自定义(内存)后端,理解接口契约 |
|
|
54
|
+
| `06-find-operators.ts` | Repository `where` 的 FindOperator:In/Like/Not/IsNull/MoreThan/Between、数组 OR |
|
|
55
|
+
|
|
56
|
+
## 最小可运行骨架
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import 'reflect-metadata';
|
|
60
|
+
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
|
61
|
+
import { configureJsonStorage, getJsonDataSource } from '@cicctencent/json-orm';
|
|
62
|
+
|
|
63
|
+
@Entity('t_user')
|
|
64
|
+
class User {
|
|
65
|
+
@PrimaryGeneratedColumn() id!: number;
|
|
66
|
+
@Column({ name: 'Fname', type: 'varchar' }) name!: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
configureJsonStorage({ dbDir: './data/db' });
|
|
70
|
+
const userRepo = getJsonDataSource().getRepository(User);
|
|
71
|
+
await userRepo.save({ name: 'Alice' });
|
|
72
|
+
console.log(await userRepo.find());
|
|
73
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": false,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"allowSyntheticDefaultImports": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"experimentalDecorators": true,
|
|
15
|
+
"emitDecoratorMetadata": true,
|
|
16
|
+
"types": ["node", "reflect-metadata"]
|
|
17
|
+
},
|
|
18
|
+
"include": ["./**/*.ts"],
|
|
19
|
+
"exclude": ["../dist"]
|
|
20
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cicctencent/json-orm",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "TypeORM-compatible JSON file storage engine — datasource facade, repository shim, query builder and in-memory SQL evaluator, with injectable storage root (framework-agnostic).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/esm/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/esm/index.d.ts",
|
|
12
|
+
"import": "./dist/esm/index.js",
|
|
13
|
+
"require": "./dist/cjs/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./*.js": {
|
|
16
|
+
"types": "./dist/esm/*.d.ts",
|
|
17
|
+
"import": "./dist/esm/*.js",
|
|
18
|
+
"require": "./dist/cjs/*.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "npm run build:esm && npm run build:cjs && npm run build:fixup",
|
|
23
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
24
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
25
|
+
"build:fixup": "node scripts/fixup-cjs.js",
|
|
26
|
+
"typecheck": "tsc -p tsconfig.esm.json --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"reflect-metadata": "^0.2.2",
|
|
34
|
+
"typeorm": "^0.3.31"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.20.1",
|
|
38
|
+
"reflect-metadata": "^0.2.2",
|
|
39
|
+
"typeorm": "^0.3.31",
|
|
40
|
+
"typescript": "~5.8.3",
|
|
41
|
+
"vitest": "^2.1.9"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=20.0.0"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/**/*",
|
|
48
|
+
"examples/**/*",
|
|
49
|
+
"README.md",
|
|
50
|
+
"package.json"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT"
|
|
53
|
+
}
|