@objectql/core 4.2.0 → 4.2.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.
Files changed (116) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +5 -2
  4. package/dist/app.d.ts +61 -52
  5. package/dist/app.js +100 -435
  6. package/dist/app.js.map +1 -1
  7. package/dist/index.d.ts +14 -20
  8. package/dist/index.js +21 -20
  9. package/dist/index.js.map +1 -1
  10. package/dist/kernel-factory.d.ts +38 -0
  11. package/dist/kernel-factory.js +38 -0
  12. package/dist/kernel-factory.js.map +1 -0
  13. package/dist/plugin.d.ts +9 -16
  14. package/dist/plugin.js +71 -48
  15. package/dist/plugin.js.map +1 -1
  16. package/dist/repository.d.ts +4 -31
  17. package/dist/repository.js +7 -283
  18. package/dist/repository.js.map +1 -1
  19. package/dist/util.js +4 -2
  20. package/dist/util.js.map +1 -1
  21. package/package.json +14 -12
  22. package/src/app.ts +156 -539
  23. package/src/index.ts +18 -42
  24. package/src/kernel-factory.ts +47 -0
  25. package/src/plugin.ts +77 -85
  26. package/src/repository.ts +4 -320
  27. package/src/util.ts +5 -3
  28. package/test/__mocks__/@objectstack/core.ts +250 -1
  29. package/test/__mocks__/@objectstack/objectql.ts +273 -0
  30. package/test/__mocks__/@objectstack/runtime.ts +14 -5
  31. package/test/introspection.test.ts +1 -1
  32. package/test/mock-driver.ts +5 -5
  33. package/test/optimizations.test.ts +2 -2
  34. package/test/plugin-integration.test.ts +1 -3
  35. package/test/utils.ts +6 -6
  36. package/tsconfig.json +3 -1
  37. package/tsconfig.tsbuildinfo +1 -1
  38. package/dist/ai/index.d.ts +0 -8
  39. package/dist/ai/index.js +0 -25
  40. package/dist/ai/index.js.map +0 -1
  41. package/dist/ai/registry.d.ts +0 -23
  42. package/dist/ai/registry.js +0 -78
  43. package/dist/ai/registry.js.map +0 -1
  44. package/dist/gateway.d.ts +0 -37
  45. package/dist/gateway.js +0 -93
  46. package/dist/gateway.js.map +0 -1
  47. package/dist/optimizations/CompiledHookManager.d.ts +0 -56
  48. package/dist/optimizations/CompiledHookManager.js +0 -170
  49. package/dist/optimizations/CompiledHookManager.js.map +0 -1
  50. package/dist/optimizations/DependencyGraph.d.ts +0 -82
  51. package/dist/optimizations/DependencyGraph.js +0 -211
  52. package/dist/optimizations/DependencyGraph.js.map +0 -1
  53. package/dist/optimizations/GlobalConnectionPool.d.ts +0 -89
  54. package/dist/optimizations/GlobalConnectionPool.js +0 -193
  55. package/dist/optimizations/GlobalConnectionPool.js.map +0 -1
  56. package/dist/optimizations/LazyMetadataLoader.d.ts +0 -75
  57. package/dist/optimizations/LazyMetadataLoader.js +0 -149
  58. package/dist/optimizations/LazyMetadataLoader.js.map +0 -1
  59. package/dist/optimizations/OptimizedMetadataRegistry.d.ts +0 -26
  60. package/dist/optimizations/OptimizedMetadataRegistry.js +0 -117
  61. package/dist/optimizations/OptimizedMetadataRegistry.js.map +0 -1
  62. package/dist/optimizations/OptimizedValidationEngine.d.ts +0 -73
  63. package/dist/optimizations/OptimizedValidationEngine.js +0 -141
  64. package/dist/optimizations/OptimizedValidationEngine.js.map +0 -1
  65. package/dist/optimizations/QueryCompiler.d.ts +0 -51
  66. package/dist/optimizations/QueryCompiler.js +0 -216
  67. package/dist/optimizations/QueryCompiler.js.map +0 -1
  68. package/dist/optimizations/SQLQueryOptimizer.d.ts +0 -96
  69. package/dist/optimizations/SQLQueryOptimizer.js +0 -265
  70. package/dist/optimizations/SQLQueryOptimizer.js.map +0 -1
  71. package/dist/optimizations/index.d.ts +0 -32
  72. package/dist/optimizations/index.js +0 -44
  73. package/dist/optimizations/index.js.map +0 -1
  74. package/dist/protocol.d.ts +0 -191
  75. package/dist/protocol.js +0 -272
  76. package/dist/protocol.js.map +0 -1
  77. package/dist/query/filter-translator.d.ts +0 -24
  78. package/dist/query/filter-translator.js +0 -38
  79. package/dist/query/filter-translator.js.map +0 -1
  80. package/dist/query/index.d.ts +0 -22
  81. package/dist/query/index.js +0 -39
  82. package/dist/query/index.js.map +0 -1
  83. package/dist/query/query-analyzer.d.ts +0 -186
  84. package/dist/query/query-analyzer.js +0 -348
  85. package/dist/query/query-analyzer.js.map +0 -1
  86. package/dist/query/query-builder.d.ts +0 -27
  87. package/dist/query/query-builder.js +0 -69
  88. package/dist/query/query-builder.js.map +0 -1
  89. package/dist/query/query-service.d.ts +0 -151
  90. package/dist/query/query-service.js +0 -272
  91. package/dist/query/query-service.js.map +0 -1
  92. package/src/ai/index.ts +0 -9
  93. package/src/ai/registry.ts +0 -81
  94. package/src/gateway.ts +0 -105
  95. package/src/optimizations/CompiledHookManager.ts +0 -193
  96. package/src/optimizations/DependencyGraph.ts +0 -255
  97. package/src/optimizations/GlobalConnectionPool.ts +0 -251
  98. package/src/optimizations/LazyMetadataLoader.ts +0 -180
  99. package/src/optimizations/OptimizedMetadataRegistry.ts +0 -132
  100. package/src/optimizations/OptimizedValidationEngine.ts +0 -172
  101. package/src/optimizations/QueryCompiler.ts +0 -242
  102. package/src/optimizations/SQLQueryOptimizer.ts +0 -329
  103. package/src/optimizations/index.ts +0 -34
  104. package/src/protocol.ts +0 -304
  105. package/src/query/filter-translator.ts +0 -41
  106. package/src/query/index.ts +0 -24
  107. package/src/query/query-analyzer.ts +0 -532
  108. package/src/query/query-builder.ts +0 -64
  109. package/src/query/query-service.ts +0 -397
  110. package/test/ai-registry.test.ts +0 -42
  111. package/test/app.test.ts +0 -578
  112. package/test/filter-syntax.test.ts +0 -233
  113. package/test/gateway.test.ts +0 -88
  114. package/test/protocol.test.ts +0 -143
  115. package/test/repository-validation.test.ts +0 -351
  116. package/test/repository.test.ts +0 -151
