@objectql/types 1.2.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 +24 -0
- package/README.md +53 -0
- package/dist/action.d.ts +80 -0
- package/dist/action.js +3 -0
- package/dist/action.js.map +1 -0
- package/dist/driver.d.ts +18 -0
- package/dist/driver.js +3 -0
- package/dist/driver.js.map +1 -0
- package/dist/field.d.ts +77 -0
- package/dist/field.js +3 -0
- package/dist/field.js.map +1 -0
- package/dist/hook.d.ts +105 -0
- package/dist/hook.js +3 -0
- package/dist/hook.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/object.d.ts +43 -0
- package/dist/object.js +3 -0
- package/dist/object.js.map +1 -0
- package/dist/query.d.ts +18 -0
- package/dist/query.js +3 -0
- package/dist/query.js.map +1 -0
- package/dist/registry.d.ts +16 -0
- package/dist/registry.js +46 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +103 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/jest.config.js +5 -0
- package/package.json +11 -0
- package/src/action.ts +96 -0
- package/src/driver.ts +27 -0
- package/src/field.ts +124 -0
- package/src/hook.ts +132 -0
- package/src/index.ts +10 -0
- package/src/object.ts +49 -0
- package/src/query.ts +23 -0
- package/src/registry.ts +54 -0
- package/src/types.ts +120 -0
- package/test/dynamic.test.ts +34 -0
- package/test/fixtures/project.action.ts +6 -0
- package/test/fixtures/project.object.yml +41 -0
- package/test/loader.test.ts +22 -0
- package/test/metadata.test.ts +49 -0
- package/test/mock-driver.ts +86 -0
- package/test/repository.test.ts +151 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ObjectConfig } from "./object";
|
|
2
|
+
import { Driver } from "./driver";
|
|
3
|
+
import { UnifiedQuery } from "./query";
|
|
4
|
+
import { ObjectRegistry } from "./registry";
|
|
5
|
+
import { HookName, HookHandler, HookContext } from "./hook";
|
|
6
|
+
import { ActionHandler, ActionContext } from "./action";
|
|
7
|
+
export { ObjectConfig } from "./object";
|
|
8
|
+
export { ObjectRegistry } from "./registry";
|
|
9
|
+
export * from "./hook";
|
|
10
|
+
export * from "./action";
|
|
11
|
+
export interface IObjectRepository {
|
|
12
|
+
find(query?: UnifiedQuery): Promise<any[]>;
|
|
13
|
+
findOne(idOrQuery: string | number | UnifiedQuery): Promise<any>;
|
|
14
|
+
count(filters: any): Promise<number>;
|
|
15
|
+
create(doc: any): Promise<any>;
|
|
16
|
+
update(id: string | number, doc: any, options?: any): Promise<any>;
|
|
17
|
+
delete(id: string | number): Promise<any>;
|
|
18
|
+
aggregate(query: any): Promise<any>;
|
|
19
|
+
distinct(field: string, filters?: any): Promise<any[]>;
|
|
20
|
+
findOneAndUpdate?(filters: any, update: any, options?: any): Promise<any>;
|
|
21
|
+
createMany(data: any[]): Promise<any>;
|
|
22
|
+
updateMany(filters: any, data: any): Promise<any>;
|
|
23
|
+
deleteMany(filters: any): Promise<any>;
|
|
24
|
+
execute(actionName: string, id: string | number | undefined, params: any): Promise<any>;
|
|
25
|
+
}
|
|
26
|
+
export interface ObjectQLPlugin {
|
|
27
|
+
name: string;
|
|
28
|
+
setup(app: IObjectQL): void | Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export interface ObjectQLConfig {
|
|
31
|
+
registry?: ObjectRegistry;
|
|
32
|
+
datasources?: Record<string, Driver>;
|
|
33
|
+
/**
|
|
34
|
+
* Optional connection string for auto-configuration.
|
|
35
|
+
* e.g. "sqlite://dev.db", "postgres://localhost/db", "mongodb://localhost/db"
|
|
36
|
+
*/
|
|
37
|
+
connection?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Path(s) to the directory containing schema files (*.object.yml).
|
|
40
|
+
*/
|
|
41
|
+
source?: string | string[];
|
|
42
|
+
objects?: Record<string, ObjectConfig>;
|
|
43
|
+
/**
|
|
44
|
+
* @deprecated Use 'presets' instead.
|
|
45
|
+
*/
|
|
46
|
+
packages?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* List of npm packages or presets to load.
|
|
49
|
+
*/
|
|
50
|
+
presets?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* List of plugins to load.
|
|
53
|
+
* Can be an instance of ObjectQLPlugin or a package name string.
|
|
54
|
+
*/
|
|
55
|
+
plugins?: (ObjectQLPlugin | string)[];
|
|
56
|
+
}
|
|
57
|
+
export interface IObjectQL {
|
|
58
|
+
getObject(name: string): ObjectConfig | undefined;
|
|
59
|
+
getConfigs(): Record<string, ObjectConfig>;
|
|
60
|
+
datasource(name: string): Driver;
|
|
61
|
+
init(): Promise<void>;
|
|
62
|
+
addPackage(name: string): void;
|
|
63
|
+
removePackage(name: string): void;
|
|
64
|
+
metadata: ObjectRegistry;
|
|
65
|
+
on(event: HookName, objectName: string, handler: HookHandler): void;
|
|
66
|
+
triggerHook(event: HookName, objectName: string, ctx: HookContext): Promise<void>;
|
|
67
|
+
registerAction(objectName: string, actionName: string, handler: ActionHandler): void;
|
|
68
|
+
executeAction(objectName: string, actionName: string, ctx: ActionContext): Promise<any>;
|
|
69
|
+
}
|
|
70
|
+
export interface ObjectQLContext {
|
|
71
|
+
userId?: string;
|
|
72
|
+
spaceId?: string;
|
|
73
|
+
roles: string[];
|
|
74
|
+
/**
|
|
75
|
+
* Sudo Mode / System Bypass.
|
|
76
|
+
*/
|
|
77
|
+
isSystem?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Returns a repository proxy bound to this context.
|
|
80
|
+
* All operations performed via this proxy inherit userId, spaceId, and transaction.
|
|
81
|
+
*/
|
|
82
|
+
object(entityName: string): IObjectRepository;
|
|
83
|
+
/**
|
|
84
|
+
* Execute a function within a transaction.
|
|
85
|
+
* The callback receives a new context 'trxCtx' which inherits userId, spaceId from this context.
|
|
86
|
+
*/
|
|
87
|
+
transaction(callback: (trxCtx: ObjectQLContext) => Promise<any>): Promise<any>;
|
|
88
|
+
/**
|
|
89
|
+
* Returns a new context with system privileges (isSystem: true).
|
|
90
|
+
* It shares the same transaction scope as the current context.
|
|
91
|
+
*/
|
|
92
|
+
sudo(): ObjectQLContext;
|
|
93
|
+
/**
|
|
94
|
+
* Internal: Driver-specific transaction handle.
|
|
95
|
+
*/
|
|
96
|
+
transactionHandle?: any;
|
|
97
|
+
}
|
|
98
|
+
export interface ObjectQLContextOptions {
|
|
99
|
+
userId?: string;
|
|
100
|
+
spaceId?: string;
|
|
101
|
+
roles?: string[];
|
|
102
|
+
isSystem?: boolean;
|
|
103
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.ObjectRegistry = void 0;
|
|
18
|
+
var registry_1 = require("./registry");
|
|
19
|
+
Object.defineProperty(exports, "ObjectRegistry", { enumerable: true, get: function () { return registry_1.ObjectRegistry; } });
|
|
20
|
+
__exportStar(require("./hook"), exports);
|
|
21
|
+
__exportStar(require("./action"), exports);
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAQA,uCAA4C;AAAnC,0GAAA,cAAc,OAAA;AACvB,yCAAuB;AACvB,2CAAyB"}
|
package/jest.config.js
ADDED
package/package.json
ADDED
package/src/action.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { FieldConfig } from "./field";
|
|
2
|
+
import { HookAPI } from "./hook"; // Reuse the restricted API interface
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Defines the scope of the action.
|
|
6
|
+
* - `record`: Acts on a specific record instance (e.g. "Approve Order").
|
|
7
|
+
* - `global`: Acts on the collection or system (e.g. "Import CSV", "Daily Report").
|
|
8
|
+
*/
|
|
9
|
+
export type ActionType = 'record' | 'global';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Re-using FieldConfig allows us to describe input parameters
|
|
13
|
+
* using the same rich vocabulary as database fields (validation, UI hints, etc).
|
|
14
|
+
*/
|
|
15
|
+
export type ActionInputDefinition = Record<string, FieldConfig>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Context passed to the action handler execution.
|
|
19
|
+
*/
|
|
20
|
+
export interface ActionContext<BaseT = any, InputT = any> {
|
|
21
|
+
/** The object this action belongs to. */
|
|
22
|
+
objectName: string;
|
|
23
|
+
|
|
24
|
+
/** The name of the action being executed. */
|
|
25
|
+
actionName: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The ID of the record being acted upon.
|
|
29
|
+
* Only available if type is 'record'.
|
|
30
|
+
*/
|
|
31
|
+
id?: string | number;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The validated input arguments.
|
|
35
|
+
*/
|
|
36
|
+
input: InputT;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Database Access API (Same as Hooks).
|
|
40
|
+
*/
|
|
41
|
+
api: HookAPI;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* User Session.
|
|
45
|
+
*/
|
|
46
|
+
user?: {
|
|
47
|
+
id: string | number;
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The configuration of an Action visible to the Metadata engine (YAML/JSON side).
|
|
54
|
+
*/
|
|
55
|
+
export interface ActionConfig {
|
|
56
|
+
label?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
icon?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Default: 'global' if no fields defined, but usually specified explicitly.
|
|
62
|
+
*/
|
|
63
|
+
type?: ActionType; // 'record' | 'global'
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Message to show before executing. If present, UI should prompt confirmation.
|
|
67
|
+
*/
|
|
68
|
+
confirm_text?: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* If true, this action is not exposed via API directly (server-internal).
|
|
72
|
+
*/
|
|
73
|
+
internal?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Input parameter schema.
|
|
77
|
+
*/
|
|
78
|
+
params?: ActionInputDefinition;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Output data shape description (optional, for content negotiation).
|
|
82
|
+
*/
|
|
83
|
+
return_type?: string | FieldConfig;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The full implementation definition (Code side).
|
|
88
|
+
*/
|
|
89
|
+
export interface ActionDefinition<BaseT = any, InputT = any, ReturnT = any> extends ActionConfig {
|
|
90
|
+
/**
|
|
91
|
+
* The business logic implementation.
|
|
92
|
+
*/
|
|
93
|
+
handler: (ctx: ActionContext<BaseT, InputT>) => Promise<ReturnT>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type ActionHandler<BaseT = any, InputT = any, ReturnT = any> = (ctx: ActionContext<BaseT, InputT>) => Promise<ReturnT>;
|
package/src/driver.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
// Schema / Lifecycle
|
|
10
|
+
init?(objects: any[]): Promise<void>;
|
|
11
|
+
|
|
12
|
+
// Advanced
|
|
13
|
+
aggregate?(objectName: string, query: any, options?: any): Promise<any>;
|
|
14
|
+
distinct?(objectName: string, field: string, filters?: any, options?: any): Promise<any[]>;
|
|
15
|
+
|
|
16
|
+
// Bulk / Atomic
|
|
17
|
+
createMany?(objectName: string, data: any[], options?: any): Promise<any>;
|
|
18
|
+
updateMany?(objectName: string, filters: any, data: any, options?: any): Promise<any>;
|
|
19
|
+
deleteMany?(objectName: string, filters: any, options?: any): Promise<any>;
|
|
20
|
+
findOneAndUpdate?(objectName: string, filters: any, update: any, options?: any): Promise<any>;
|
|
21
|
+
|
|
22
|
+
// Transaction
|
|
23
|
+
beginTransaction?(): Promise<any>;
|
|
24
|
+
commitTransaction?(trx: any): Promise<void>;
|
|
25
|
+
rollbackTransaction?(trx: any): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
package/src/field.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the supported field data types in the ObjectQL schema.
|
|
3
|
+
* These types determine how data is stored, validated, and rendered.
|
|
4
|
+
*
|
|
5
|
+
* - `text`: Simple string.
|
|
6
|
+
* - `textarea`: Long string.
|
|
7
|
+
* - `select`: Choice from a list.
|
|
8
|
+
* - `lookup`: Relationship to another object.
|
|
9
|
+
*/
|
|
10
|
+
export type FieldType =
|
|
11
|
+
| 'text'
|
|
12
|
+
| 'textarea'
|
|
13
|
+
| 'markdown'
|
|
14
|
+
| 'html'
|
|
15
|
+
| 'select'
|
|
16
|
+
| 'date'
|
|
17
|
+
| 'datetime'
|
|
18
|
+
| 'time'
|
|
19
|
+
| 'number'
|
|
20
|
+
| 'currency'
|
|
21
|
+
| 'percent'
|
|
22
|
+
| 'boolean'
|
|
23
|
+
| 'email'
|
|
24
|
+
| 'phone'
|
|
25
|
+
| 'url'
|
|
26
|
+
| 'image'
|
|
27
|
+
| 'file'
|
|
28
|
+
| 'avatar'
|
|
29
|
+
| 'location'
|
|
30
|
+
| 'lookup'
|
|
31
|
+
| 'master_detail'
|
|
32
|
+
| 'password'
|
|
33
|
+
| 'formula'
|
|
34
|
+
| 'summary'
|
|
35
|
+
| 'auto_number'
|
|
36
|
+
| 'object'
|
|
37
|
+
| 'vector'
|
|
38
|
+
| 'grid';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Defines a single option for select/multiselect fields.
|
|
42
|
+
*/
|
|
43
|
+
export interface FieldOption {
|
|
44
|
+
/** The display label for the option. */
|
|
45
|
+
label: string;
|
|
46
|
+
/** The actual value stored in the database. */
|
|
47
|
+
value: string | number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Configuration for a single field on an object.
|
|
52
|
+
* This defines the schema, validation rules, and UI hints for the attribute.
|
|
53
|
+
*/
|
|
54
|
+
export interface FieldConfig {
|
|
55
|
+
/**
|
|
56
|
+
* The unique API name of the field.
|
|
57
|
+
* If defined within an object map, this is often automatically populated from the key.
|
|
58
|
+
*/
|
|
59
|
+
name?: string;
|
|
60
|
+
|
|
61
|
+
/** The human-readable label used in UIs. */
|
|
62
|
+
label?: string;
|
|
63
|
+
|
|
64
|
+
/** Description of the field for documentation or tooltip. */
|
|
65
|
+
description?: string;
|
|
66
|
+
|
|
67
|
+
/** The data type of the field. */
|
|
68
|
+
type: FieldType;
|
|
69
|
+
|
|
70
|
+
/** Whether the field is mandatory. Defaults to false. */
|
|
71
|
+
required?: boolean;
|
|
72
|
+
|
|
73
|
+
/** Whether the field is unique in the table. */
|
|
74
|
+
unique?: boolean;
|
|
75
|
+
|
|
76
|
+
/** Whether to create a database index for this field. */
|
|
77
|
+
index?: boolean;
|
|
78
|
+
|
|
79
|
+
/** Whether the field is read-only in UI. */
|
|
80
|
+
readonly?: boolean;
|
|
81
|
+
|
|
82
|
+
/** Whether the field is hidden from default UI/API response. */
|
|
83
|
+
hidden?: boolean;
|
|
84
|
+
|
|
85
|
+
/** The default value if not provided during creation. */
|
|
86
|
+
defaultValue?: any;
|
|
87
|
+
|
|
88
|
+
/** Tooltip or help text for the user. */
|
|
89
|
+
help_text?: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether the field allows multiple values.
|
|
93
|
+
* Supported by 'select', 'lookup', 'file', 'image'.
|
|
94
|
+
*/
|
|
95
|
+
multiple?: boolean;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Options for select fields.
|
|
99
|
+
* List of available choices for select/multiselect fields.
|
|
100
|
+
*/
|
|
101
|
+
options?: FieldOption[];
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Reference to another object for lookup/master_detail fields.
|
|
105
|
+
* Specifies the target object name for relationship fields.
|
|
106
|
+
*/
|
|
107
|
+
reference_to?: string;
|
|
108
|
+
|
|
109
|
+
// Validation properties
|
|
110
|
+
/** Minimum for number/currency/percent. */
|
|
111
|
+
min?: number;
|
|
112
|
+
/** Maximum for number/currency/percent. */
|
|
113
|
+
max?: number;
|
|
114
|
+
/** Minimum length for text based fields. */
|
|
115
|
+
min_length?: number;
|
|
116
|
+
/** Maximum length for text based fields. */
|
|
117
|
+
max_length?: number;
|
|
118
|
+
/** Regular expression pattern for validation. */
|
|
119
|
+
regex?: string;
|
|
120
|
+
|
|
121
|
+
// Vector properties
|
|
122
|
+
/** Dimension of the vector for 'vector' type fields. */
|
|
123
|
+
dimension?: number;
|
|
124
|
+
}
|
package/src/hook.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { UnifiedQuery } from "./query";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Standard CRUD operations supported by hooks.
|
|
5
|
+
*/
|
|
6
|
+
export type HookOperation = 'find' | 'count' | 'create' | 'update' | 'delete';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Execution timing relative to the database operation.
|
|
10
|
+
*/
|
|
11
|
+
export type HookTiming = 'before' | 'after';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Minimal API surface exposed to hooks for performing side-effects or checks.
|
|
15
|
+
*/
|
|
16
|
+
export interface HookAPI {
|
|
17
|
+
// We use 'any' here to avoid circular dependencies with core/ObjectQL
|
|
18
|
+
// In practice, this is the ObjectQL instance.
|
|
19
|
+
find(objectName: string, query?: any): Promise<any[]>;
|
|
20
|
+
findOne(objectName: string, id: string | number): Promise<any>;
|
|
21
|
+
count(objectName: string, query?: any): Promise<number>;
|
|
22
|
+
create(objectName: string, data: any): Promise<any>;
|
|
23
|
+
update(objectName: string, id: string | number, data: any): Promise<any>;
|
|
24
|
+
delete(objectName: string, id: string | number): Promise<any>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base context available in all hooks.
|
|
29
|
+
*/
|
|
30
|
+
export interface BaseHookContext<T = any> {
|
|
31
|
+
/** The name of the object (entity) being acted upon. */
|
|
32
|
+
objectName: string;
|
|
33
|
+
|
|
34
|
+
/** The triggering operation. */
|
|
35
|
+
operation: HookOperation;
|
|
36
|
+
|
|
37
|
+
/** Access to the database/engine to perform extra queries. */
|
|
38
|
+
api: HookAPI;
|
|
39
|
+
|
|
40
|
+
/** User/Session context (Authentication info). */
|
|
41
|
+
user?: {
|
|
42
|
+
id: string | number;
|
|
43
|
+
[key: string]: any;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Shared state for passing data between matching 'before' and 'after' hooks.
|
|
48
|
+
* e.g. Calculate a diff in 'beforeUpdate' and read it in 'afterUpdate'.
|
|
49
|
+
*/
|
|
50
|
+
state: Record<string, any>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Context for Retrieval operations (Find, Count).
|
|
55
|
+
*/
|
|
56
|
+
export interface RetrievalHookContext<T = any> extends BaseHookContext<T> {
|
|
57
|
+
operation: 'find' | 'count';
|
|
58
|
+
|
|
59
|
+
/** The query criteria being executed. Modifiable in 'before' hooks. */
|
|
60
|
+
query: any;
|
|
61
|
+
|
|
62
|
+
/** The result of the query. Only available in 'after' hooks. */
|
|
63
|
+
result?: T[] | number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Context for Modification operations (Create, Update, Delete).
|
|
68
|
+
*/
|
|
69
|
+
export interface MutationHookContext<T = any> extends BaseHookContext<T> {
|
|
70
|
+
operation: 'create' | 'update' | 'delete';
|
|
71
|
+
|
|
72
|
+
/** The record ID. Undefined for 'create'. */
|
|
73
|
+
id?: string | number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The incoming data changes.
|
|
77
|
+
* - For 'create': The full object to insert.
|
|
78
|
+
* - For 'update': The partial fields to update.
|
|
79
|
+
* - For 'delete': Undefined.
|
|
80
|
+
*/
|
|
81
|
+
data?: Partial<T>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The final result record from the database.
|
|
85
|
+
* Only available in 'after' hooks.
|
|
86
|
+
*/
|
|
87
|
+
result?: T;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Specialized context for Updates, including change tracking.
|
|
92
|
+
*/
|
|
93
|
+
export interface UpdateHookContext<T = any> extends MutationHookContext<T> {
|
|
94
|
+
operation: 'update';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The record state BEFORE the update.
|
|
98
|
+
* Useful for comparison logic (e.g. status changed from A to B).
|
|
99
|
+
* Note: This may require a pre-fetch lookup depending on engine configuration.
|
|
100
|
+
*/
|
|
101
|
+
previousData?: T;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Helper to check if a specific field is being modified.
|
|
105
|
+
* Checks if the field exists in 'data' AND is different from 'previousData'.
|
|
106
|
+
*/
|
|
107
|
+
isModified(field: keyof T): boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Definition interface for a set of hooks for a specific object.
|
|
112
|
+
*/
|
|
113
|
+
export interface ObjectHookDefinition<T = any> {
|
|
114
|
+
beforeFind?: (ctx: RetrievalHookContext<T>) => Promise<void> | void;
|
|
115
|
+
afterFind?: (ctx: RetrievalHookContext<T>) => Promise<void> | void;
|
|
116
|
+
|
|
117
|
+
beforeCount?: (ctx: RetrievalHookContext<T>) => Promise<void> | void;
|
|
118
|
+
afterCount?: (ctx: RetrievalHookContext<T>) => Promise<void> | void;
|
|
119
|
+
|
|
120
|
+
beforeDelete?: (ctx: MutationHookContext<T>) => Promise<void> | void;
|
|
121
|
+
afterDelete?: (ctx: MutationHookContext<T>) => Promise<void> | void;
|
|
122
|
+
|
|
123
|
+
beforeCreate?: (ctx: MutationHookContext<T>) => Promise<void> | void;
|
|
124
|
+
afterCreate?: (ctx: MutationHookContext<T>) => Promise<void> | void;
|
|
125
|
+
|
|
126
|
+
beforeUpdate?: (ctx: UpdateHookContext<T>) => Promise<void> | void;
|
|
127
|
+
afterUpdate?: (ctx: UpdateHookContext<T>) => Promise<void> | void;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type HookName = keyof ObjectHookDefinition;
|
|
131
|
+
export type HookContext<T = any> = RetrievalHookContext<T> | MutationHookContext<T> | UpdateHookContext<T>;
|
|
132
|
+
export type HookHandler<T = any> = (ctx: HookContext<T>) => Promise<void> | void;
|
package/src/index.ts
ADDED
package/src/object.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { FieldConfig } from './field';
|
|
2
|
+
import { ActionConfig } from './action';
|
|
3
|
+
|
|
4
|
+
export interface IndexConfig {
|
|
5
|
+
/** List of fields involved in the index */
|
|
6
|
+
fields: string[];
|
|
7
|
+
/** Whether the index enforces uniqueness */
|
|
8
|
+
unique?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AiSearchConfig {
|
|
12
|
+
/** Enable semantic search for this object */
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
/** Fields to include in the embedding generation */
|
|
15
|
+
fields: string[];
|
|
16
|
+
/** The AI model to use for embedding (e.g. 'openai/text-embedding-3-small') */
|
|
17
|
+
model?: string;
|
|
18
|
+
/** Optional: Target vector field name if manually defined */
|
|
19
|
+
target_field?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ObjectAiConfig {
|
|
23
|
+
/** Configuration for semantic search / RAG */
|
|
24
|
+
search?: AiSearchConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ObjectConfig {
|
|
28
|
+
name: string;
|
|
29
|
+
datasource?: string; // The name of the datasource to use
|
|
30
|
+
label?: string;
|
|
31
|
+
icon?: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
|
|
34
|
+
fields: Record<string, FieldConfig>;
|
|
35
|
+
indexes?: Record<string, IndexConfig>;
|
|
36
|
+
/** AI capabilities configuration */
|
|
37
|
+
ai?: ObjectAiConfig;
|
|
38
|
+
actions?: Record<string, ActionConfig>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Base interface for all ObjectQL documents.
|
|
43
|
+
*/
|
|
44
|
+
export interface ObjectDoc {
|
|
45
|
+
_id?: string | number;
|
|
46
|
+
created_at?: Date | string;
|
|
47
|
+
updated_at?: Date | string;
|
|
48
|
+
[key: string]: any;
|
|
49
|
+
}
|
package/src/query.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type FilterCriterion = [string, string, any];
|
|
2
|
+
export type FilterExpression = FilterCriterion | 'and' | 'or' | FilterExpression[];
|
|
3
|
+
|
|
4
|
+
export type AggregateFunction = 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
5
|
+
|
|
6
|
+
export interface AggregateOption {
|
|
7
|
+
func: AggregateFunction;
|
|
8
|
+
field: string;
|
|
9
|
+
alias?: string; // Optional: rename the result field
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UnifiedQuery {
|
|
13
|
+
fields?: string[];
|
|
14
|
+
filters?: FilterExpression[];
|
|
15
|
+
sort?: [string, 'asc' | 'desc'][];
|
|
16
|
+
skip?: number;
|
|
17
|
+
limit?: number;
|
|
18
|
+
expand?: Record<string, UnifiedQuery>;
|
|
19
|
+
|
|
20
|
+
// === Aggregation Support ===
|
|
21
|
+
groupBy?: string[];
|
|
22
|
+
aggregate?: AggregateOption[];
|
|
23
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface Metadata {
|
|
2
|
+
type: string;
|
|
3
|
+
id: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
package?: string;
|
|
6
|
+
content: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ObjectRegistry {
|
|
10
|
+
// Map<type, Map<id, Metadata>>
|
|
11
|
+
private store: Map<string, Map<string, Metadata>> = new Map();
|
|
12
|
+
|
|
13
|
+
register(type: string, metadata: Metadata) {
|
|
14
|
+
if (!this.store.has(type)) {
|
|
15
|
+
this.store.set(type, new Map());
|
|
16
|
+
}
|
|
17
|
+
this.store.get(type)!.set(metadata.id, metadata);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
unregister(type: string, id: string) {
|
|
21
|
+
this.store.get(type)?.delete(id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
unregisterPackage(packageName: string) {
|
|
25
|
+
for (const [type, map] of this.store.entries()) {
|
|
26
|
+
const entriesToDelete: string[] = [];
|
|
27
|
+
|
|
28
|
+
for (const [id, meta] of map.entries()) {
|
|
29
|
+
if (meta.package === packageName) {
|
|
30
|
+
entriesToDelete.push(id);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Delete all collected entries
|
|
35
|
+
for (const id of entriesToDelete) {
|
|
36
|
+
map.delete(id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get<T = any>(type: string, id: string): T | undefined {
|
|
42
|
+
return this.store.get(type)?.get(id)?.content as T;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
list<T = any>(type: string): T[] {
|
|
46
|
+
const map = this.store.get(type);
|
|
47
|
+
if (!map) return [];
|
|
48
|
+
return Array.from(map.values()).map(m => m.content as T);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getEntry(type: string, id: string): Metadata | undefined {
|
|
52
|
+
return this.store.get(type)?.get(id);
|
|
53
|
+
}
|
|
54
|
+
}
|