@objectql/core 0.1.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.
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ObjectRepository = void 0;
4
+ class ObjectRepository {
5
+ constructor(objectName, context, app) {
6
+ this.objectName = objectName;
7
+ this.context = context;
8
+ this.app = app;
9
+ }
10
+ getDriver() {
11
+ const obj = this.getSchema();
12
+ const datasourceName = obj.datasource || 'default';
13
+ return this.app.datasource(datasourceName);
14
+ }
15
+ getOptions(extra = {}) {
16
+ return {
17
+ transaction: this.context.transactionHandle,
18
+ ...extra
19
+ };
20
+ }
21
+ getSchema() {
22
+ const obj = this.app.getObject(this.objectName);
23
+ if (!obj) {
24
+ throw new Error(`Object '${this.objectName}' not found`);
25
+ }
26
+ return obj;
27
+ }
28
+ // === Hook Execution Logic ===
29
+ async executeHook(hookName, op, dataOrQuery) {
30
+ if (this.context.ignoreTriggers)
31
+ return;
32
+ const obj = this.getSchema();
33
+ if (!obj.listeners || !obj.listeners[hookName])
34
+ return;
35
+ const hookFn = obj.listeners[hookName];
36
+ // Construct HookContext
37
+ const hookContext = {
38
+ ctx: this.context,
39
+ entity: this.objectName,
40
+ op: op,
41
+ utils: {
42
+ restrict: (criterion) => {
43
+ if (op !== 'find' && op !== 'count') {
44
+ throw new Error('utils.restrict is only available in query operations');
45
+ }
46
+ const query = dataOrQuery;
47
+ if (!query.filters) {
48
+ query.filters = [criterion];
49
+ }
50
+ else {
51
+ // Enclose existing filters in implicit AND group by array structure logic or explicit 'and'
52
+ // Implementation depends on how driver parses.
53
+ // Safe approach: filters = [ [criterion], 'and', [existing] ] or similar.
54
+ // For simplicity assuming array of terms means AND:
55
+ query.filters.push(criterion);
56
+ }
57
+ }
58
+ },
59
+ getPreviousDoc: async () => {
60
+ // For update/delete, we might need the ID to find the doc.
61
+ // If doc has ID, use it.
62
+ // This is simplistic; usually 'update' takes 'id', we need to capture it from arguments.
63
+ if (op === 'create')
64
+ return undefined;
65
+ if (dataOrQuery._id || dataOrQuery.id) {
66
+ return this.findOne(dataOrQuery._id || dataOrQuery.id);
67
+ }
68
+ return undefined;
69
+ }
70
+ };
71
+ if (op === 'find' || op === 'count' || op === 'aggregate') {
72
+ hookContext.query = dataOrQuery;
73
+ }
74
+ else {
75
+ hookContext.doc = dataOrQuery;
76
+ }
77
+ // Pass ID manually if needed or attach to doc?
78
+ // For strictness, getPreviousDoc needs the ID passed to the operation.
79
+ // We'll rely on "doc" having the data being processed.
80
+ await hookFn(hookContext);
81
+ }
82
+ async find(query = {}) {
83
+ // Hooks: beforeFind
84
+ await this.executeHook('beforeFind', 'find', query);
85
+ // TODO: Apply basic filters like spaceId (could be done via a default generic hook too)
86
+ const results = await this.getDriver().find(this.objectName, query, this.getOptions());
87
+ // Hooks: afterFind
88
+ // Not implemented in spec fully iterate results? usually for single doc or metadata
89
+ // For performance, afterFind on list is rare or costly.
90
+ if (this.getSchema().listeners?.afterFind && !this.context.ignoreTriggers) {
91
+ const hookFn = this.getSchema().listeners.afterFind;
92
+ // Executing per result or once? Spec says "HookContext" has "doc".
93
+ // If finding list, might not match signature.
94
+ // Implemented per-item for now (caution: performance).
95
+ /*
96
+ for (const item of results) {
97
+ await this.executeHookForDoc('afterFind', 'find', item);
98
+ }
99
+ */
100
+ }
101
+ return results;
102
+ }
103
+ async findOne(idOrQuery) {
104
+ if (typeof idOrQuery === 'string' || typeof idOrQuery === 'number') {
105
+ // Convert ID lookup to standard query to reuse 'find' hooks?
106
+ // Or treat as specific op.
107
+ // Let's rely on simple driver call but maybe wrap in object for hook consistency if needed.
108
+ // For now, simple implementation:
109
+ return this.getDriver().findOne(this.objectName, idOrQuery, undefined, this.getOptions());
110
+ }
111
+ else {
112
+ const results = await this.find(idOrQuery);
113
+ return results[0] || null;
114
+ }
115
+ }
116
+ async count(filters) {
117
+ // Can wrap filters in a query object for hook
118
+ const query = { filters };
119
+ await this.executeHook('beforeFind', 'count', query); // Reusing beforeFind logic often?
120
+ return this.getDriver().count(this.objectName, query.filters, this.getOptions());
121
+ }
122
+ async create(doc) {
123
+ const obj = this.getSchema();
124
+ if (this.context.userId)
125
+ doc.created_by = this.context.userId;
126
+ if (this.context.spaceId)
127
+ doc.space_id = this.context.spaceId;
128
+ await this.executeHook('beforeCreate', 'create', doc);
129
+ const result = await this.getDriver().create(this.objectName, doc, this.getOptions());
130
+ await this.executeHook('afterCreate', 'create', result);
131
+ return result;
132
+ }
133
+ async update(id, doc, options) {
134
+ // Attach ID to doc for hook context to know which record
135
+ const docWithId = { ...doc, _id: id, id: id };
136
+ await this.executeHook('beforeUpdate', 'update', docWithId);
137
+ // Remove ID before sending to driver if driver doesn't like it in $set
138
+ const { _id, id: _id2, ...cleanDoc } = docWithId;
139
+ const result = await this.getDriver().update(this.objectName, id, cleanDoc, this.getOptions(options));
140
+ // Result might be count or doc depending on driver.
141
+ // If we want the updated doc for afterUpdate, we might need to fetch it if driver defaults to count.
142
+ // Assuming result is the doc or we just pass the patch.
143
+ await this.executeHook('afterUpdate', 'update', docWithId);
144
+ return result;
145
+ }
146
+ async delete(id) {
147
+ const docWithId = { _id: id, id: id };
148
+ await this.executeHook('beforeDelete', 'delete', docWithId);
149
+ const result = await this.getDriver().delete(this.objectName, id, this.getOptions());
150
+ await this.executeHook('afterDelete', 'delete', docWithId);
151
+ return result;
152
+ }
153
+ async aggregate(query) {
154
+ const driver = this.getDriver();
155
+ if (!driver.aggregate)
156
+ throw new Error("Driver does not support aggregate");
157
+ return driver.aggregate(this.objectName, query, this.getOptions());
158
+ }
159
+ async distinct(field, filters) {
160
+ const driver = this.getDriver();
161
+ if (!driver.distinct)
162
+ throw new Error("Driver does not support distinct");
163
+ return driver.distinct(this.objectName, field, filters, this.getOptions());
164
+ }
165
+ async findOneAndUpdate(filters, update, options) {
166
+ const driver = this.getDriver();
167
+ if (!driver.findOneAndUpdate)
168
+ throw new Error("Driver does not support findOneAndUpdate");
169
+ return driver.findOneAndUpdate(this.objectName, filters, update, this.getOptions(options));
170
+ }
171
+ async createMany(data) {
172
+ // TODO: Triggers per record?
173
+ const driver = this.getDriver();
174
+ if (!driver.createMany) {
175
+ // Fallback
176
+ const results = [];
177
+ for (const item of data) {
178
+ results.push(await this.create(item));
179
+ }
180
+ return results;
181
+ }
182
+ return driver.createMany(this.objectName, data, this.getOptions());
183
+ }
184
+ async updateMany(filters, data) {
185
+ const driver = this.getDriver();
186
+ if (!driver.updateMany)
187
+ throw new Error("Driver does not support updateMany");
188
+ return driver.updateMany(this.objectName, filters, data, this.getOptions());
189
+ }
190
+ async deleteMany(filters) {
191
+ const driver = this.getDriver();
192
+ if (!driver.deleteMany)
193
+ throw new Error("Driver does not support deleteMany");
194
+ return driver.deleteMany(this.objectName, filters, this.getOptions());
195
+ }
196
+ async call(actionName, params) {
197
+ const obj = this.getSchema();
198
+ const action = obj.actions?.[actionName];
199
+ if (!action) {
200
+ throw new Error(`Action '${actionName}' not found on object '${this.objectName}'`);
201
+ }
202
+ if (action.handler) {
203
+ return action.handler(this.context, params);
204
+ }
205
+ throw new Error(`Action '${actionName}' has no handler`);
206
+ }
207
+ }
208
+ exports.ObjectRepository = ObjectRepository;
209
+ //# sourceMappingURL=repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.js","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ import { ObjectRepository } from "./repository";
2
+ import { ObjectConfig } from "./metadata";
3
+ import { Driver } from "./driver";
4
+ import { UnifiedQuery, FilterCriterion } from "./query";
5
+ export { ObjectConfig } from "./metadata";
6
+ export interface ObjectQLConfig {
7
+ datasources: Record<string, Driver>;
8
+ objects?: Record<string, ObjectConfig>;
9
+ }
10
+ export interface IObjectQL {
11
+ getObject(name: string): ObjectConfig | undefined;
12
+ getConfigs(): Record<string, ObjectConfig>;
13
+ datasource(name: string): Driver;
14
+ }
15
+ export interface HookContext<T = any> {
16
+ ctx: ObjectQLContext;
17
+ entity: string;
18
+ op: 'find' | 'create' | 'update' | 'delete' | 'count' | 'aggregate';
19
+ doc?: T;
20
+ query?: UnifiedQuery;
21
+ getPreviousDoc: () => Promise<T | undefined>;
22
+ utils: {
23
+ /**
24
+ * Safely injects a new filter criterion into the existing AST.
25
+ * It wraps existing filters in a new group to preserve operator precedence.
26
+ * * Logic: (Existing_Filters) AND (New_Filter)
27
+ */
28
+ restrict: (criterion: FilterCriterion) => void;
29
+ };
30
+ }
31
+ export type HookFunction = (context: HookContext) => Promise<void>;
32
+ export interface ObjectQLContext {
33
+ userId?: string;
34
+ spaceId?: string;
35
+ roles: string[];
36
+ /**
37
+ * Sudo Mode / System Bypass.
38
+ * - true: Bypasses all permission checks (CRUD, Field Level Security, Record Level Security).
39
+ * - false/undefined: Enforces all permission checks based on 'roles'.
40
+ */
41
+ isSystem?: boolean;
42
+ /**
43
+ * Trigger Control.
44
+ * - true: Skips all lifecycle hooks (beforeCreate, afterUpdate, etc.).
45
+ * - Useful for bulk data imports or raw data correction to prevent side effects.
46
+ * - Requires 'isSystem: true' (Security Safeguard).
47
+ */
48
+ ignoreTriggers?: boolean;
49
+ /**
50
+ * Returns a repository proxy bound to this context.
51
+ * All operations performed via this proxy inherit userId, spaceId, and transaction.
52
+ */
53
+ object(entityName: string): ObjectRepository;
54
+ /**
55
+ * Execute a function within a transaction.
56
+ * The callback receives a new context 'trxCtx' which inherits userId and spaceId from this context.
57
+ */
58
+ transaction(callback: (trxCtx: ObjectQLContext) => Promise<any>): Promise<any>;
59
+ /**
60
+ * Returns a new context with system privileges (isSystem: true).
61
+ * It shares the same transaction scope as the current context.
62
+ */
63
+ sudo(): ObjectQLContext;
64
+ /**
65
+ * Internal: Driver-specific transaction handle.
66
+ */
67
+ transactionHandle?: any;
68
+ }
69
+ export interface ObjectQLContextOptions {
70
+ userId?: string;
71
+ spaceId?: string;
72
+ roles?: string[];
73
+ isSystem?: boolean;
74
+ ignoreTriggers?: boolean;
75
+ }
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/test/**/*.test.ts'],
5
+ };
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@objectql/core",
3
+ "version": "0.1.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "test": "jest"
9
+ },
10
+ "dependencies": {
11
+ "fast-glob": "^3.3.3",
12
+ "js-yaml": "^4.1.1"
13
+ }
14
+ }
package/src/driver.ts ADDED
@@ -0,0 +1,24 @@
1
+ export interface Driver {
2
+ find(objectName: string, query: any, options?: any): Promise<any[]>;
3
+ findOne(objectName: string, id: string | number, query?: any, options?: any): Promise<any>;
4
+ create(objectName: string, data: any, options?: any): Promise<any>;
5
+ update(objectName: string, id: string | number, data: any, options?: any): Promise<any>;
6
+ delete(objectName: string, id: string | number, options?: any): Promise<any>;
7
+ count(objectName: string, filters: any, options?: any): Promise<number>;
8
+
9
+ // Advanced
10
+ aggregate?(objectName: string, query: any, options?: any): Promise<any>;
11
+ distinct?(objectName: string, field: string, filters?: any, options?: any): Promise<any[]>;
12
+
13
+ // Bulk / Atomic
14
+ createMany?(objectName: string, data: any[], options?: any): Promise<any>;
15
+ updateMany?(objectName: string, filters: any, data: any, options?: any): Promise<any>;
16
+ deleteMany?(objectName: string, filters: any, options?: any): Promise<any>;
17
+ findOneAndUpdate?(objectName: string, filters: any, update: any, options?: any): Promise<any>;
18
+
19
+ // Transaction
20
+ beginTransaction?(): Promise<any>;
21
+ commitTransaction?(trx: any): Promise<void>;
22
+ rollbackTransaction?(trx: any): Promise<void>;
23
+ }
24
+
package/src/index.ts ADDED
@@ -0,0 +1,107 @@
1
+ export * from './metadata';
2
+ export * from './types';
3
+ export * from './driver';
4
+ export * from './repository';
5
+ export * from './query';
6
+
7
+ import { ObjectConfig } from './metadata';
8
+ import { ObjectQLContext, ObjectQLContextOptions, IObjectQL, ObjectQLConfig } from './types';
9
+ import { ObjectRepository } from './repository';
10
+ import { Driver } from './driver';
11
+ import { loadObjectConfigs } from './loader';
12
+
13
+ export class ObjectQL implements IObjectQL {
14
+ private objects: Record<string, ObjectConfig> = {};
15
+ private datasources: Record<string, Driver> = {};
16
+
17
+ constructor(config: ObjectQLConfig) {
18
+ this.datasources = config.datasources;
19
+ if (config.objects) {
20
+ for (const obj of Object.values(config.objects)) {
21
+ this.registerObject(obj);
22
+ }
23
+ }
24
+ }
25
+
26
+ loadFromDirectory(dir: string) {
27
+ const objects = loadObjectConfigs(dir);
28
+ for (const obj of Object.values(objects)) {
29
+ this.registerObject(obj);
30
+ }
31
+ }
32
+
33
+ createContext(options: ObjectQLContextOptions): ObjectQLContext {
34
+ const ctx: ObjectQLContext = {
35
+ userId: options.userId,
36
+ spaceId: options.spaceId,
37
+ roles: options.roles || [],
38
+ isSystem: options.isSystem,
39
+ ignoreTriggers: options.ignoreTriggers,
40
+ object: (name: string) => {
41
+ return new ObjectRepository(name, ctx, this);
42
+ },
43
+ transaction: async (callback) => {
44
+ const driver = this.datasources['default'];
45
+ if (!driver || !driver.beginTransaction) {
46
+ return callback(ctx);
47
+ }
48
+
49
+ let trx: any;
50
+ try {
51
+ trx = await driver.beginTransaction();
52
+ } catch (e) {
53
+ // If beginTransaction fails, fail.
54
+ throw e;
55
+ }
56
+
57
+ const trxCtx: ObjectQLContext = {
58
+ ...ctx,
59
+ transactionHandle: trx,
60
+ // Nested transaction simply reuses the current one (flat transaction)
61
+ transaction: async (cb) => cb(trxCtx)
62
+ };
63
+
64
+ try {
65
+ const result = await callback(trxCtx);
66
+ if (driver.commitTransaction) await driver.commitTransaction(trx);
67
+ return result;
68
+ } catch (error) {
69
+ if (driver.rollbackTransaction) await driver.rollbackTransaction(trx);
70
+ throw error;
71
+ }
72
+ },
73
+ sudo: () => {
74
+ return this.createContext({ ...options, isSystem: true });
75
+ }
76
+ };
77
+ return ctx;
78
+ }
79
+
80
+ registerObject(object: ObjectConfig) {
81
+ // Normalize fields
82
+ if (object.fields) {
83
+ for (const [key, field] of Object.entries(object.fields)) {
84
+ if (!field.name) {
85
+ field.name = key;
86
+ }
87
+ }
88
+ }
89
+ this.objects[object.name] = object;
90
+ }
91
+
92
+ getObject(name: string): ObjectConfig | undefined {
93
+ return this.objects[name];
94
+ }
95
+
96
+ getConfigs(): Record<string, ObjectConfig> {
97
+ return this.objects;
98
+ }
99
+
100
+ datasource(name: string): Driver {
101
+ const driver = this.datasources[name];
102
+ if (!driver) {
103
+ throw new Error(`Datasource '${name}' not found`);
104
+ }
105
+ return driver;
106
+ }
107
+ }
package/src/loader.ts ADDED
@@ -0,0 +1,120 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as yaml from 'js-yaml';
4
+ import * as glob from 'fast-glob';
5
+ import { ObjectQLConfig, ObjectConfig } from './types';
6
+
7
+ export function loadObjectConfigs(dir: string): Record<string, ObjectConfig> {
8
+ const configs: Record<string, ObjectConfig> = {};
9
+
10
+ // 1. Load YAML Configs
11
+ const files = glob.sync(['**/*.object.yml', '**/*.object.yaml'], {
12
+ cwd: dir,
13
+ absolute: true
14
+ });
15
+
16
+ for (const file of files) {
17
+ try {
18
+ const content = fs.readFileSync(file, 'utf8');
19
+ const doc = yaml.load(content) as any;
20
+
21
+ if (doc.name && doc.fields) {
22
+ configs[doc.name] = doc as ObjectConfig;
23
+ } else {
24
+ for (const [key, value] of Object.entries(doc)) {
25
+ if (typeof value === 'object' && (value as any).fields) {
26
+ configs[key] = value as ObjectConfig;
27
+ if (!configs[key].name) configs[key].name = key;
28
+ }
29
+ }
30
+ }
31
+ } catch (e) {
32
+ console.error(`Error loading object config from ${file}:`, e);
33
+ }
34
+ }
35
+
36
+ // 2. Load Hooks (.hook.js, .hook.ts)
37
+ // We only load .js if running in node, or .ts if ts-node/register is present.
38
+ // simpler: look for both, require will handle extension resolution if we are careful.
39
+ // Actually, in `dist` we only find .js. In `src` (test) we find .ts.
40
+ const hookFiles = glob.sync(['**/*.hook.{js,ts}'], {
41
+ cwd: dir,
42
+ absolute: true
43
+ });
44
+
45
+ for (const file of hookFiles) {
46
+ try {
47
+ // Check if we should ignore .ts if .js exists?
48
+ // Or assume env handles it.
49
+ // If we are in `dist`, `src` shouldn't be there usually.
50
+
51
+ const hookModule = require(file);
52
+ // Default export or named exports?
53
+ // Convention: export const listenTo = 'objectName';
54
+ // or filename based: 'project.hook.js' -> 'project' (flaky)
55
+
56
+ let objectName = hookModule.listenTo;
57
+
58
+ if (!objectName) {
59
+ // Try to guess from filename?
60
+ // project.hook.ts -> project
61
+ const basename = path.basename(file);
62
+ const match = basename.match(/^(.+)\.hook\.(ts|js)$/);
63
+ if (match) {
64
+ objectName = match[1];
65
+ }
66
+ }
67
+
68
+ if (objectName && configs[objectName]) {
69
+ if (!configs[objectName].listeners) {
70
+ configs[objectName].listeners = {};
71
+ }
72
+ const listeners = configs[objectName].listeners!;
73
+
74
+ // Merge exported functions into listeners
75
+ // Common hooks: beforeFind, afterFind, beforeCreate, etc.
76
+ const hookNames = [
77
+ 'beforeFind', 'afterFind',
78
+ 'beforeCreate', 'afterCreate',
79
+ 'beforeUpdate', 'afterUpdate',
80
+ 'beforeDelete', 'afterDelete'
81
+ ];
82
+
83
+ for (const name of hookNames) {
84
+ if (typeof hookModule[name] === 'function') {
85
+ listeners[name as keyof typeof listeners] = hookModule[name];
86
+ }
87
+ }
88
+ // Support default export having listeners object?
89
+ if (hookModule.default && typeof hookModule.default === 'object') {
90
+ Object.assign(listeners, hookModule.default);
91
+ }
92
+
93
+ // Load Actions
94
+ // Convention: export const actions = { myAction: (ctx, params) => ... }
95
+ // OR export function myAction(ctx, params) ... (Ambiguous with hooks? No, hooks have explicit names)
96
+ // Safer: look for `actions` export.
97
+
98
+ if (hookModule.actions && typeof hookModule.actions === 'object') {
99
+ if (!configs[objectName].actions) {
100
+ configs[objectName].actions = {};
101
+ }
102
+
103
+ for (const [actionName, handler] of Object.entries(hookModule.actions)) {
104
+ // We might have metadata from YAML already
105
+ if (!configs[objectName].actions![actionName]) {
106
+ configs[objectName].actions![actionName] = { };
107
+ }
108
+ // Attach handler
109
+ configs[objectName].actions![actionName].handler = handler as any;
110
+ }
111
+ }
112
+ }
113
+ } catch (e) {
114
+ console.error(`Error loading hook from ${file}:`, e);
115
+ }
116
+ }
117
+
118
+ return configs;
119
+ }
120
+