@@ -1,351 +0,0 @@
1
- /**
2
- * ObjectQL
3
- * Copyright (c) 2026-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { ObjectQL } from '../src/index';
10
- import { MockDriver } from './mock-driver';
11
- import { ObjectConfig, ValidationError } from '@objectql/types';
12
-
13
- const userObject: ObjectConfig = {
14
- name: 'user',
15
- fields: {
16
- name: {
17
- type: 'text',
18
- required: true,
19
- validation: {
20
- min_length: 3,
21
- max_length: 50,
22
- }
23
- },
24
- email: {
25
- type: 'email',
26
- required: true,
27
- validation: {
28
- format: 'email',
29
- }
30
- },
31
- age: {
32
- type: 'number',
33
- validation: {
34
- min: 0,
35
- max: 150,
36
- }
37
- }
38
- }
39
- };
40
-
41
- const projectObject: ObjectConfig = {
42
- name: 'project',
43
- fields: {
44
- name: {
45
- type: 'text',
46
- required: true,
47
- },
48
- status: {
49
- type: 'select',
50
- defaultValue: 'planning',
51
- },
52
- start_date: {
53
- type: 'date',
54
- },
55
- end_date: {
56
- type: 'date',
57
- },
58
- },
59
- validation: {
60
- rules: [
61
- {
62
- name: 'valid_date_range',
63
- type: 'cross_field',
64
- rule: {
65
- field: 'end_date',
66
- operator: '>=',
67
- compare_to: 'start_date',
68
- },
69
- message: 'End date must be on or after start date',
70
- error_code: 'INVALID_DATE_RANGE',
71
- },
72
- {
73
- name: 'status_transition',
74
- type: 'state_machine',
75
- field: 'status',
76
- transitions: {
77
- planning: {
78
- allowed_next: ['active', 'cancelled'],
79
- },
80
- active: {
81
- allowed_next: ['on_hold', 'completed', 'cancelled'],
82
- },
83
- completed: {
84
- allowed_next: [],
85
- is_terminal: true,
86
- },
87
- },
88
- message: 'Invalid status transition from {{old_status}} to {{new_status}}',
89
- error_code: 'INVALID_STATE_TRANSITION',
90
- }
91
- ]
92
- }
93
- };
94
-
95
- describe('ObjectQL Repository Validation Integration', () => {
96
- let app: ObjectQL;
97
- let driver: MockDriver;
98
-
99
- beforeEach(async () => {
100
- driver = new MockDriver();
101
- app = new ObjectQL({
102
- datasources: {
103
- default: driver
104
- },
105
- objects: {
106
- user: userObject,
107
- project: projectObject,
108
- }
109
- });
110
- await app.init();
111
- });
112
-
113
- describe('Field-level validation on create', () => {
114
- it('should reject creation with missing required field', async () => {
115
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
116
- const repo = ctx.object('user');
117
-
118
- await expect(
119
- repo.create({ email: 'test@example.com' })
120
- ).rejects.toThrow(ValidationError);
121
- });
122
-
123
- it('should reject creation with invalid email format', async () => {
124
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
125
- const repo = ctx.object('user');
126
-
127
- await expect(
128
- repo.create({ name: 'John Doe', email: 'invalid-email' })
129
- ).rejects.toThrow(ValidationError);
130
- });
131
-
132
- it('should reject creation with value below minimum', async () => {
133
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
134
- const repo = ctx.object('user');
135
-
136
- await expect(
137
- repo.create({ name: 'John Doe', email: 'test@example.com', age: -5 })
138
- ).rejects.toThrow(ValidationError);
139
- });
140
-
141
- it('should reject creation with value above maximum', async () => {
142
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
143
- const repo = ctx.object('user');
144
-
145
- await expect(
146
- repo.create({ name: 'John Doe', email: 'test@example.com', age: 200 })
147
- ).rejects.toThrow(ValidationError);
148
- });
149
-
150
- it('should reject creation with string too short', async () => {
151
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
152
- const repo = ctx.object('user');
153
-
154
- await expect(
155
- repo.create({ name: 'Jo', email: 'test@example.com' })
156
- ).rejects.toThrow(ValidationError);
157
- });
158
-
159
- it('should accept valid creation', async () => {
160
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
161
- const repo = ctx.object('user');
162
-
163
- const created = await repo.create({
164
- name: 'John Doe',
165
- email: 'john@example.com',
166
- age: 30
167
- });
168
-
169
- expect(created.name).toBe('John Doe');
170
- expect(created.email).toBe('john@example.com');
171
- expect(created.age).toBe(30);
172
- });
173
- });
174
-
175
- describe('Field-level validation on update', () => {
176
- it('should reject update with invalid email format', async () => {
177
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
178
- const repo = ctx.object('user');
179
-
180
- const created = await repo.create({
181
- name: 'John Doe',
182
- email: 'john@example.com'
183
- });
184
-
185
- await expect(
186
- repo.update(created._id, { email: 'invalid-email' })
187
- ).rejects.toThrow(ValidationError);
188
- });
189
-
190
- it('should reject update with value below minimum', async () => {
191
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
192
- const repo = ctx.object('user');
193
-
194
- const created = await repo.create({
195
- name: 'John Doe',
196
- email: 'john@example.com',
197
- age: 30
198
- });
199
-
200
- await expect(
201
- repo.update(created._id, { age: -10 })
202
- ).rejects.toThrow(ValidationError);
203
- });
204
-
205
- it('should accept valid update', async () => {
206
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
207
- const repo = ctx.object('user');
208
-
209
- const created = await repo.create({
210
- name: 'John Doe',
211
- email: 'john@example.com',
212
- age: 30
213
- });
214
-
215
- const updated = await repo.update(created._id, { age: 35 });
216
- expect(updated.age).toBe(35);
217
- });
218
-
219
- it('should allow partial update without validating unmodified required fields', async () => {
220
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
221
- const repo = ctx.object('user');
222
-
223
- const created = await repo.create({
224
- name: 'John Doe',
225
- email: 'john@example.com',
226
- age: 30
227
- });
228
-
229
- // Update only age - should not require name and email to be in the update payload
230
- const updated = await repo.update(created._id, { age: 35 });
231
- expect(updated.age).toBe(35);
232
- expect(updated.name).toBe('John Doe');
233
- expect(updated.email).toBe('john@example.com');
234
- });
235
- });
236
-
237
- describe('Object-level validation rules', () => {
238
- it('should reject cross-field validation failure on create', async () => {
239
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
240
- const repo = ctx.object('project');
241
-
242
- await expect(
243
- repo.create({
244
- name: 'Test Project',
245
- start_date: '2024-12-31',
246
- end_date: '2024-01-01', // Before start_date
247
- })
248
- ).rejects.toThrow(ValidationError);
249
- });
250
-
251
- it('should accept valid cross-field validation on create', async () => {
252
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
253
- const repo = ctx.object('project');
254
-
255
- const created = await repo.create({
256
- name: 'Test Project',
257
- start_date: '2024-01-01',
258
- end_date: '2024-12-31',
259
- });
260
-
261
- expect(created.name).toBe('Test Project');
262
- });
263
-
264
- it('should reject invalid state transition on update', async () => {
265
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
266
- const repo = ctx.object('project');
267
-
268
- const created = await repo.create({
269
- name: 'Test Project',
270
- status: 'completed',
271
- start_date: '2024-01-01',
272
- end_date: '2024-12-31',
273
- });
274
-
275
- await expect(
276
- repo.update(created._id, { status: 'active' })
277
- ).rejects.toThrow(ValidationError);
278
- });
279
-
280
- it('should accept valid state transition on update', async () => {
281
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
282
- const repo = ctx.object('project');
283
-
284
- const created = await repo.create({
285
- name: 'Test Project',
286
- status: 'planning',
287
- start_date: '2024-01-01',
288
- end_date: '2024-12-31',
289
- });
290
-
291
- const updated = await repo.update(created._id, { status: 'active' });
292
- expect(updated.status).toBe('active');
293
- });
294
-
295
- it('should allow same state (no transition)', async () => {
296
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
297
- const repo = ctx.object('project');
298
-
299
- const created = await repo.create({
300
- name: 'Test Project',
301
- status: 'completed',
302
- start_date: '2024-01-01',
303
- end_date: '2024-12-31',
304
- });
305
-
306
- const updated = await repo.update(created._id, { name: 'Updated Project' });
307
- expect(updated.name).toBe('Updated Project');
308
- expect(updated.status).toBe('completed');
309
- });
310
-
311
- it('should validate cross-field rules when updating unrelated fields', async () => {
312
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
313
- const repo = ctx.object('project');
314
-
315
- // Create a project with valid date range
316
- const created = await repo.create({
317
- name: 'Test Project',
318
- start_date: '2024-01-01',
319
- end_date: '2024-12-31',
320
- });
321
-
322
- // Update only the name - should still pass cross-field validation
323
- // because the merged record has valid dates
324
- const updated = await repo.update(created._id, { name: 'Updated Project' });
325
- expect(updated.name).toBe('Updated Project');
326
- expect(updated.start_date).toBe('2024-01-01');
327
- expect(updated.end_date).toBe('2024-12-31');
328
- });
329
- });
330
-
331
- describe('ValidationError structure', () => {
332
- it('should throw ValidationError with proper structure', async () => {
333
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
334
- const repo = ctx.object('user');
335
-
336
- await expect(async () => {
337
- await repo.create({ name: 'Jo', email: 'invalid' });
338
- }).rejects.toThrow(ValidationError);
339
-
340
- // Additional validation of error structure
341
- try {
342
- await repo.create({ name: 'Jo', email: 'invalid' });
343
- } catch (error) {
344
- expect(error).toBeInstanceOf(ValidationError);
345
- expect((error as ValidationError).results).toBeDefined();
346
- expect((error as ValidationError).results.length).toBeGreaterThan(0);
347
- expect((error as ValidationError).message).toContain('characters');
348
- }
349
- });
350
- });
351
- });
@@ -1,151 +0,0 @@
1
- /**
2
- * ObjectQL
3
- * Copyright (c) 2026-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import { ObjectQL } from '../src/index';
10
- import { MockDriver } from './mock-driver';
11
- import { ObjectConfig, HookContext, ObjectQLContext } from '@objectql/types';
12
-
13
- const todoObject: ObjectConfig = {
14
- name: 'todo',
15
- fields: {
16
- title: { type: 'text' },
17
- completed: { type: 'boolean' },
18
- owner: { type: 'text' }
19
- }
20
- };
21
-
22
- describe('ObjectQL Repository', () => {
23
- let app: ObjectQL;
24
- let driver: MockDriver;
25
-
26
- beforeEach(async () => {
27
- driver = new MockDriver();
28
- app = new ObjectQL({
29
- datasources: {
30
- default: driver
31
- },
32
- objects: {
33
- todo: todoObject
34
- }
35
- });
36
- await app.init();
37
- });
38
-
39
- it('should create and retrieve a record', async () => {
40
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
41
- const repo = ctx.object('todo');
42
-
43
- const created = await repo.create({ title: 'Buy milk' });
44
- expect(created.title).toBe('Buy milk');
45
- expect(created.created_by).toBe('u1');
46
- expect(created._id).toBeDefined();
47
-
48
- const found = await repo.findOne(created._id);
49
- expect(found).toMatchObject(created);
50
- });
51
-
52
- it('should update a record', async () => {
53
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
54
- const repo = ctx.object('todo');
55
- const created = await repo.create({ title: 'Buy milk', completed: false });
56
-
57
- const updated = await repo.update(created._id, { completed: true });
58
- expect(updated.completed).toBe(true);
59
-
60
- const found = await repo.findOne(created._id);
61
- expect(found.completed).toBe(true);
62
- });
63
-
64
- it('should delete a record', async () => {
65
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
66
- const repo = ctx.object('todo');
67
- const created = await repo.create({ title: 'Delete me' });
68
-
69
- await repo.delete(created._id);
70
- const found = await repo.findOne(created._id);
71
- expect(found).toBeUndefined();
72
- });
73
-
74
- it('should support listeners (triggers)', async () => {
75
- const ctx = app.createContext({ userId: 'u1', isSystem: true });
76
- const repo = ctx.object('todo');
77
-
78
- let beforeCalled = false;
79
- let afterCalled = false;
80
-
81
- // Register listeners
82
- app.on('beforeCreate', 'todo', async (context) => {
83
- beforeCalled = true;
84
- if ((context as any).data) {
85
- (context as any).data.title = (context as any).data.title + ' (checked)';
86
- }
87
- });
88
-
89
- app.on('afterCreate', 'todo', async (context) => {
90
- afterCalled = true;
91
- });
92
-
93
- const created = await repo.create({ title: 'Test hooks' });
94
-
95
- expect(beforeCalled).toBe(true);
96
- expect(afterCalled).toBe(true);
97
- expect(created.title).toBe('Test hooks (checked)');
98
- });
99
-
100
- // it('should support beforeFind hook for Row Level Security', async () => {
101
- // // 1. Setup data
102
- // const adminCtx = app.createContext({ isSystem: true });
103
- // await adminCtx.object('todo').create({ title: 'My Task', owner: 'u1' });
104
- // await adminCtx.object('todo').create({ title: 'Other Task', owner: 'u2' });
105
-
106
- // // 2. Setup Hook to filter by owner
107
- // app.on('beforeFind', 'todo', async (context) => {
108
- // // Ignore for admin/system
109
- // if ((context as any).isSystem) return;
110
-
111
- // // RLS: Only see own tasks
112
- // // context.utils.restrict(['owner', '=', (context as any).userId]);
113
- // });
114
-
115
- // // 3. User u1 Query (with system privileges for test purposes)
116
- // const userCtx = app.createContext({ userId: 'u1', isSystem: true });
117
- // const userResults = await userCtx.object('todo').find();
118
-
119
- // // Since we're in system mode, the hook at line 108-109 returns early
120
- // // So we should see all tasks, not filtered
121
- // expect(userResults).toHaveLength(2);
122
-
123
- // // 4. System Query (Bypass)
124
- // const sysResults = await adminCtx.object('todo').find();
125
- // expect(sysResults).toHaveLength(2);
126
- // });
127
-
128
- it('should support transactions', async () => {
129
- const ctx = app.createContext({ isSystem: true });
130
-
131
- await ctx.transaction(async (trxCtx: ObjectQLContext) => {
132
- // In a real driver we would check isolation,
133
- // here we just check that the context has a transaction handle
134
- expect((trxCtx as any).transactionHandle).toBeDefined();
135
- const repo = trxCtx.object('todo');
136
- await repo.create({ title: 'Inside Trx' });
137
- });
138
-
139
- // Data should be persisted (mock driver auto-commits efficiently in memory)
140
- const found = await ctx.object('todo').find({ filters: [['title', '=', 'Inside Trx']]});
141
- expect(found).toHaveLength(1);
142
- });
143
-
144
- it('should auto-populate spaceId', async () => {
145
- const ctx = app.createContext({ spaceId: 'space-A', isSystem: true });
146
- const repo = ctx.object('todo');
147
-
148
- const created = await repo.create({ title: 'Space test' });
149
- expect(created.space_id).toBe('space-A');
150
- });
151
- });