Skip to content

Commit 9fbc589

Browse files
committed
添加数据种子元数据文档,支持隐式和显式命名格式
1 parent 9acab4e commit 9fbc589

7 files changed

Lines changed: 93 additions & 20 deletions

File tree

docs/spec/data.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Data Seeding Metadata
2+
3+
Data source files allow you to seed the database with initial data. This is useful for lookup tables, configuration, or demo content.
4+
5+
## 1. Overview
6+
7+
- **Implicit Naming**: Name the file `<object_name>.data.yml` to automatically map it to an object.
8+
- **Explicit Naming**: Use the `object` and `records` properties in the YAML content.
9+
- **Auto-deduplication**: Currently, data seeding attempts to create records. Duplicate key errors are typically ignored (depending on driver implementation), allowing for basic idempotency.
10+
11+
## 2. File Format
12+
13+
### Option A: Implicit (Recommended)
14+
15+
File Name: `status.data.yml`
16+
Target Object: `status`
17+
18+
```yaml
19+
- code: "draft"
20+
label: "Draft"
21+
is_active: true
22+
23+
- code: "published"
24+
label: "Published"
25+
is_active: true
26+
```
27+
28+
### Option B: Explicit
29+
30+
File Name: `initial_setup.data.yml` (can be anything)
31+
32+
```yaml
33+
object: status
34+
records:
35+
- code: "draft"
36+
label: "Draft"
37+
38+
- code: "published"
39+
label: "Published"
40+
```
41+
42+
### Option C: Multiple Objects (Bundled)
43+
44+
Not currently supported in a single file. Please use separate files.
45+
46+
## 3. Best Practices
47+
48+
1. **Use Immutable IDs**: If possible, provide explicit IDs (`_id`) to ensure consistent referencing across environments.
49+
2. **Versioning**: Include a metadata field if you need to track data versions.
50+
3. **Order Matters**: If you have relationships, ensure the parent data is loaded before child data (ObjectQL loads data files in alphabetical order).

docs/spec/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ src/
8686
reports/ # Analytics
8787
*.report.yml # Report definitions
8888
*.dashboard.yml # Dashboard configurations
89+
90+
data/ # Initial Seeding
91+
*.data.yml # Seed data
8992

9093
navigation/ # App structure
9194
*.app.yml # Application definitions

packages/core/src/app.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,18 @@ export class ObjectQL implements IObjectQL {
321321

322322
for (const entry of dataEntries) {
323323
// Expected format:
324-
// object: User
325-
// records:
326-
// - name: Admin
327-
// email: admin@example.com
324+
// 1. { object: 'User', records: [...] }
325+
// 2. [ record1, record2 ] (with name property added by loader inferred from filename)
328326

329-
const objectName = entry.object;
330-
const records = entry.records;
327+
let objectName = entry.object;
328+
let records = entry.records;
329+
330+
if (Array.isArray(entry)) {
331+
records = entry;
332+
if (!objectName && (entry as any).name) {
333+
objectName = (entry as any).name;
334+
}
335+
}
331336

332337
if (!objectName || !records || !Array.isArray(records)) {
333338
console.warn(`Skipping invalid data entry:`, entry);

packages/core/src/loader.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ export class ObjectLoader {
2222
if (!doc) return;
2323

2424
if (doc.name && doc.fields) {
25-
registerObject(ctx.registry, doc, ctx.file, ctx.packageName || ctx.registry.getEntry('package-map', ctx.file)?.package);
25+
const packageEntry = ctx.registry.getEntry('package-map', ctx.file);
26+
registerObject(ctx.registry, doc, ctx.file, ctx.packageName || (packageEntry && packageEntry.package));
2627
} else {
2728
for (const [key, value] of Object.entries(doc)) {
2829
if (typeof value === 'object' && (value as any).fields) {
@@ -116,20 +117,14 @@ export class ObjectLoader {
116117

117118
// Use 'name' from doc, or filename base (without extension)
118119
let id = doc.name;
119-
if (!id && type !== 'data') {
120+
if (!id) {
120121
const basename = path.basename(ctx.file);
121122
// e.g. "my-view.view.yml" -> "my-view"
122123
// Regex: remove .type.yml or .type.yaml
123124
const re = new RegExp(`\\.${type}\\.(yml|yaml)$`);
124125
id = basename.replace(re, '');
125126
}
126127

127-
// Data entries might not need a name, but for registry we need an ID.
128-
// For data, we can use filename if not present.
129-
if (!id && type === 'data') {
130-
id = path.basename(ctx.file);
131-
}
132-
133128
// Ensure name is in the doc for consistency
134129
if (!doc.name) doc.name = id;
135130

packages/driver-knex/src/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,21 @@ export class KnexDriver implements Driver {
9292
}
9393

9494
if (query.sort && Array.isArray(query.sort)) {
95-
for (const [field, dir] of query.sort) {
96-
builder.orderBy(this.mapSortField(field), dir);
95+
for (const item of query.sort) {
96+
let field: string | undefined;
97+
let dir: string | undefined;
98+
99+
if (Array.isArray(item)) {
100+
[field, dir] = item;
101+
} else if (typeof item === 'object' && item !== null) {
102+
// Support object format { field: 'name', order: 'asc' }
103+
field = (item as any).field;
104+
dir = (item as any).order || (item as any).direction || (item as any).dir;
105+
}
106+
107+
if (field) {
108+
builder.orderBy(this.mapSortField(field), dir);
109+
}
97110
}
98111
}
99112

packages/types/src/registry.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export class MetadataRegistry {
1818
}
1919

2020
unregister(type: string, id: string) {
21-
this.store.get(type)?.delete(id);
21+
const map = this.store.get(type);
22+
if (map) {
23+
map.delete(id);
24+
}
2225
}
2326

2427
unregisterPackage(packageName: string) {
@@ -39,7 +42,10 @@ export class MetadataRegistry {
3942
}
4043

4144
get<T = any>(type: string, id: string): T | undefined {
42-
return this.store.get(type)?.get(id)?.content as T;
45+
const map = this.store.get(type);
46+
if (!map) return undefined;
47+
const entry = map.get(id);
48+
return entry ? entry.content as T : undefined;
4349
}
4450

4551
list<T = any>(type: string): T[] {
@@ -49,6 +55,7 @@ export class MetadataRegistry {
4955
}
5056

5157
getEntry(type: string, id: string): Metadata | undefined {
52-
return this.store.get(type)?.get(id);
58+
const map = this.store.get(type);
59+
return map ? map.get(id) : undefined;
5360
}
5461
}

tsconfig.base.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "ES2020",
3+
"target": "ES2019",
44
"module": "commonjs",
55
"declaration": true,
66
"strict": true,

0 commit comments

Comments
 (0)