@objectstack/objectql 4.0.3 → 4.0.5
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/dist/index.d.mts +500 -1111
- package/dist/index.d.ts +500 -1111
- package/dist/index.js +1364 -279
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1359 -279
- package/dist/index.mjs.map +1 -1
- package/package.json +32 -6
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -711
- package/src/engine.test.ts +0 -599
- package/src/engine.ts +0 -1548
- package/src/index.ts +0 -41
- package/src/kernel-factory.ts +0 -48
- package/src/metadata-facade.ts +0 -96
- package/src/plugin.integration.test.ts +0 -995
- package/src/plugin.ts +0 -534
- package/src/protocol-data.test.ts +0 -245
- package/src/protocol-discovery.test.ts +0 -213
- package/src/protocol-feed.test.ts +0 -303
- package/src/protocol-meta.test.ts +0 -440
- package/src/protocol.ts +0 -1235
- package/src/registry.test.ts +0 -494
- package/src/registry.ts +0 -716
- package/src/util.test.ts +0 -226
- package/src/util.ts +0 -219
- package/tsconfig.json +0 -10
|
@@ -1,995 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
4
|
-
import { ObjectKernel } from '@objectstack/core';
|
|
5
|
-
import { ObjectQLPlugin } from '../src/plugin';
|
|
6
|
-
import { SchemaRegistry } from '../src/registry';
|
|
7
|
-
import { ObjectSchema } from '@objectstack/spec/data';
|
|
8
|
-
|
|
9
|
-
describe('ObjectQLPlugin - Metadata Service Integration', () => {
|
|
10
|
-
let kernel: ObjectKernel;
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
SchemaRegistry.reset();
|
|
14
|
-
kernel = new ObjectKernel({ logLevel: 'silent' });
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('Simple Mode (ObjectQL-only)', () => {
|
|
18
|
-
it('should register objectql, data, and protocol services', async () => {
|
|
19
|
-
// Arrange
|
|
20
|
-
const plugin = new ObjectQLPlugin();
|
|
21
|
-
await kernel.use(plugin);
|
|
22
|
-
|
|
23
|
-
// Act
|
|
24
|
-
await kernel.bootstrap();
|
|
25
|
-
|
|
26
|
-
// Assert — ObjectQL no longer registers metadata (kernel provides fallback)
|
|
27
|
-
const objectql = kernel.getService('objectql');
|
|
28
|
-
expect(objectql).toBeDefined();
|
|
29
|
-
expect(kernel.getService('data')).toBeDefined();
|
|
30
|
-
expect(kernel.getService('protocol')).toBeDefined();
|
|
31
|
-
// metadata is provided by kernel's core fallback, not ObjectQL
|
|
32
|
-
const metadataService = kernel.getService('metadata');
|
|
33
|
-
expect(metadataService).toBeDefined();
|
|
34
|
-
expect((metadataService as any)._fallback).toBe(true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should serve in-memory metadata definitions', async () => {
|
|
38
|
-
// Arrange
|
|
39
|
-
const plugin = new ObjectQLPlugin();
|
|
40
|
-
await kernel.use(plugin);
|
|
41
|
-
await kernel.bootstrap();
|
|
42
|
-
|
|
43
|
-
const objectql = kernel.getService('objectql') as any;
|
|
44
|
-
const testObject: ObjectSchema = {
|
|
45
|
-
name: 'test_object',
|
|
46
|
-
label: 'Test Object',
|
|
47
|
-
fields: {
|
|
48
|
-
name: {
|
|
49
|
-
name: 'name',
|
|
50
|
-
label: 'Name',
|
|
51
|
-
type: 'text'
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
// Act - Register object programmatically via the SchemaRegistry API
|
|
57
|
-
objectql.registry.registerObject(testObject, 'test', 'test');
|
|
58
|
-
|
|
59
|
-
// Assert - Should be retrievable via registry (getAllObjects returns ServiceObject[])
|
|
60
|
-
const objects = objectql.registry.getAllObjects();
|
|
61
|
-
const fqns = objects.map((o: any) => o.name);
|
|
62
|
-
expect(fqns).toContain('test__test_object');
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
describe('Service Registration', () => {
|
|
67
|
-
it('should register manifest service', async () => {
|
|
68
|
-
// Arrange
|
|
69
|
-
const plugin = new ObjectQLPlugin();
|
|
70
|
-
await kernel.use(plugin);
|
|
71
|
-
|
|
72
|
-
// Act
|
|
73
|
-
await kernel.bootstrap();
|
|
74
|
-
|
|
75
|
-
// Assert
|
|
76
|
-
expect(kernel.getService('objectql')).toBeDefined();
|
|
77
|
-
expect(kernel.getService('data')).toBeDefined();
|
|
78
|
-
expect(kernel.getService('protocol')).toBeDefined();
|
|
79
|
-
expect(kernel.getService('manifest')).toBeDefined();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should respect existing metadata service', async () => {
|
|
83
|
-
// Arrange - Register a mock metadata service first
|
|
84
|
-
const mockMetadataService = {
|
|
85
|
-
load: async () => null,
|
|
86
|
-
loadMany: async () => [],
|
|
87
|
-
save: async () => ({ success: true }),
|
|
88
|
-
exists: async () => false,
|
|
89
|
-
list: async () => []
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
await kernel.use({
|
|
93
|
-
name: 'mock-metadata',
|
|
94
|
-
type: 'test',
|
|
95
|
-
version: '1.0.0',
|
|
96
|
-
init: async (ctx) => {
|
|
97
|
-
ctx.registerService('metadata', mockMetadataService);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const plugin = new ObjectQLPlugin();
|
|
102
|
-
await kernel.use(plugin);
|
|
103
|
-
|
|
104
|
-
// Act
|
|
105
|
-
await kernel.bootstrap();
|
|
106
|
-
|
|
107
|
-
// Assert - metadata service should be the mock, not ObjectQL
|
|
108
|
-
const metadataService = kernel.getService('metadata');
|
|
109
|
-
expect(metadataService).toBe(mockMetadataService);
|
|
110
|
-
|
|
111
|
-
const objectql = kernel.getService('objectql');
|
|
112
|
-
expect(metadataService).not.toBe(objectql);
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('Driver and App Discovery', () => {
|
|
117
|
-
it('should discover and register drivers from kernel services', async () => {
|
|
118
|
-
// Arrange
|
|
119
|
-
const mockDriver = {
|
|
120
|
-
name: 'mock-driver',
|
|
121
|
-
connect: async () => {},
|
|
122
|
-
disconnect: async () => {},
|
|
123
|
-
query: async () => ({ rows: [] }),
|
|
124
|
-
insert: async () => ({ id: '1' }),
|
|
125
|
-
update: async () => ({ count: 1 }),
|
|
126
|
-
delete: async () => ({ count: 1 })
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
await kernel.use({
|
|
130
|
-
name: 'mock-driver-plugin',
|
|
131
|
-
type: 'driver',
|
|
132
|
-
version: '1.0.0',
|
|
133
|
-
init: async (ctx) => {
|
|
134
|
-
ctx.registerService('driver.mock', mockDriver);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const plugin = new ObjectQLPlugin();
|
|
139
|
-
await kernel.use(plugin);
|
|
140
|
-
|
|
141
|
-
// Act
|
|
142
|
-
await kernel.bootstrap();
|
|
143
|
-
|
|
144
|
-
// Assert
|
|
145
|
-
const objectql = kernel.getService('objectql') as any;
|
|
146
|
-
expect(objectql.drivers?.has('mock-driver')).toBe(true);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should register apps via manifest service', async () => {
|
|
150
|
-
// Arrange
|
|
151
|
-
const plugin = new ObjectQLPlugin();
|
|
152
|
-
await kernel.use(plugin);
|
|
153
|
-
|
|
154
|
-
// Plugin that uses the manifest service directly
|
|
155
|
-
await kernel.use({
|
|
156
|
-
name: 'mock-app-plugin',
|
|
157
|
-
type: 'app',
|
|
158
|
-
version: '1.0.0',
|
|
159
|
-
dependencies: ['com.objectstack.engine.objectql'],
|
|
160
|
-
init: async (ctx) => {
|
|
161
|
-
ctx.getService<{ register(m: any): void }>('manifest').register({
|
|
162
|
-
id: 'test-app',
|
|
163
|
-
name: 'test_app',
|
|
164
|
-
version: '1.0.0',
|
|
165
|
-
type: 'app',
|
|
166
|
-
apps: [{ name: 'Test App' }],
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Act
|
|
172
|
-
await kernel.bootstrap();
|
|
173
|
-
|
|
174
|
-
// Assert
|
|
175
|
-
const objectql = kernel.getService('objectql') as any;
|
|
176
|
-
expect(objectql.registry).toBeDefined();
|
|
177
|
-
const apps = objectql.registry.getAllApps();
|
|
178
|
-
expect(apps.some((a: any) => a.name === 'Test App')).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('should register manifests from start() phase via manifest service', async () => {
|
|
182
|
-
// Arrange — simulates SetupPlugin's pattern (registers in start, not init)
|
|
183
|
-
const plugin = new ObjectQLPlugin();
|
|
184
|
-
await kernel.use(plugin);
|
|
185
|
-
|
|
186
|
-
await kernel.use({
|
|
187
|
-
name: 'late-registerer',
|
|
188
|
-
type: 'standard',
|
|
189
|
-
version: '1.0.0',
|
|
190
|
-
dependencies: ['com.objectstack.engine.objectql'],
|
|
191
|
-
init: async () => {},
|
|
192
|
-
start: async (ctx) => {
|
|
193
|
-
ctx.getService<{ register(m: any): void }>('manifest').register({
|
|
194
|
-
id: 'late-app',
|
|
195
|
-
name: 'late_app',
|
|
196
|
-
version: '1.0.0',
|
|
197
|
-
type: 'plugin',
|
|
198
|
-
apps: [{ name: 'Late App' }],
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Act
|
|
204
|
-
await kernel.bootstrap();
|
|
205
|
-
|
|
206
|
-
// Assert
|
|
207
|
-
const objectql = kernel.getService('objectql') as any;
|
|
208
|
-
const apps = objectql.registry.getAllApps();
|
|
209
|
-
expect(apps.some((a: any) => a.name === 'Late App')).toBe(true);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it('should still discover apps registered via legacy app.* convention', async () => {
|
|
213
|
-
// Arrange — legacy pattern for backward compatibility
|
|
214
|
-
const mockApp = {
|
|
215
|
-
manifest: {
|
|
216
|
-
id: 'test-app',
|
|
217
|
-
name: 'test_app',
|
|
218
|
-
version: '1.0.0',
|
|
219
|
-
type: 'app'
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
await kernel.use({
|
|
224
|
-
name: 'mock-app-plugin',
|
|
225
|
-
type: 'app',
|
|
226
|
-
version: '1.0.0',
|
|
227
|
-
init: async (ctx) => {
|
|
228
|
-
ctx.registerService('app.test', mockApp.manifest);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
const plugin = new ObjectQLPlugin();
|
|
233
|
-
await kernel.use(plugin);
|
|
234
|
-
|
|
235
|
-
// Act
|
|
236
|
-
await kernel.bootstrap();
|
|
237
|
-
|
|
238
|
-
// Assert — legacy pattern still works
|
|
239
|
-
const objectql = kernel.getService('objectql') as any;
|
|
240
|
-
expect(objectql.registry).toBeDefined();
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe('Metadata Sync from External Service', () => {
|
|
245
|
-
it('should load metadata from external service into ObjectQL registry', async () => {
|
|
246
|
-
// Arrange - Mock external metadata service with test data
|
|
247
|
-
const testObject: ObjectSchema = {
|
|
248
|
-
name: 'external_object',
|
|
249
|
-
label: 'External Object',
|
|
250
|
-
fields: {
|
|
251
|
-
title: {
|
|
252
|
-
name: 'title',
|
|
253
|
-
label: 'Title',
|
|
254
|
-
type: 'text'
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const mockMetadataService = {
|
|
260
|
-
load: async (type: string, name: string) => {
|
|
261
|
-
if (type === 'object' && name === 'external_object') {
|
|
262
|
-
return testObject;
|
|
263
|
-
}
|
|
264
|
-
return null;
|
|
265
|
-
},
|
|
266
|
-
loadMany: async (type: string) => {
|
|
267
|
-
if (type === 'object') {
|
|
268
|
-
return [testObject];
|
|
269
|
-
}
|
|
270
|
-
return [];
|
|
271
|
-
},
|
|
272
|
-
save: async () => ({ success: true, path: '/test' }),
|
|
273
|
-
exists: async () => false,
|
|
274
|
-
list: async () => []
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
// Register mock metadata service BEFORE ObjectQL
|
|
278
|
-
await kernel.use({
|
|
279
|
-
name: 'mock-metadata',
|
|
280
|
-
type: 'metadata',
|
|
281
|
-
version: '1.0.0',
|
|
282
|
-
init: async (ctx) => {
|
|
283
|
-
ctx.registerService('metadata', mockMetadataService);
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const plugin = new ObjectQLPlugin();
|
|
288
|
-
await kernel.use(plugin);
|
|
289
|
-
|
|
290
|
-
// Act
|
|
291
|
-
await kernel.bootstrap();
|
|
292
|
-
|
|
293
|
-
// Assert - Metadata should be synced
|
|
294
|
-
const metadataService = kernel.getService('metadata');
|
|
295
|
-
expect(metadataService).toBe(mockMetadataService);
|
|
296
|
-
|
|
297
|
-
const objectql = kernel.getService('objectql') as any;
|
|
298
|
-
expect(objectql.registry).toBeDefined();
|
|
299
|
-
|
|
300
|
-
// Note: The actual sync happens in start phase
|
|
301
|
-
// We can verify by checking if ObjectQL detected external service
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe('Schema Sync on Start', () => {
|
|
306
|
-
it('should call syncSchema for each registered object after init', async () => {
|
|
307
|
-
// Arrange - driver that tracks syncSchema calls
|
|
308
|
-
const synced: Array<{ object: string; schema: any }> = [];
|
|
309
|
-
const mockDriver = {
|
|
310
|
-
name: 'sync-driver',
|
|
311
|
-
version: '1.0.0',
|
|
312
|
-
connect: async () => {},
|
|
313
|
-
disconnect: async () => {},
|
|
314
|
-
find: async () => [],
|
|
315
|
-
findOne: async () => null,
|
|
316
|
-
create: async (_o: string, d: any) => d,
|
|
317
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
318
|
-
delete: async () => true,
|
|
319
|
-
syncSchema: async (object: string, schema: any) => {
|
|
320
|
-
synced.push({ object, schema });
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
// Plugin that registers objects and a driver
|
|
325
|
-
await kernel.use({
|
|
326
|
-
name: 'mock-driver-plugin',
|
|
327
|
-
type: 'driver',
|
|
328
|
-
version: '1.0.0',
|
|
329
|
-
init: async (ctx) => {
|
|
330
|
-
ctx.registerService('driver.sync', mockDriver);
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const appManifest = {
|
|
335
|
-
id: 'com.test.auth',
|
|
336
|
-
name: 'auth',
|
|
337
|
-
namespace: 'sys',
|
|
338
|
-
version: '1.0.0',
|
|
339
|
-
objects: [
|
|
340
|
-
{
|
|
341
|
-
name: 'user',
|
|
342
|
-
label: 'User',
|
|
343
|
-
fields: {
|
|
344
|
-
name: { name: 'name', label: 'Name', type: 'text' },
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
name: 'role',
|
|
349
|
-
label: 'Role',
|
|
350
|
-
fields: {
|
|
351
|
-
title: { name: 'title', label: 'Title', type: 'text' },
|
|
352
|
-
},
|
|
353
|
-
},
|
|
354
|
-
],
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
await kernel.use({
|
|
358
|
-
name: 'mock-app-plugin',
|
|
359
|
-
type: 'app',
|
|
360
|
-
version: '1.0.0',
|
|
361
|
-
init: async (ctx) => {
|
|
362
|
-
ctx.registerService('app.auth', appManifest);
|
|
363
|
-
},
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
const plugin = new ObjectQLPlugin();
|
|
367
|
-
await kernel.use(plugin);
|
|
368
|
-
|
|
369
|
-
// Act
|
|
370
|
-
await kernel.bootstrap();
|
|
371
|
-
|
|
372
|
-
// Assert - syncSchema should have been called for each object
|
|
373
|
-
const syncedObjects = synced.map((s) => s.object).sort();
|
|
374
|
-
expect(syncedObjects).toContain('sys__user');
|
|
375
|
-
expect(syncedObjects).toContain('sys__role');
|
|
376
|
-
expect(synced.length).toBeGreaterThanOrEqual(2);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('should tolerate drivers without syncSchema', async () => {
|
|
380
|
-
// Arrange - driver without syncSchema
|
|
381
|
-
const mockDriver = {
|
|
382
|
-
name: 'no-sync-driver',
|
|
383
|
-
version: '1.0.0',
|
|
384
|
-
connect: async () => {},
|
|
385
|
-
disconnect: async () => {},
|
|
386
|
-
find: async () => [],
|
|
387
|
-
findOne: async () => null,
|
|
388
|
-
create: async (_o: string, d: any) => d,
|
|
389
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
390
|
-
delete: async () => true,
|
|
391
|
-
// No syncSchema method
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
await kernel.use({
|
|
395
|
-
name: 'mock-driver-plugin',
|
|
396
|
-
type: 'driver',
|
|
397
|
-
version: '1.0.0',
|
|
398
|
-
init: async (ctx) => {
|
|
399
|
-
ctx.registerService('driver.nosync', mockDriver);
|
|
400
|
-
},
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const appManifest = {
|
|
404
|
-
id: 'com.test.simple',
|
|
405
|
-
name: 'simple',
|
|
406
|
-
namespace: 'test',
|
|
407
|
-
version: '1.0.0',
|
|
408
|
-
objects: [
|
|
409
|
-
{
|
|
410
|
-
name: 'item',
|
|
411
|
-
label: 'Item',
|
|
412
|
-
fields: {
|
|
413
|
-
title: { name: 'title', label: 'Title', type: 'text' },
|
|
414
|
-
},
|
|
415
|
-
},
|
|
416
|
-
],
|
|
417
|
-
};
|
|
418
|
-
|
|
419
|
-
await kernel.use({
|
|
420
|
-
name: 'mock-app-plugin',
|
|
421
|
-
type: 'app',
|
|
422
|
-
version: '1.0.0',
|
|
423
|
-
init: async (ctx) => {
|
|
424
|
-
ctx.registerService('app.simple', appManifest);
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
const plugin = new ObjectQLPlugin();
|
|
429
|
-
await kernel.use(plugin);
|
|
430
|
-
|
|
431
|
-
// Act & Assert - should not throw
|
|
432
|
-
await expect(kernel.bootstrap()).resolves.not.toThrow();
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
it('should tolerate syncSchema failures per object without aborting', async () => {
|
|
436
|
-
// Arrange - driver where syncSchema fails for one object
|
|
437
|
-
const synced: string[] = [];
|
|
438
|
-
const mockDriver = {
|
|
439
|
-
name: 'fail-driver',
|
|
440
|
-
version: '1.0.0',
|
|
441
|
-
connect: async () => {},
|
|
442
|
-
disconnect: async () => {},
|
|
443
|
-
find: async () => [],
|
|
444
|
-
findOne: async () => null,
|
|
445
|
-
create: async (_o: string, d: any) => d,
|
|
446
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
447
|
-
delete: async () => true,
|
|
448
|
-
syncSchema: async (object: string) => {
|
|
449
|
-
if (object.includes('bad')) {
|
|
450
|
-
throw new Error('sync failed for bad object');
|
|
451
|
-
}
|
|
452
|
-
synced.push(object);
|
|
453
|
-
},
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
await kernel.use({
|
|
457
|
-
name: 'mock-driver-plugin',
|
|
458
|
-
type: 'driver',
|
|
459
|
-
version: '1.0.0',
|
|
460
|
-
init: async (ctx) => {
|
|
461
|
-
ctx.registerService('driver.fail', mockDriver);
|
|
462
|
-
},
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
const appManifest = {
|
|
466
|
-
id: 'com.test.mixed',
|
|
467
|
-
name: 'mixed',
|
|
468
|
-
namespace: 'mix',
|
|
469
|
-
version: '1.0.0',
|
|
470
|
-
objects: [
|
|
471
|
-
{
|
|
472
|
-
name: 'good',
|
|
473
|
-
label: 'Good',
|
|
474
|
-
fields: { a: { name: 'a', label: 'A', type: 'text' } },
|
|
475
|
-
},
|
|
476
|
-
{
|
|
477
|
-
name: 'bad',
|
|
478
|
-
label: 'Bad',
|
|
479
|
-
fields: { b: { name: 'b', label: 'B', type: 'text' } },
|
|
480
|
-
},
|
|
481
|
-
],
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
await kernel.use({
|
|
485
|
-
name: 'mock-app-plugin',
|
|
486
|
-
type: 'app',
|
|
487
|
-
version: '1.0.0',
|
|
488
|
-
init: async (ctx) => {
|
|
489
|
-
ctx.registerService('app.mixed', appManifest);
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
const plugin = new ObjectQLPlugin();
|
|
494
|
-
await kernel.use(plugin);
|
|
495
|
-
|
|
496
|
-
// Act - should not throw despite one object failing
|
|
497
|
-
await expect(kernel.bootstrap()).resolves.not.toThrow();
|
|
498
|
-
|
|
499
|
-
// Assert - the good object should still have been synced
|
|
500
|
-
expect(synced).toContain('mix__good');
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
it('should work without any registered objects', async () => {
|
|
504
|
-
// Arrange - no objects, just a driver
|
|
505
|
-
const mockDriver = {
|
|
506
|
-
name: 'empty-driver',
|
|
507
|
-
version: '1.0.0',
|
|
508
|
-
connect: async () => {},
|
|
509
|
-
disconnect: async () => {},
|
|
510
|
-
find: async () => [],
|
|
511
|
-
findOne: async () => null,
|
|
512
|
-
create: async (_o: string, d: any) => d,
|
|
513
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
514
|
-
delete: async () => true,
|
|
515
|
-
syncSchema: async () => {},
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
await kernel.use({
|
|
519
|
-
name: 'mock-driver-plugin',
|
|
520
|
-
type: 'driver',
|
|
521
|
-
version: '1.0.0',
|
|
522
|
-
init: async (ctx) => {
|
|
523
|
-
ctx.registerService('driver.empty', mockDriver);
|
|
524
|
-
},
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
const plugin = new ObjectQLPlugin();
|
|
528
|
-
await kernel.use(plugin);
|
|
529
|
-
|
|
530
|
-
// Act & Assert - should not throw
|
|
531
|
-
await expect(kernel.bootstrap()).resolves.not.toThrow();
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
it('should use tableName for syncSchema when objects have auto-derived tableName', async () => {
|
|
535
|
-
// Arrange - driver that tracks syncSchema calls
|
|
536
|
-
const synced: Array<{ object: string; schema: any }> = [];
|
|
537
|
-
const mockDriver = {
|
|
538
|
-
name: 'table-name-driver',
|
|
539
|
-
version: '1.0.0',
|
|
540
|
-
connect: async () => {},
|
|
541
|
-
disconnect: async () => {},
|
|
542
|
-
find: async () => [],
|
|
543
|
-
findOne: async () => null,
|
|
544
|
-
create: async (_o: string, d: any) => d,
|
|
545
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
546
|
-
delete: async () => true,
|
|
547
|
-
syncSchema: async (object: string, schema: any) => {
|
|
548
|
-
synced.push({ object, schema });
|
|
549
|
-
},
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
await kernel.use({
|
|
553
|
-
name: 'mock-driver-plugin',
|
|
554
|
-
type: 'driver',
|
|
555
|
-
version: '1.0.0',
|
|
556
|
-
init: async (ctx) => {
|
|
557
|
-
ctx.registerService('driver.table-name', mockDriver);
|
|
558
|
-
},
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
// Objects with tableName (simulating ObjectSchema.create() output)
|
|
562
|
-
const appManifest = {
|
|
563
|
-
id: 'com.test.system',
|
|
564
|
-
name: 'system',
|
|
565
|
-
namespace: 'sys',
|
|
566
|
-
version: '1.0.0',
|
|
567
|
-
objects: [
|
|
568
|
-
{
|
|
569
|
-
name: 'user',
|
|
570
|
-
label: 'User',
|
|
571
|
-
namespace: 'sys',
|
|
572
|
-
tableName: 'sys_user',
|
|
573
|
-
fields: {
|
|
574
|
-
email: { name: 'email', label: 'Email', type: 'text' },
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
name: 'session',
|
|
579
|
-
label: 'Session',
|
|
580
|
-
namespace: 'sys',
|
|
581
|
-
tableName: 'sys_session',
|
|
582
|
-
fields: {
|
|
583
|
-
token: { name: 'token', label: 'Token', type: 'text' },
|
|
584
|
-
},
|
|
585
|
-
},
|
|
586
|
-
],
|
|
587
|
-
};
|
|
588
|
-
|
|
589
|
-
await kernel.use({
|
|
590
|
-
name: 'mock-app-plugin',
|
|
591
|
-
type: 'app',
|
|
592
|
-
version: '1.0.0',
|
|
593
|
-
init: async (ctx) => {
|
|
594
|
-
ctx.registerService('app.system', appManifest);
|
|
595
|
-
},
|
|
596
|
-
});
|
|
597
|
-
|
|
598
|
-
const plugin = new ObjectQLPlugin();
|
|
599
|
-
await kernel.use(plugin);
|
|
600
|
-
|
|
601
|
-
// Act
|
|
602
|
-
await kernel.bootstrap();
|
|
603
|
-
|
|
604
|
-
// Assert - syncSchema should use tableName (single underscore) not FQN (double underscore)
|
|
605
|
-
const syncedNames = synced.map((s) => s.object).sort();
|
|
606
|
-
expect(syncedNames).toContain('sys_user');
|
|
607
|
-
expect(syncedNames).toContain('sys_session');
|
|
608
|
-
// Should NOT contain double-underscore FQN
|
|
609
|
-
expect(syncedNames).not.toContain('sys__user');
|
|
610
|
-
expect(syncedNames).not.toContain('sys__session');
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
it('should use syncSchemasBatch when driver supports batchSchemaSync', async () => {
|
|
614
|
-
// Arrange - driver that supports batch schema sync
|
|
615
|
-
const batchCalls: Array<{ object: string; schema: any }[]> = [];
|
|
616
|
-
const singleCalls: Array<{ object: string; schema: any }> = [];
|
|
617
|
-
const mockDriver = {
|
|
618
|
-
name: 'batch-driver',
|
|
619
|
-
version: '1.0.0',
|
|
620
|
-
supports: { batchSchemaSync: true },
|
|
621
|
-
connect: async () => {},
|
|
622
|
-
disconnect: async () => {},
|
|
623
|
-
find: async () => [],
|
|
624
|
-
findOne: async () => null,
|
|
625
|
-
create: async (_o: string, d: any) => d,
|
|
626
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
627
|
-
delete: async () => true,
|
|
628
|
-
syncSchema: async (object: string, schema: any) => {
|
|
629
|
-
singleCalls.push({ object, schema });
|
|
630
|
-
},
|
|
631
|
-
syncSchemasBatch: async (schemas: Array<{ object: string; schema: any }>) => {
|
|
632
|
-
batchCalls.push(schemas);
|
|
633
|
-
},
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
await kernel.use({
|
|
637
|
-
name: 'mock-batch-driver-plugin',
|
|
638
|
-
type: 'driver',
|
|
639
|
-
version: '1.0.0',
|
|
640
|
-
init: async (ctx) => {
|
|
641
|
-
ctx.registerService('driver.batch', mockDriver);
|
|
642
|
-
},
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
const appManifest = {
|
|
646
|
-
id: 'com.test.batchapp',
|
|
647
|
-
name: 'batchapp',
|
|
648
|
-
namespace: 'bat',
|
|
649
|
-
version: '1.0.0',
|
|
650
|
-
objects: [
|
|
651
|
-
{
|
|
652
|
-
name: 'alpha',
|
|
653
|
-
label: 'Alpha',
|
|
654
|
-
fields: { a: { name: 'a', label: 'A', type: 'text' } },
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
name: 'beta',
|
|
658
|
-
label: 'Beta',
|
|
659
|
-
fields: { b: { name: 'b', label: 'B', type: 'text' } },
|
|
660
|
-
},
|
|
661
|
-
{
|
|
662
|
-
name: 'gamma',
|
|
663
|
-
label: 'Gamma',
|
|
664
|
-
fields: { c: { name: 'c', label: 'C', type: 'text' } },
|
|
665
|
-
},
|
|
666
|
-
],
|
|
667
|
-
};
|
|
668
|
-
|
|
669
|
-
await kernel.use({
|
|
670
|
-
name: 'mock-batch-app-plugin',
|
|
671
|
-
type: 'app',
|
|
672
|
-
version: '1.0.0',
|
|
673
|
-
init: async (ctx) => {
|
|
674
|
-
ctx.registerService('app.batchapp', appManifest);
|
|
675
|
-
},
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
const plugin = new ObjectQLPlugin();
|
|
679
|
-
await kernel.use(plugin);
|
|
680
|
-
|
|
681
|
-
// Act
|
|
682
|
-
await kernel.bootstrap();
|
|
683
|
-
|
|
684
|
-
// Assert - syncSchemasBatch should have been called once with all objects
|
|
685
|
-
expect(batchCalls.length).toBe(1);
|
|
686
|
-
const batchedObjects = batchCalls[0].map((s) => s.object).sort();
|
|
687
|
-
expect(batchedObjects).toContain('bat__alpha');
|
|
688
|
-
expect(batchedObjects).toContain('bat__beta');
|
|
689
|
-
expect(batchedObjects).toContain('bat__gamma');
|
|
690
|
-
// syncSchema should NOT have been called individually
|
|
691
|
-
expect(singleCalls.length).toBe(0);
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
it('should fall back to sequential syncSchema when batch fails', async () => {
|
|
695
|
-
// Arrange - driver where batch fails
|
|
696
|
-
const singleCalls: Array<{ object: string; schema: any }> = [];
|
|
697
|
-
const mockDriver = {
|
|
698
|
-
name: 'fallback-driver',
|
|
699
|
-
version: '1.0.0',
|
|
700
|
-
supports: { batchSchemaSync: true },
|
|
701
|
-
connect: async () => {},
|
|
702
|
-
disconnect: async () => {},
|
|
703
|
-
find: async () => [],
|
|
704
|
-
findOne: async () => null,
|
|
705
|
-
create: async (_o: string, d: any) => d,
|
|
706
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
707
|
-
delete: async () => true,
|
|
708
|
-
syncSchema: async (object: string, schema: any) => {
|
|
709
|
-
singleCalls.push({ object, schema });
|
|
710
|
-
},
|
|
711
|
-
syncSchemasBatch: async () => {
|
|
712
|
-
throw new Error('batch not supported at runtime');
|
|
713
|
-
},
|
|
714
|
-
};
|
|
715
|
-
|
|
716
|
-
await kernel.use({
|
|
717
|
-
name: 'mock-fallback-driver-plugin',
|
|
718
|
-
type: 'driver',
|
|
719
|
-
version: '1.0.0',
|
|
720
|
-
init: async (ctx) => {
|
|
721
|
-
ctx.registerService('driver.fallback', mockDriver);
|
|
722
|
-
},
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
const appManifest = {
|
|
726
|
-
id: 'com.test.fallback',
|
|
727
|
-
name: 'fallback',
|
|
728
|
-
namespace: 'fb',
|
|
729
|
-
version: '1.0.0',
|
|
730
|
-
objects: [
|
|
731
|
-
{
|
|
732
|
-
name: 'one',
|
|
733
|
-
label: 'One',
|
|
734
|
-
fields: { x: { name: 'x', label: 'X', type: 'text' } },
|
|
735
|
-
},
|
|
736
|
-
{
|
|
737
|
-
name: 'two',
|
|
738
|
-
label: 'Two',
|
|
739
|
-
fields: { y: { name: 'y', label: 'Y', type: 'text' } },
|
|
740
|
-
},
|
|
741
|
-
],
|
|
742
|
-
};
|
|
743
|
-
|
|
744
|
-
await kernel.use({
|
|
745
|
-
name: 'mock-fallback-app-plugin',
|
|
746
|
-
type: 'app',
|
|
747
|
-
version: '1.0.0',
|
|
748
|
-
init: async (ctx) => {
|
|
749
|
-
ctx.registerService('app.fallback', appManifest);
|
|
750
|
-
},
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
const plugin = new ObjectQLPlugin();
|
|
754
|
-
await kernel.use(plugin);
|
|
755
|
-
|
|
756
|
-
// Act - should not throw
|
|
757
|
-
await expect(kernel.bootstrap()).resolves.not.toThrow();
|
|
758
|
-
|
|
759
|
-
// Assert - sequential fallback should have been used
|
|
760
|
-
const syncedObjects = singleCalls.map((s) => s.object).sort();
|
|
761
|
-
expect(syncedObjects).toContain('fb__one');
|
|
762
|
-
expect(syncedObjects).toContain('fb__two');
|
|
763
|
-
});
|
|
764
|
-
|
|
765
|
-
it('should not use batch when driver does not support batchSchemaSync', async () => {
|
|
766
|
-
// Arrange - driver without batch support (but with syncSchema)
|
|
767
|
-
const singleCalls: string[] = [];
|
|
768
|
-
const mockDriver = {
|
|
769
|
-
name: 'nobatch-driver',
|
|
770
|
-
version: '1.0.0',
|
|
771
|
-
connect: async () => {},
|
|
772
|
-
disconnect: async () => {},
|
|
773
|
-
find: async () => [],
|
|
774
|
-
findOne: async () => null,
|
|
775
|
-
create: async (_o: string, d: any) => d,
|
|
776
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
777
|
-
delete: async () => true,
|
|
778
|
-
syncSchema: async (object: string) => {
|
|
779
|
-
singleCalls.push(object);
|
|
780
|
-
},
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
await kernel.use({
|
|
784
|
-
name: 'mock-nobatch-driver-plugin',
|
|
785
|
-
type: 'driver',
|
|
786
|
-
version: '1.0.0',
|
|
787
|
-
init: async (ctx) => {
|
|
788
|
-
ctx.registerService('driver.nobatch', mockDriver);
|
|
789
|
-
},
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
const appManifest = {
|
|
793
|
-
id: 'com.test.nobatch',
|
|
794
|
-
name: 'nobatch',
|
|
795
|
-
namespace: 'nb',
|
|
796
|
-
version: '1.0.0',
|
|
797
|
-
objects: [
|
|
798
|
-
{
|
|
799
|
-
name: 'item',
|
|
800
|
-
label: 'Item',
|
|
801
|
-
fields: { z: { name: 'z', label: 'Z', type: 'text' } },
|
|
802
|
-
},
|
|
803
|
-
],
|
|
804
|
-
};
|
|
805
|
-
|
|
806
|
-
await kernel.use({
|
|
807
|
-
name: 'mock-nobatch-app-plugin',
|
|
808
|
-
type: 'app',
|
|
809
|
-
version: '1.0.0',
|
|
810
|
-
init: async (ctx) => {
|
|
811
|
-
ctx.registerService('app.nobatch', appManifest);
|
|
812
|
-
},
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
const plugin = new ObjectQLPlugin();
|
|
816
|
-
await kernel.use(plugin);
|
|
817
|
-
|
|
818
|
-
// Act
|
|
819
|
-
await kernel.bootstrap();
|
|
820
|
-
|
|
821
|
-
// Assert - sequential syncSchema should have been used
|
|
822
|
-
expect(singleCalls).toContain('nb__item');
|
|
823
|
-
});
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
describe('Cold-Start Metadata Restoration', () => {
|
|
827
|
-
it('should restore metadata from sys_metadata via protocol.loadMetaFromDb on start', async () => {
|
|
828
|
-
// Arrange — a driver whose find() returns persisted metadata records
|
|
829
|
-
const findCalls: Array<{ object: string; query: any }> = [];
|
|
830
|
-
const mockDriver = {
|
|
831
|
-
name: 'restore-driver',
|
|
832
|
-
version: '1.0.0',
|
|
833
|
-
connect: async () => {},
|
|
834
|
-
disconnect: async () => {},
|
|
835
|
-
find: async (object: string, query: any) => {
|
|
836
|
-
findCalls.push({ object, query });
|
|
837
|
-
if (object === 'sys_metadata') {
|
|
838
|
-
return [
|
|
839
|
-
{
|
|
840
|
-
id: '1',
|
|
841
|
-
type: 'apps',
|
|
842
|
-
name: 'custom_crm',
|
|
843
|
-
state: 'active',
|
|
844
|
-
metadata: JSON.stringify({ name: 'custom_crm', label: 'Custom CRM' }),
|
|
845
|
-
},
|
|
846
|
-
{
|
|
847
|
-
id: '2',
|
|
848
|
-
type: 'object',
|
|
849
|
-
name: 'invoice',
|
|
850
|
-
state: 'active',
|
|
851
|
-
metadata: JSON.stringify({
|
|
852
|
-
name: 'invoice',
|
|
853
|
-
label: 'Invoice',
|
|
854
|
-
fields: { amount: { name: 'amount', label: 'Amount', type: 'number' } },
|
|
855
|
-
}),
|
|
856
|
-
packageId: 'user_pkg',
|
|
857
|
-
},
|
|
858
|
-
];
|
|
859
|
-
}
|
|
860
|
-
return [];
|
|
861
|
-
},
|
|
862
|
-
findOne: async () => null,
|
|
863
|
-
create: async (_o: string, d: any) => d,
|
|
864
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
865
|
-
delete: async () => true,
|
|
866
|
-
syncSchema: async () => {},
|
|
867
|
-
};
|
|
868
|
-
|
|
869
|
-
await kernel.use({
|
|
870
|
-
name: 'mock-restore-driver',
|
|
871
|
-
type: 'driver',
|
|
872
|
-
version: '1.0.0',
|
|
873
|
-
init: async (ctx) => {
|
|
874
|
-
ctx.registerService('driver.restore', mockDriver);
|
|
875
|
-
},
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
const plugin = new ObjectQLPlugin();
|
|
879
|
-
await kernel.use(plugin);
|
|
880
|
-
|
|
881
|
-
// Act
|
|
882
|
-
await kernel.bootstrap();
|
|
883
|
-
|
|
884
|
-
// Assert — sys_metadata should have been queried
|
|
885
|
-
const metaQuery = findCalls.find((c) => c.object === 'sys_metadata');
|
|
886
|
-
expect(metaQuery).toBeDefined();
|
|
887
|
-
expect(metaQuery!.query.where).toEqual({ state: 'active' });
|
|
888
|
-
|
|
889
|
-
// Assert — items should be restored into the registry
|
|
890
|
-
const registry = (kernel.getService('objectql') as any).registry;
|
|
891
|
-
expect(registry.getAllApps()).toContainEqual({
|
|
892
|
-
name: 'custom_crm',
|
|
893
|
-
label: 'Custom CRM',
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
it('should not throw when protocol.loadMetaFromDb fails (graceful degradation)', async () => {
|
|
898
|
-
// Arrange — driver that throws on find('sys_metadata')
|
|
899
|
-
const mockDriver = {
|
|
900
|
-
name: 'failing-db-driver',
|
|
901
|
-
version: '1.0.0',
|
|
902
|
-
connect: async () => {},
|
|
903
|
-
disconnect: async () => {},
|
|
904
|
-
find: async (object: string) => {
|
|
905
|
-
if (object === 'sys_metadata') {
|
|
906
|
-
throw new Error('SQLITE_ERROR: no such table: sys_metadata');
|
|
907
|
-
}
|
|
908
|
-
return [];
|
|
909
|
-
},
|
|
910
|
-
findOne: async () => null,
|
|
911
|
-
create: async (_o: string, d: any) => d,
|
|
912
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
913
|
-
delete: async () => true,
|
|
914
|
-
syncSchema: async () => {},
|
|
915
|
-
};
|
|
916
|
-
|
|
917
|
-
await kernel.use({
|
|
918
|
-
name: 'mock-fail-driver',
|
|
919
|
-
type: 'driver',
|
|
920
|
-
version: '1.0.0',
|
|
921
|
-
init: async (ctx) => {
|
|
922
|
-
ctx.registerService('driver.faildb', mockDriver);
|
|
923
|
-
},
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
const plugin = new ObjectQLPlugin();
|
|
927
|
-
await kernel.use(plugin);
|
|
928
|
-
|
|
929
|
-
// Act & Assert — should not throw
|
|
930
|
-
await expect(kernel.bootstrap()).resolves.not.toThrow();
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
it('should restore metadata before syncRegisteredSchemas so restored objects get table sync', async () => {
|
|
934
|
-
// Arrange — track the order of operations
|
|
935
|
-
const operations: string[] = [];
|
|
936
|
-
const mockDriver = {
|
|
937
|
-
name: 'order-driver',
|
|
938
|
-
version: '1.0.0',
|
|
939
|
-
connect: async () => {},
|
|
940
|
-
disconnect: async () => {},
|
|
941
|
-
find: async (object: string) => {
|
|
942
|
-
if (object === 'sys_metadata') {
|
|
943
|
-
operations.push('loadMetaFromDb');
|
|
944
|
-
return [
|
|
945
|
-
{
|
|
946
|
-
id: '1',
|
|
947
|
-
type: 'object',
|
|
948
|
-
name: 'restored_obj',
|
|
949
|
-
state: 'active',
|
|
950
|
-
metadata: JSON.stringify({
|
|
951
|
-
name: 'restored_obj',
|
|
952
|
-
label: 'Restored Object',
|
|
953
|
-
fields: { title: { name: 'title', label: 'Title', type: 'text' } },
|
|
954
|
-
}),
|
|
955
|
-
packageId: 'user_pkg',
|
|
956
|
-
},
|
|
957
|
-
];
|
|
958
|
-
}
|
|
959
|
-
return [];
|
|
960
|
-
},
|
|
961
|
-
findOne: async () => null,
|
|
962
|
-
create: async (_o: string, d: any) => d,
|
|
963
|
-
update: async (_o: string, _i: any, d: any) => d,
|
|
964
|
-
delete: async () => true,
|
|
965
|
-
syncSchema: async (object: string) => {
|
|
966
|
-
operations.push(`syncSchema:${object}`);
|
|
967
|
-
},
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
await kernel.use({
|
|
971
|
-
name: 'mock-order-driver',
|
|
972
|
-
type: 'driver',
|
|
973
|
-
version: '1.0.0',
|
|
974
|
-
init: async (ctx) => {
|
|
975
|
-
ctx.registerService('driver.order', mockDriver);
|
|
976
|
-
},
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
const plugin = new ObjectQLPlugin();
|
|
980
|
-
await kernel.use(plugin);
|
|
981
|
-
|
|
982
|
-
// Act
|
|
983
|
-
await kernel.bootstrap();
|
|
984
|
-
|
|
985
|
-
// Assert — loadMetaFromDb must appear before any syncSchema call
|
|
986
|
-
const loadIdx = operations.indexOf('loadMetaFromDb');
|
|
987
|
-
expect(loadIdx).toBeGreaterThanOrEqual(0);
|
|
988
|
-
|
|
989
|
-
const firstSync = operations.findIndex((op) => op.startsWith('syncSchema:'));
|
|
990
|
-
if (firstSync >= 0) {
|
|
991
|
-
expect(loadIdx).toBeLessThan(firstSync);
|
|
992
|
-
}
|
|
993
|
-
});
|
|
994
|
-
});
|
|
995
|
-
});
|