@objectstack/objectql 0.4.2 → 0.6.0

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,22 @@
1
1
  # @objectstack/objectql
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b2df5f7: Unified version bump to 0.5.0
8
+
9
+ - Standardized all package versions to 0.5.0 across the monorepo
10
+ - Fixed driver-memory package.json paths for proper module resolution
11
+ - Ensured all packages are in sync for the 0.5.0 release
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [b2df5f7]
16
+ - @objectstack/spec@0.6.0
17
+ - @objectstack/types@0.6.0
18
+ - @objectstack/core@0.6.0
19
+
3
20
  ## 0.4.2
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -12,16 +12,18 @@
12
12
 
13
13
  ## Usage
14
14
 
15
+ ### 1. Standalone Usage
16
+
15
17
  ```typescript
16
18
  import { ObjectQL } from '@objectstack/objectql';
17
- import { MemoryDriver } from '@objectstack/driver-memory'; // Example driver
19
+ import { InMemoryDriver } from '@objectstack/driver-memory'; // Note: Package name might export InMemoryDriver now
18
20
 
19
21
  async function main() {
20
22
  // 1. Initialize Engine
21
23
  const ql = new ObjectQL();
22
24
 
23
25
  // 2. Register Drivers
24
- const memDriver = new MemoryDriver({ name: 'default' });
26
+ const memDriver = new InMemoryDriver({ name: 'default' });
25
27
  ql.registerDriver(memDriver, true);
26
28
 
27
29
  // 3. Load Schema (via Plugin/Manifest)
@@ -61,6 +63,26 @@ async function main() {
61
63
  }
62
64
  ```
63
65
 
66
+ ### 2. Using with ObjectKernel (Recommended)
67
+
68
+ When building full applications, use the `ObjectKernel` to manage plugins and configuration.
69
+
70
+ ```typescript
71
+ import { ObjectKernel } from '@objectstack/core';
72
+ import { ObjectQLPlugin, DriverPlugin } from '@objectstack/runtime';
73
+ import { InMemoryDriver } from '@objectstack/driver-memory';
74
+
75
+ const kernel = new ObjectKernel();
76
+
77
+ // Register Engine and Drivers as Kernel Plugins
78
+ kernel.use(new ObjectQLPlugin())
79
+ .use(new DriverPlugin(new InMemoryDriver(), 'default'));
80
+
81
+ await kernel.bootstrap();
82
+
83
+ // The engine automatically discovers drivers and apps registered in the kernel.
84
+ ```
85
+
64
86
  ## Architecture
65
87
 
66
88
  - **SchemaRegistry**: Central store for all metadata (Objects, Apps, Config).
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { HookContext } from '@objectstack/spec/data';
2
- import { DriverInterface, DriverOptions } from '@objectstack/spec/system';
2
+ import { DriverOptions } from '@objectstack/spec/system';
3
+ import { DriverInterface, IDataEngine, DataEngineQueryOptions } from '@objectstack/core';
3
4
  export { SchemaRegistry } from './registry';
5
+ export { ObjectStackProtocolImplementation } from './protocol';
4
6
  export type HookHandler = (context: HookContext) => Promise<void> | void;
5
7
  /**
6
8
  * Host Context provided to plugins
@@ -12,8 +14,10 @@ export interface PluginContext {
12
14
  }
13
15
  /**
14
16
  * ObjectQL Engine
17
+ *
18
+ * Implements the IDataEngine interface for data persistence.
15
19
  */
