agentlang 0.10.9 → 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/interpreter.d.ts.map +1 -1
- package/out/runtime/interpreter.js +26 -25
- package/out/runtime/interpreter.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 +1 -0
- package/out/runtime/modules/ai.d.ts.map +1 -1
- package/out/runtime/modules/ai.js +1 -0
- 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 +35 -12
- 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/interpreter.ts +70 -27
- package/src/runtime/logger.ts +27 -0
- package/src/runtime/module.ts +45 -13
- package/src/runtime/modules/ai.ts +1 -0
- package/src/runtime/modules/auth.ts +35 -12
- 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
|
@@ -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,
|
|
@@ -1045,7 +1046,7 @@ function fetchAuthImpl(): AgentlangAuth {
|
|
|
1045
1046
|
if (runtimeAuth) {
|
|
1046
1047
|
return runtimeAuth;
|
|
1047
1048
|
} else {
|
|
1048
|
-
throw
|
|
1049
|
+
throw createCodedError('Auth not initialized', 'AL_AUTH_NOT_INITIALIZED');
|
|
1049
1050
|
}
|
|
1050
1051
|
}
|
|
1051
1052
|
|
|
@@ -1115,7 +1116,9 @@ export async function forgotPasswordUser(username: string, env: Environment): Pr
|
|
|
1115
1116
|
const email = username.toLowerCase();
|
|
1116
1117
|
const allowed = await fetchAuthImpl().userExistsInIdentityProvider(email, env);
|
|
1117
1118
|
if (!allowed) {
|
|
1118
|
-
throw new UserNotFoundError('Email not registered'
|
|
1119
|
+
throw new UserNotFoundError('Email not registered', {
|
|
1120
|
+
agentlangCode: 'AL_AUTH_EMAIL_NOT_REGISTERED',
|
|
1121
|
+
});
|
|
1119
1122
|
}
|
|
1120
1123
|
await fetchAuthImpl().forgotPassword(email, env);
|
|
1121
1124
|
return { status: 'ok', message: 'Password reset code sent' };
|
|
@@ -1265,7 +1268,9 @@ export async function changePassword(
|
|
|
1265
1268
|
return undefined;
|
|
1266
1269
|
}
|
|
1267
1270
|
} else {
|
|
1268
|
-
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
|
+
});
|
|
1269
1274
|
}
|
|
1270
1275
|
}
|
|
1271
1276
|
|
|
@@ -1292,7 +1297,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1292
1297
|
try {
|
|
1293
1298
|
// Validate JWT structure first
|
|
1294
1299
|
if (!isJwtToken(token)) {
|
|
1295
|
-
throw new UnauthorisedError('Invalid JWT token structure'
|
|
1300
|
+
throw new UnauthorisedError('Invalid JWT token structure', {
|
|
1301
|
+
agentlangCode: 'AL_AUTH_JWT_STRUCTURE_INVALID',
|
|
1302
|
+
});
|
|
1296
1303
|
}
|
|
1297
1304
|
|
|
1298
1305
|
// Verify the JWT token directly with Cognito
|
|
@@ -1307,7 +1314,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1307
1314
|
const email = payload.email || payload['cognito:username'];
|
|
1308
1315
|
|
|
1309
1316
|
if (!userId) {
|
|
1310
|
-
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
|
+
});
|
|
1311
1320
|
}
|
|
1312
1321
|
|
|
1313
1322
|
let localUser = null;
|
|
@@ -1323,7 +1332,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1323
1332
|
logger.warn(
|
|
1324
1333
|
`User not found in local database for JWT token. Email: ${email}, UserId: ${userId}`
|
|
1325
1334
|
);
|
|
1326
|
-
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
|
+
});
|
|
1327
1338
|
}
|
|
1328
1339
|
|
|
1329
1340
|
// Use the local user's ID for consistency
|
|
@@ -1332,12 +1343,16 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1332
1343
|
// Check if user status is 'Active'
|
|
1333
1344
|
const userStatus = localUser.lookup('status');
|
|
1334
1345
|
if (userStatus !== 'Active') {
|
|
1335
|
-
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
|
+
});
|
|
1336
1349
|
}
|
|
1337
1350
|
|
|
1338
1351
|
const sess = await findUserSession(localUserId, env);
|
|
1339
1352
|
if (!sess) {
|
|
1340
|
-
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
|
+
});
|
|
1341
1356
|
}
|
|
1342
1357
|
// For JWT tokens, we use the token itself as sessionId for tracking
|
|
1343
1358
|
return { sessionId: sess.lookup('id'), userId: localUserId };
|
|
@@ -1349,7 +1364,9 @@ async function verifyJwtToken(token: string, env?: Environment): Promise<ActiveS
|
|
|
1349
1364
|
errorName: err.name,
|
|
1350
1365
|
errorMessage: err.message,
|
|
1351
1366
|
});
|
|
1352
|
-
throw new UnauthorisedError('JWT token verification failed'
|
|
1367
|
+
throw new UnauthorisedError('JWT token verification failed', {
|
|
1368
|
+
agentlangCode: 'AL_AUTH_JWT_VERIFY_WRAPPER',
|
|
1369
|
+
});
|
|
1353
1370
|
}
|
|
1354
1371
|
};
|
|
1355
1372
|
if (needCommit) {
|
|
@@ -1372,7 +1389,9 @@ async function verifySessionToken(token: string, env?: Environment): Promise<Act
|
|
|
1372
1389
|
if (user) {
|
|
1373
1390
|
const userStatus = user.lookup('status');
|
|
1374
1391
|
if (userStatus !== 'Active') {
|
|
1375
|
-
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
|
+
});
|
|
1376
1395
|
}
|
|
1377
1396
|
}
|
|
1378
1397
|
|
|
@@ -1382,7 +1401,9 @@ async function verifySessionToken(token: string, env?: Environment): Promise<Act
|
|
|
1382
1401
|
return { sessionId: sessId, userId: userId };
|
|
1383
1402
|
} else {
|
|
1384
1403
|
logger.warn(`No active session found for user '${userId}'`);
|
|
1385
|
-
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
|
+
});
|
|
1386
1407
|
}
|
|
1387
1408
|
} catch (err: any) {
|
|
1388
1409
|
if (err instanceof UnauthorisedError) {
|
|
@@ -1394,7 +1415,9 @@ async function verifySessionToken(token: string, env?: Environment): Promise<Act
|
|
|
1394
1415
|
errorMessage: err.message,
|
|
1395
1416
|
sessionId: sessId,
|
|
1396
1417
|
});
|
|
1397
|
-
throw new UnauthorisedError('Session verification failed'
|
|
1418
|
+
throw new UnauthorisedError('Session verification failed', {
|
|
1419
|
+
agentlangCode: 'AL_AUTH_SESSION_VERIFY_WRAPPER',
|
|
1420
|
+
});
|
|
1398
1421
|
}
|
|
1399
1422
|
};
|
|
1400
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
|
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { QueryFailedError } from 'typeorm';
|
|
2
|
+
import { createCodedError, isCodedError } from '../../errors/coded-error.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize TypeORM / driver failures into stable Agentlang codes for HTTP and logging.
|
|
6
|
+
* Preserves the original message text on the thrown Error.
|
|
7
|
+
*/
|
|
8
|
+
export function mapDatabaseError(err: unknown): never {
|
|
9
|
+
if (isCodedError(err)) {
|
|
10
|
+
throw err;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const anyErr = err as any;
|
|
14
|
+
const msg = anyErr instanceof Error ? anyErr.message : String(err);
|
|
15
|
+
const driver = anyErr?.driverError ?? anyErr;
|
|
16
|
+
const sqlState =
|
|
17
|
+
driver?.code !== undefined && driver?.code !== null
|
|
18
|
+
? String(driver.code)
|
|
19
|
+
: anyErr?.code !== undefined && anyErr?.code !== null
|
|
20
|
+
? String(anyErr.code)
|
|
21
|
+
: undefined;
|
|
22
|
+
const errno = driver?.errno ?? anyErr?.errno;
|
|
23
|
+
|
|
24
|
+
// PostgreSQL (SQLSTATE)
|
|
25
|
+
if (sqlState === '23505') {
|
|
26
|
+
throw createCodedError(msg, 'AL_DB_UNIQUE_VIOLATION');
|
|
27
|
+
}
|
|
28
|
+
if (sqlState === '23503') {
|
|
29
|
+
throw createCodedError(msg, 'AL_DB_FOREIGN_KEY_VIOLATION');
|
|
30
|
+
}
|
|
31
|
+
if (sqlState === '23502') {
|
|
32
|
+
throw createCodedError(msg, 'AL_DB_NOT_NULL_VIOLATION');
|
|
33
|
+
}
|
|
34
|
+
if (sqlState === '23514') {
|
|
35
|
+
throw createCodedError(msg, 'AL_DB_CHECK_VIOLATION');
|
|
36
|
+
}
|
|
37
|
+
// Invalid text representation / datetime / numeric range (e.g. non-UUID passed to a UUID column).
|
|
38
|
+
if (sqlState === '22P02' || sqlState === '22007' || sqlState === '22003') {
|
|
39
|
+
throw createCodedError(msg, 'AL_DB_INVALID_TYPE');
|
|
40
|
+
}
|
|
41
|
+
if (sqlState === '40P01') {
|
|
42
|
+
throw createCodedError(msg, 'AL_DB_DEADLOCK');
|
|
43
|
+
}
|
|
44
|
+
if (sqlState === '40001' || sqlState === '25P02') {
|
|
45
|
+
throw createCodedError(msg, 'AL_DB_SERIALIZATION_FAILURE');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MySQL / MariaDB (errno)
|
|
49
|
+
if (errno === 1062) {
|
|
50
|
+
throw createCodedError(msg, 'AL_DB_UNIQUE_VIOLATION');
|
|
51
|
+
}
|
|
52
|
+
if (errno === 1451 || errno === 1452) {
|
|
53
|
+
throw createCodedError(msg, 'AL_DB_FOREIGN_KEY_VIOLATION');
|
|
54
|
+
}
|
|
55
|
+
if (errno === 1048) {
|
|
56
|
+
throw createCodedError(msg, 'AL_DB_NOT_NULL_VIOLATION');
|
|
57
|
+
}
|
|
58
|
+
if (errno === 1213) {
|
|
59
|
+
throw createCodedError(msg, 'AL_DB_DEADLOCK');
|
|
60
|
+
}
|
|
61
|
+
if (errno === 1205) {
|
|
62
|
+
throw createCodedError(msg, 'AL_DB_LOCK_WAIT_TIMEOUT');
|
|
63
|
+
}
|
|
64
|
+
if (errno === 1366 || errno === 1292) {
|
|
65
|
+
throw createCodedError(msg, 'AL_DB_INVALID_TYPE');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const m = msg.toLowerCase();
|
|
69
|
+
|
|
70
|
+
// Cross-driver message heuristics (SQLite, sql.js, edge cases)
|
|
71
|
+
if (
|
|
72
|
+
/unique constraint|duplicate key value|duplicate entry|unique failed|constraint failed.*unique/i.test(
|
|
73
|
+
m
|
|
74
|
+
)
|
|
75
|
+
) {
|
|
76
|
+
throw createCodedError(msg, 'AL_DB_UNIQUE_VIOLATION');
|
|
77
|
+
}
|
|
78
|
+
if (/foreign key constraint|foreign key mismatch|references.*fails/i.test(m)) {
|
|
79
|
+
throw createCodedError(msg, 'AL_DB_FOREIGN_KEY_VIOLATION');
|
|
80
|
+
}
|
|
81
|
+
if (/not-null constraint|null value in column|cannot be null|not null constraint/i.test(m)) {
|
|
82
|
+
throw createCodedError(msg, 'AL_DB_NOT_NULL_VIOLATION');
|
|
83
|
+
}
|
|
84
|
+
if (/check constraint/i.test(m)) {
|
|
85
|
+
throw createCodedError(msg, 'AL_DB_CHECK_VIOLATION');
|
|
86
|
+
}
|
|
87
|
+
if (/deadlock/i.test(m)) {
|
|
88
|
+
throw createCodedError(msg, 'AL_DB_DEADLOCK');
|
|
89
|
+
}
|
|
90
|
+
if (/lock wait timeout|could not obtain lock/i.test(m)) {
|
|
91
|
+
throw createCodedError(msg, 'AL_DB_LOCK_WAIT_TIMEOUT');
|
|
92
|
+
}
|
|
93
|
+
if (/could not serialize|serialization failure/i.test(m)) {
|
|
94
|
+
throw createCodedError(msg, 'AL_DB_SERIALIZATION_FAILURE');
|
|
95
|
+
}
|
|
96
|
+
if (/invalid input syntax|incorrect .* value|out of range|datatype mismatch/i.test(m)) {
|
|
97
|
+
throw createCodedError(msg, 'AL_DB_INVALID_TYPE');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (anyErr instanceof QueryFailedError && /syntax error|parse error/i.test(m)) {
|
|
101
|
+
throw createCodedError(msg, 'AL_DB_SYNTAX_ERROR');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (anyErr instanceof QueryFailedError) {
|
|
105
|
+
throw createCodedError(msg, 'AL_DB_QUERY_FAILED');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (anyErr instanceof Error) {
|
|
109
|
+
throw createCodedError(msg, 'AL_DB_QUERY_FAILED');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw createCodedError(String(err), 'AL_DB_QUERY_FAILED');
|
|
113
|
+
}
|
package/src/runtime/state.ts
CHANGED
|
@@ -140,6 +140,13 @@ export const ConfigSchema = z.object({
|
|
|
140
140
|
})
|
|
141
141
|
)
|
|
142
142
|
.optional(),
|
|
143
|
+
customErrorMessages: z
|
|
144
|
+
.object({
|
|
145
|
+
// When true, error responses are resolved against the `agentlang/errorMessage`
|
|
146
|
+
// system entity before falling back to the default message.
|
|
147
|
+
enabled: z.boolean().default(false),
|
|
148
|
+
})
|
|
149
|
+
.optional(),
|
|
143
150
|
});
|
|
144
151
|
|
|
145
152
|
export type Config = z.infer<typeof ConfigSchema>;
|
package/src/xlsx.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare module 'xlsx' {
|
|
2
|
+
interface WorkSheet {}
|
|
3
|
+
interface WorkBook {
|
|
4
|
+
SheetNames: string[];
|
|
5
|
+
Sheets: { [name: string]: WorkSheet };
|
|
6
|
+
}
|
|
7
|
+
export const utils: {
|
|
8
|
+
book_new(): WorkBook;
|
|
9
|
+
json_to_sheet<T extends object>(data: T[]): WorkSheet;
|
|
10
|
+
aoa_to_sheet(data: unknown[][]): WorkSheet;
|
|
11
|
+
book_append_sheet(workbook: WorkBook, sheet: WorkSheet, name: string): void;
|
|
12
|
+
};
|
|
13
|
+
export function write(
|
|
14
|
+
workbook: WorkBook,
|
|
15
|
+
opts: { type: 'buffer'; bookType: 'xlsx' | 'csv' }
|
|
16
|
+
): Buffer;
|
|
17
|
+
}
|