@objectstack/objectql 0.9.0 → 0.9.2
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/CHANGELOG.md +19 -0
- package/README.md +286 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @objectstack/objectql
|
|
2
2
|
|
|
3
|
+
## 0.9.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies
|
|
8
|
+
- @objectstack/spec@0.9.2
|
|
9
|
+
- @objectstack/core@0.9.2
|
|
10
|
+
- @objectstack/types@0.9.2
|
|
11
|
+
|
|
12
|
+
## 0.9.1
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Patch release for maintenance and stability improvements. All packages updated with unified versioning.
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @objectstack/spec@0.9.1
|
|
19
|
+
- @objectstack/core@0.9.1
|
|
20
|
+
- @objectstack/types@0.9.1
|
|
21
|
+
|
|
3
22
|
## 0.8.2
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -10,6 +10,19 @@
|
|
|
10
10
|
- **Plugin System**: Load objects and logic via standard Manifests.
|
|
11
11
|
- **Middleware**: (Planned) Support for Hooks and Validators.
|
|
12
12
|
|
|
13
|
+
## 🤖 AI Development Context
|
|
14
|
+
|
|
15
|
+
**Role**: Data Engine / Query Layer
|
|
16
|
+
**Usage**:
|
|
17
|
+
- Use this package to execute data operations (`find`, `insert`).
|
|
18
|
+
- Do NOT bypass this layer to access drivers directly unless building a driver.
|
|
19
|
+
- This is the "Backend Brain" for data.
|
|
20
|
+
|
|
21
|
+
**Key Concepts**:
|
|
22
|
+
- `SchemaRegistry`: Holds all object definitions.
|
|
23
|
+
- `ObjectQL`: The main facade.
|
|
24
|
+
- `Driver`: Interface for storage adapters.
|
|
25
|
+
|
|
13
26
|
## Usage
|
|
14
27
|
|
|
15
28
|
### 1. Standalone Usage
|
|
@@ -91,6 +104,279 @@ await kernel.bootstrap();
|
|
|
91
104
|
- **QueryPlanner**: (Internal) Normalizes simplified queries into `QueryAST`.
|
|
92
105
|
- **Executor**: Routes AST to the correct driver.
|
|
93
106
|
|
|
107
|
+
## Advanced Examples
|
|
108
|
+
|
|
109
|
+
### Complex Queries
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Filtering with multiple conditions
|
|
113
|
+
const activeTasks = await ql.find('todo_task', {
|
|
114
|
+
where: {
|
|
115
|
+
$and: [
|
|
116
|
+
{ status: 'active' },
|
|
117
|
+
{ priority: { $gte: 3 } },
|
|
118
|
+
{ due_date: { $lte: new Date() } }
|
|
119
|
+
]
|
|
120
|
+
},
|
|
121
|
+
orderBy: [
|
|
122
|
+
{ field: 'priority', order: 'desc' },
|
|
123
|
+
{ field: 'due_date', order: 'asc' }
|
|
124
|
+
],
|
|
125
|
+
limit: 50
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Text search
|
|
129
|
+
const searchResults = await ql.find('todo_task', {
|
|
130
|
+
where: {
|
|
131
|
+
$or: [
|
|
132
|
+
{ title: { $contains: 'urgent' } },
|
|
133
|
+
{ description: { $contains: 'urgent' } }
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Nested conditions
|
|
139
|
+
const complexQuery = await ql.find('project', {
|
|
140
|
+
where: {
|
|
141
|
+
$and: [
|
|
142
|
+
{ status: { $in: ['active', 'planning'] } },
|
|
143
|
+
{
|
|
144
|
+
$or: [
|
|
145
|
+
{ budget: { $gt: 100000 } },
|
|
146
|
+
{ priority: { $eq: 1 } }
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Batch Operations
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Batch insert
|
|
158
|
+
const newTasks = [
|
|
159
|
+
{ title: 'Task 1', priority: 1 },
|
|
160
|
+
{ title: 'Task 2', priority: 2 },
|
|
161
|
+
{ title: 'Task 3', priority: 3 }
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
for (const task of newTasks) {
|
|
165
|
+
await ql.insert('todo_task', task);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Batch update
|
|
169
|
+
const tasksToUpdate = await ql.find('todo_task', {
|
|
170
|
+
where: { status: 'pending' }
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
for (const task of tasksToUpdate) {
|
|
174
|
+
await ql.update('todo_task', task.id, {
|
|
175
|
+
status: 'in_progress',
|
|
176
|
+
started_at: new Date()
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Working with Relationships
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// One-to-Many: Get project with tasks
|
|
185
|
+
const project = await ql.find('project', {
|
|
186
|
+
where: { id: 'proj_123' },
|
|
187
|
+
include: ['tasks'] // Assumes 'tasks' is a relation field
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Many-to-One: Get task with project details
|
|
191
|
+
const task = await ql.find('todo_task', {
|
|
192
|
+
where: { id: 'task_456' },
|
|
193
|
+
include: ['project']
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Manual join (when driver doesn't support relations)
|
|
197
|
+
const tasks = await ql.find('todo_task', {
|
|
198
|
+
where: { project_id: 'proj_123' }
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const project = await ql.find('project', {
|
|
202
|
+
where: { id: 'proj_123' }
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const projectWithTasks = {
|
|
206
|
+
...project[0],
|
|
207
|
+
tasks: tasks
|
|
208
|
+
};
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Aggregations
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Count records
|
|
215
|
+
const totalTasks = await ql.count('todo_task', {
|
|
216
|
+
where: { status: 'active' }
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Sum (if driver supports)
|
|
220
|
+
const totalBudget = await ql.aggregate('project', {
|
|
221
|
+
operation: 'sum',
|
|
222
|
+
field: 'budget',
|
|
223
|
+
where: { status: 'active' }
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Group by (if driver supports)
|
|
227
|
+
const tasksByStatus = await ql.aggregate('todo_task', {
|
|
228
|
+
operation: 'count',
|
|
229
|
+
groupBy: ['status']
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Multi-Datasource Queries
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { PostgresDriver } from '@objectstack/driver-postgres';
|
|
237
|
+
import { MongoDBDriver } from '@objectstack/driver-mongodb';
|
|
238
|
+
import { RedisDriver } from '@objectstack/driver-redis';
|
|
239
|
+
|
|
240
|
+
const ql = new ObjectQL();
|
|
241
|
+
|
|
242
|
+
// Register multiple drivers
|
|
243
|
+
const pgDriver = new PostgresDriver({ connectionString: 'postgres://...' });
|
|
244
|
+
const mongoDriver = new MongoDBDriver({ url: 'mongodb://...' });
|
|
245
|
+
const redisDriver = new RedisDriver({ host: 'localhost' });
|
|
246
|
+
|
|
247
|
+
ql.registerDriver(pgDriver, false, 'postgres');
|
|
248
|
+
ql.registerDriver(mongoDriver, false, 'mongodb');
|
|
249
|
+
ql.registerDriver(redisDriver, false, 'redis');
|
|
250
|
+
|
|
251
|
+
// Configure objects to use different datasources
|
|
252
|
+
await ql.use({
|
|
253
|
+
name: 'multi-db-app',
|
|
254
|
+
objects: [
|
|
255
|
+
{
|
|
256
|
+
name: 'user',
|
|
257
|
+
datasource: 'postgres', // Users in PostgreSQL
|
|
258
|
+
fields: { /* ... */ }
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'product',
|
|
262
|
+
datasource: 'mongodb', // Products in MongoDB
|
|
263
|
+
fields: { /* ... */ }
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'session',
|
|
267
|
+
datasource: 'redis', // Sessions in Redis
|
|
268
|
+
fields: { /* ... */ }
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await ql.init();
|
|
274
|
+
|
|
275
|
+
// Query automatically routes to correct datasource
|
|
276
|
+
const users = await ql.find('user'); // → PostgreSQL
|
|
277
|
+
const products = await ql.find('product'); // → MongoDB
|
|
278
|
+
const sessions = await ql.find('session'); // → Redis
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Custom Hooks and Middleware
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// Register hooks for data operations
|
|
285
|
+
ql.registerHook('beforeInsert', async (ctx) => {
|
|
286
|
+
console.log(`Creating ${ctx.object}:`, ctx.data);
|
|
287
|
+
|
|
288
|
+
// Add timestamps
|
|
289
|
+
ctx.data.created_at = new Date();
|
|
290
|
+
ctx.data.updated_at = new Date();
|
|
291
|
+
|
|
292
|
+
// Validate
|
|
293
|
+
if (ctx.object === 'user' && !ctx.data.email) {
|
|
294
|
+
throw new Error('Email is required');
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
ql.registerHook('afterInsert', async (ctx) => {
|
|
299
|
+
console.log(`Created ${ctx.object}:`, ctx.result);
|
|
300
|
+
|
|
301
|
+
// Trigger events - get event bus from kernel or context
|
|
302
|
+
// Note: eventBus should be injected or accessed from context
|
|
303
|
+
const eventBus = ctx.getService?.('event-bus');
|
|
304
|
+
if (eventBus) {
|
|
305
|
+
await eventBus.emit('data:created', {
|
|
306
|
+
object: ctx.object,
|
|
307
|
+
id: ctx.result.id
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
ql.registerHook('beforeUpdate', async (ctx) => {
|
|
313
|
+
// Update timestamp
|
|
314
|
+
ctx.data.updated_at = new Date();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
ql.registerHook('beforeDelete', async (ctx) => {
|
|
318
|
+
// Soft delete
|
|
319
|
+
if (ctx.object === 'user') {
|
|
320
|
+
throw new Error('Cannot delete users, use deactivate instead');
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Transaction Support
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// If driver supports transactions
|
|
329
|
+
const driver = ql.getDriver('default');
|
|
330
|
+
|
|
331
|
+
if (driver.supports.transactions) {
|
|
332
|
+
await driver.beginTransaction();
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
await ql.insert('order', {
|
|
336
|
+
customer_id: 'cust_123',
|
|
337
|
+
total: 100.00
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await ql.update('inventory', 'inv_456', {
|
|
341
|
+
quantity: { $decrement: 1 }
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await driver.commitTransaction();
|
|
345
|
+
} catch (error) {
|
|
346
|
+
await driver.rollbackTransaction();
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Schema Migration
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
// Add new field to existing object
|
|
356
|
+
const currentSchema = ql.getSchema('todo_task');
|
|
357
|
+
|
|
358
|
+
const updatedSchema = {
|
|
359
|
+
...currentSchema,
|
|
360
|
+
fields: {
|
|
361
|
+
...currentSchema.fields,
|
|
362
|
+
assignee: {
|
|
363
|
+
type: 'lookup',
|
|
364
|
+
reference: 'user',
|
|
365
|
+
label: 'Assignee'
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Re-register schema
|
|
371
|
+
ql.registerObject(updatedSchema);
|
|
372
|
+
|
|
373
|
+
// Driver might need to sync schema
|
|
374
|
+
const driver = ql.getDriver('default');
|
|
375
|
+
if (driver.syncSchema) {
|
|
376
|
+
await driver.syncSchema('todo_task', updatedSchema);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
94
380
|
## Roadmap
|
|
95
381
|
|
|
96
382
|
- [x] Basic CRUD
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/objectql",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Isomorphic ObjectQL Engine for ObjectStack",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@objectstack/core": "0.9.
|
|
9
|
-
"@objectstack/spec": "0.9.
|
|
10
|
-
"@objectstack/types": "0.9.
|
|
8
|
+
"@objectstack/core": "0.9.2",
|
|
9
|
+
"@objectstack/spec": "0.9.2",
|
|
10
|
+
"@objectstack/types": "0.9.2"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"typescript": "^5.0.0",
|