@objectql/core 4.2.0 → 4.2.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.
Files changed (115) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +23 -0
  3. package/dist/app.d.ts +61 -52
  4. package/dist/app.js +101 -435
  5. package/dist/app.js.map +1 -1
  6. package/dist/index.d.ts +4 -18
  7. package/dist/index.js +11 -18
  8. package/dist/index.js.map +1 -1
  9. package/dist/kernel-factory.d.ts +38 -0
  10. package/dist/kernel-factory.js +38 -0
  11. package/dist/kernel-factory.js.map +1 -0
  12. package/dist/plugin.d.ts +9 -16
  13. package/dist/plugin.js +71 -48
  14. package/dist/plugin.js.map +1 -1
  15. package/dist/repository.d.ts +4 -31
  16. package/dist/repository.js +7 -283
  17. package/dist/repository.js.map +1 -1
  18. package/dist/util.js +4 -2
  19. package/dist/util.js.map +1 -1
  20. package/package.json +12 -11
  21. package/src/app.ts +157 -539
  22. package/src/index.ts +8 -40
  23. package/src/kernel-factory.ts +47 -0
  24. package/src/plugin.ts +77 -85
  25. package/src/repository.ts +4 -320
  26. package/src/util.ts +5 -3
  27. package/test/__mocks__/@objectstack/core.ts +250 -1
  28. package/test/__mocks__/@objectstack/objectql.ts +127 -0
  29. package/test/__mocks__/@objectstack/runtime.ts +14 -5
  30. package/test/introspection.test.ts +1 -1
  31. package/test/mock-driver.ts +5 -5
  32. package/test/optimizations.test.ts +2 -2
  33. package/test/plugin-integration.test.ts +1 -3
  34. package/test/utils.ts +6 -6
  35. package/tsconfig.json +3 -1
  36. package/tsconfig.tsbuildinfo +1 -1
  37. package/dist/ai/index.d.ts +0 -8
  38. package/dist/ai/index.js +0 -25
  39. package/dist/ai/index.js.map +0 -1
  40. package/dist/ai/registry.d.ts +0 -23
  41. package/dist/ai/registry.js +0 -78
  42. package/dist/ai/registry.js.map +0 -1
  43. package/dist/gateway.d.ts +0 -37
  44. package/dist/gateway.js +0 -93
  45. package/dist/gateway.js.map +0 -1
  46. package/dist/optimizations/CompiledHookManager.d.ts +0 -56
  47. package/dist/optimizations/CompiledHookManager.js +0 -170
  48. package/dist/optimizations/CompiledHookManager.js.map +0 -1
  49. package/dist/optimizations/DependencyGraph.d.ts +0 -82
  50. package/dist/optimizations/DependencyGraph.js +0 -211
  51. package/dist/optimizations/DependencyGraph.js.map +0 -1
  52. package/dist/optimizations/GlobalConnectionPool.d.ts +0 -89
  53. package/dist/optimizations/GlobalConnectionPool.js +0 -193
  54. package/dist/optimizations/GlobalConnectionPool.js.map +0 -1
  55. package/dist/optimizations/LazyMetadataLoader.d.ts +0 -75
  56. package/dist/optimizations/LazyMetadataLoader.js +0 -149
  57. package/dist/optimizations/LazyMetadataLoader.js.map +0 -1
  58. package/dist/optimizations/OptimizedMetadataRegistry.d.ts +0 -26
  59. package/dist/optimizations/OptimizedMetadataRegistry.js +0 -117
  60. package/dist/optimizations/OptimizedMetadataRegistry.js.map +0 -1
  61. package/dist/optimizations/OptimizedValidationEngine.d.ts +0 -73
  62. package/dist/optimizations/OptimizedValidationEngine.js +0 -141
  63. package/dist/optimizations/OptimizedValidationEngine.js.map +0 -1
  64. package/dist/optimizations/QueryCompiler.d.ts +0 -51
  65. package/dist/optimizations/QueryCompiler.js +0 -216
  66. package/dist/optimizations/QueryCompiler.js.map +0 -1
  67. package/dist/optimizations/SQLQueryOptimizer.d.ts +0 -96
  68. package/dist/optimizations/SQLQueryOptimizer.js +0 -265
  69. package/dist/optimizations/SQLQueryOptimizer.js.map +0 -1
  70. package/dist/optimizations/index.d.ts +0 -32
  71. package/dist/optimizations/index.js +0 -44
  72. package/dist/optimizations/index.js.map +0 -1
  73. package/dist/protocol.d.ts +0 -191
  74. package/dist/protocol.js +0 -272
  75. package/dist/protocol.js.map +0 -1
  76. package/dist/query/filter-translator.d.ts +0 -24
  77. package/dist/query/filter-translator.js +0 -38
  78. package/dist/query/filter-translator.js.map +0 -1
  79. package/dist/query/index.d.ts +0 -22
  80. package/dist/query/index.js +0 -39
  81. package/dist/query/index.js.map +0 -1
  82. package/dist/query/query-analyzer.d.ts +0 -186
  83. package/dist/query/query-analyzer.js +0 -348
  84. package/dist/query/query-analyzer.js.map +0 -1
  85. package/dist/query/query-builder.d.ts +0 -27
  86. package/dist/query/query-builder.js +0 -69
  87. package/dist/query/query-builder.js.map +0 -1
  88. package/dist/query/query-service.d.ts +0 -151
  89. package/dist/query/query-service.js +0 -272
  90. package/dist/query/query-service.js.map +0 -1
  91. package/src/ai/index.ts +0 -9
  92. package/src/ai/registry.ts +0 -81
  93. package/src/gateway.ts +0 -105
  94. package/src/optimizations/CompiledHookManager.ts +0 -193
  95. package/src/optimizations/DependencyGraph.ts +0 -255
  96. package/src/optimizations/GlobalConnectionPool.ts +0 -251
  97. package/src/optimizations/LazyMetadataLoader.ts +0 -180
  98. package/src/optimizations/OptimizedMetadataRegistry.ts +0 -132
  99. package/src/optimizations/OptimizedValidationEngine.ts +0 -172
  100. package/src/optimizations/QueryCompiler.ts +0 -242
  101. package/src/optimizations/SQLQueryOptimizer.ts +0 -329
  102. package/src/optimizations/index.ts +0 -34
  103. package/src/protocol.ts +0 -304
  104. package/src/query/filter-translator.ts +0 -41
  105. package/src/query/index.ts +0 -24
  106. package/src/query/query-analyzer.ts +0 -532
  107. package/src/query/query-builder.ts +0 -64
  108. package/src/query/query-service.ts +0 -397
  109. package/test/ai-registry.test.ts +0 -42
  110. package/test/app.test.ts +0 -578
  111. package/test/filter-syntax.test.ts +0 -233
  112. package/test/gateway.test.ts +0 -88
  113. package/test/protocol.test.ts +0 -143
  114. package/test/repository-validation.test.ts +0 -351
  115. 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
- });