Skip to content

Commit 22cce02

Browse files
committed
Add support for loading initial data from .data.yml files
Introduces the ability to load and initialize object data from .data.yml files in the project structure. The ObjectQL core now loads these files during configuration, attaches the data to object configs, and provides an init() method to insert initial records if they do not already exist. The Express example server is updated to use this mechanism for seeding data, and the ObjectConfig interface is extended to support a data property.
1 parent 329db5b commit 22cce02

7 files changed

Lines changed: 84 additions & 48 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
- _id: PROJ-001
2+
name: "Website Redesign (From Data)"
3+
description: "Initial data loaded from projects.data.yml"
4+
status: "in_progress"
5+
priority: "high"
6+
owner: "u-123"
7+
8+
- _id: PROJ-002
9+
name: "Mobile App (From Data)"
10+
description: "Initial data loaded from projects.data.yml"
11+
status: "planned"
12+
priority: "medium"
13+
owner: "u-999"

examples/servers/express/src/index.ts

Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,20 @@ import cors from 'cors';
55
import * as path from 'path';
66
import config from './objectql.config';
77

8-
const app = new ObjectQL({
8+
const objectql = new ObjectQL({
99
datasources: config.datasources,
1010
packages: config.packages
1111
});
1212

1313
console.log(`ObjectQL Server started with ${config.packages.length} packages.`);
1414

15-
const projectObj = app.getObject('projects');
16-
if (!projectObj) {
17-
console.error("Project object not found after loading!");
18-
}
19-
20-
// Example: Find orders with amount > 1000 and expand customer details
21-
const query: UnifiedQuery = {
22-
fields: ['name', 'status', 'priority'],
23-
filters: [
24-
['status', '=', 'in_progress'],
25-
'and',
26-
['priority', '=', 'high']
27-
],
28-
sort: [['name', 'desc']],
29-
// Note: Expansion requires relationship support.
30-
// If 'tasks' looks up 'project', we can query tasks and expand project.
31-
// Querying project and expanding tasks usually requires a defined detail relationship.
32-
// Keeping simplified for now.
33-
};
34-
35-
3615
(async () => {
3716
try {
3817
console.log("Starting server...");
3918

40-
// --- 1. Seed Data (Optional, reused from previous logic) ---
41-
// Create some dummy data (System context to bypass hooks/setup data)
42-
const systemCtx = app.createContext({ isSystem: true });
43-
44-
try {
45-
await systemCtx.object('projects').create({
46-
name: 'Website Redesign',
47-
status: 'in_progress',
48-
priority: 'high',
49-
owner: 'u-123'
50-
});
51-
await systemCtx.object('projects').create({
52-
name: 'Mobile App',
53-
status: 'planned',
54-
priority: 'high',
55-
owner: 'u-999'
56-
});
57-
} catch (e) {
58-
console.log("Error seeding data (might already exist):", e);
59-
}
19+
// --- 1. Init Data (from .data.yml) ---
20+
await objectql.init();
21+
6022

6123
// --- 2. Start Express Server ---
6224
const server = express();
@@ -69,7 +31,7 @@ const query: UnifiedQuery = {
6931
// Mount ObjectQL API
7032
// Example: /api/projects
7133
server.use('/api', createObjectQLRouter({
72-
objectql: app,
34+
objectql: objectql,
7335
swagger: {
7436
enabled: true,
7537
path: '/docs'
@@ -78,9 +40,9 @@ const query: UnifiedQuery = {
7840
// Simulate Authentication: Read User ID from header
7941
const userId = req.headers['x-user-id'] as string;
8042
if (userId === 'admin') {
81-
return app.createContext({ isSystem: true });
43+
return objectql.createContext({ isSystem: true });
8244
}
83-
return app.createContext({
45+
return objectql.createContext({
8446
userId: userId || undefined
8547
});
8648
}

examples/servers/express/src/objectql.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { MongoDriver } from '@objectql/driver-mongo';
2-
import path from 'path';
32

43
export default {
54
datasources: {

packages/core/src/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,29 @@ export class ObjectQL implements IObjectQL {
120120
}
121121
return driver;
122122
}
123+
124+
async init() {
125+
const ctx = this.createContext({ isSystem: true });
126+
for (const objectName in this.objects) {
127+
const obj = this.objects[objectName];
128+
if (obj.data && obj.data.length > 0) {
129+
console.log(`Initializing data for object ${objectName}...`);
130+
const repo = ctx.object(objectName);
131+
for (const record of obj.data) {
132+
try {
133+
if (record._id) {
134+
const existing = await repo.findOne(record._id);
135+
if (existing) {
136+
continue;
137+
}
138+
}
139+
await repo.create(record);
140+
console.log(`Inserted init data for ${objectName}: ${record._id || 'unknown id'}`);
141+
} catch (e) {
142+
console.error(`Failed to insert init data for ${objectName}:`, e);
143+
}
144+
}
145+
}
146+
}
147+
}
123148
}

packages/core/src/loader.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,41 @@ export function loadObjectConfigs(dir: string): Record<string, ObjectConfig> {
115115
}
116116
}
117117

118-
// 3. Load Actions (.action.js, .action.ts)
118+
// 3. Load Data (.data.yml, .data.yaml)
119+
const dataFiles = glob.sync(['**/*.data.yml', '**/*.data.yaml'], {
120+
cwd: dir,
121+
absolute: true
122+
});
123+
124+
for (const file of dataFiles) {
125+
try {
126+
const content = fs.readFileSync(file, 'utf8');
127+
const data = yaml.load(content);
128+
129+
if (!Array.isArray(data)) {
130+
console.warn(`Data file ${file} does not contain an array. Skipping.`);
131+
continue;
132+
}
133+
134+
// Guess object name from filename
135+
// project.data.yml -> project
136+
const basename = path.basename(file);
137+
const objectName = basename.replace(/\.data\.ya?ml$/, '');
138+
139+
if (configs[objectName]) {
140+
configs[objectName].data = data;
141+
} else {
142+
// Maybe the object config hasn't been found yet?
143+
// loadObjectConfigs runs glob for objects first, so configs should be populated.
144+
console.warn(`Found data for unknown object '${objectName}' in ${file}`);
145+
}
146+
147+
} catch (e) {
148+
console.error(`Error loading data from ${file}:`, e);
149+
}
150+
}
151+
152+
// 4. Load Actions (.action.js, .action.ts)
119153
const actionFiles = glob.sync(['**/*.action.{js,ts}'], {
120154
cwd: dir,
121155
absolute: true

packages/core/src/metadata.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/**
32
* Represents the supported field data types in the ObjectQL schema.
43
* These types determine how data is stored, validated, and rendered.
@@ -138,4 +137,7 @@ export interface ObjectConfig {
138137

139138
/** Lifecycle hooks. */
140139
listeners?: ObjectListeners;
140+
141+
/** Initial data to populate when system starts. */
142+
data?: any[];
141143
}

packages/core/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface IObjectQL {
1515
getObject(name: string): ObjectConfig | undefined;
1616
getConfigs(): Record<string, ObjectConfig>;
1717
datasource(name: string): Driver;
18+
init(): Promise<void>;
1819
}
1920

2021
export interface HookContext<T = any> {

0 commit comments

Comments
 (0)