agentlang 0.10.8 → 0.11.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/README.md +60 -0
- package/out/api/http.d.ts.map +1 -1
- package/out/api/http.js +64 -39
- package/out/api/http.js.map +1 -1
- package/out/cli/main.d.ts +1 -1
- package/out/cli/main.d.ts.map +1 -1
- package/out/cli/main.js +7 -3
- package/out/cli/main.js.map +1 -1
- package/out/extension/main.cjs +250 -250
- package/out/extension/main.cjs.map +2 -2
- package/out/language/main.cjs +504 -504
- package/out/language/main.cjs.map +3 -3
- package/out/runtime/auth/cognito.d.ts.map +1 -1
- package/out/runtime/auth/cognito.js +129 -64
- package/out/runtime/auth/cognito.js.map +1 -1
- package/out/runtime/defs.d.ts +22 -9
- package/out/runtime/defs.d.ts.map +1 -1
- package/out/runtime/defs.js +44 -9
- package/out/runtime/defs.js.map +1 -1
- package/out/runtime/document-retriever.d.ts +24 -0
- package/out/runtime/document-retriever.d.ts.map +1 -0
- package/out/runtime/document-retriever.js +258 -0
- package/out/runtime/document-retriever.js.map +1 -0
- package/out/runtime/errors/coded-error.d.ts +8 -0
- package/out/runtime/errors/coded-error.d.ts.map +1 -0
- package/out/runtime/errors/coded-error.js +13 -0
- package/out/runtime/errors/coded-error.js.map +1 -0
- package/out/runtime/errors/http-error.d.ts +25 -0
- package/out/runtime/errors/http-error.d.ts.map +1 -0
- package/out/runtime/errors/http-error.js +169 -0
- package/out/runtime/errors/http-error.js.map +1 -0
- package/out/runtime/excel-resolver.d.ts +4 -0
- package/out/runtime/excel-resolver.d.ts.map +1 -0
- package/out/runtime/excel-resolver.js +96 -0
- package/out/runtime/excel-resolver.js.map +1 -0
- package/out/runtime/excel.d.ts +25 -0
- package/out/runtime/excel.d.ts.map +1 -0
- package/out/runtime/excel.js +127 -0
- package/out/runtime/excel.js.map +1 -0
- package/out/runtime/exec-graph-cache.d.ts +5 -0
- package/out/runtime/exec-graph-cache.d.ts.map +1 -0
- package/out/runtime/exec-graph-cache.js +9 -0
- package/out/runtime/exec-graph-cache.js.map +1 -0
- package/out/runtime/exec-graph.d.ts.map +1 -1
- package/out/runtime/exec-graph.js +2 -1
- package/out/runtime/exec-graph.js.map +1 -1
- package/out/runtime/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +30 -27
- package/out/runtime/interpreter.js.map +1 -1
- package/out/runtime/loader.d.ts.map +1 -1
- package/out/runtime/loader.js +5 -1
- package/out/runtime/loader.js.map +1 -1
- package/out/runtime/logger.d.ts +6 -0
- package/out/runtime/logger.d.ts.map +1 -1
- package/out/runtime/logger.js +21 -0
- package/out/runtime/logger.js.map +1 -1
- package/out/runtime/module.d.ts.map +1 -1
- package/out/runtime/module.js +14 -13
- package/out/runtime/module.js.map +1 -1
- package/out/runtime/modules/ai.d.ts +2 -1
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +7 -2
- package/out/runtime/modules/ai.js.map +1 -1
- package/out/runtime/modules/auth.d.ts.map +1 -1
- package/out/runtime/modules/auth.js +44 -16
- package/out/runtime/modules/auth.js.map +1 -1
- package/out/runtime/modules/core.d.ts +6 -0
- package/out/runtime/modules/core.d.ts.map +1 -1
- package/out/runtime/modules/core.js +20 -0
- package/out/runtime/modules/core.js.map +1 -1
- package/out/runtime/resolvers/sqldb/database.d.ts.map +1 -1
- package/out/runtime/resolvers/sqldb/database.js +76 -39
- package/out/runtime/resolvers/sqldb/database.js.map +1 -1
- package/out/runtime/resolvers/sqldb/db-errors.d.ts +6 -0
- package/out/runtime/resolvers/sqldb/db-errors.d.ts.map +1 -0
- package/out/runtime/resolvers/sqldb/db-errors.js +100 -0
- package/out/runtime/resolvers/sqldb/db-errors.js.map +1 -0
- package/out/runtime/resolvers/vector/lancedb-store.d.ts +16 -0
- package/out/runtime/resolvers/vector/lancedb-store.d.ts.map +1 -0
- package/out/runtime/resolvers/vector/lancedb-store.js +159 -0
- package/out/runtime/resolvers/vector/lancedb-store.js.map +1 -0
- package/out/runtime/resolvers/vector/types.d.ts +32 -0
- package/out/runtime/resolvers/vector/types.d.ts.map +1 -0
- package/out/runtime/resolvers/vector/types.js +2 -0
- package/out/runtime/resolvers/vector/types.js.map +1 -0
- package/out/runtime/state.d.ts +3 -0
- package/out/runtime/state.d.ts.map +1 -1
- package/out/runtime/state.js +7 -0
- package/out/runtime/state.js.map +1 -1
- package/out/setupClassic.d.ts +98 -0
- package/out/setupClassic.d.ts.map +1 -0
- package/out/setupClassic.js +38 -0
- package/out/setupClassic.js.map +1 -0
- package/out/setupCommon.d.ts +2 -0
- package/out/setupCommon.d.ts.map +1 -0
- package/out/setupCommon.js +33 -0
- package/out/setupCommon.js.map +1 -0
- package/out/setupExtended.d.ts +40 -0
- package/out/setupExtended.d.ts.map +1 -0
- package/out/setupExtended.js +67 -0
- package/out/setupExtended.js.map +1 -0
- package/package.json +19 -18
- package/src/api/http.ts +71 -37
- package/src/cli/main.ts +12 -4
- package/src/runtime/auth/cognito.ts +187 -65
- package/src/runtime/defs.ts +51 -18
- package/src/runtime/errors/coded-error.ts +18 -0
- package/src/runtime/errors/http-error.ts +197 -0
- package/src/runtime/exec-graph-cache.ts +12 -0
- package/src/runtime/exec-graph.ts +2 -2
- package/src/runtime/interpreter.ts +73 -28
- package/src/runtime/loader.ts +5 -0
- package/src/runtime/logger.ts +27 -0
- package/src/runtime/module.ts +45 -13
- package/src/runtime/modules/ai.ts +11 -2
- package/src/runtime/modules/auth.ts +45 -18
- package/src/runtime/modules/core.ts +26 -0
- package/src/runtime/resolvers/sqldb/database.ts +88 -37
- package/src/runtime/resolvers/sqldb/db-errors.ts +113 -0
- package/src/runtime/state.ts +7 -0
- package/src/xlsx.d.ts +17 -0
package/src/runtime/module.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { createCodedError } from './errors/coded-error.js';
|
|
2
3
|
import {
|
|
3
4
|
AttributeDefinition,
|
|
4
5
|
Expr,
|
|
@@ -2631,7 +2632,11 @@ export class Module {
|
|
|
2631
2632
|
|
|
2632
2633
|
getEntry(entryName: string): ModuleEntry {
|
|
2633
2634
|
const idx: number = this.getEntryIndex(entryName);
|
|
2634
|
-
if (idx < 0)
|
|
2635
|
+
if (idx < 0)
|
|
2636
|
+
throw createCodedError(
|
|
2637
|
+
`Entry ${entryName} not found in module ${this.name}`,
|
|
2638
|
+
'AL_MOD_ENTRY_NOT_FOUND'
|
|
2639
|
+
);
|
|
2635
2640
|
return this.entries[idx];
|
|
2636
2641
|
}
|
|
2637
2642
|
|
|
@@ -2646,7 +2651,10 @@ export class Module {
|
|
|
2646
2651
|
if (e instanceof Record) {
|
|
2647
2652
|
return e as Record;
|
|
2648
2653
|
}
|
|
2649
|
-
throw
|
|
2654
|
+
throw createCodedError(
|
|
2655
|
+
`${recordName} is not a record in module ${this.name}`,
|
|
2656
|
+
'AL_MOD_NOT_A_RECORD'
|
|
2657
|
+
);
|
|
2650
2658
|
}
|
|
2651
2659
|
|
|
2652
2660
|
removeEntry(entryName: string): boolean {
|
|
@@ -2942,7 +2950,7 @@ export function isModule(name: string): boolean {
|
|
|
2942
2950
|
export function fetchModule(moduleName: string): Module {
|
|
2943
2951
|
const module: Module | undefined = getModuleDb().get(moduleName);
|
|
2944
2952
|
if (module === undefined) {
|
|
2945
|
-
throw
|
|
2953
|
+
throw createCodedError(`Module not found - ${moduleName}`, 'AL_MOD_MODULE_NOT_FOUND');
|
|
2946
2954
|
}
|
|
2947
2955
|
return module;
|
|
2948
2956
|
}
|
|
@@ -3481,7 +3489,7 @@ export function getEntity(name: string, moduleName: string): Entity | undefined
|
|
|
3481
3489
|
export function fetchEntity(path: Path): Entity {
|
|
3482
3490
|
const e = getEntity(path.getEntryName(), path.getModuleName());
|
|
3483
3491
|
if (e === undefined) {
|
|
3484
|
-
throw
|
|
3492
|
+
throw createCodedError(`Entity not found - ${path.asFqName()}`, 'AL_MOD_ENTITY_NOT_FOUND');
|
|
3485
3493
|
}
|
|
3486
3494
|
return e;
|
|
3487
3495
|
}
|
|
@@ -3513,7 +3521,10 @@ export function getEvent(name: string, moduleName: string): Event {
|
|
|
3513
3521
|
if (fr.module.isEvent(fr.entryName)) {
|
|
3514
3522
|
return fr.module.getEntry(fr.entryName) as Event;
|
|
3515
3523
|
}
|
|
3516
|
-
throw
|
|
3524
|
+
throw createCodedError(
|
|
3525
|
+
`Event ${fr.entryName} not found in module ${fr.moduleName}`,
|
|
3526
|
+
'AL_MOD_EVENT_NOT_FOUND'
|
|
3527
|
+
);
|
|
3517
3528
|
}
|
|
3518
3529
|
|
|
3519
3530
|
export function maybeGetEvent(name: string, moduleName: string): Event | undefined {
|
|
@@ -3529,7 +3540,10 @@ export function getRecord(name: string, moduleName: string): Record {
|
|
|
3529
3540
|
if (fr.module.isRecord(fr.entryName)) {
|
|
3530
3541
|
return fr.module.getEntry(fr.entryName) as Record;
|
|
3531
3542
|
}
|
|
3532
|
-
throw
|
|
3543
|
+
throw createCodedError(
|
|
3544
|
+
`Record ${fr.entryName} not found in module ${fr.moduleName}`,
|
|
3545
|
+
'AL_MOD_RECORD_NOT_FOUND'
|
|
3546
|
+
);
|
|
3533
3547
|
}
|
|
3534
3548
|
|
|
3535
3549
|
export function getRelationship(name: string, moduleName: string): Relationship {
|
|
@@ -3537,7 +3551,10 @@ export function getRelationship(name: string, moduleName: string): Relationship
|
|
|
3537
3551
|
if (fr.module.isRelationship(fr.entryName)) {
|
|
3538
3552
|
return fr.module.getEntry(fr.entryName) as Relationship;
|
|
3539
3553
|
}
|
|
3540
|
-
throw
|
|
3554
|
+
throw createCodedError(
|
|
3555
|
+
`Relationship ${fr.entryName} not found in module ${fr.moduleName}`,
|
|
3556
|
+
'AL_MOD_RELATIONSHIP_NOT_FOUND'
|
|
3557
|
+
);
|
|
3541
3558
|
}
|
|
3542
3559
|
|
|
3543
3560
|
export function getAllBetweenRelationships(): Relationship[] {
|
|
@@ -3707,7 +3724,10 @@ function checkOneOfValue(attrSpec: AttributeSpec, attrName: string, attrValue: a
|
|
|
3707
3724
|
const vals: Set<string> | undefined = getEnumValues(attrSpec);
|
|
3708
3725
|
if (vals) {
|
|
3709
3726
|
if (!vals.has(attrValue as string)) {
|
|
3710
|
-
throw
|
|
3727
|
+
throw createCodedError(
|
|
3728
|
+
`Value of ${attrName} must be one of [${[...vals]}]`,
|
|
3729
|
+
'AL_MOD_TYPE_MISMATCH'
|
|
3730
|
+
);
|
|
3711
3731
|
}
|
|
3712
3732
|
return true;
|
|
3713
3733
|
}
|
|
@@ -3725,7 +3745,10 @@ function getCheckPredicate(attrSpec: AttributeSpec): any {
|
|
|
3725
3745
|
function validateType(attrName: string, attrValue: any, attrSpec: AttributeSpec) {
|
|
3726
3746
|
if (attrSpec.type == 'Path') {
|
|
3727
3747
|
if (!isPath(attrValue, getRefSpec(attrSpec))) {
|
|
3728
|
-
throw
|
|
3748
|
+
throw createCodedError(
|
|
3749
|
+
`Failed to validate Path ${attrValue} passed to ${attrName}`,
|
|
3750
|
+
'AL_MOD_TYPE_MISMATCH'
|
|
3751
|
+
);
|
|
3729
3752
|
}
|
|
3730
3753
|
}
|
|
3731
3754
|
let predic = getCheckPredicate(attrSpec);
|
|
@@ -3733,16 +3756,22 @@ function validateType(attrName: string, attrValue: any, attrSpec: AttributeSpec)
|
|
|
3733
3756
|
if (predic !== undefined) {
|
|
3734
3757
|
if (isArrayAttribute(attrSpec)) {
|
|
3735
3758
|
if (!(attrValue instanceof Array)) {
|
|
3736
|
-
throw
|
|
3759
|
+
throw createCodedError(`${attrName} expects an array of values`, 'AL_MOD_TYPE_MISMATCH');
|
|
3737
3760
|
} else {
|
|
3738
3761
|
if (!attrValue.every(predic)) {
|
|
3739
|
-
throw
|
|
3762
|
+
throw createCodedError(
|
|
3763
|
+
`Invalid value in the array passed to ${attrName}`,
|
|
3764
|
+
'AL_MOD_TYPE_MISMATCH'
|
|
3765
|
+
);
|
|
3740
3766
|
}
|
|
3741
3767
|
}
|
|
3742
3768
|
} else {
|
|
3743
3769
|
if (!checkOneOfValue(attrSpec, attrName, attrValue)) {
|
|
3744
3770
|
if (!predic(attrValue)) {
|
|
3745
|
-
throw
|
|
3771
|
+
throw createCodedError(
|
|
3772
|
+
`Invalid value ${attrValue} specified for ${attrName}`,
|
|
3773
|
+
'AL_MOD_TYPE_MISMATCH'
|
|
3774
|
+
);
|
|
3746
3775
|
}
|
|
3747
3776
|
}
|
|
3748
3777
|
}
|
|
@@ -4307,7 +4336,10 @@ export function makeInstance(
|
|
|
4307
4336
|
if (schema.size > 0) {
|
|
4308
4337
|
attributes.forEach((value: any, key: string) => {
|
|
4309
4338
|
if (!schema.has(key)) {
|
|
4310
|
-
throw
|
|
4339
|
+
throw createCodedError(
|
|
4340
|
+
`Invalid attribute '${key}' specified for ${moduleName}/${entryName}`,
|
|
4341
|
+
'AL_MOD_INVALID_ATTR'
|
|
4342
|
+
);
|
|
4311
4343
|
}
|
|
4312
4344
|
const spec: AttributeSpec = getAttributeSpec(schema, key);
|
|
4313
4345
|
if (value !== null && value !== undefined) validateType(key, value, spec);
|
|
@@ -85,6 +85,7 @@ const AgentEvalType = 'eval';
|
|
|
85
85
|
// --- Agent cancellation infrastructure ---
|
|
86
86
|
|
|
87
87
|
export class AgentCancelledException extends Error {
|
|
88
|
+
readonly agentlangCode = 'AL_AGENT_CANCELLED';
|
|
88
89
|
constructor(chatId: string) {
|
|
89
90
|
super(`Agent cancelled for chatId: ${chatId}`);
|
|
90
91
|
this.name = 'AgentCancelledException';
|
|
@@ -1286,8 +1287,16 @@ async function parseHelper(stmt: string, env: Environment): Promise<any> {
|
|
|
1286
1287
|
return env.getLastResult();
|
|
1287
1288
|
}
|
|
1288
1289
|
|
|
1289
|
-
export async function findAgentByName(name: string,
|
|
1290
|
-
|
|
1290
|
+
export async function findAgentByName(name: string, _env: Environment): Promise<AgentInstance> {
|
|
1291
|
+
// Match findProviderForLLM: Agent rows are upserted at load under GlobalEnvironment / bootstrap
|
|
1292
|
+
// tenant. A request env (e.g. after setActiveEvent) may not see those rows; use a GlobalEnvironment
|
|
1293
|
+
// child for the lookup.
|
|
1294
|
+
const lookupEnv = new Environment('agent-by-name-lookup', GlobalEnvironment);
|
|
1295
|
+
const result = await parseAndEvaluateStatement(
|
|
1296
|
+
`{${AgentFqName} {name? "${name}"}}`,
|
|
1297
|
+
undefined,
|
|
1298
|
+
lookupEnv
|
|
1299
|
+
);
|
|
1291
1300
|
if (result instanceof Array && result.length > 0) {
|
|
1292
1301
|
const agentInstance: Instance = result[0];
|
|
1293
1302
|
return AgentInstance.FromInstance(agentInstance);
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
set_getUserTenantId,
|
|
28
28
|
PathAttributeName,
|
|
29
29
|
} from '../defs.js';
|
|
30
|
+
import { createCodedError } from '../errors/coded-error.js';
|
|
30
31
|
import {
|
|
31
32
|
DbContext,
|
|
32
33
|
getManyByRawQuery,
|
|
@@ -934,6 +935,11 @@ async function updatePermissionCacheForRole(role: string, env: Environment) {
|
|
|
934
935
|
}
|
|
935
936
|
}
|
|
936
937
|
|
|
938
|
+
/** Matches the built-in superuser role name (case-insensitive). */
|
|
939
|
+
function isGlobalAdminRoleName(role: string | undefined | null): boolean {
|
|
940
|
+
return typeof role === 'string' && role.length > 0 && role.toLowerCase() === 'admin';
|
|
941
|
+
}
|
|
942
|
+
|
|
937
943
|
export async function userHasPermissions(
|
|
938
944
|
userId: string,
|
|
939
945
|
resourceFqName: string,
|
|
@@ -959,6 +965,10 @@ export async function userHasPermissions(
|
|
|
959
965
|
}
|
|
960
966
|
UserRoleCache.set(userId, userRoles);
|
|
961
967
|
}
|
|
968
|
+
// Real admin users keep full access even when agents/workflows set assumedRole (narrowing).
|
|
969
|
+
if (userRoles?.some((r: string) => isGlobalAdminRoleName(r))) {
|
|
970
|
+
return true;
|
|
971
|
+
}
|
|
962
972
|
let tempRoles = userRoles;
|
|
963
973
|
const assumedRole = env.getAssumedRole();
|
|
964
974
|
if (assumedRole) {
|
|
@@ -967,12 +977,7 @@ export async function userHasPermissions(
|
|
|
967
977
|
await updatePermissionCacheForRole(assumedRole, env);
|
|
968
978
|
}
|
|
969
979
|
}
|
|
970
|
-
if (
|
|
971
|
-
tempRoles &&
|
|
972
|
-
tempRoles.find((role: string) => {
|
|
973
|
-
return role === 'admin';
|
|
974
|
-
})
|
|
975
|
-
) {
|
|
980
|
+
if (tempRoles?.some((r: string) => isGlobalAdminRoleName(r))) {
|
|
976
981
|
return true;
|
|
977
982
|
}
|
|
978
983
|
const [c, r, u, d] = [
|
|
@@ -1041,7 +1046,7 @@ function fetchAuthImpl(): AgentlangAuth {
|
|
|
1041
1046
|
if (runtimeAuth) {
|
|
1042
1047
|
return runtimeAuth;
|
|
1043
1048
|
} else {
|
|
1044
|
-
throw
|
|
1049
|
+
throw createCodedError('Auth not initialized', 'AL_AUTH_NOT_INITIALIZED');
|
|
1045
1050
|
}
|
|
1046
1051
|
}
|
|
1047
1052
|
|
|
@@ -1111,7 +1116,9 @@ export async function forgotPasswordUser(username: string, env: Environment): Pr
|
|
|
1111
1116
|
const email = username.toLowerCase();
|
|
1112
1117
|
const allowed = await fetchAuthImpl().userExistsInIdentityProvider(email, env);
|
|
1113
1118
|
if (!allowed) {
|
|
1114
|
-
throw new UserNotFoundError('Email not registered'
|
|
1119
|
+
throw new UserNotFoundError('Email not registered', {
|
|
1120
|
+
agentlangCode: 'AL_AUTH_EMAIL_NOT_REGISTERED',
|
|
1121
|
+
});
|
|
1115
1122
|
}
|
|
1116
1123
|
await fetchAuthImpl().forgotPassword(email, env);
|
|
1117
1124
|
return { status: 'ok', message: 'Password reset code sent' };
|
|
@@ -1261,7 +1268,9 @@ export async function changePassword(
|
|
|
1261
1268
|
return undefined;
|
|
1262
1269
|
}
|
|
1263
1270
|
} else {
|
|
1264
|
-
throw new UnauthorisedError(`No active session for user ${user}
|
|
1271
|
+
throw new UnauthorisedError(`No active session for user ${user}`, {
|
|
1272
|
+
agentlangCode: 'AL_AUTH_NO_ACTIVE_SESSION_CHANGE_PW',
|
|
1273
|
+
});
|
|
1265
1274
|
}
|
|
1266
1275
|
}
|
|
1267
1276
|
|
|
@@ -1288,7 +1297,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1288
1297
|
try {
|
|
1289
1298
|
// Validate JWT structure first
|
|
1290
1299
|
if (!isJwtToken(token)) {
|
|
1291
|
-
throw new UnauthorisedError('Invalid JWT token structure'
|
|
1300
|
+
throw new UnauthorisedError('Invalid JWT token structure', {
|
|
1301
|
+
agentlangCode: 'AL_AUTH_JWT_STRUCTURE_INVALID',
|
|
1302
|
+
});
|
|
1292
1303
|
}
|
|
1293
1304
|
|
|
1294
1305
|
// Verify the JWT token directly with Cognito
|
|
@@ -1303,7 +1314,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1303
1314
|
const email = payload.email || payload['cognito:username'];
|
|
1304
1315
|
|
|
1305
1316
|
if (!userId) {
|
|
1306
|
-
throw new UnauthorisedError('Invalid JWT token: missing user identifier'
|
|
1317
|
+
throw new UnauthorisedError('Invalid JWT token: missing user identifier', {
|
|
1318
|
+
agentlangCode: 'AL_AUTH_JWT_MISSING_USER_ID',
|
|
1319
|
+
});
|
|
1307
1320
|
}
|
|
1308
1321
|
|
|
1309
1322
|
let localUser = null;
|
|
@@ -1319,7 +1332,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1319
1332
|
logger.warn(
|
|
1320
1333
|
`User not found in local database for JWT token. Email: ${email}, UserId: ${userId}`
|
|
1321
1334
|
);
|
|
1322
|
-
throw new UnauthorisedError(`User not found in local database
|
|
1335
|
+
throw new UnauthorisedError(`User not found in local database`, {
|
|
1336
|
+
agentlangCode: 'AL_AUTH_JWT_USER_NOT_IN_DB',
|
|
1337
|
+
});
|
|
1323
1338
|
}
|
|
1324
1339
|
|
|
1325
1340
|
// Use the local user's ID for consistency
|
|
@@ -1328,12 +1343,16 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1328
1343
|
// Check if user status is 'Active'
|
|
1329
1344
|
const userStatus = localUser.lookup('status');
|
|
1330
1345
|
if (userStatus !== 'Active') {
|
|
1331
|
-
throw new UnauthorisedError(`User account is not active. Status: ${userStatus}
|
|
1346
|
+
throw new UnauthorisedError(`User account is not active. Status: ${userStatus}`, {
|
|
1347
|
+
agentlangCode: 'AL_AUTH_USER_NOT_ACTIVE_JWT',
|
|
1348
|
+
});
|
|
1332
1349
|
}
|
|
1333
1350
|
|
|
1334
1351
|
const sess = await findUserSession(localUserId, env);
|
|
1335
1352
|
if (!sess) {
|
|
1336
|
-
throw new UnauthorisedError(`No session found for user ${email}, UserId: ${userId}
|
|
1353
|
+
throw new UnauthorisedError(`No session found for user ${email}, UserId: ${userId}`, {
|
|
1354
|
+
agentlangCode: 'AL_AUTH_NO_SESSION_JWT',
|
|
1355
|
+
});
|
|
1337
1356
|
}
|
|
1338
1357
|
// For JWT tokens, we use the token itself as sessionId for tracking
|
|
1339
1358
|
return { sessionId: sess.lookup('id'), userId: localUserId };
|
|
@@ -1345,7 +1364,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1345
1364
|
errorName: err.name,
|
|
1346
1365
|
errorMessage: err.message,
|
|
1347
1366
|
});
|
|
1348
|
-
throw new UnauthorisedError('JWT token verification failed'
|
|
1367
|
+
throw new UnauthorisedError('JWT token verification failed', {
|
|
1368
|
+
agentlangCode: 'AL_AUTH_JWT_VERIFY_WRAPPER',
|
|
1369
|
+
});
|
|
1349
1370
|
}
|
|
1350
1371
|
};
|
|
1351
1372
|
if (needCommit) {
|
|
@@ -1368,7 +1389,9 @@ async function verifySessionToken(token: string, env?: Environment): Promise<Act
|
|
|
1368
1389
|
if (user) {
|
|
1369
1390
|
const userStatus = user.lookup('status');
|
|
1370
1391
|
if (userStatus !== 'Active') {
|
|
1371
|
-
throw new UnauthorisedError(`User account is not active. Status: ${userStatus}
|
|
1392
|
+
throw new UnauthorisedError(`User account is not active. Status: ${userStatus}`, {
|
|
1393
|
+
agentlangCode: 'AL_AUTH_USER_NOT_ACTIVE_SESSION',
|
|
1394
|
+
});
|
|
1372
1395
|
}
|
|
1373
1396
|
}
|
|
1374
1397
|
|
|
@@ -1378,7 +1401,9 @@ async function verifySessionToken(token: string, env?: Environment): Promise<Act
|
|
|
1378
1401
|
return { sessionId: sessId, userId: userId };
|
|
1379
1402
|
} else {
|
|
1380
1403
|
logger.warn(`No active session found for user '${userId}'`);
|
|
1381
|
-
throw new UnauthorisedError(`No active session for user '${userId}'
|
|
1404
|
+
throw new UnauthorisedError(`No active session for user '${userId}'`, {
|
|
1405
|
+
agentlangCode: 'AL_AUTH_NO_ACTIVE_SESSION_TOKEN',
|
|
1406
|
+
});
|
|
1382
1407
|
}
|
|
1383
1408
|
} catch (err: any) {
|
|
1384
1409
|
if (err instanceof UnauthorisedError) {
|
|
@@ -1390,7 +1415,9 @@ async function verifySessionToken(token: string, env?: Environment): Promise<Act
|
|
|
1390
1415
|
errorMessage: err.message,
|
|
1391
1416
|
sessionId: sessId,
|
|
1392
1417
|
});
|
|
1393
|
-
throw new UnauthorisedError('Session verification failed'
|
|
1418
|
+
throw new UnauthorisedError('Session verification failed', {
|
|
1419
|
+
agentlangCode: 'AL_AUTH_SESSION_VERIFY_WRAPPER',
|
|
1420
|
+
});
|
|
1394
1421
|
}
|
|
1395
1422
|
};
|
|
1396
1423
|
if (needCommit) {
|
|
@@ -167,6 +167,16 @@ entity Migration {
|
|
|
167
167
|
downs String @optional
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// Custom HTTP error messages. \`target\` is "*" for a universal (code-only) override,
|
|
171
|
+
// or "module/Entry" for an entity-specific one. \`message\` may contain the
|
|
172
|
+
// {{code}} and {{message}} placeholders, substituted at resolution time.
|
|
173
|
+
entity errorMessage {
|
|
174
|
+
id UUID @id @default(uuid()),
|
|
175
|
+
target String @default("*") @indexed,
|
|
176
|
+
code String @indexed,
|
|
177
|
+
message String
|
|
178
|
+
}
|
|
179
|
+
|
|
170
180
|
@public event Query {
|
|
171
181
|
q Any
|
|
172
182
|
}
|
|
@@ -283,6 +293,22 @@ export async function lookupTimersWithRunningStatus(): Promise<Instance[]> {
|
|
|
283
293
|
return await parseAndEvaluateStatement(`{agentlang/timer {status? "R"}}`);
|
|
284
294
|
}
|
|
285
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Look up a custom error-message template stored in the `agentlang/errorMessage`
|
|
298
|
+
* system entity. `target` is "*" for a universal override or "module/Entry" for an
|
|
299
|
+
* entity-specific one. Returns the stored `message`, or undefined when none exists.
|
|
300
|
+
*/
|
|
301
|
+
export async function lookupCustomErrorMessage(
|
|
302
|
+
target: string,
|
|
303
|
+
code: string
|
|
304
|
+
): Promise<string | undefined> {
|
|
305
|
+
const r: any = await parseAndEvaluateStatement(
|
|
306
|
+
`{${DefaultModuleName}/errorMessage {target? "${escapeSpecialChars(target)}", code? "${escapeSpecialChars(code)}"}}`
|
|
307
|
+
);
|
|
308
|
+
const insts: Instance[] = Array.isArray(r) ? r : r ? [r] : [];
|
|
309
|
+
return insts.length > 0 ? insts[0].lookup('message') : undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
286
312
|
async function addAudit(
|
|
287
313
|
env: Environment,
|
|
288
314
|
action: 'c' | 'd' | 'u',
|
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
TenantAttributeName,
|
|
46
46
|
UnauthorisedError,
|
|
47
47
|
} from '../../defs.js';
|
|
48
|
+
import { createCodedError } from '../../errors/coded-error.js';
|
|
49
|
+
import { mapDatabaseError } from './db-errors.js';
|
|
48
50
|
import { saveMigration } from '../../modules/core.js';
|
|
49
51
|
import { getAppSpec } from '../../loader.js';
|
|
50
52
|
import { WhereClause } from '../interface.js';
|
|
@@ -393,9 +395,10 @@ async function validateSchemaInProd(dataSource: DataSource) {
|
|
|
393
395
|
);
|
|
394
396
|
if (pendingQueries.length > 0) {
|
|
395
397
|
const pending = pendingQueries.join('\n ');
|
|
396
|
-
throw
|
|
398
|
+
throw createCodedError(
|
|
397
399
|
`Schema mismatch detected: the app model does not match the database schema. ` +
|
|
398
|
-
`Run migrations before starting in production mode.\n Pending changes:\n ${pending}
|
|
400
|
+
`Run migrations before starting in production mode.\n Pending changes:\n ${pending}`,
|
|
401
|
+
'AL_DB_SCHEMA_MISMATCH_PROD'
|
|
399
402
|
);
|
|
400
403
|
}
|
|
401
404
|
}
|
|
@@ -431,8 +434,9 @@ async function maybeHandleMigrations(dataSource: DataSource) {
|
|
|
431
434
|
const simulation = await simulateMigration(dataSource);
|
|
432
435
|
if (!simulation.success) {
|
|
433
436
|
logger.error(`Migration simulation failed:\n ${simulation.errors.join('\n ')}`);
|
|
434
|
-
throw
|
|
435
|
-
`Migration aborted: simulation failed.\n ${simulation.errors.join('\n ')}
|
|
437
|
+
throw createCodedError(
|
|
438
|
+
`Migration aborted: simulation failed.\n ${simulation.errors.join('\n ')}`,
|
|
439
|
+
'AL_DB_MIGRATION_SIMULATION_FAILED'
|
|
436
440
|
);
|
|
437
441
|
}
|
|
438
442
|
logger.info('Migration simulation passed.');
|
|
@@ -443,8 +447,9 @@ async function maybeHandleMigrations(dataSource: DataSource) {
|
|
|
443
447
|
const simulation = await simulateMigration(dataSource);
|
|
444
448
|
if (!simulation.success) {
|
|
445
449
|
logger.error(`Migration simulation failed:\n ${simulation.errors.join('\n ')}`);
|
|
446
|
-
throw
|
|
447
|
-
`Migration aborted: simulation failed.\n ${simulation.errors.join('\n ')}
|
|
450
|
+
throw createCodedError(
|
|
451
|
+
`Migration aborted: simulation failed.\n ${simulation.errors.join('\n ')}`,
|
|
452
|
+
'AL_DB_MIGRATION_SIMULATION_FAILED'
|
|
448
453
|
);
|
|
449
454
|
}
|
|
450
455
|
logger.info('Migration simulation passed, applying changes...');
|
|
@@ -530,7 +535,10 @@ function getDsFunction(
|
|
|
530
535
|
case 'sqljs':
|
|
531
536
|
return makeSqljsDataSource;
|
|
532
537
|
default:
|
|
533
|
-
throw
|
|
538
|
+
throw createCodedError(
|
|
539
|
+
`Unsupported database type - ${config?.type}`,
|
|
540
|
+
'AL_DB_UNSUPPORTED_TYPE'
|
|
541
|
+
);
|
|
534
542
|
}
|
|
535
543
|
}
|
|
536
544
|
|
|
@@ -595,7 +603,10 @@ export async function initDatabase(config: DatabaseConfig | undefined) {
|
|
|
595
603
|
await initVectorStore(vectEnts, DbContext.getGlobalContext());
|
|
596
604
|
}
|
|
597
605
|
} else {
|
|
598
|
-
throw
|
|
606
|
+
throw createCodedError(
|
|
607
|
+
`Unsupported database type - ${getDbType(AppConfig?.store)}`,
|
|
608
|
+
'AL_DB_UNSUPPORTED_TYPE_INIT'
|
|
609
|
+
);
|
|
599
610
|
}
|
|
600
611
|
}
|
|
601
612
|
}
|
|
@@ -617,9 +628,13 @@ async function insertRowsHelper(
|
|
|
617
628
|
ctx: DbContext,
|
|
618
629
|
doUpsert: boolean
|
|
619
630
|
): Promise<void> {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
631
|
+
try {
|
|
632
|
+
const repo = getDatasourceForTransaction(ctx.txnId).getRepository(tableName);
|
|
633
|
+
if (doUpsert) await repo.save(rows);
|
|
634
|
+
else await repo.insert(rows);
|
|
635
|
+
} catch (err) {
|
|
636
|
+
mapDatabaseError(err);
|
|
637
|
+
}
|
|
623
638
|
}
|
|
624
639
|
|
|
625
640
|
export async function addRowForFullTextSearch(
|
|
@@ -884,7 +899,10 @@ export async function insertRows(
|
|
|
884
899
|
}
|
|
885
900
|
}
|
|
886
901
|
} else {
|
|
887
|
-
throw new UnauthorisedError(
|
|
902
|
+
throw new UnauthorisedError(
|
|
903
|
+
{ opr: 'insert', entity: tableName },
|
|
904
|
+
{ agentlangCode: 'AL_DB_INSERT_UNAUTHORIZED' }
|
|
905
|
+
);
|
|
888
906
|
}
|
|
889
907
|
}
|
|
890
908
|
|
|
@@ -926,7 +944,10 @@ export async function insertBetweenRow(
|
|
|
926
944
|
const row = Object.fromEntries(attrs);
|
|
927
945
|
await insertRow(n, row, ctx.clone().setNeedAuthCheck(false), false);
|
|
928
946
|
} else {
|
|
929
|
-
throw new UnauthorisedError(
|
|
947
|
+
throw new UnauthorisedError(
|
|
948
|
+
{ opr: 'insert', entity: n },
|
|
949
|
+
{ agentlangCode: 'AL_DB_BETWEEN_INSERT_UNAUTHORIZED' }
|
|
950
|
+
);
|
|
930
951
|
}
|
|
931
952
|
}
|
|
932
953
|
|
|
@@ -1046,12 +1067,16 @@ export async function updateRow(
|
|
|
1046
1067
|
updateObj: object,
|
|
1047
1068
|
ctx: DbContext
|
|
1048
1069
|
): Promise<boolean> {
|
|
1049
|
-
|
|
1050
|
-
.
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1070
|
+
try {
|
|
1071
|
+
await getDatasourceForTransaction(ctx.txnId)
|
|
1072
|
+
.createQueryBuilder()
|
|
1073
|
+
.update(tableName)
|
|
1074
|
+
.set(updateObj)
|
|
1075
|
+
.where(objectToWhereClause(queryObj, queryVals), queryVals)
|
|
1076
|
+
.execute();
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
mapDatabaseError(err);
|
|
1079
|
+
}
|
|
1055
1080
|
return true;
|
|
1056
1081
|
}
|
|
1057
1082
|
|
|
@@ -1069,12 +1094,16 @@ function queryObjectAsWhereClause(qobj: QueryObject): string {
|
|
|
1069
1094
|
|
|
1070
1095
|
export async function hardDeleteRow(tableName: string, queryObject: QueryObject, ctx: DbContext) {
|
|
1071
1096
|
const clause = queryObjectAsWhereClause(queryObject);
|
|
1072
|
-
|
|
1073
|
-
.
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1097
|
+
try {
|
|
1098
|
+
await getDatasourceForTransaction(ctx.txnId)
|
|
1099
|
+
.createQueryBuilder()
|
|
1100
|
+
.delete()
|
|
1101
|
+
.from(tableName)
|
|
1102
|
+
.where(clause, Object.fromEntries(queryObject))
|
|
1103
|
+
.execute();
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
mapDatabaseError(err);
|
|
1106
|
+
}
|
|
1078
1107
|
return true;
|
|
1079
1108
|
}
|
|
1080
1109
|
|
|
@@ -1090,7 +1119,7 @@ function mkBetweenClause(tableName: string | undefined, k: string, queryVals: an
|
|
|
1090
1119
|
delete queryVals[k];
|
|
1091
1120
|
return s;
|
|
1092
1121
|
} else {
|
|
1093
|
-
throw
|
|
1122
|
+
throw createCodedError(`between requires an array argument, not ${ov}`, 'AL_DB_BETWEEN_ARRAY');
|
|
1094
1123
|
}
|
|
1095
1124
|
}
|
|
1096
1125
|
|
|
@@ -1112,7 +1141,10 @@ function objectToWhereClause(queryObj: object, queryVals: any, tableName?: strin
|
|
|
1112
1141
|
} else if (op === '<>' || op === '!=') {
|
|
1113
1142
|
op = 'IS NOT';
|
|
1114
1143
|
} else {
|
|
1115
|
-
throw
|
|
1144
|
+
throw createCodedError(
|
|
1145
|
+
`Operator ${op} cannot be appplied to SQL NULL`,
|
|
1146
|
+
'AL_DB_NULL_OPERATOR'
|
|
1147
|
+
);
|
|
1116
1148
|
}
|
|
1117
1149
|
}
|
|
1118
1150
|
const v = isnullcheck ? 'NULL' : `:${k}`;
|
|
@@ -1143,7 +1175,10 @@ function objectToRawWhereClause(queryObj: object, queryVals: any, tableName?: st
|
|
|
1143
1175
|
} else if (op === '<>' || op === '!=') {
|
|
1144
1176
|
op = 'IS NOT';
|
|
1145
1177
|
} else {
|
|
1146
|
-
throw
|
|
1178
|
+
throw createCodedError(
|
|
1179
|
+
`Operator ${op} cannot be appplied to SQL NULL`,
|
|
1180
|
+
'AL_DB_NULL_OPERATOR'
|
|
1181
|
+
);
|
|
1147
1182
|
}
|
|
1148
1183
|
}
|
|
1149
1184
|
let clause = '';
|
|
@@ -1274,8 +1309,12 @@ export async function getMany(
|
|
|
1274
1309
|
qb.skip(querySpec.offset);
|
|
1275
1310
|
}
|
|
1276
1311
|
qb.where(queryStr, querySpec.queryVals);
|
|
1277
|
-
|
|
1278
|
-
|
|
1312
|
+
try {
|
|
1313
|
+
if (hasAggregates) return await qb.getRawMany();
|
|
1314
|
+
else return await qb.getMany();
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
mapDatabaseError(err);
|
|
1317
|
+
}
|
|
1279
1318
|
}
|
|
1280
1319
|
|
|
1281
1320
|
export async function getManyByRawQuery(
|
|
@@ -1287,7 +1326,11 @@ export async function getManyByRawQuery(
|
|
|
1287
1326
|
.getRepository(tableName)
|
|
1288
1327
|
.createQueryBuilder();
|
|
1289
1328
|
qb.where('', querySpec.queryVals);
|
|
1290
|
-
|
|
1329
|
+
try {
|
|
1330
|
+
return await qb.getMany();
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
mapDatabaseError(err);
|
|
1333
|
+
}
|
|
1291
1334
|
}
|
|
1292
1335
|
|
|
1293
1336
|
export async function getManyByJoin(
|
|
@@ -1348,7 +1391,7 @@ export async function getManyByJoin(
|
|
|
1348
1391
|
}
|
|
1349
1392
|
});
|
|
1350
1393
|
if (querySpec.intoSpec === undefined) {
|
|
1351
|
-
throw
|
|
1394
|
+
throw createCodedError('SELECT-INTO pattern is missing', 'AL_DB_SELECT_INTO_MISSING');
|
|
1352
1395
|
}
|
|
1353
1396
|
const intos = querySpec.intoSpec.size > 0 ? intoSpecToSql(querySpec.intoSpec) : '';
|
|
1354
1397
|
const intos_sep = intos.length === 0 ? '' : ',';
|
|
@@ -1370,7 +1413,11 @@ export async function getManyByJoin(
|
|
|
1370
1413
|
}
|
|
1371
1414
|
logger.debug(`Join Query: ${sql}`);
|
|
1372
1415
|
const qb = getDatasourceForTransaction(ctx.txnId).getRepository(tableName).manager;
|
|
1373
|
-
|
|
1416
|
+
try {
|
|
1417
|
+
return await qb.query(sql);
|
|
1418
|
+
} catch (err) {
|
|
1419
|
+
mapDatabaseError(err);
|
|
1420
|
+
}
|
|
1374
1421
|
}
|
|
1375
1422
|
|
|
1376
1423
|
function intoSpecToSql(intoSpec: Map<string, string>): string {
|
|
@@ -1440,7 +1487,11 @@ export async function getAllConnected(
|
|
|
1440
1487
|
connAlias,
|
|
1441
1488
|
buildQueryFromConnnectionInfo(connAlias, alias, connInfo)
|
|
1442
1489
|
);
|
|
1443
|
-
|
|
1490
|
+
try {
|
|
1491
|
+
return await qb.getRawMany();
|
|
1492
|
+
} catch (err) {
|
|
1493
|
+
mapDatabaseError(err);
|
|
1494
|
+
}
|
|
1444
1495
|
}
|
|
1445
1496
|
|
|
1446
1497
|
const transactionsDb: Map<string, QueryRunner> = new Map<string, QueryRunner>();
|
|
@@ -1453,7 +1504,7 @@ export async function startDbTransaction(): Promise<string> {
|
|
|
1453
1504
|
transactionsDb.set(txnId, queryRunner);
|
|
1454
1505
|
return txnId;
|
|
1455
1506
|
} else {
|
|
1456
|
-
throw
|
|
1507
|
+
throw createCodedError('Database not initialized', 'AL_DB_NOT_INITIALIZED');
|
|
1457
1508
|
}
|
|
1458
1509
|
}
|
|
1459
1510
|
|
|
@@ -1461,13 +1512,13 @@ function getDatasourceForTransaction(txnId: string | undefined): DataSource | En
|
|
|
1461
1512
|
if (txnId) {
|
|
1462
1513
|
const qr: QueryRunner | undefined = transactionsDb.get(txnId);
|
|
1463
1514
|
if (qr === undefined) {
|
|
1464
|
-
throw
|
|
1515
|
+
throw createCodedError(`Transaction not found - ${txnId}`, 'AL_DB_TXN_NOT_FOUND');
|
|
1465
1516
|
} else {
|
|
1466
1517
|
return qr.manager;
|
|
1467
1518
|
}
|
|
1468
1519
|
} else {
|
|
1469
1520
|
if (defaultDataSource !== undefined) return defaultDataSource;
|
|
1470
|
-
else throw
|
|
1521
|
+
else throw createCodedError('No default datasource is initialized', 'AL_DB_NO_DATASOURCE');
|
|
1471
1522
|
}
|
|
1472
1523
|
}
|
|
1473
1524
|
|