16
- export declare class ObjectQL {
20
+ export declare class ObjectQL implements IDataEngine {
17
21
  private drivers;
18
22
  private defaultDriver;
19
23
  private hooks;
@@ -30,6 +34,7 @@ export declare class ObjectQL {
30
34
  */
31
35
  registerHook(event: string, handler: HookHandler): void;
32
36
  private triggerHooks;
37
+ registerApp(manifestPart: any): void;
33
38
  /**
34
39
  * Register a new storage driver
35
40
  */
@@ -155,10 +160,40 @@ export declare class ObjectQL {
155
160
  */
156
161
  init(): Promise<void>;
157
162
  destroy(): Promise<void>;
158
- find(object: string, query?: any, options?: DriverOptions): Promise<any>;
159
- findOne(object: string, idOrQuery: string | any, options?: DriverOptions): Promise<Record<string, any> | null>;
160
- insert(object: string, data: Record<string, any>, options?: DriverOptions): Promise<any>;
161
- update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions): Promise<any>;
162
- delete(object: string, id: string | number, options?: DriverOptions): Promise<any>;
163
+ /**
164
+ * Find records matching a query (IDataEngine interface)
165
+ *
166
+ * @param object - Object name
167
+ * @param query - Query options (IDataEngine format)
168
+ * @returns Promise resolving to array of records
169
+ */
170
+ find(object: string, query?: DataEngineQueryOptions): Promise<any[]>;
171
+ findOne(object: string, idOrQuery: string | any, options?: DriverOptions): Promise<any>;
172
+ /**
173
+ * Insert a new record (IDataEngine interface)
174
+ *
175
+ * @param object - Object name
176
+ * @param data - Data to insert
177
+ * @returns Promise resolving to the created record
178
+ */
179
+ insert(object: string, data: any): Promise<any>;
180
+ /**
181
+ * Update a record by ID (IDataEngine interface)
182
+ *
183
+ * @param object - Object name
184
+ * @param id - Record ID
185
+ * @param data - Updated data
186
+ * @returns Promise resolving to the updated record
187
+ */
188
+ update(object: string, id: any, data: any): Promise<any>;
189
+ /**
190
+ * Delete a record by ID (IDataEngine interface)
191
+ *
192
+ * @param object - Object name
193
+ * @param id - Record ID
194
+ * @returns Promise resolving to true if deleted, false otherwise
195
+ */
196
+ delete(object: string, id: any): Promise<boolean>;
163
197
  }
198
+ export * from './plugin';
164
199
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAI1E,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAEzE;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAEhB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,aAAa,CAAuB;IAG5C,OAAO,CAAC,KAAK,CAKX;IAGF,OAAO,CAAC,WAAW,CAA2B;gBAElC,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM;IAKjD;;OAEG;IACG,GAAG,CAAC,YAAY,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,GAAG;IA4D9C;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW;YAQlC,YAAY;IAQ1B;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,eAAe,EAAE,SAAS,GAAE,OAAe;IAclE;;OAEG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM;;;;;;;;;;;;;mBAyMi/T,CAAC;;;qBAA2E,CAAC;uBAAyC,CAAC;;eAA2D,CAAC;eAAiC,CAAC;mBAAqC,CAAC;iBAAmC,CAAC;qBAAuC,CAAC;gBAAkC,CAAC;uBAAyC,CAAC;kBAAoC,CAAC;wBAA0C,CAAC;qBAAwB,CAAC;qBAAuC,CAAC;iBAAmC,CAAC;qBAAuC,CAAC;4BAA8C,CAAC;mCAAuD,CAAC;sBAAyC,CAAC;6BAA+C,CAAC;;;;;oBAAiK,CAAC;iBAAmC,CAAC;uBAAyC,CAAC;qBAAwC,CAAC;qBAAuC,CAAC;sBAAyC,CAAC;0BAA6C,CAAC;yBAA4C,CAAC;uBAAgE,CAAC;sBAAgE,CAAC;wBAA2C,CAAC;gBAAoC,CAAC;qBAAuC,CAAC;iBAAoC,CAAC;yBAA2D,CAAC;6BAAyG,CAAC;wBAAyD,CAAC;yBAA4C,CAAC;0BAA6C,CAAC;;;;;wBAAkK,CAAC;;;;;yBAAyM,CAAC;;;;;;;;;;;;;yBAA4V,CAAC;mBAAuC,CAAC;;;;;;;gBAA0M,CAAC;gBAA6D,CAAC;kBAAoC,CAAC;;;;;;;;;;;;;;;sBAA8a,CAAC;;;IArM/ya;;OAEG;IACH,OAAO,CAAC,SAAS;IAqCjB;;OAEG;IACG,IAAI;IAYJ,OAAO;IAUP,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,GAAQ,EAAE,OAAO,CAAC,EAAE,aAAa;IAqC7D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,CAAC,EAAE,aAAa;IAwBxE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;IA+BzE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,aAAa;IAoB9F,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;CAmB1E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { SchemaRegistry } from './registry';
2
2
  // Export Registry for consumers
3
3
  export { SchemaRegistry } from './registry';
4
+ export { ObjectStackProtocolImplementation } from './protocol';
4
5
  /**
5
6
  * ObjectQL Engine
7
+ *
8
+ * Implements the IDataEngine interface for data persistence.
6
9
  */
7
10
  export class ObjectQL {
8
11
  constructor(hostContext = {}) {
@@ -26,37 +29,7 @@ export class ObjectQL {
26
29
  async use(manifestPart, runtimePart) {
27
30
  // 1. Validate / Register Manifest
28
31
  if (manifestPart) {
29
- // 1. Handle Module Imports (commonjs/esm interop)
30
- // If the passed object is a module namespace with a default export, use that.
31
- const manifest = manifestPart.default || manifestPart;
32
- // In a real scenario, we might strictly parse this using Zod
33
- // For now, simple ID check
34
- const id = manifest.id || manifest.name;
35
- if (!id) {
36
- console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
37
- // Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
38
- // return;
39
- }
40
- console.log(`[ObjectQL] Loading Plugin: ${id}`);
41
- SchemaRegistry.registerPlugin(manifest);
42
- // Register Objects from App/Plugin
43
- if (manifest.objects) {
44
- for (const obj of manifest.objects) {
45
- // Ensure object name is registered globally
46
- SchemaRegistry.registerObject(obj);
47
- console.log(`[ObjectQL] Registered Object: ${obj.name}`);
48
- }
49
- }
50
- // Register contributions
51
- if (manifest.contributes?.kinds) {
52
- for (const kind of manifest.contributes.kinds) {
53
- SchemaRegistry.registerKind(kind);
54
- }
55
- }
56
- // Register Data Seeding (Lazy execution or immediate?)
57
- // We store it in a temporary registry or execute immediately if engine is ready.
58
- // Since `use` is init time, we might need to store it and run later in `seed()`.
59
- // For this MVP, let's attach it to the manifest object in registry so Kernel can find it.
32
+ this.registerApp(manifestPart);
60
33
  }
61
34
  // 2. Execute Runtime
62
35
  if (runtimePart) {
@@ -94,6 +67,38 @@ export class ObjectQL {
94
67
  await handler(context);
95
68
  }
96
69
  }
70
+ registerApp(manifestPart) {
71
+ // 1. Handle Module Imports (commonjs/esm interop)
72
+ // If the passed object is a module namespace with a default export, use that.
73
+ const raw = manifestPart.default || manifestPart;
74
+ // Support nested manifest property (Stack Definition)
75
+ // We merge the inner manifest metadata (id, version, etc) with the outer container (objects, apps)
76
+ const manifest = raw.manifest ? { ...raw, ...raw.manifest } : raw;
77
+ // In a real scenario, we might strictly parse this using Zod
78
+ // For now, simple ID check
79
+ const id = manifest.id || manifest.name;
80
+ if (!id) {
81
+ console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
82
+ // Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
83
+ // return;
84
+ }
85
+ console.log(`[ObjectQL] Loading App: ${id}`);
86
+ SchemaRegistry.registerPlugin(manifest);
87
+ // Register Objects from App/Plugin
88
+ if (manifest.objects) {
89
+ for (const obj of manifest.objects) {
90
+ // Ensure object name is registered globally
91
+ SchemaRegistry.registerObject(obj);
92
+ console.log(`[ObjectQL] Registered Object: ${obj.name}`);
93
+ }
94
+ }
95
+ // Register contributions
96
+ if (manifest.contributes?.kinds) {
97
+ for (const kind of manifest.contributes.kinds) {
98
+ SchemaRegistry.registerKind(kind);
99
+ }
100
+ }
101
+ }
97
102
  /**
98
103
  * Register a new storage driver
99
104
  */
@@ -171,25 +176,54 @@ export class ObjectQL {
171
176
  }
172
177
  }
173
178
  // ============================================
174
- // Data Access Methods
179
+ // Data Access Methods (IDataEngine Interface)
175
180
  // ============================================
176
- async find(object, query = {}, options) {
181
+ /**
182
+ * Find records matching a query (IDataEngine interface)
183
+ *
184
+ * @param object - Object name
185
+ * @param query - Query options (IDataEngine format)
186
+ * @returns Promise resolving to array of records
187
+ */
188
+ async find(object, query) {
177
189
  const driver = this.getDriver(object);
178
- // Normalize QueryAST
179
- let ast;
180
- if (query.where || query.fields || query.orderBy || query.limit) {
181
- ast = { object, ...query };
182
- }
183
- else {
184
- ast = { object, where: query };
190
+ // Convert DataEngineQueryOptions to QueryAST
191
+ let ast = { object };
192
+ if (query) {
193
+ // Map DataEngineQueryOptions to QueryAST
194
+ if (query.filter) {
195
+ ast.where = query.filter;
196
+ }
197
+ if (query.select) {
198
+ ast.fields = query.select;
199
+ }
200
+ if (query.sort) {
201
+ // Convert sort Record to orderBy array
202
+ // sort: { createdAt: -1, name: 'asc' } => orderBy: [{ field: 'createdAt', order: 'desc' }, { field: 'name', order: 'asc' }]
203
+ ast.orderBy = Object.entries(query.sort).map(([field, order]) => ({
204
+ field,
205
+ order: (order === -1 || order === 'desc') ? 'desc' : 'asc'
206
+ }));
207
+ }
208
+ // Handle both limit and top (top takes precedence)
209
+ if (query.top !== undefined) {
210
+ ast.limit = query.top;
211
+ }
212
+ else if (query.limit !== undefined) {
213
+ ast.limit = query.limit;
214
+ }
215
+ if (query.skip !== undefined) {
216
+ ast.offset = query.skip;
217
+ }
185
218
  }
219
+ // Set default limit if not specified
186
220
  if (ast.limit === undefined)
187
221
  ast.limit = 100;
188
222
  // Trigger Before Hook
189
223
  const hookContext = {
190
224
  object,
191
225
  event: 'beforeFind',
192
- input: { ast, options }, // Hooks can modify AST here
226
+ input: { ast, options: undefined },
193
227
  ql: this
194
228
  };
195
229
  await this.triggerHooks('beforeFind', hookContext);
@@ -229,7 +263,14 @@ export class ObjectQL {
229
263
  ast.limit = 1;
230
264
  return driver.findOne(object, ast, options);
231
265
  }
232
- async insert(object, data, options) {
266
+ /**
267
+ * Insert a new record (IDataEngine interface)
268
+ *
269
+ * @param object - Object name
270
+ * @param data - Data to insert
271
+ * @returns Promise resolving to the created record
272
+ */
273
+ async insert(object, data) {
233
274
  const driver = this.getDriver(object);
234
275
  // 1. Get Schema
235
276
  const schema = SchemaRegistry.getObject(object);
@@ -241,7 +282,7 @@ export class ObjectQL {
241
282
  const hookContext = {
242
283
  object,
243
284
  event: 'beforeInsert',
244
- input: { data, options },
285
+ input: { data, options: undefined },
245
286
  ql: this
246
287
  };
247
288
  await this.triggerHooks('beforeInsert', hookContext);
@@ -253,12 +294,20 @@ export class ObjectQL {
253
294
  await this.triggerHooks('afterInsert', hookContext);
254
295
  return hookContext.result;
255
296
  }
256
- async update(object, id, data, options) {
297
+ /**
298
+ * Update a record by ID (IDataEngine interface)
299
+ *
300
+ * @param object - Object name
301
+ * @param id - Record ID
302
+ * @param data - Updated data
303
+ * @returns Promise resolving to the updated record
304
+ */
305
+ async update(object, id, data) {
257
306
  const driver = this.getDriver(object);
258
307
  const hookContext = {
259
308
  object,
260
309
  event: 'beforeUpdate',
261
- input: { id, data, options },
310
+ input: { id, data, options: undefined },
262
311
  ql: this
263
312
  };
264
313
  await this.triggerHooks('beforeUpdate', hookContext);
@@ -268,12 +317,19 @@ export class ObjectQL {
268
317
  await this.triggerHooks('afterUpdate', hookContext);
269
318
  return hookContext.result;
270
319
  }
271
- async delete(object, id, options) {
320
+ /**
321
+ * Delete a record by ID (IDataEngine interface)
322
+ *
323
+ * @param object - Object name
324
+ * @param id - Record ID
325
+ * @returns Promise resolving to true if deleted, false otherwise
326
+ */
327
+ async delete(object, id) {
272
328
  const driver = this.getDriver(object);
273
329
  const hookContext = {
274
330
  object,
275
331
  event: 'beforeDelete',
276
- input: { id, options },
332
+ input: { id, options: undefined },
277
333
  ql: this
278
334
  };
279
335
  await this.triggerHooks('beforeDelete', hookContext);
@@ -281,6 +337,8 @@ export class ObjectQL {
281
337
  hookContext.event = 'afterDelete';
282
338
  hookContext.result = result;
283
339
  await this.triggerHooks('afterDelete', hookContext);
340
+ // Driver.delete() already returns boolean per DriverInterface spec
284
341
  return hookContext.result;
285
342
  }
286
343
  }
344
+ export * from './plugin';
@@ -0,0 +1,14 @@
1
+ import { ObjectQL } from './index';
2
+ import { Plugin, PluginContext } from '@objectstack/core';
3
+ export type { Plugin, PluginContext };
4
+ export declare class ObjectQLPlugin implements Plugin {
5
+ name: string;
6
+ type: "objectql";
7
+ version: string;
8
+ private ql;
9
+ private hostContext?;
10
+ constructor(ql?: ObjectQL, hostContext?: Record<string, any>);
11
+ init(ctx: PluginContext): Promise<void>;
12
+ start(ctx: PluginContext): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAE1D,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AAEtC,qBAAa,cAAe,YAAW,MAAM;IAC3C,IAAI,SAAqC;IACzC,IAAI,EAAG,UAAU,CAAU;IAC3B,OAAO,SAAW;IAElB,OAAO,CAAC,EAAE,CAAuB;IACjC,OAAO,CAAC,WAAW,CAAC,CAAsB;gBAE9B,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAStD,IAAI,CAAC,GAAG,EAAE,aAAa;IAkBvB,KAAK,CAAC,GAAG,EAAE,aAAa;CAoB/B"}
package/dist/plugin.js ADDED
@@ -0,0 +1,54 @@
1
+ import { ObjectQL } from './index';
2
+ import { ObjectStackProtocolImplementation } from './protocol';
3
+ export class ObjectQLPlugin {
4
+ constructor(ql, hostContext) {
5
+ this.name = 'com.objectstack.engine.objectql';
6
+ this.type = 'objectql';
7
+ this.version = '1.0.0';
8
+ if (ql) {
9
+ this.ql = ql;
10
+ }
11
+ else {
12
+ this.hostContext = hostContext;
13
+ // Lazily created in init
14
+ }
15
+ }
16
+ async init(ctx) {
17
+ if (!this.ql) {
18
+ this.ql = new ObjectQL(this.hostContext);
19
+ }
20
+ ctx.registerService('objectql', this.ql);
21
+ if (ctx.logger)
22
+ ctx.logger.log(`[ObjectQLPlugin] ObjectQL engine registered as service`);
23
+ // Register Protocol Implementation
24
+ if (!this.ql) {
25
+ throw new Error('ObjectQL engine not initialized');
26
+ }
27
+ const protocolShim = new ObjectStackProtocolImplementation(this.ql);
28
+ ctx.registerService('protocol', protocolShim);
29
+ if (ctx.logger)
30
+ ctx.logger.log(`[ObjectQLPlugin] Protocol service registered`);
31
+ }
32
+ async start(ctx) {
33
+ if (ctx.logger)
34
+ ctx.logger.log(`[ObjectQLPlugin] ObjectQL engine initialized`);
35
+ // Discover features from Kernel Services
36
+ if (ctx.getServices && this.ql) {
37
+ const services = ctx.getServices();
38
+ for (const [name, service] of services.entries()) {
39
+ if (name.startsWith('driver.')) {
40
+ // Register Driver
41
+ this.ql.registerDriver(service);
42
+ if (ctx.logger)
43
+ ctx.logger.log(`[ObjectQLPlugin] Discovered and registered driver service: ${name}`);
44
+ }
45
+ if (name.startsWith('app.')) {
46
+ // Register App
47
+ this.ql.registerApp(service); // service is Manifest
48
+ if (ctx.logger)
49
+ ctx.logger.log(`[ObjectQLPlugin] Discovered and registered app service: ${name}`);
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,43 @@
1
+ import { IObjectStackProtocol } from '@objectstack/spec/api';
2
+ import { IDataEngine } from '@objectstack/core';
3
+ export declare class ObjectStackProtocolImplementation implements IObjectStackProtocol {
4
+ private engine;
5
+ constructor(engine: IDataEngine);
6
+ getDiscovery(): {
7
+ name: string;
8
+ version: string;
9
+ capabilities: {
10
+ metadata: boolean;
11
+ data: boolean;
12
+ ui: boolean;
13
+ };
14
+ };
15
+ getMetaTypes(): string[];
16
+ getMetaItems(type: string): unknown[];
17
+ getMetaItem(type: string, name: string): unknown;
18
+ getUiView(object: string, type: 'list' | 'form'): {
19
+ type: string;
20
+ object: string;
21
+ columns: {
22
+ field: string;
23
+ label: string;
24
+ }[];
25
+ sections?: undefined;
26
+ } | {
27
+ type: string;
28
+ object: string;
29
+ sections: {
30
+ label: string;
31
+ fields: {
32
+ field: string;
33
+ }[];
34
+ }[];
35
+ columns?: undefined;
36
+ };
37
+ findData(object: string, query: any): Promise<any[]>;
38
+ getData(object: string, id: string): Promise<any>;
39
+ createData(object: string, data: any): Promise<any>;
40
+ updateData(object: string, id: string, data: any): Promise<any>;
41
+ deleteData(object: string, id: string): Promise<boolean>;
42
+ }
43
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,qBAAa,iCAAkC,YAAW,oBAAoB;IAC1E,OAAO,CAAC,MAAM,CAAc;gBAEhB,MAAM,EAAE,WAAW;IAI/B,YAAY;;;;;;;;;IAYZ,YAAY;IAIZ,YAAY,CAAC,IAAI,EAAE,MAAM;IAIzB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAItC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;;;;;;;;;;;;;;;;;;;IA6B/C,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;IAI7B,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;IAsBxC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;IAIpC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;IAIhD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;CAGxC"}
@@ -0,0 +1,87 @@
1
+ // We import SchemaRegistry directly since this class lives in the same package
2
+ import { SchemaRegistry } from './registry';
3
+ export class ObjectStackProtocolImplementation {
4
+ constructor(engine) {
5
+ this.engine = engine;
6
+ }
7
+ getDiscovery() {
8
+ return {
9
+ name: 'ObjectStack API',
10
+ version: '1.0',
11
+ capabilities: {
12
+ metadata: true,
13
+ data: true,
14
+ ui: true
15
+ }
16
+ };
17
+ }
18
+ getMetaTypes() {
19
+ return SchemaRegistry.getRegisteredTypes();
20
+ }
21
+ getMetaItems(type) {
22
+ return SchemaRegistry.listItems(type);
23
+ }
24
+ getMetaItem(type, name) {
25
+ return SchemaRegistry.getItem(type, name);
26
+ }
27
+ getUiView(object, type) {
28
+ const schema = SchemaRegistry.getObject(object);
29
+ if (!schema)
30
+ throw new Error(`Object ${object} not found`);
31
+ if (type === 'list') {
32
+ return {
33
+ type: 'list',
34
+ object: object,
35
+ columns: Object.keys(schema.fields || {}).slice(0, 5).map(f => ({
36
+ field: f,
37
+ label: schema.fields[f].label || f
38
+ }))
39
+ };
40
+ }
41
+ else {
42
+ return {
43
+ type: 'form',
44
+ object: object,
45
+ sections: [
46
+ {
47
+ label: 'General',
48
+ fields: Object.keys(schema.fields || {}).map(f => ({
49
+ field: f
50
+ }))
51
+ }
52
+ ]
53
+ };
54
+ }
55
+ }
56
+ findData(object, query) {
57
+ return this.engine.find(object, query);
58
+ }
59
+ async getData(object, id) {
60
+ // IDataEngine doesn't have findOne, so we simulate it with find and limit 1
61
+ // Assuming the ID field is named '_id' or 'id'.
62
+ // For broad compatibility, we might need to know the ID field name.
63
+ // But traditionally it is _id in ObjectStack/mongo or id in others.
64
+ // Let's rely on finding by ID if the engine supports it via find?
65
+ // Actually, ObjectQL (the implementation) DOES have findOne.
66
+ // But we are programming against IDataEngine interface here.
67
+ // If the engine IS ObjectQL (which it practically is), we could cast it.
68
+ // But let's try to stick to interface.
69
+ const results = await this.engine.find(object, {
70
+ filter: { _id: id }, // Default Assumption: _id
71
+ limit: 1
72
+ });
73
+ if (results && results.length > 0) {
74
+ return results[0];
75
+ }
76
+ throw new Error(`Record ${id} not found in ${object}`);
77
+ }
78
+ createData(object, data) {
79
+ return this.engine.insert(object, data);
80
+ }
81
+ updateData(object, id, data) {
82
+ return this.engine.update(object, id, data);
83
+ }
84
+ deleteData(object, id) {
85
+ return this.engine.delete(object, id);
86
+ }
87
+ }
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@objectstack/objectql",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "Isomorphic ObjectQL Engine for ObjectStack",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
7
7
  "dependencies": {
8
- "@objectstack/spec": "0.4.2"
8
+ "@objectstack/core": "0.6.0",
9
+ "@objectstack/spec": "0.6.0",
10
+ "@objectstack/types": "0.6.0"
9
11
  },
10
12
  "devDependencies": {
11
13
  "typescript": "^5.0.0",
package/src/index.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { QueryAST, HookContext } from '@objectstack/spec/data';
2
2
  import { ObjectStackManifest } from '@objectstack/spec/system';
3
- import { DriverInterface, DriverOptions } from '@objectstack/spec/system';
3
+ import { DriverOptions } from '@objectstack/spec/system';
4
+ import { DriverInterface, IDataEngine, DataEngineQueryOptions } from '@objectstack/core';
4
5
  import { SchemaRegistry } from './registry';
5
6
 
6
7
  // Export Registry for consumers
7
8
  export { SchemaRegistry } from './registry';
9
+ export { ObjectStackProtocolImplementation } from './protocol';
10
+
8
11
 
9
12
  export type HookHandler = (context: HookContext) => Promise<void> | void;
10
13
 
@@ -20,8 +23,10 @@ export interface PluginContext {
20
23
 
21
24
  /**
22
25
  * ObjectQL Engine
26
+ *
27
+ * Implements the IDataEngine interface for data persistence.
23
28
  */
24
- export class ObjectQL {
29
+ export class ObjectQL implements IDataEngine {
25
30
  private drivers = new Map<string, DriverInterface>();
26
31
  private defaultDriver: string | null = null;
27
32
 
@@ -47,42 +52,7 @@ export class ObjectQL {
47
52
  async use(manifestPart: any, runtimePart?: any) {
48
53
  // 1. Validate / Register Manifest
49
54
  if (manifestPart) {
50
- // 1. Handle Module Imports (commonjs/esm interop)
51
- // If the passed object is a module namespace with a default export, use that.
52
- const manifest = manifestPart.default || manifestPart;
53
-
54
- // In a real scenario, we might strictly parse this using Zod
55
- // For now, simple ID check
56
- const id = manifest.id || manifest.name;
57
- if (!id) {
58
- console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
59
- // Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
60
- // return;
61
- }
62
-
63
- console.log(`[ObjectQL] Loading Plugin: ${id}`);
64
- SchemaRegistry.registerPlugin(manifest as ObjectStackManifest);
65
-
66
- // Register Objects from App/Plugin
67
- if (manifest.objects) {
68
- for (const obj of manifest.objects) {
69
- // Ensure object name is registered globally
70
- SchemaRegistry.registerObject(obj);
71
- console.log(`[ObjectQL] Registered Object: ${obj.name}`);
72
- }
73
- }
74
-
75
- // Register contributions
76
- if (manifest.contributes?.kinds) {
77
- for (const kind of manifest.contributes.kinds) {
78
- SchemaRegistry.registerKind(kind);
79
- }
80
- }
81
-
82
- // Register Data Seeding (Lazy execution or immediate?)
83
- // We store it in a temporary registry or execute immediately if engine is ready.
84
- // Since `use` is init time, we might need to store it and run later in `seed()`.
85
- // For this MVP, let's attach it to the manifest object in registry so Kernel can find it.
55
+ this.registerApp(manifestPart);
86
56
  }
87
57
 
88
58
  // 2. Execute Runtime
@@ -125,6 +95,44 @@ export class ObjectQL {
125
95
  }
126
96
  }
127
97
 
98
+ registerApp(manifestPart: any) {
99
+ // 1. Handle Module Imports (commonjs/esm interop)
100
+ // If the passed object is a module namespace with a default export, use that.
101
+ const raw = manifestPart.default || manifestPart;
102
+
103
+ // Support nested manifest property (Stack Definition)
104
+ // We merge the inner manifest metadata (id, version, etc) with the outer container (objects, apps)
105
+ const manifest = raw.manifest ? { ...raw, ...raw.manifest } : raw;
106
+
107
+ // In a real scenario, we might strictly parse this using Zod
108
+ // For now, simple ID check
109
+ const id = manifest.id || manifest.name;
110
+ if (!id) {
111
+ console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
112
+ // Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
113
+ // return;
114
+ }
115
+
116
+ console.log(`[ObjectQL] Loading App: ${id}`);
117
+ SchemaRegistry.registerPlugin(manifest as ObjectStackManifest);
118
+
119
+ // Register Objects from App/Plugin
120
+ if (manifest.objects) {
121
+ for (const obj of manifest.objects) {
122
+ // Ensure object name is registered globally
123
+ SchemaRegistry.registerObject(obj);
124
+ console.log(`[ObjectQL] Registered Object: ${obj.name}`);
125
+ }
126
+ }
127
+
128
+ // Register contributions
129
+ if (manifest.contributes?.kinds) {
130
+ for (const kind of manifest.contributes.kinds) {
131
+ SchemaRegistry.registerKind(kind);
132
+ }
133
+ }
134
+ }
135
+
128
136
  /**
129
137
  * Register a new storage driver
130
138
  */
@@ -211,27 +219,57 @@ export class ObjectQL {
211
219
  }
212
220
 
213
221
  // ============================================
214
- // Data Access Methods
222
+ // Data Access Methods (IDataEngine Interface)
215
223
  // ============================================
216
224
 
217
- async find(object: string, query: any = {}, options?: DriverOptions) {
225
+ /**
226
+ * Find records matching a query (IDataEngine interface)
227
+ *
228
+ * @param object - Object name
229
+ * @param query - Query options (IDataEngine format)
230
+ * @returns Promise resolving to array of records
231
+ */
232
+ async find(object: string, query?: DataEngineQueryOptions): Promise<any[]> {
218
233
  const driver = this.getDriver(object);
219
234
 
220
- // Normalize QueryAST
221
- let ast: QueryAST;
222
- if (query.where || query.fields || query.orderBy || query.limit) {
223
- ast = { object, ...query } as QueryAST;
224
- } else {
225
- ast = { object, where: query } as QueryAST;
235
+ // Convert DataEngineQueryOptions to QueryAST
236
+ let ast: QueryAST = { object };
237
+
238
+ if (query) {
239
+ // Map DataEngineQueryOptions to QueryAST
240
+ if (query.filter) {
241
+ ast.where = query.filter;
242
+ }
243
+ if (query.select) {
244
+ ast.fields = query.select;
245
+ }
246
+ if (query.sort) {
247
+ // Convert sort Record to orderBy array
248
+ // sort: { createdAt: -1, name: 'asc' } => orderBy: [{ field: 'createdAt', order: 'desc' }, { field: 'name', order: 'asc' }]
249
+ ast.orderBy = Object.entries(query.sort).map(([field, order]) => ({
250
+ field,
251
+ order: (order === -1 || order === 'desc') ? 'desc' : 'asc'
252
+ }));
253
+ }
254
+ // Handle both limit and top (top takes precedence)
255
+ if (query.top !== undefined) {
256
+ ast.limit = query.top;
257
+ } else if (query.limit !== undefined) {
258
+ ast.limit = query.limit;
259
+ }
260
+ if (query.skip !== undefined) {
261
+ ast.offset = query.skip;
262
+ }
226
263
  }
227
264
 
265
+ // Set default limit if not specified
228
266
  if (ast.limit === undefined) ast.limit = 100;
229
267
 
230
268
  // Trigger Before Hook
231
269
  const hookContext: HookContext = {
232
270
  object,
233
271
  event: 'beforeFind',
234
- input: { ast, options }, // Hooks can modify AST here
272
+ input: { ast, options: undefined },
235
273
  ql: this
236
274
  };
237
275
  await this.triggerHooks('beforeFind', hookContext);
@@ -275,7 +313,14 @@ export class ObjectQL {
275
313
  return driver.findOne(object, ast, options);
276
314
  }
277
315
 
278
- async insert(object: string, data: Record<string, any>, options?: DriverOptions) {
316
+ /**
317
+ * Insert a new record (IDataEngine interface)
318
+ *
319
+ * @param object - Object name
320
+ * @param data - Data to insert
321
+ * @returns Promise resolving to the created record
322
+ */
323
+ async insert(object: string, data: any): Promise<any> {
279
324
  const driver = this.getDriver(object);
280
325
 
281
326
  // 1. Get Schema
@@ -290,7 +335,7 @@ export class ObjectQL {
290
335
  const hookContext: HookContext = {
291
336
  object,
292
337
  event: 'beforeInsert',
293
- input: { data, options },
338
+ input: { data, options: undefined },
294
339
  ql: this
295
340
  };
296
341
  await this.triggerHooks('beforeInsert', hookContext);
@@ -306,13 +351,21 @@ export class ObjectQL {
306
351
  return hookContext.result;
307
352
  }
308
353
 
309
- async update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions) {
354
+ /**
355
+ * Update a record by ID (IDataEngine interface)
356
+ *
357
+ * @param object - Object name
358
+ * @param id - Record ID
359
+ * @param data - Updated data
360
+ * @returns Promise resolving to the updated record
361
+ */
362
+ async update(object: string, id: any, data: any): Promise<any> {
310
363
  const driver = this.getDriver(object);
311
364
 
312
365
  const hookContext: HookContext = {
313
366
  object,
314
367
  event: 'beforeUpdate',
315
- input: { id, data, options },
368
+ input: { id, data, options: undefined },
316
369
  ql: this
317
370
  };
318
371
  await this.triggerHooks('beforeUpdate', hookContext);
@@ -326,13 +379,20 @@ export class ObjectQL {
326
379
  return hookContext.result;
327
380
  }
328
381
 
329
- async delete(object: string, id: string | number, options?: DriverOptions) {
382
+ /**
383
+ * Delete a record by ID (IDataEngine interface)
384
+ *
385
+ * @param object - Object name
386
+ * @param id - Record ID
387
+ * @returns Promise resolving to true if deleted, false otherwise
388
+ */
389
+ async delete(object: string, id: any): Promise<boolean> {
330
390
  const driver = this.getDriver(object);
331
391
 
332
392
  const hookContext: HookContext = {
333
393
  object,
334
394
  event: 'beforeDelete',
335
- input: { id, options },
395
+ input: { id, options: undefined },
336
396
  ql: this
337
397
  };
338
398
  await this.triggerHooks('beforeDelete', hookContext);
@@ -343,6 +403,8 @@ export class ObjectQL {
343
403
  hookContext.result = result;
344
404
  await this.triggerHooks('afterDelete', hookContext);
345
405
 
406
+ // Driver.delete() already returns boolean per DriverInterface spec
346
407
  return hookContext.result;
347
408
  }
348
409
  }
410
+ export * from './plugin';
package/src/plugin.ts ADDED
@@ -0,0 +1,62 @@
1
+ import { ObjectQL } from './index';
2
+ import { ObjectStackProtocolImplementation } from './protocol';
3
+ import { Plugin, PluginContext } from '@objectstack/core';
4
+
5
+ export type { Plugin, PluginContext };
6
+
7
+ export class ObjectQLPlugin implements Plugin {
8
+ name = 'com.objectstack.engine.objectql';
9
+ type = 'objectql' as const;
10
+ version = '1.0.0';
11
+
12
+ private ql: ObjectQL | undefined;
13
+ private hostContext?: Record<string, any>;
14
+
15
+ constructor(ql?: ObjectQL, hostContext?: Record<string, any>) {
16
+ if (ql) {
17
+ this.ql = ql;
18
+ } else {
19
+ this.hostContext = hostContext;
20
+ // Lazily created in init
21
+ }
22
+ }
23
+
24
+ async init(ctx: PluginContext) {
25
+ if (!this.ql) {
26
+ this.ql = new ObjectQL(this.hostContext);
27
+ }
28
+
29
+ ctx.registerService('objectql', this.ql);
30
+ if(ctx.logger) ctx.logger.log(`[ObjectQLPlugin] ObjectQL engine registered as service`);
31
+
32
+ // Register Protocol Implementation
33
+ if (!this.ql) {
34
+ throw new Error('ObjectQL engine not initialized');
35
+ }
36
+ const protocolShim = new ObjectStackProtocolImplementation(this.ql);
37
+
38
+ ctx.registerService('protocol', protocolShim);
39
+ if(ctx.logger) ctx.logger.log(`[ObjectQLPlugin] Protocol service registered`);
40
+ }
41
+
42
+ async start(ctx: PluginContext) {
43
+ if(ctx.logger) ctx.logger.log(`[ObjectQLPlugin] ObjectQL engine initialized`);
44
+
45
+ // Discover features from Kernel Services
46
+ if (ctx.getServices && this.ql) {
47
+ const services = ctx.getServices();
48
+ for (const [name, service] of services.entries()) {
49
+ if (name.startsWith('driver.')) {
50
+ // Register Driver
51
+ this.ql.registerDriver(service);
52
+ if(ctx.logger) ctx.logger.log(`[ObjectQLPlugin] Discovered and registered driver service: ${name}`);
53
+ }
54
+ if (name.startsWith('app.')) {
55
+ // Register App
56
+ this.ql.registerApp(service); // service is Manifest
57
+ if(ctx.logger) ctx.logger.log(`[ObjectQLPlugin] Discovered and registered app service: ${name}`);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,104 @@
1
+ import { IObjectStackProtocol } from '@objectstack/spec/api';
2
+ import { IDataEngine } from '@objectstack/core';
3
+
4
+ // We import SchemaRegistry directly since this class lives in the same package
5
+ import { SchemaRegistry } from './registry';
6
+
7
+ export class ObjectStackProtocolImplementation implements IObjectStackProtocol {
8
+ private engine: IDataEngine;
9
+
10
+ constructor(engine: IDataEngine) {
11
+ this.engine = engine;
12
+ }
13
+
14
+ getDiscovery() {
15
+ return {
16
+ name: 'ObjectStack API',
17
+ version: '1.0',
18
+ capabilities: {
19
+ metadata: true,
20
+ data: true,
21
+ ui: true
22
+ }
23
+ };
24
+ }
25
+
26
+ getMetaTypes() {
27
+ return SchemaRegistry.getRegisteredTypes();
28
+ }
29
+
30
+ getMetaItems(type: string) {
31
+ return SchemaRegistry.listItems(type);
32
+ }
33
+
34
+ getMetaItem(type: string, name: string) {
35
+ return SchemaRegistry.getItem(type, name);
36
+ }
37
+
38
+ getUiView(object: string, type: 'list' | 'form') {
39
+ const schema = SchemaRegistry.getObject(object);
40
+ if (!schema) throw new Error(`Object ${object} not found`);
41
+
42
+ if (type === 'list') {
43
+ return {
44
+ type: 'list',
45
+ object: object,
46
+ columns: Object.keys(schema.fields || {}).slice(0, 5).map(f => ({
47
+ field: f,
48
+ label: schema.fields[f].label || f
49
+ }))
50
+ };
51
+ } else {
52
+ return {
53
+ type: 'form',
54
+ object: object,
55
+ sections: [
56
+ {
57
+ label: 'General',
58
+ fields: Object.keys(schema.fields || {}).map(f => ({
59
+ field: f
60
+ }))
61
+ }
62
+ ]
63
+ };
64
+ }
65
+ }
66
+
67
+ findData(object: string, query: any) {
68
+ return this.engine.find(object, query);
69
+ }
70
+
71
+ async getData(object: string, id: string) {
72
+ // IDataEngine doesn't have findOne, so we simulate it with find and limit 1
73
+ // Assuming the ID field is named '_id' or 'id'.
74
+ // For broad compatibility, we might need to know the ID field name.
75
+ // But traditionally it is _id in ObjectStack/mongo or id in others.
76
+ // Let's rely on finding by ID if the engine supports it via find?
77
+ // Actually, ObjectQL (the implementation) DOES have findOne.
78
+ // But we are programming against IDataEngine interface here.
79
+
80
+ // If the engine IS ObjectQL (which it practically is), we could cast it.
81
+ // But let's try to stick to interface.
82
+
83
+ const results = await this.engine.find(object, {
84
+ filter: { _id: id }, // Default Assumption: _id
85
+ limit: 1
86
+ });
87
+ if (results && results.length > 0) {
88
+ return results[0];
89
+ }
90
+ throw new Error(`Record ${id} not found in ${object}`);
91
+ }
92
+
93
+ createData(object: string, data: any) {
94
+ return this.engine.insert(object, data);
95
+ }
96
+
97
+ updateData(object: string, id: string, data: any) {
98
+ return this.engine.update(object, id, data);
99
+ }
100
+
101
+ deleteData(object: string, id: string) {
102
+ return this.engine.delete(object, id);
103
+ }
104
+ }