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