@objectql/core 3.0.1 → 4.0.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/IMPLEMENTATION_STATUS.md +364 -0
- package/README.md +31 -9
- package/RUNTIME_INTEGRATION.md +391 -0
- package/dist/ai-agent.d.ts +4 -3
- package/dist/ai-agent.js +10 -3
- package/dist/ai-agent.js.map +1 -1
- package/dist/app.d.ts +29 -6
- package/dist/app.js +117 -58
- package/dist/app.js.map +1 -1
- package/dist/formula-engine.d.ts +7 -0
- package/dist/formula-engine.js +9 -2
- package/dist/formula-engine.js.map +1 -1
- package/dist/formula-plugin.d.ts +52 -0
- package/dist/formula-plugin.js +107 -0
- package/dist/formula-plugin.js.map +1 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts +89 -0
- package/dist/plugin.js +99 -0
- package/dist/plugin.js.map +1 -0
- package/dist/query/filter-translator.d.ts +37 -0
- package/dist/query/filter-translator.js +135 -0
- package/dist/query/filter-translator.js.map +1 -0
- package/dist/query/index.d.ts +22 -0
- package/dist/query/index.js +39 -0
- package/dist/query/index.js.map +1 -0
- package/dist/query/query-analyzer.d.ts +186 -0
- package/dist/query/query-analyzer.js +349 -0
- package/dist/query/query-analyzer.js.map +1 -0
- package/dist/query/query-builder.d.ts +27 -0
- package/dist/query/query-builder.js +71 -0
- package/dist/query/query-builder.js.map +1 -0
- package/dist/query/query-service.d.ts +150 -0
- package/dist/query/query-service.js +268 -0
- package/dist/query/query-service.js.map +1 -0
- package/dist/repository.d.ts +23 -2
- package/dist/repository.js +62 -13
- package/dist/repository.js.map +1 -1
- package/dist/util.d.ts +7 -0
- package/dist/util.js +18 -3
- package/dist/util.js.map +1 -1
- package/dist/validator-plugin.d.ts +56 -0
- package/dist/validator-plugin.js +106 -0
- package/dist/validator-plugin.js.map +1 -0
- package/dist/validator.d.ts +7 -0
- package/dist/validator.js +10 -8
- package/dist/validator.js.map +1 -1
- package/jest.config.js +16 -0
- package/package.json +8 -5
- package/src/ai-agent.ts +8 -0
- package/src/app.ts +136 -72
- package/src/formula-engine.ts +8 -0
- package/src/formula-plugin.ts +141 -0
- package/src/index.ts +25 -3
- package/src/plugin.ts +179 -0
- package/src/query/filter-translator.ts +147 -0
- package/src/query/index.ts +24 -0
- package/src/query/query-analyzer.ts +535 -0
- package/src/query/query-builder.ts +80 -0
- package/src/query/query-service.ts +392 -0
- package/src/repository.ts +81 -17
- package/src/util.ts +19 -3
- package/src/validator-plugin.ts +140 -0
- package/src/validator.ts +12 -5
- package/test/__mocks__/@objectstack/runtime.ts +255 -0
- package/test/app.test.ts +23 -35
- package/test/filter-syntax.test.ts +233 -0
- package/test/formula-engine.test.ts +8 -0
- package/test/formula-integration.test.ts +8 -0
- package/test/formula-plugin.test.ts +197 -0
- package/test/introspection.test.ts +8 -0
- package/test/mock-driver.ts +8 -0
- package/test/plugin-integration.test.ts +213 -0
- package/test/repository-validation.test.ts +8 -0
- package/test/repository.test.ts +8 -0
- package/test/util.test.ts +9 -1
- package/test/utils.ts +8 -0
- package/test/validator-plugin.test.ts +126 -0
- package/test/validator.test.ts +8 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/action.d.ts +0 -7
- package/dist/action.js +0 -23
- package/dist/action.js.map +0 -1
- package/dist/hook.d.ts +0 -8
- package/dist/hook.js +0 -25
- package/dist/hook.js.map +0 -1
- package/dist/object.d.ts +0 -3
- package/dist/object.js +0 -28
- package/dist/object.js.map +0 -1
- package/src/action.ts +0 -40
- package/src/hook.ts +0 -42
- package/src/object.ts +0 -26
- package/test/action.test.ts +0 -276
- package/test/hook.test.ts +0 -343
- package/test/object.test.ts +0 -183
package/dist/action.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { ActionContext, ActionHandler, MetadataRegistry } from '@objectql/types';
|
|
2
|
-
export interface ActionEntry {
|
|
3
|
-
handler: ActionHandler;
|
|
4
|
-
packageName?: string;
|
|
5
|
-
}
|
|
6
|
-
export declare function registerActionHelper(actions: Record<string, ActionEntry>, objectName: string, actionName: string, handler: ActionHandler, packageName?: string): void;
|
|
7
|
-
export declare function executeActionHelper(metadata: MetadataRegistry, runtimeActions: Record<string, ActionEntry>, objectName: string, actionName: string, ctx: ActionContext): Promise<any>;
|
package/dist/action.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerActionHelper = registerActionHelper;
|
|
4
|
-
exports.executeActionHelper = executeActionHelper;
|
|
5
|
-
function registerActionHelper(actions, objectName, actionName, handler, packageName) {
|
|
6
|
-
const key = `${objectName}:${actionName}`;
|
|
7
|
-
actions[key] = { handler, packageName };
|
|
8
|
-
}
|
|
9
|
-
async function executeActionHelper(metadata, runtimeActions, objectName, actionName, ctx) {
|
|
10
|
-
// 1. Programmatic
|
|
11
|
-
const key = `${objectName}:${actionName}`;
|
|
12
|
-
const actionEntry = runtimeActions[key];
|
|
13
|
-
if (actionEntry) {
|
|
14
|
-
return await actionEntry.handler(ctx);
|
|
15
|
-
}
|
|
16
|
-
// 2. Registry (File-based)
|
|
17
|
-
const fileActions = metadata.get('action', objectName);
|
|
18
|
-
if (fileActions && typeof fileActions[actionName] === 'function') {
|
|
19
|
-
return await fileActions[actionName](ctx);
|
|
20
|
-
}
|
|
21
|
-
throw new Error(`Action '${actionName}' not found for object '${objectName}'`);
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=action.js.map
|
package/dist/action.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":";;AAOA,oDASC;AAED,kDAqBC;AAhCD,SAAgB,oBAAoB,CAChC,OAAoC,EACpC,UAAkB,EAClB,UAAkB,EAClB,OAAsB,EACtB,WAAoB;IAEpB,MAAM,GAAG,GAAG,GAAG,UAAU,IAAI,UAAU,EAAE,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAC5C,CAAC;AAEM,KAAK,UAAU,mBAAmB,CACrC,QAA0B,EAC1B,cAA2C,EAC3C,UAAkB,EAClB,UAAkB,EAClB,GAAkB;IAElB,kBAAkB;IAClB,MAAM,GAAG,GAAG,GAAG,UAAU,IAAI,UAAU,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,WAAW,EAAE,CAAC;QACd,OAAO,MAAM,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,2BAA2B;IAC3B,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAM,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC5D,IAAI,WAAW,IAAI,OAAO,WAAW,CAAC,UAAU,CAAC,KAAK,UAAU,EAAE,CAAC;QAC/D,OAAO,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,WAAW,UAAU,2BAA2B,UAAU,GAAG,CAAC,CAAC;AACnF,CAAC"}
|
package/dist/hook.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { HookContext, HookHandler, HookName, MetadataRegistry } from '@objectql/types';
|
|
2
|
-
export interface HookEntry {
|
|
3
|
-
objectName: string;
|
|
4
|
-
handler: HookHandler;
|
|
5
|
-
packageName?: string;
|
|
6
|
-
}
|
|
7
|
-
export declare function registerHookHelper(hooks: Record<string, HookEntry[]>, event: HookName, objectName: string, handler: HookHandler, packageName?: string): void;
|
|
8
|
-
export declare function triggerHookHelper(metadata: MetadataRegistry, runtimeHooks: Record<string, HookEntry[]>, event: HookName, objectName: string, ctx: HookContext): Promise<void>;
|
package/dist/hook.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerHookHelper = registerHookHelper;
|
|
4
|
-
exports.triggerHookHelper = triggerHookHelper;
|
|
5
|
-
function registerHookHelper(hooks, event, objectName, handler, packageName) {
|
|
6
|
-
if (!hooks[event]) {
|
|
7
|
-
hooks[event] = [];
|
|
8
|
-
}
|
|
9
|
-
hooks[event].push({ objectName, handler, packageName });
|
|
10
|
-
}
|
|
11
|
-
async function triggerHookHelper(metadata, runtimeHooks, event, objectName, ctx) {
|
|
12
|
-
// 1. Registry Hooks (File-based)
|
|
13
|
-
const fileHooks = metadata.get('hook', objectName);
|
|
14
|
-
if (fileHooks && typeof fileHooks[event] === 'function') {
|
|
15
|
-
await fileHooks[event](ctx);
|
|
16
|
-
}
|
|
17
|
-
// 2. Programmatic Hooks
|
|
18
|
-
const hooks = runtimeHooks[event] || [];
|
|
19
|
-
for (const hook of hooks) {
|
|
20
|
-
if (hook.objectName === '*' || hook.objectName === objectName) {
|
|
21
|
-
await hook.handler(ctx);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
//# sourceMappingURL=hook.js.map
|
package/dist/hook.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hook.js","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":";;AAQA,gDAWC;AAED,8CAoBC;AAjCD,SAAgB,kBAAkB,CAC9B,KAAkC,EAClC,KAAe,EACf,UAAkB,EAClB,OAAoB,EACpB,WAAoB;IAEpB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAChB,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;IACtB,CAAC;IACD,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;AAC5D,CAAC;AAEM,KAAK,UAAU,iBAAiB,CACnC,QAA0B,EAC1B,YAAyC,EACzC,KAAe,EACf,UAAkB,EAClB,GAAgB;IAEhB,iCAAiC;IACjC,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAM,MAAM,EAAE,UAAU,CAAC,CAAC;IACxD,IAAI,SAAS,IAAI,OAAO,SAAS,CAAC,KAAK,CAAC,KAAK,UAAU,EAAE,CAAC;QACtD,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,wBAAwB;IACxB,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,UAAU,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YAC5D,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;AACL,CAAC"}
|
package/dist/object.d.ts
DELETED
package/dist/object.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.registerObjectHelper = registerObjectHelper;
|
|
4
|
-
exports.getConfigsHelper = getConfigsHelper;
|
|
5
|
-
function registerObjectHelper(metadata, object) {
|
|
6
|
-
// Normalize fields
|
|
7
|
-
if (object.fields) {
|
|
8
|
-
for (const [key, field] of Object.entries(object.fields)) {
|
|
9
|
-
if (!field.name) {
|
|
10
|
-
field.name = key;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
metadata.register('object', {
|
|
15
|
-
type: 'object',
|
|
16
|
-
id: object.name,
|
|
17
|
-
content: object
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function getConfigsHelper(metadata) {
|
|
21
|
-
const result = {};
|
|
22
|
-
const objects = metadata.list('object');
|
|
23
|
-
for (const obj of objects) {
|
|
24
|
-
result[obj.name] = obj;
|
|
25
|
-
}
|
|
26
|
-
return result;
|
|
27
|
-
}
|
|
28
|
-
//# sourceMappingURL=object.js.map
|
package/dist/object.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"object.js","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":";;AAEA,oDAcC;AAED,4CAOC;AAvBD,SAAgB,oBAAoB,CAAC,QAA0B,EAAE,MAAoB;IACjF,mBAAmB;IACnB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAChB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;gBACd,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC;YACrB,CAAC;QACL,CAAC;IACL,CAAC;IACD,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE;QACxB,IAAI,EAAE,QAAQ;QACd,EAAE,EAAE,MAAM,CAAC,IAAI;QACf,OAAO,EAAE,MAAM;KAClB,CAAC,CAAC;AACP,CAAC;AAED,SAAgB,gBAAgB,CAAC,QAA0B;IACvD,MAAM,MAAM,GAAiC,EAAE,CAAC;IAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAe,QAAQ,CAAC,CAAC;IACtD,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
package/src/action.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { ActionContext, ActionHandler, MetadataRegistry } from '@objectql/types';
|
|
2
|
-
|
|
3
|
-
export interface ActionEntry {
|
|
4
|
-
handler: ActionHandler;
|
|
5
|
-
packageName?: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function registerActionHelper(
|
|
9
|
-
actions: Record<string, ActionEntry>,
|
|
10
|
-
objectName: string,
|
|
11
|
-
actionName: string,
|
|
12
|
-
handler: ActionHandler,
|
|
13
|
-
packageName?: string
|
|
14
|
-
) {
|
|
15
|
-
const key = `${objectName}:${actionName}`;
|
|
16
|
-
actions[key] = { handler, packageName };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function executeActionHelper(
|
|
20
|
-
metadata: MetadataRegistry,
|
|
21
|
-
runtimeActions: Record<string, ActionEntry>,
|
|
22
|
-
objectName: string,
|
|
23
|
-
actionName: string,
|
|
24
|
-
ctx: ActionContext
|
|
25
|
-
) {
|
|
26
|
-
// 1. Programmatic
|
|
27
|
-
const key = `${objectName}:${actionName}`;
|
|
28
|
-
const actionEntry = runtimeActions[key];
|
|
29
|
-
if (actionEntry) {
|
|
30
|
-
return await actionEntry.handler(ctx);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 2. Registry (File-based)
|
|
34
|
-
const fileActions = metadata.get<any>('action', objectName);
|
|
35
|
-
if (fileActions && typeof fileActions[actionName] === 'function') {
|
|
36
|
-
return await fileActions[actionName](ctx);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
throw new Error(`Action '${actionName}' not found for object '${objectName}'`);
|
|
40
|
-
}
|
package/src/hook.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { HookContext, HookHandler, HookName, MetadataRegistry } from '@objectql/types';
|
|
2
|
-
|
|
3
|
-
export interface HookEntry {
|
|
4
|
-
objectName: string;
|
|
5
|
-
handler: HookHandler;
|
|
6
|
-
packageName?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function registerHookHelper(
|
|
10
|
-
hooks: Record<string, HookEntry[]>,
|
|
11
|
-
event: HookName,
|
|
12
|
-
objectName: string,
|
|
13
|
-
handler: HookHandler,
|
|
14
|
-
packageName?: string
|
|
15
|
-
) {
|
|
16
|
-
if (!hooks[event]) {
|
|
17
|
-
hooks[event] = [];
|
|
18
|
-
}
|
|
19
|
-
hooks[event].push({ objectName, handler, packageName });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function triggerHookHelper(
|
|
23
|
-
metadata: MetadataRegistry,
|
|
24
|
-
runtimeHooks: Record<string, HookEntry[]>,
|
|
25
|
-
event: HookName,
|
|
26
|
-
objectName: string,
|
|
27
|
-
ctx: HookContext
|
|
28
|
-
) {
|
|
29
|
-
// 1. Registry Hooks (File-based)
|
|
30
|
-
const fileHooks = metadata.get<any>('hook', objectName);
|
|
31
|
-
if (fileHooks && typeof fileHooks[event] === 'function') {
|
|
32
|
-
await fileHooks[event](ctx);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 2. Programmatic Hooks
|
|
36
|
-
const hooks = runtimeHooks[event] || [];
|
|
37
|
-
for (const hook of hooks) {
|
|
38
|
-
if (hook.objectName === '*' || hook.objectName === objectName) {
|
|
39
|
-
await hook.handler(ctx);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
package/src/object.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ObjectConfig, MetadataRegistry } from '@objectql/types';
|
|
2
|
-
|
|
3
|
-
export function registerObjectHelper(metadata: MetadataRegistry, object: ObjectConfig) {
|
|
4
|
-
// Normalize fields
|
|
5
|
-
if (object.fields) {
|
|
6
|
-
for (const [key, field] of Object.entries(object.fields)) {
|
|
7
|
-
if (!field.name) {
|
|
8
|
-
field.name = key;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
metadata.register('object', {
|
|
13
|
-
type: 'object',
|
|
14
|
-
id: object.name,
|
|
15
|
-
content: object
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getConfigsHelper(metadata: MetadataRegistry): Record<string, ObjectConfig> {
|
|
20
|
-
const result: Record<string, ObjectConfig> = {};
|
|
21
|
-
const objects = metadata.list<ObjectConfig>('object');
|
|
22
|
-
for (const obj of objects) {
|
|
23
|
-
result[obj.name] = obj;
|
|
24
|
-
}
|
|
25
|
-
return result;
|
|
26
|
-
}
|
package/test/action.test.ts
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { ObjectQL } from '../src';
|
|
2
|
-
import { MockDriver } from './mock-driver';
|
|
3
|
-
|
|
4
|
-
describe('ObjectQL Actions', () => {
|
|
5
|
-
let app: ObjectQL;
|
|
6
|
-
let driver: MockDriver;
|
|
7
|
-
|
|
8
|
-
beforeEach(async () => {
|
|
9
|
-
driver = new MockDriver();
|
|
10
|
-
app = new ObjectQL({
|
|
11
|
-
datasources: {
|
|
12
|
-
default: driver
|
|
13
|
-
},
|
|
14
|
-
objects: {
|
|
15
|
-
'invoice': {
|
|
16
|
-
name: 'invoice',
|
|
17
|
-
fields: {
|
|
18
|
-
amount: { type: 'number' },
|
|
19
|
-
status: { type: 'text' },
|
|
20
|
-
paid_amount: { type: 'number' }
|
|
21
|
-
},
|
|
22
|
-
actions: {
|
|
23
|
-
'pay': {
|
|
24
|
-
type: 'record',
|
|
25
|
-
label: 'Pay Invoice',
|
|
26
|
-
params: {
|
|
27
|
-
method: { type: 'text' }
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
'import_invoices': {
|
|
31
|
-
type: 'global',
|
|
32
|
-
label: 'Import Invoices',
|
|
33
|
-
params: {
|
|
34
|
-
source: { type: 'text' }
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
await app.init();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('Record Actions', () => {
|
|
45
|
-
it('should execute record action with id parameter', async () => {
|
|
46
|
-
const repo = app.createContext({}).object('invoice');
|
|
47
|
-
|
|
48
|
-
// Create an invoice first
|
|
49
|
-
const invoice = await repo.create({ amount: 1000, status: 'pending' });
|
|
50
|
-
|
|
51
|
-
let actionCalled = false;
|
|
52
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
53
|
-
actionCalled = true;
|
|
54
|
-
expect(ctx.objectName).toBe('invoice');
|
|
55
|
-
expect(ctx.actionName).toBe('pay');
|
|
56
|
-
expect(ctx.id).toBe(invoice._id);
|
|
57
|
-
expect(ctx.input.method).toBe('credit_card');
|
|
58
|
-
|
|
59
|
-
// Update the invoice status
|
|
60
|
-
await ctx.api.update('invoice', ctx.id!, {
|
|
61
|
-
status: 'paid',
|
|
62
|
-
paid_amount: ctx.input.amount || 1000
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return { success: true, paid: true };
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const result = await repo.execute('pay', invoice._id, { method: 'credit_card', amount: 1000 });
|
|
69
|
-
|
|
70
|
-
expect(actionCalled).toBe(true);
|
|
71
|
-
expect(result.success).toBe(true);
|
|
72
|
-
expect(result.paid).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should provide access to record data via api', async () => {
|
|
76
|
-
const repo = app.createContext({}).object('invoice');
|
|
77
|
-
|
|
78
|
-
const invoice = await repo.create({ amount: 500, status: 'pending' });
|
|
79
|
-
|
|
80
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
81
|
-
// Fetch current record
|
|
82
|
-
const current = await ctx.api.findOne('invoice', ctx.id!);
|
|
83
|
-
expect(current).toBeDefined();
|
|
84
|
-
expect(current.amount).toBe(500);
|
|
85
|
-
|
|
86
|
-
return { currentAmount: current.amount };
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const result = await repo.execute('pay', invoice._id, { method: 'cash' });
|
|
90
|
-
expect(result.currentAmount).toBe(500);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should validate business rules in record action', async () => {
|
|
94
|
-
const repo = app.createContext({}).object('invoice');
|
|
95
|
-
|
|
96
|
-
const invoice = await repo.create({ amount: 1000, status: 'paid' });
|
|
97
|
-
|
|
98
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
99
|
-
const current = await ctx.api.findOne('invoice', ctx.id!);
|
|
100
|
-
if (current.status === 'paid') {
|
|
101
|
-
throw new Error('Invoice is already paid');
|
|
102
|
-
}
|
|
103
|
-
return { success: true };
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
await expect(repo.execute('pay', invoice._id, { method: 'credit_card' }))
|
|
107
|
-
.rejects
|
|
108
|
-
.toThrow('Invoice is already paid');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should provide user context in action', async () => {
|
|
112
|
-
const repo = app.createContext({ userId: 'user123', userName: 'John Doe' }).object('invoice');
|
|
113
|
-
|
|
114
|
-
const invoice = await repo.create({ amount: 100, status: 'pending' });
|
|
115
|
-
|
|
116
|
-
let capturedUser: any;
|
|
117
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
118
|
-
capturedUser = ctx.user;
|
|
119
|
-
return { success: true };
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
await repo.execute('pay', invoice._id, { method: 'cash' });
|
|
123
|
-
|
|
124
|
-
expect(capturedUser).toBeDefined();
|
|
125
|
-
expect(capturedUser.id).toBe('user123');
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe('Global Actions', () => {
|
|
130
|
-
it('should execute global action without id parameter', async () => {
|
|
131
|
-
const repo = app.createContext({}).object('invoice');
|
|
132
|
-
|
|
133
|
-
let actionCalled = false;
|
|
134
|
-
app.registerAction('invoice', 'import_invoices', async (ctx) => {
|
|
135
|
-
actionCalled = true;
|
|
136
|
-
expect(ctx.objectName).toBe('invoice');
|
|
137
|
-
expect(ctx.actionName).toBe('import_invoices');
|
|
138
|
-
expect(ctx.id).toBeUndefined();
|
|
139
|
-
expect(ctx.input.source).toBe('external_api');
|
|
140
|
-
|
|
141
|
-
// Create multiple records
|
|
142
|
-
await ctx.api.create('invoice', { amount: 100, status: 'pending' });
|
|
143
|
-
await ctx.api.create('invoice', { amount: 200, status: 'pending' });
|
|
144
|
-
|
|
145
|
-
return { imported: 2 };
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const result = await repo.execute('import_invoices', undefined, { source: 'external_api' });
|
|
149
|
-
|
|
150
|
-
expect(actionCalled).toBe(true);
|
|
151
|
-
expect(result.imported).toBe(2);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should perform batch operations in global action', async () => {
|
|
155
|
-
const repo = app.createContext({}).object('invoice');
|
|
156
|
-
|
|
157
|
-
// Create some test invoices
|
|
158
|
-
await repo.create({ amount: 100, status: 'pending' });
|
|
159
|
-
await repo.create({ amount: 200, status: 'pending' });
|
|
160
|
-
await repo.create({ amount: 300, status: 'paid' });
|
|
161
|
-
|
|
162
|
-
app.registerAction('invoice', 'import_invoices', async (ctx) => {
|
|
163
|
-
// Count pending invoices
|
|
164
|
-
const count = await ctx.api.count('invoice', {
|
|
165
|
-
filters: [['status', '=', 'pending']]
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
return { pendingCount: count };
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const result = await repo.execute('import_invoices', undefined, { source: 'test' });
|
|
172
|
-
expect(result.pendingCount).toBe(2);
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
describe('Action Input Validation', () => {
|
|
177
|
-
it('should receive validated input parameters', async () => {
|
|
178
|
-
const repo = app.createContext({}).object('invoice');
|
|
179
|
-
|
|
180
|
-
const invoice = await repo.create({ amount: 1000, status: 'pending' });
|
|
181
|
-
|
|
182
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
183
|
-
// Input should match the params defined in action config
|
|
184
|
-
expect(ctx.input).toBeDefined();
|
|
185
|
-
expect(typeof ctx.input.method).toBe('string');
|
|
186
|
-
|
|
187
|
-
return { method: ctx.input.method };
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const result = await repo.execute('pay', invoice._id, { method: 'bank_transfer' });
|
|
191
|
-
expect(result.method).toBe('bank_transfer');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('should handle missing optional parameters', async () => {
|
|
195
|
-
const repo = app.createContext({}).object('invoice');
|
|
196
|
-
|
|
197
|
-
const invoice = await repo.create({ amount: 1000, status: 'pending' });
|
|
198
|
-
|
|
199
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
200
|
-
// Optional parameters might be undefined
|
|
201
|
-
const comment = ctx.input.comment || 'No comment';
|
|
202
|
-
return { comment };
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
const result = await repo.execute('pay', invoice._id, { method: 'cash' });
|
|
206
|
-
expect(result.comment).toBe('No comment');
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
describe('Error Handling', () => {
|
|
211
|
-
it('should throw error if action not registered', async () => {
|
|
212
|
-
const repo = app.createContext({}).object('invoice');
|
|
213
|
-
await expect(repo.execute('refund', '1', {}))
|
|
214
|
-
.rejects
|
|
215
|
-
.toThrow("Action 'refund' not found for object 'invoice'");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('should propagate errors from action handler', async () => {
|
|
219
|
-
const repo = app.createContext({}).object('invoice');
|
|
220
|
-
|
|
221
|
-
const invoice = await repo.create({ amount: 1000, status: 'pending' });
|
|
222
|
-
|
|
223
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
224
|
-
throw new Error('Payment gateway is down');
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
await expect(repo.execute('pay', invoice._id, { method: 'credit_card' }))
|
|
228
|
-
.rejects
|
|
229
|
-
.toThrow('Payment gateway is down');
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('Complex Action Workflows', () => {
|
|
234
|
-
it('should perform multi-step operations in action', async () => {
|
|
235
|
-
const repo = app.createContext({}).object('invoice');
|
|
236
|
-
|
|
237
|
-
const invoice = await repo.create({ amount: 1000, status: 'pending', paid_amount: 0 });
|
|
238
|
-
|
|
239
|
-
app.registerAction('invoice', 'pay', async (ctx) => {
|
|
240
|
-
// Step 1: Fetch current state
|
|
241
|
-
const current = await ctx.api.findOne('invoice', ctx.id!);
|
|
242
|
-
|
|
243
|
-
// Step 2: Validate
|
|
244
|
-
if (current.status === 'paid') {
|
|
245
|
-
throw new Error('Already paid');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Step 3: Update invoice
|
|
249
|
-
await ctx.api.update('invoice', ctx.id!, {
|
|
250
|
-
status: 'paid',
|
|
251
|
-
paid_amount: current.amount
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Step 4: Could create related records (e.g., payment record)
|
|
255
|
-
// await ctx.api.create('payment', { ... });
|
|
256
|
-
|
|
257
|
-
return {
|
|
258
|
-
success: true,
|
|
259
|
-
amount: current.amount,
|
|
260
|
-
newStatus: 'paid'
|
|
261
|
-
};
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const result = await repo.execute('pay', invoice._id, { method: 'credit_card' });
|
|
265
|
-
|
|
266
|
-
expect(result.success).toBe(true);
|
|
267
|
-
expect(result.amount).toBe(1000);
|
|
268
|
-
expect(result.newStatus).toBe('paid');
|
|
269
|
-
|
|
270
|
-
// Verify the update
|
|
271
|
-
const updated = await repo.findOne(invoice._id);
|
|
272
|
-
expect(updated.status).toBe('paid');
|
|
273
|
-
expect(updated.paid_amount).toBe(1000);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
});
|