@maykonpaulo/maestro-core 0.3.0-rc.0 → 0.3.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/dist/index.d.ts +203 -21
- package/dist/index.js +746 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -521,7 +521,10 @@ function validateMaestroConfig(config) {
|
|
|
521
521
|
errors.push({ path: "datasources", message: "At least one datasource is required." });
|
|
522
522
|
}
|
|
523
523
|
if (!config.entities || config.entities.length === 0) {
|
|
524
|
-
errors.push({
|
|
524
|
+
errors.push({
|
|
525
|
+
path: "entities",
|
|
526
|
+
message: "At least one entity is required. Provide entities directly or via declarations.entities."
|
|
527
|
+
});
|
|
525
528
|
}
|
|
526
529
|
const entityIds = /* @__PURE__ */ new Set();
|
|
527
530
|
for (const entity of config.entities ?? []) {
|
|
@@ -718,7 +721,7 @@ function buildBulkOperations(entity, operations) {
|
|
|
718
721
|
var MetadataEngine = class {
|
|
719
722
|
normalize(config) {
|
|
720
723
|
const operations = config.operations ?? [];
|
|
721
|
-
const entities = config.entities.map((e) => this.normalizeEntity(e, operations));
|
|
724
|
+
const entities = (config.entities ?? []).map((e) => this.normalizeEntity(e, operations));
|
|
722
725
|
const relations = config.relations ?? [];
|
|
723
726
|
const entityRelationMap = /* @__PURE__ */ new Map();
|
|
724
727
|
for (const rel of relations) {
|
|
@@ -890,21 +893,54 @@ var CsvExportProvider = class {
|
|
|
890
893
|
|
|
891
894
|
// src/engine/MaestroEngine.ts
|
|
892
895
|
var MaestroEngine = class {
|
|
893
|
-
constructor(metadata, datasources, operations, audit) {
|
|
896
|
+
constructor(metadata, datasources, operations, audit, consumers = []) {
|
|
894
897
|
this.metadata = metadata;
|
|
895
898
|
this.datasources = datasources;
|
|
896
899
|
this.operations = operations;
|
|
897
900
|
this.audit = audit;
|
|
898
901
|
this.rbac = new RbacEngine(metadata.policies);
|
|
902
|
+
this.consumerMap = new Map(consumers.map((c) => [c.consumer, c]));
|
|
899
903
|
}
|
|
900
904
|
metadata;
|
|
901
905
|
datasources;
|
|
902
906
|
operations;
|
|
903
907
|
audit;
|
|
904
908
|
rbac;
|
|
909
|
+
consumerMap;
|
|
905
910
|
getMetadata() {
|
|
906
911
|
return this.metadata;
|
|
907
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Returns the resolved consumer projection for the given consumer ID, or undefined if not found.
|
|
915
|
+
* Each call returns an independent copy — mutations to the returned object do not affect internal state.
|
|
916
|
+
* Consumer projections are only available when declarations.consumers were provided to createMaestro().
|
|
917
|
+
*/
|
|
918
|
+
getConsumer(consumerId) {
|
|
919
|
+
const p = this.consumerMap.get(consumerId);
|
|
920
|
+
if (!p) return void 0;
|
|
921
|
+
return {
|
|
922
|
+
consumer: p.consumer,
|
|
923
|
+
entity: p.entity,
|
|
924
|
+
fields: {
|
|
925
|
+
list: [...p.fields.list],
|
|
926
|
+
detail: [...p.fields.detail],
|
|
927
|
+
create: [...p.fields.create],
|
|
928
|
+
update: [...p.fields.update],
|
|
929
|
+
clone: [...p.fields.clone]
|
|
930
|
+
},
|
|
931
|
+
operations: {
|
|
932
|
+
row: [...p.operations.row],
|
|
933
|
+
bulk: [...p.operations.bulk],
|
|
934
|
+
global: [...p.operations.global]
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Returns the IDs of all registered consumer projections.
|
|
940
|
+
*/
|
|
941
|
+
listConsumers() {
|
|
942
|
+
return [...this.consumerMap.keys()];
|
|
943
|
+
}
|
|
908
944
|
async list(entityId, query, actor) {
|
|
909
945
|
const entity = this.requireEntity(entityId);
|
|
910
946
|
this.requireCapability(entity, "list", actor, `entity.${entityId}.list`);
|
|
@@ -1131,25 +1167,724 @@ var MaestroEngine = class {
|
|
|
1131
1167
|
}
|
|
1132
1168
|
};
|
|
1133
1169
|
|
|
1170
|
+
// src/declarative/DeclarativeValidator.ts
|
|
1171
|
+
var SUPPORTED_FIELD_TYPES = [
|
|
1172
|
+
"string",
|
|
1173
|
+
"text",
|
|
1174
|
+
"number",
|
|
1175
|
+
"integer",
|
|
1176
|
+
"decimal",
|
|
1177
|
+
"currency",
|
|
1178
|
+
"boolean",
|
|
1179
|
+
"date",
|
|
1180
|
+
"datetime",
|
|
1181
|
+
"time",
|
|
1182
|
+
"email",
|
|
1183
|
+
"phone",
|
|
1184
|
+
"url",
|
|
1185
|
+
"document",
|
|
1186
|
+
"uuid",
|
|
1187
|
+
"enum",
|
|
1188
|
+
"json",
|
|
1189
|
+
"relation",
|
|
1190
|
+
"array"
|
|
1191
|
+
];
|
|
1192
|
+
var SUPPORTED_OPERATIONAL_RISKS = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
|
|
1193
|
+
var SUPPORTED_OPERATION_SCOPES = ["global", "entity", "record", "bulk"];
|
|
1194
|
+
var SUPPORTED_RELATION_TYPES = [
|
|
1195
|
+
"one-to-one",
|
|
1196
|
+
"one-to-many",
|
|
1197
|
+
"many-to-one",
|
|
1198
|
+
"many-to-many"
|
|
1199
|
+
];
|
|
1200
|
+
function validateEntityDeclaration(input) {
|
|
1201
|
+
const errors = [];
|
|
1202
|
+
if (!input || typeof input !== "object") {
|
|
1203
|
+
return { valid: false, errors: [{ path: "", message: "Entity declaration must be an object." }] };
|
|
1204
|
+
}
|
|
1205
|
+
const decl = input;
|
|
1206
|
+
if (!decl["entity"] || typeof decl["entity"] !== "string" || decl["entity"].trim() === "") {
|
|
1207
|
+
errors.push({ path: "entity", message: "Entity name is required." });
|
|
1208
|
+
}
|
|
1209
|
+
const fields = decl["fields"];
|
|
1210
|
+
if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
|
|
1211
|
+
errors.push({ path: "fields", message: "Entity must declare at least one field." });
|
|
1212
|
+
} else {
|
|
1213
|
+
const fieldMap = fields;
|
|
1214
|
+
if (Object.keys(fieldMap).length === 0) {
|
|
1215
|
+
errors.push({ path: "fields", message: "Entity must declare at least one field." });
|
|
1216
|
+
} else {
|
|
1217
|
+
for (const [name, rawField] of Object.entries(fieldMap)) {
|
|
1218
|
+
validateFieldDeclaration(name, rawField, errors);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
const operations = decl["operations"];
|
|
1223
|
+
if (operations !== void 0) {
|
|
1224
|
+
if (typeof operations !== "object" || Array.isArray(operations)) {
|
|
1225
|
+
errors.push({ path: "operations", message: "Operations must be an object." });
|
|
1226
|
+
} else {
|
|
1227
|
+
const opMap = operations;
|
|
1228
|
+
for (const [id, rawOp] of Object.entries(opMap)) {
|
|
1229
|
+
validateOperationDeclaration(id, rawOp, errors);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
const relationships = decl["relationships"];
|
|
1234
|
+
if (relationships !== void 0) {
|
|
1235
|
+
if (!Array.isArray(relationships)) {
|
|
1236
|
+
errors.push({ path: "relationships", message: "Relationships must be an array." });
|
|
1237
|
+
} else {
|
|
1238
|
+
const knownFields = fields && typeof fields === "object" && !Array.isArray(fields) ? new Set(Object.keys(fields)) : /* @__PURE__ */ new Set();
|
|
1239
|
+
const seenRelIds = /* @__PURE__ */ new Set();
|
|
1240
|
+
for (let i = 0; i < relationships.length; i++) {
|
|
1241
|
+
validateRelationDeclaration(i, relationships[i], knownFields, seenRelIds, errors);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
const capabilities = decl["capabilities"];
|
|
1246
|
+
if (capabilities !== void 0) {
|
|
1247
|
+
if (typeof capabilities !== "object" || Array.isArray(capabilities)) {
|
|
1248
|
+
errors.push({ path: "capabilities", message: "Capabilities must be an object." });
|
|
1249
|
+
} else {
|
|
1250
|
+
const caps = capabilities;
|
|
1251
|
+
const booleanKeys = [
|
|
1252
|
+
"list",
|
|
1253
|
+
"detail",
|
|
1254
|
+
"create",
|
|
1255
|
+
"update",
|
|
1256
|
+
"clone",
|
|
1257
|
+
"delete",
|
|
1258
|
+
"softDelete",
|
|
1259
|
+
"export",
|
|
1260
|
+
"bulkActions"
|
|
1261
|
+
];
|
|
1262
|
+
for (const key of Object.keys(caps)) {
|
|
1263
|
+
if (!booleanKeys.includes(key)) {
|
|
1264
|
+
errors.push({
|
|
1265
|
+
path: `capabilities.${key}`,
|
|
1266
|
+
message: `Unknown capability '${key}'. Valid capabilities: ${booleanKeys.join(", ")}.`
|
|
1267
|
+
});
|
|
1268
|
+
} else if (typeof caps[key] !== "boolean") {
|
|
1269
|
+
errors.push({
|
|
1270
|
+
path: `capabilities.${key}`,
|
|
1271
|
+
message: `Capability '${key}' must be a boolean.`
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return { valid: errors.length === 0, errors };
|
|
1278
|
+
}
|
|
1279
|
+
function validateFieldDeclaration(name, rawField, errors) {
|
|
1280
|
+
const prefix = `fields[${name}]`;
|
|
1281
|
+
if (!rawField || typeof rawField !== "object") {
|
|
1282
|
+
errors.push({ path: prefix, message: `Field '${name}' must be an object.` });
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
const field = rawField;
|
|
1286
|
+
if (!field.type) {
|
|
1287
|
+
errors.push({ path: `${prefix}.type`, message: "Field type is required." });
|
|
1288
|
+
} else if (!SUPPORTED_FIELD_TYPES.includes(field.type)) {
|
|
1289
|
+
errors.push({
|
|
1290
|
+
path: `${prefix}.type`,
|
|
1291
|
+
message: `Field type '${field.type}' is not supported. Supported types: ${SUPPORTED_FIELD_TYPES.join(", ")}.`
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
if (field.sensitive === true && field.exportable === true) {
|
|
1295
|
+
errors.push({
|
|
1296
|
+
path: prefix,
|
|
1297
|
+
message: `Field '${name}' cannot be both sensitive and exportable.`
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
if (field.enumOptions !== void 0) {
|
|
1301
|
+
if (field.type !== "enum") {
|
|
1302
|
+
errors.push({
|
|
1303
|
+
path: `${prefix}.enumOptions`,
|
|
1304
|
+
message: `Field '${name}' has enumOptions but type is '${field.type}'. enumOptions is only valid for type 'enum'.`
|
|
1305
|
+
});
|
|
1306
|
+
} else {
|
|
1307
|
+
validateEnumOptions(name, field.enumOptions, errors);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
if (field.relationEntity !== void 0) {
|
|
1311
|
+
if (field.type !== "relation") {
|
|
1312
|
+
errors.push({
|
|
1313
|
+
path: `${prefix}.relationEntity`,
|
|
1314
|
+
message: `Field '${name}' has relationEntity but type is '${field.type}'. relationEntity is only valid for type 'relation'.`
|
|
1315
|
+
});
|
|
1316
|
+
} else if (typeof field.relationEntity !== "string" || field.relationEntity.trim() === "") {
|
|
1317
|
+
errors.push({
|
|
1318
|
+
path: `${prefix}.relationEntity`,
|
|
1319
|
+
message: `Field '${name}' relationEntity must be a non-empty string.`
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function validateEnumOptions(fieldName, rawOptions, errors) {
|
|
1325
|
+
const prefix = `fields[${fieldName}].enumOptions`;
|
|
1326
|
+
if (!Array.isArray(rawOptions)) {
|
|
1327
|
+
errors.push({ path: prefix, message: `enumOptions for field '${fieldName}' must be an array.` });
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1330
|
+
if (rawOptions.length === 0) {
|
|
1331
|
+
errors.push({ path: prefix, message: `enumOptions for field '${fieldName}' must not be empty.` });
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
for (let i = 0; i < rawOptions.length; i++) {
|
|
1335
|
+
const opt = rawOptions[i];
|
|
1336
|
+
if (!opt || typeof opt !== "object") {
|
|
1337
|
+
errors.push({ path: `${prefix}[${i}]`, message: `Enum option at index ${i} must be an object.` });
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
if (opt.value === void 0 || opt.value === null) {
|
|
1341
|
+
errors.push({ path: `${prefix}[${i}].value`, message: `Enum option at index ${i} is missing 'value'.` });
|
|
1342
|
+
}
|
|
1343
|
+
if (!opt.label || typeof opt.label !== "string" || opt.label.trim() === "") {
|
|
1344
|
+
errors.push({ path: `${prefix}[${i}].label`, message: `Enum option at index ${i} is missing 'label'.` });
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
function validateOperationDeclaration(id, rawOp, errors) {
|
|
1349
|
+
const prefix = `operations[${id}]`;
|
|
1350
|
+
if (!rawOp || typeof rawOp !== "object") {
|
|
1351
|
+
errors.push({ path: prefix, message: `Operation '${id}' must be an object.` });
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
const op = rawOp;
|
|
1355
|
+
if (!op.label || typeof op.label !== "string" || op.label.trim() === "") {
|
|
1356
|
+
errors.push({ path: `${prefix}.label`, message: "Operation label is required." });
|
|
1357
|
+
}
|
|
1358
|
+
if (op.risk !== void 0 && !SUPPORTED_OPERATIONAL_RISKS.includes(op.risk)) {
|
|
1359
|
+
errors.push({
|
|
1360
|
+
path: `${prefix}.risk`,
|
|
1361
|
+
message: `Operation risk '${op.risk}' is not valid. Valid values: ${SUPPORTED_OPERATIONAL_RISKS.join(", ")}.`
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
if (op.scope !== void 0 && !SUPPORTED_OPERATION_SCOPES.includes(op.scope)) {
|
|
1365
|
+
errors.push({
|
|
1366
|
+
path: `${prefix}.scope`,
|
|
1367
|
+
message: `Operation scope '${op.scope}' is not valid. Valid values: ${SUPPORTED_OPERATION_SCOPES.join(", ")}.`
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
if (op.confirmation !== void 0) {
|
|
1371
|
+
const conf = op.confirmation;
|
|
1372
|
+
if (conf.approvers !== void 0 && (typeof conf.approvers !== "number" || conf.approvers < 1)) {
|
|
1373
|
+
errors.push({
|
|
1374
|
+
path: `${prefix}.confirmation.approvers`,
|
|
1375
|
+
message: "Confirmation approvers must be at least 1."
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function validateRelationDeclaration(index, rawRel, knownFields, seenIds, errors) {
|
|
1381
|
+
const prefix = `relationships[${index}]`;
|
|
1382
|
+
if (!rawRel || typeof rawRel !== "object") {
|
|
1383
|
+
errors.push({ path: prefix, message: `Relationship at index ${index} must be an object.` });
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const rel = rawRel;
|
|
1387
|
+
if (rel.id !== void 0) {
|
|
1388
|
+
if (typeof rel.id !== "string" || rel.id.trim() === "") {
|
|
1389
|
+
errors.push({ path: `${prefix}.id`, message: "Relationship id must be a non-empty string." });
|
|
1390
|
+
} else if (seenIds.has(rel.id)) {
|
|
1391
|
+
errors.push({ path: `${prefix}.id`, message: `Duplicate relationship id '${rel.id}'.` });
|
|
1392
|
+
} else {
|
|
1393
|
+
seenIds.add(rel.id);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (!rel.type) {
|
|
1397
|
+
errors.push({ path: `${prefix}.type`, message: "Relationship type is required." });
|
|
1398
|
+
} else if (!SUPPORTED_RELATION_TYPES.includes(rel.type)) {
|
|
1399
|
+
errors.push({
|
|
1400
|
+
path: `${prefix}.type`,
|
|
1401
|
+
message: `Relationship type '${rel.type}' is not valid. Valid values: ${SUPPORTED_RELATION_TYPES.join(", ")}.`
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
if (!rel.fromField || typeof rel.fromField !== "string" || rel.fromField.trim() === "") {
|
|
1405
|
+
errors.push({ path: `${prefix}.fromField`, message: "Relationship fromField is required." });
|
|
1406
|
+
} else if (knownFields.size > 0 && !knownFields.has(rel.fromField)) {
|
|
1407
|
+
errors.push({
|
|
1408
|
+
path: `${prefix}.fromField`,
|
|
1409
|
+
message: `Relationship fromField '${rel.fromField}' does not exist in the entity's fields.`
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
if (!rel.toEntity || typeof rel.toEntity !== "string" || rel.toEntity.trim() === "") {
|
|
1413
|
+
errors.push({ path: `${prefix}.toEntity`, message: "Relationship toEntity is required." });
|
|
1414
|
+
}
|
|
1415
|
+
if (!rel.toField || typeof rel.toField !== "string" || rel.toField.trim() === "") {
|
|
1416
|
+
errors.push({ path: `${prefix}.toField`, message: "Relationship toField is required." });
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
function validateConsumerDeclaration(input, entity) {
|
|
1420
|
+
const errors = [];
|
|
1421
|
+
if (!input || typeof input !== "object") {
|
|
1422
|
+
return { valid: false, errors: [{ path: "", message: "Consumer declaration must be an object." }] };
|
|
1423
|
+
}
|
|
1424
|
+
const decl = input;
|
|
1425
|
+
if (!decl["consumer"] || typeof decl["consumer"] !== "string" || decl["consumer"].trim() === "") {
|
|
1426
|
+
errors.push({ path: "consumer", message: "Consumer name is required." });
|
|
1427
|
+
}
|
|
1428
|
+
if (!decl["entity"] || typeof decl["entity"] !== "string" || decl["entity"].trim() === "") {
|
|
1429
|
+
errors.push({ path: "entity", message: "Entity name is required." });
|
|
1430
|
+
}
|
|
1431
|
+
if (entity) {
|
|
1432
|
+
const knownFields = new Set(Object.keys(entity.fields));
|
|
1433
|
+
const knownOps = new Set(Object.keys(entity.operations ?? {}));
|
|
1434
|
+
const entityName = entity.entity;
|
|
1435
|
+
validateFieldRefs("list.fields", decl["list"], knownFields, entityName, errors);
|
|
1436
|
+
validateFieldRefs("detail.fields", decl["detail"], knownFields, entityName, errors);
|
|
1437
|
+
const forms = decl["forms"];
|
|
1438
|
+
if (forms && typeof forms === "object") {
|
|
1439
|
+
validateFieldRefs("forms.create.fields", forms["create"], knownFields, entityName, errors);
|
|
1440
|
+
validateFieldRefs("forms.update.fields", forms["update"], knownFields, entityName, errors);
|
|
1441
|
+
validateFieldRefs("forms.clone.fields", forms["clone"], knownFields, entityName, errors);
|
|
1442
|
+
}
|
|
1443
|
+
const actions = decl["actions"];
|
|
1444
|
+
if (actions && typeof actions === "object") {
|
|
1445
|
+
validateOperationRefs("actions.row", actions["row"], knownOps, entityName, errors);
|
|
1446
|
+
validateOperationRefs("actions.bulk", actions["bulk"], knownOps, entityName, errors);
|
|
1447
|
+
validateOperationRefs("actions.global", actions["global"], knownOps, entityName, errors);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return { valid: errors.length === 0, errors };
|
|
1451
|
+
}
|
|
1452
|
+
function validateFieldRefs(path, container, knownFields, entityName, errors) {
|
|
1453
|
+
if (!container || typeof container !== "object") return;
|
|
1454
|
+
const obj = container;
|
|
1455
|
+
const fields = obj["fields"];
|
|
1456
|
+
if (!Array.isArray(fields)) return;
|
|
1457
|
+
for (let i = 0; i < fields.length; i++) {
|
|
1458
|
+
const name = fields[i];
|
|
1459
|
+
if (!knownFields.has(name)) {
|
|
1460
|
+
errors.push({
|
|
1461
|
+
path: `${path}[${i}]`,
|
|
1462
|
+
message: `Field '${name}' does not exist in entity '${entityName}'.`
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function validateOperationRefs(path, refs, knownOps, entityName, errors) {
|
|
1468
|
+
if (!Array.isArray(refs)) return;
|
|
1469
|
+
for (let i = 0; i < refs.length; i++) {
|
|
1470
|
+
const id = refs[i];
|
|
1471
|
+
if (!knownOps.has(id)) {
|
|
1472
|
+
errors.push({
|
|
1473
|
+
path: `${path}[${i}]`,
|
|
1474
|
+
message: `Operation '${id}' does not exist in entity '${entityName}'.`
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// src/declarative/DeclarativeMetadataCompiler.ts
|
|
1481
|
+
function capitalizeFirst(name) {
|
|
1482
|
+
if (!name) return name;
|
|
1483
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
1484
|
+
}
|
|
1485
|
+
function fieldDeclToFieldSchema(name, field) {
|
|
1486
|
+
return {
|
|
1487
|
+
name,
|
|
1488
|
+
label: field.label ?? capitalizeFirst(name),
|
|
1489
|
+
type: field.type,
|
|
1490
|
+
required: field.required,
|
|
1491
|
+
readonly: field.readonly,
|
|
1492
|
+
// FieldDeclaration uses visible (default true); FieldSchema uses hidden (default false)
|
|
1493
|
+
hidden: field.visible === false ? true : void 0,
|
|
1494
|
+
sensitive: field.sensitive,
|
|
1495
|
+
searchable: field.searchable,
|
|
1496
|
+
sortable: field.sortable,
|
|
1497
|
+
filterable: field.filterable,
|
|
1498
|
+
exportable: field.exportable,
|
|
1499
|
+
description: field.description,
|
|
1500
|
+
enumOptions: field.enumOptions,
|
|
1501
|
+
relationEntity: field.relationEntity
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function opDeclToStubOperationDef(entityId, opKey, op) {
|
|
1505
|
+
const id = `${entityId}.${opKey}`;
|
|
1506
|
+
return {
|
|
1507
|
+
id,
|
|
1508
|
+
label: op.label,
|
|
1509
|
+
description: op.description,
|
|
1510
|
+
entity: entityId,
|
|
1511
|
+
scope: op.scope ?? "record",
|
|
1512
|
+
requiredPermission: op.permission,
|
|
1513
|
+
requiresConfirmation: op.confirmation?.required,
|
|
1514
|
+
execute: async () => {
|
|
1515
|
+
throw new MaestroError(
|
|
1516
|
+
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
|
|
1517
|
+
`Operation '${id}' declared but has no registered implementation. Provide an OperationDef with id '${id}' in config.operations.`
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
function entityDeclToEntitySchema(decl, datasource) {
|
|
1523
|
+
const primaryKeyEntry = Object.entries(decl.fields).find(([, f]) => f.primary);
|
|
1524
|
+
const primaryKey = primaryKeyEntry ? primaryKeyEntry[0] : "id";
|
|
1525
|
+
const singularLabel = decl.label ?? capitalizeFirst(decl.entity);
|
|
1526
|
+
const pluralLabel = decl.pluralLabel ?? decl.label ?? capitalizeFirst(decl.entity);
|
|
1527
|
+
const fields = Object.entries(decl.fields).map(
|
|
1528
|
+
([name, field]) => fieldDeclToFieldSchema(name, field)
|
|
1529
|
+
);
|
|
1530
|
+
return {
|
|
1531
|
+
id: decl.entity,
|
|
1532
|
+
label: { singular: singularLabel, plural: pluralLabel },
|
|
1533
|
+
description: decl.description,
|
|
1534
|
+
source: {
|
|
1535
|
+
datasource,
|
|
1536
|
+
table: decl.entity,
|
|
1537
|
+
primaryKey
|
|
1538
|
+
},
|
|
1539
|
+
capabilities: decl.capabilities,
|
|
1540
|
+
fields
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
function relationDeclToRelationSchema(fromEntity, rel) {
|
|
1544
|
+
const id = rel.id ?? `${fromEntity}.${rel.toEntity}`;
|
|
1545
|
+
const label = rel.label ?? capitalizeFirst(rel.toEntity);
|
|
1546
|
+
return {
|
|
1547
|
+
id,
|
|
1548
|
+
type: rel.type,
|
|
1549
|
+
from: { entity: fromEntity, field: rel.fromField },
|
|
1550
|
+
to: { entity: rel.toEntity, field: rel.toField },
|
|
1551
|
+
label,
|
|
1552
|
+
display: rel.display ? { tab: rel.display.tab ?? false, label: rel.display.label } : void 0
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
function compileDeclarations(config, options) {
|
|
1556
|
+
const errors = [];
|
|
1557
|
+
const datasource = options.defaultDatasource ?? (options.datasourceIds.length === 1 ? options.datasourceIds[0] : void 0);
|
|
1558
|
+
if (options.datasourceIds.length > 1 && !datasource) {
|
|
1559
|
+
errors.push({
|
|
1560
|
+
path: "declarations.defaultDatasource",
|
|
1561
|
+
message: "Multiple datasources are configured. Set declarations.defaultDatasource to specify which one to use for declared entities."
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
const entityDeclarationMap = /* @__PURE__ */ new Map();
|
|
1565
|
+
const compiledEntities = [];
|
|
1566
|
+
const compiledOperations = [];
|
|
1567
|
+
const compiledRelations = [];
|
|
1568
|
+
for (const decl of config.entities) {
|
|
1569
|
+
const entityId = typeof decl?.entity === "string" ? decl.entity : "?";
|
|
1570
|
+
const prefix = `declarations.entities[${entityId}]`;
|
|
1571
|
+
const validation = validateEntityDeclaration(decl);
|
|
1572
|
+
if (!validation.valid) {
|
|
1573
|
+
for (const err of validation.errors) {
|
|
1574
|
+
errors.push({ path: `${prefix}.${err.path}`.replace(/\.$/, ""), message: err.message });
|
|
1575
|
+
}
|
|
1576
|
+
continue;
|
|
1577
|
+
}
|
|
1578
|
+
if (entityDeclarationMap.has(decl.entity)) {
|
|
1579
|
+
errors.push({
|
|
1580
|
+
path: prefix,
|
|
1581
|
+
message: `Duplicate entity '${decl.entity}' in declarations.entities.`
|
|
1582
|
+
});
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
entityDeclarationMap.set(decl.entity, decl);
|
|
1586
|
+
if (datasource) {
|
|
1587
|
+
compiledEntities.push(entityDeclToEntitySchema(decl, datasource));
|
|
1588
|
+
for (const [opKey, opDecl] of Object.entries(decl.operations ?? {})) {
|
|
1589
|
+
compiledOperations.push(opDeclToStubOperationDef(decl.entity, opKey, opDecl));
|
|
1590
|
+
}
|
|
1591
|
+
const seenRelIds = /* @__PURE__ */ new Set();
|
|
1592
|
+
for (let i = 0; i < (decl.relationships ?? []).length; i++) {
|
|
1593
|
+
const rel = decl.relationships[i];
|
|
1594
|
+
const relSchema = relationDeclToRelationSchema(decl.entity, rel);
|
|
1595
|
+
if (seenRelIds.has(relSchema.id)) {
|
|
1596
|
+
errors.push({
|
|
1597
|
+
path: `${prefix}.relationships[${i}].id`,
|
|
1598
|
+
message: `Duplicate relation id '${relSchema.id}' in entity '${decl.entity}'.`
|
|
1599
|
+
});
|
|
1600
|
+
} else {
|
|
1601
|
+
seenRelIds.add(relSchema.id);
|
|
1602
|
+
compiledRelations.push(relSchema);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
const consumerNames = /* @__PURE__ */ new Set();
|
|
1608
|
+
const validConsumers = [];
|
|
1609
|
+
for (const consumer of config.consumers ?? []) {
|
|
1610
|
+
const consumerId = typeof consumer?.consumer === "string" ? consumer.consumer : "?";
|
|
1611
|
+
const prefix = `declarations.consumers[${consumerId}]`;
|
|
1612
|
+
const entityDecl = entityDeclarationMap.get(consumer?.entity ?? "");
|
|
1613
|
+
const validation = validateConsumerDeclaration(consumer, entityDecl);
|
|
1614
|
+
if (!validation.valid) {
|
|
1615
|
+
for (const err of validation.errors) {
|
|
1616
|
+
errors.push({ path: `${prefix}.${err.path}`.replace(/\.$/, ""), message: err.message });
|
|
1617
|
+
}
|
|
1618
|
+
continue;
|
|
1619
|
+
}
|
|
1620
|
+
if (consumerNames.has(consumer.consumer)) {
|
|
1621
|
+
errors.push({
|
|
1622
|
+
path: prefix,
|
|
1623
|
+
message: `Duplicate consumer '${consumer.consumer}' in declarations.consumers.`
|
|
1624
|
+
});
|
|
1625
|
+
continue;
|
|
1626
|
+
}
|
|
1627
|
+
consumerNames.add(consumer.consumer);
|
|
1628
|
+
validConsumers.push(consumer);
|
|
1629
|
+
}
|
|
1630
|
+
return {
|
|
1631
|
+
valid: errors.length === 0,
|
|
1632
|
+
errors,
|
|
1633
|
+
entities: compiledEntities,
|
|
1634
|
+
operations: compiledOperations,
|
|
1635
|
+
consumers: validConsumers,
|
|
1636
|
+
relations: compiledRelations
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function resolveConsumerProjections(consumers, metadata) {
|
|
1640
|
+
const results = [];
|
|
1641
|
+
for (const consumer of consumers) {
|
|
1642
|
+
const entityMeta = metadata.entities.find((e) => e.id === consumer.entity);
|
|
1643
|
+
if (!entityMeta) continue;
|
|
1644
|
+
const allFields = entityMeta.fields;
|
|
1645
|
+
const entityOps = metadata.operations.filter((o) => o.entity === consumer.entity);
|
|
1646
|
+
const resolveFields = (names) => {
|
|
1647
|
+
if (!names || names.length === 0) return [...allFields];
|
|
1648
|
+
return names.map((name) => allFields.find((f) => f.name === name)).filter((f) => f !== void 0);
|
|
1649
|
+
};
|
|
1650
|
+
const resolveOps = (keys, defaultScope) => {
|
|
1651
|
+
if (!keys || keys.length === 0) {
|
|
1652
|
+
return defaultScope ? entityOps.filter((o) => o.scope === defaultScope) : [...entityOps];
|
|
1653
|
+
}
|
|
1654
|
+
return keys.map((key) => {
|
|
1655
|
+
const fullId = `${consumer.entity}.${key}`;
|
|
1656
|
+
return entityOps.find((o) => o.id === fullId);
|
|
1657
|
+
}).filter((o) => o !== void 0);
|
|
1658
|
+
};
|
|
1659
|
+
results.push({
|
|
1660
|
+
consumer: consumer.consumer,
|
|
1661
|
+
entity: consumer.entity,
|
|
1662
|
+
fields: {
|
|
1663
|
+
list: resolveFields(consumer.list?.fields),
|
|
1664
|
+
detail: resolveFields(consumer.detail?.fields),
|
|
1665
|
+
create: resolveFields(consumer.forms?.create?.fields),
|
|
1666
|
+
update: resolveFields(consumer.forms?.update?.fields),
|
|
1667
|
+
clone: resolveFields(consumer.forms?.clone?.fields)
|
|
1668
|
+
},
|
|
1669
|
+
operations: {
|
|
1670
|
+
row: resolveOps(consumer.actions?.row, "record"),
|
|
1671
|
+
bulk: resolveOps(consumer.actions?.bulk, "bulk"),
|
|
1672
|
+
global: resolveOps(consumer.actions?.global, "global")
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
return results;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1134
1679
|
// src/engine/createMaestro.ts
|
|
1680
|
+
function bindAndValidateOperations(stubs, configOps) {
|
|
1681
|
+
const errors = [];
|
|
1682
|
+
const configOpMap = /* @__PURE__ */ new Map();
|
|
1683
|
+
for (const op of configOps) {
|
|
1684
|
+
if (configOpMap.has(op.id)) {
|
|
1685
|
+
errors.push({
|
|
1686
|
+
path: `operations[${op.id}]`,
|
|
1687
|
+
message: `Duplicate operation id '${op.id}' in config.operations. Each operation must have a unique id.`
|
|
1688
|
+
});
|
|
1689
|
+
} else {
|
|
1690
|
+
configOpMap.set(op.id, op);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const stubIds = new Set(stubs.map((s) => s.id));
|
|
1694
|
+
const bound = [];
|
|
1695
|
+
for (const stub of stubs) {
|
|
1696
|
+
const impl = configOpMap.get(stub.id);
|
|
1697
|
+
if (!impl) {
|
|
1698
|
+
errors.push({
|
|
1699
|
+
path: `declarations.entities[${stub.entity}].operations`,
|
|
1700
|
+
message: `Declared operation '${stub.id}' has no implementation. Provide an OperationDef with id '${stub.id}' in config.operations.`
|
|
1701
|
+
});
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
if (impl.entity !== void 0 && impl.entity !== stub.entity) {
|
|
1705
|
+
errors.push({
|
|
1706
|
+
path: `operations[${impl.id}].entity`,
|
|
1707
|
+
message: `Operation '${impl.id}' is declared for entity '${stub.entity}' but config.operations specifies entity '${impl.entity}'.`
|
|
1708
|
+
});
|
|
1709
|
+
continue;
|
|
1710
|
+
}
|
|
1711
|
+
bound.push(impl);
|
|
1712
|
+
}
|
|
1713
|
+
for (const op of configOpMap.values()) {
|
|
1714
|
+
if (!stubIds.has(op.id)) {
|
|
1715
|
+
bound.push(op);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return { operations: bound, errors };
|
|
1719
|
+
}
|
|
1720
|
+
function validateRelationRefs(relations, metadata) {
|
|
1721
|
+
const errors = [];
|
|
1722
|
+
const entityMap = new Map(metadata.entities.map((e) => [e.id, e]));
|
|
1723
|
+
for (const rel of relations) {
|
|
1724
|
+
const fromEntity = entityMap.get(rel.from.entity);
|
|
1725
|
+
if (!fromEntity) {
|
|
1726
|
+
errors.push({
|
|
1727
|
+
path: `declarations.entities[${rel.from.entity}].relationships`,
|
|
1728
|
+
message: `Relation '${rel.id}': source entity '${rel.from.entity}' not found in metadata.`
|
|
1729
|
+
});
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
const fromField = fromEntity.fields.find((f) => f.name === rel.from.field);
|
|
1733
|
+
if (!fromField) {
|
|
1734
|
+
errors.push({
|
|
1735
|
+
path: `declarations.entities[${rel.from.entity}].relationships`,
|
|
1736
|
+
message: `Relation '${rel.id}': fromField '${rel.from.field}' does not exist in entity '${rel.from.entity}'.`
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
const toEntity = entityMap.get(rel.to.entity);
|
|
1740
|
+
if (!toEntity) {
|
|
1741
|
+
errors.push({
|
|
1742
|
+
path: `declarations.entities[${rel.from.entity}].relationships`,
|
|
1743
|
+
message: `Relation '${rel.id}': target entity '${rel.to.entity}' not found in metadata. Ensure the target entity is declared, introspected, or manually configured.`
|
|
1744
|
+
});
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
const toField = toEntity.fields.find((f) => f.name === rel.to.field);
|
|
1748
|
+
if (!toField) {
|
|
1749
|
+
errors.push({
|
|
1750
|
+
path: `declarations.entities[${rel.from.entity}].relationships`,
|
|
1751
|
+
message: `Relation '${rel.id}': toField '${rel.to.field}' does not exist in entity '${rel.to.entity}'.`
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return errors;
|
|
1756
|
+
}
|
|
1757
|
+
function validateConsumerRefs(consumers, metadata) {
|
|
1758
|
+
const errors = [];
|
|
1759
|
+
for (const consumer of consumers) {
|
|
1760
|
+
const entityMeta = metadata.entities.find((e) => e.id === consumer.entity);
|
|
1761
|
+
if (!entityMeta) {
|
|
1762
|
+
errors.push({
|
|
1763
|
+
path: `declarations.consumers[${consumer.consumer}].entity`,
|
|
1764
|
+
message: `Consumer '${consumer.consumer}' references entity '${consumer.entity}' which does not exist in the final metadata. Ensure the entity is declared, introspected, or manually configured.`
|
|
1765
|
+
});
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
const knownFields = new Set(entityMeta.fields.map((f) => f.name));
|
|
1769
|
+
const entityOps = metadata.operations.filter((o) => o.entity === consumer.entity);
|
|
1770
|
+
const knownOpKeys = new Set(
|
|
1771
|
+
entityOps.map((o) => o.id.startsWith(`${consumer.entity}.`) ? o.id.slice(consumer.entity.length + 1) : o.id)
|
|
1772
|
+
);
|
|
1773
|
+
const checkFields = (sectionPath, fieldNames) => {
|
|
1774
|
+
if (!fieldNames) return;
|
|
1775
|
+
for (let i = 0; i < fieldNames.length; i++) {
|
|
1776
|
+
if (!knownFields.has(fieldNames[i])) {
|
|
1777
|
+
errors.push({
|
|
1778
|
+
path: `declarations.consumers[${consumer.consumer}].${sectionPath}[${i}]`,
|
|
1779
|
+
message: `Consumer '${consumer.consumer}': field '${fieldNames[i]}' does not exist in entity '${consumer.entity}'.`
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
const checkOps = (sectionPath, opKeys) => {
|
|
1785
|
+
if (!opKeys) return;
|
|
1786
|
+
for (let i = 0; i < opKeys.length; i++) {
|
|
1787
|
+
if (!knownOpKeys.has(opKeys[i])) {
|
|
1788
|
+
errors.push({
|
|
1789
|
+
path: `declarations.consumers[${consumer.consumer}].${sectionPath}[${i}]`,
|
|
1790
|
+
message: `Consumer '${consumer.consumer}': operation '${opKeys[i]}' does not exist in entity '${consumer.entity}'.`
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
checkFields("list.fields", consumer.list?.fields);
|
|
1796
|
+
checkFields("detail.fields", consumer.detail?.fields);
|
|
1797
|
+
checkFields("forms.create.fields", consumer.forms?.create?.fields);
|
|
1798
|
+
checkFields("forms.update.fields", consumer.forms?.update?.fields);
|
|
1799
|
+
checkFields("forms.clone.fields", consumer.forms?.clone?.fields);
|
|
1800
|
+
checkOps("actions.row", consumer.actions?.row);
|
|
1801
|
+
checkOps("actions.bulk", consumer.actions?.bulk);
|
|
1802
|
+
checkOps("actions.global", consumer.actions?.global);
|
|
1803
|
+
}
|
|
1804
|
+
return errors;
|
|
1805
|
+
}
|
|
1135
1806
|
function createMaestro(config) {
|
|
1136
|
-
|
|
1807
|
+
let extraEntities = [];
|
|
1808
|
+
let declaredOperations = [];
|
|
1809
|
+
let compiledConsumers = [];
|
|
1810
|
+
let compiledRelations = [];
|
|
1811
|
+
if (config.declarations) {
|
|
1812
|
+
const datasourceIds = Object.keys(config.datasources ?? {});
|
|
1813
|
+
const defaultDatasource = config.declarations.defaultDatasource ?? (datasourceIds.length === 1 ? datasourceIds[0] : void 0);
|
|
1814
|
+
const compilation = compileDeclarations(config.declarations, {
|
|
1815
|
+
datasourceIds,
|
|
1816
|
+
defaultDatasource
|
|
1817
|
+
});
|
|
1818
|
+
if (!compilation.valid) {
|
|
1819
|
+
const messages = compilation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
1820
|
+
throw new MaestroError(
|
|
1821
|
+
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
|
|
1822
|
+
`Invalid declarative configuration: ${messages}`
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
extraEntities = compilation.entities;
|
|
1826
|
+
declaredOperations = compilation.operations;
|
|
1827
|
+
compiledConsumers = compilation.consumers;
|
|
1828
|
+
compiledRelations = compilation.relations;
|
|
1829
|
+
}
|
|
1830
|
+
const mergedEntities = [
|
|
1831
|
+
...config.entities ?? [],
|
|
1832
|
+
...extraEntities
|
|
1833
|
+
];
|
|
1834
|
+
const binding = bindAndValidateOperations(declaredOperations, config.operations ?? []);
|
|
1835
|
+
if (binding.errors.length > 0) {
|
|
1836
|
+
const messages = binding.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
1837
|
+
throw new MaestroError("CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */, `Invalid operation bindings: ${messages}`);
|
|
1838
|
+
}
|
|
1839
|
+
const mergedOperations = binding.operations;
|
|
1840
|
+
const mergedRelations = [
|
|
1841
|
+
...config.relations ?? [],
|
|
1842
|
+
...compiledRelations
|
|
1843
|
+
];
|
|
1844
|
+
const mergedConfig = {
|
|
1845
|
+
...config,
|
|
1846
|
+
entities: mergedEntities,
|
|
1847
|
+
operations: mergedOperations,
|
|
1848
|
+
relations: mergedRelations
|
|
1849
|
+
};
|
|
1850
|
+
const validation = validateMaestroConfig(mergedConfig);
|
|
1137
1851
|
if (!validation.valid) {
|
|
1138
1852
|
const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
1139
1853
|
throw new MaestroError("CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */, `Invalid Maestro configuration: ${messages}`);
|
|
1140
1854
|
}
|
|
1141
1855
|
const metadataEngine = new MetadataEngine();
|
|
1142
|
-
const metadata = metadataEngine.normalize(
|
|
1856
|
+
const metadata = metadataEngine.normalize(mergedConfig);
|
|
1857
|
+
if (compiledRelations.length > 0) {
|
|
1858
|
+
const relErrors = validateRelationRefs(compiledRelations, metadata);
|
|
1859
|
+
if (relErrors.length > 0) {
|
|
1860
|
+
const messages = relErrors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
1861
|
+
throw new MaestroError(
|
|
1862
|
+
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
|
|
1863
|
+
`Invalid relation references: ${messages}`
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
if (compiledConsumers.length > 0) {
|
|
1868
|
+
const consumerErrors = validateConsumerRefs(compiledConsumers, metadata);
|
|
1869
|
+
if (consumerErrors.length > 0) {
|
|
1870
|
+
const messages = consumerErrors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
1871
|
+
throw new MaestroError(
|
|
1872
|
+
"CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
|
|
1873
|
+
`Invalid consumer references: ${messages}`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
const consumers = resolveConsumerProjections(compiledConsumers, metadata);
|
|
1143
1878
|
const datasources = new DatasourceRegistry();
|
|
1144
1879
|
for (const [id, provider] of Object.entries(config.datasources)) {
|
|
1145
1880
|
datasources.register(id, provider);
|
|
1146
1881
|
}
|
|
1147
1882
|
const operations = new OperationRegistry();
|
|
1148
|
-
for (const operation of
|
|
1883
|
+
for (const operation of mergedOperations) {
|
|
1149
1884
|
operations.register(operation);
|
|
1150
1885
|
}
|
|
1151
1886
|
const audit = config.audit ? new AuditRecorder(config.audit) : void 0;
|
|
1152
|
-
return new MaestroEngine(metadata, datasources, operations, audit);
|
|
1887
|
+
return new MaestroEngine(metadata, datasources, operations, audit, consumers);
|
|
1153
1888
|
}
|
|
1154
1889
|
|
|
1155
1890
|
// src/introspection/utils.ts
|
|
@@ -2653,6 +3388,7 @@ export {
|
|
|
2653
3388
|
PolicyEngine,
|
|
2654
3389
|
RbacEngine,
|
|
2655
3390
|
ReportGenerator,
|
|
3391
|
+
compileDeclarations,
|
|
2656
3392
|
createMaestro,
|
|
2657
3393
|
createMaestroFromIntrospection,
|
|
2658
3394
|
createMaestroHttpHandlers,
|
|
@@ -2669,8 +3405,11 @@ export {
|
|
|
2669
3405
|
loadMaestroConfig,
|
|
2670
3406
|
mergeIntrospectionWithOverrides,
|
|
2671
3407
|
parseQueryInput,
|
|
3408
|
+
resolveConsumerProjections,
|
|
2672
3409
|
tableNameToEntityId,
|
|
2673
3410
|
tableNameToLabel,
|
|
3411
|
+
validateConsumerDeclaration,
|
|
3412
|
+
validateEntityDeclaration,
|
|
2674
3413
|
validateMaestroConfig
|
|
2675
3414
|
};
|
|
2676
3415
|
//# sourceMappingURL=index.js.map
|