@maykonpaulo/maestro-core 0.3.0-next.2 → 0.3.0-next.4

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.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({ path: "entities", message: "At least one entity is required." });
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`);
@@ -1077,79 +1113,778 @@ var MaestroEngine = class {
1077
1113
  if (!operation) {
1078
1114
  throw new MaestroError("NOT_FOUND" /* NOT_FOUND */, `Operation '${operationId}' not found.`);
1079
1115
  }
1080
- if (operation.requiredPermission && !this.rbac.can(actor, operation.requiredPermission)) {
1081
- throw new MaestroError(
1082
- "PERMISSION_DENIED" /* PERMISSION_DENIED */,
1083
- `Permission denied: ${operation.requiredPermission}`
1084
- );
1116
+ if (operation.requiredPermission && !this.rbac.can(actor, operation.requiredPermission)) {
1117
+ throw new MaestroError(
1118
+ "PERMISSION_DENIED" /* PERMISSION_DENIED */,
1119
+ `Permission denied: ${operation.requiredPermission}`
1120
+ );
1121
+ }
1122
+ const correlationId = context.correlationId ?? randomUUID3();
1123
+ const result = await operation.execute({ ...context, actor, correlationId });
1124
+ const entityMeta = context.entityId ? this.metadata.entities.find((e) => e.id === context.entityId) : void 0;
1125
+ const recordId = context.record && entityMeta ? String(context.record[entityMeta.primaryKey] ?? "unknown") : "*";
1126
+ await this.recordAudit(
1127
+ `operation.${operationId}`,
1128
+ actor,
1129
+ context.entityId ? { type: context.entityId, id: recordId } : void 0,
1130
+ result.success ? "info" : "error",
1131
+ { success: result.success },
1132
+ void 0,
1133
+ void 0,
1134
+ correlationId
1135
+ );
1136
+ return result;
1137
+ }
1138
+ requireEntity(entityId) {
1139
+ const entity = this.metadata.entities.find((e) => e.id === entityId);
1140
+ if (!entity) {
1141
+ throw new MaestroError("NOT_FOUND" /* NOT_FOUND */, `Entity '${entityId}' not found.`);
1142
+ }
1143
+ return entity;
1144
+ }
1145
+ requireCapability(entity, capability, actor, permission) {
1146
+ if (!entity.capabilities[capability]) {
1147
+ throw new MaestroError(
1148
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1149
+ `Capability '${String(capability)}' is not enabled for entity '${entity.id}'.`
1150
+ );
1151
+ }
1152
+ if (!this.rbac.can(actor, permission)) {
1153
+ throw new MaestroError("PERMISSION_DENIED" /* PERMISSION_DENIED */, `Permission denied: ${permission}`);
1154
+ }
1155
+ }
1156
+ async recordAudit(action, actor, resource, level = "info", metadata, before, after, correlationId) {
1157
+ await this.audit?.record({
1158
+ action,
1159
+ actor,
1160
+ resource,
1161
+ level,
1162
+ metadata,
1163
+ before,
1164
+ after,
1165
+ correlationId
1166
+ });
1167
+ }
1168
+ };
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
+
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
+ });
1085
1753
  }
1086
- const correlationId = context.correlationId ?? randomUUID3();
1087
- const result = await operation.execute({ ...context, actor, correlationId });
1088
- const entityMeta = context.entityId ? this.metadata.entities.find((e) => e.id === context.entityId) : void 0;
1089
- const recordId = context.record && entityMeta ? String(context.record[entityMeta.primaryKey] ?? "unknown") : "*";
1090
- await this.recordAudit(
1091
- `operation.${operationId}`,
1092
- actor,
1093
- context.entityId ? { type: context.entityId, id: recordId } : void 0,
1094
- result.success ? "info" : "error",
1095
- { success: result.success },
1096
- void 0,
1097
- void 0,
1098
- correlationId
1099
- );
1100
- return result;
1101
1754
  }
1102
- requireEntity(entityId) {
1103
- const entity = this.metadata.entities.find((e) => e.id === entityId);
1104
- if (!entity) {
1105
- throw new MaestroError("NOT_FOUND" /* NOT_FOUND */, `Entity '${entityId}' not found.`);
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;
1106
1767
  }
1107
- return entity;
1108
- }
1109
- requireCapability(entity, capability, actor, permission) {
1110
- if (!entity.capabilities[capability]) {
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
+ }
1806
+ function createMaestro(config) {
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("; ");
1111
1820
  throw new MaestroError(
1112
1821
  "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1113
- `Capability '${String(capability)}' is not enabled for entity '${entity.id}'.`
1822
+ `Invalid declarative configuration: ${messages}`
1114
1823
  );
1115
1824
  }
1116
- if (!this.rbac.can(actor, permission)) {
1117
- throw new MaestroError("PERMISSION_DENIED" /* PERMISSION_DENIED */, `Permission denied: ${permission}`);
1118
- }
1119
- }
1120
- async recordAudit(action, actor, resource, level = "info", metadata, before, after, correlationId) {
1121
- await this.audit?.record({
1122
- action,
1123
- actor,
1124
- resource,
1125
- level,
1126
- metadata,
1127
- before,
1128
- after,
1129
- correlationId
1130
- });
1825
+ extraEntities = compilation.entities;
1826
+ declaredOperations = compilation.operations;
1827
+ compiledConsumers = compilation.consumers;
1828
+ compiledRelations = compilation.relations;
1131
1829
  }
1132
- };
1133
-
1134
- // src/engine/createMaestro.ts
1135
- function createMaestro(config) {
1136
- const validation = validateMaestroConfig(config);
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(config);
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 config.operations ?? []) {
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
@@ -2557,180 +3292,6 @@ var InMemoryConfirmationRepository = class {
2557
3292
  }
2558
3293
  };
2559
3294
 
2560
- // src/declarative/DeclarativeValidator.ts
2561
- var SUPPORTED_FIELD_TYPES = [
2562
- "string",
2563
- "text",
2564
- "number",
2565
- "integer",
2566
- "decimal",
2567
- "currency",
2568
- "boolean",
2569
- "date",
2570
- "datetime",
2571
- "time",
2572
- "email",
2573
- "phone",
2574
- "url",
2575
- "document",
2576
- "uuid",
2577
- "enum",
2578
- "json",
2579
- "relation",
2580
- "array"
2581
- ];
2582
- var SUPPORTED_OPERATIONAL_RISKS = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
2583
- var SUPPORTED_OPERATION_SCOPES = ["global", "entity", "record", "bulk"];
2584
- function validateEntityDeclaration(input) {
2585
- const errors = [];
2586
- if (!input || typeof input !== "object") {
2587
- return { valid: false, errors: [{ path: "", message: "Entity declaration must be an object." }] };
2588
- }
2589
- const decl = input;
2590
- if (!decl["entity"] || typeof decl["entity"] !== "string" || decl["entity"].trim() === "") {
2591
- errors.push({ path: "entity", message: "Entity name is required." });
2592
- }
2593
- const fields = decl["fields"];
2594
- if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
2595
- errors.push({ path: "fields", message: "Entity must declare at least one field." });
2596
- } else {
2597
- const fieldMap = fields;
2598
- if (Object.keys(fieldMap).length === 0) {
2599
- errors.push({ path: "fields", message: "Entity must declare at least one field." });
2600
- } else {
2601
- for (const [name, rawField] of Object.entries(fieldMap)) {
2602
- validateFieldDeclaration(name, rawField, errors);
2603
- }
2604
- }
2605
- }
2606
- const operations = decl["operations"];
2607
- if (operations !== void 0) {
2608
- if (typeof operations !== "object" || Array.isArray(operations)) {
2609
- errors.push({ path: "operations", message: "Operations must be an object." });
2610
- } else {
2611
- const opMap = operations;
2612
- for (const [id, rawOp] of Object.entries(opMap)) {
2613
- validateOperationDeclaration(id, rawOp, errors);
2614
- }
2615
- }
2616
- }
2617
- return { valid: errors.length === 0, errors };
2618
- }
2619
- function validateFieldDeclaration(name, rawField, errors) {
2620
- const prefix = `fields[${name}]`;
2621
- if (!rawField || typeof rawField !== "object") {
2622
- errors.push({ path: prefix, message: `Field '${name}' must be an object.` });
2623
- return;
2624
- }
2625
- const field = rawField;
2626
- if (!field.type) {
2627
- errors.push({ path: `${prefix}.type`, message: "Field type is required." });
2628
- } else if (!SUPPORTED_FIELD_TYPES.includes(field.type)) {
2629
- errors.push({
2630
- path: `${prefix}.type`,
2631
- message: `Field type '${field.type}' is not supported. Supported types: ${SUPPORTED_FIELD_TYPES.join(", ")}.`
2632
- });
2633
- }
2634
- if (field.sensitive === true && field.exportable === true) {
2635
- errors.push({
2636
- path: prefix,
2637
- message: `Field '${name}' cannot be both sensitive and exportable.`
2638
- });
2639
- }
2640
- }
2641
- function validateOperationDeclaration(id, rawOp, errors) {
2642
- const prefix = `operations[${id}]`;
2643
- if (!rawOp || typeof rawOp !== "object") {
2644
- errors.push({ path: prefix, message: `Operation '${id}' must be an object.` });
2645
- return;
2646
- }
2647
- const op = rawOp;
2648
- if (!op.label || typeof op.label !== "string" || op.label.trim() === "") {
2649
- errors.push({ path: `${prefix}.label`, message: "Operation label is required." });
2650
- }
2651
- if (op.risk !== void 0 && !SUPPORTED_OPERATIONAL_RISKS.includes(op.risk)) {
2652
- errors.push({
2653
- path: `${prefix}.risk`,
2654
- message: `Operation risk '${op.risk}' is not valid. Valid values: ${SUPPORTED_OPERATIONAL_RISKS.join(", ")}.`
2655
- });
2656
- }
2657
- if (op.scope !== void 0 && !SUPPORTED_OPERATION_SCOPES.includes(op.scope)) {
2658
- errors.push({
2659
- path: `${prefix}.scope`,
2660
- message: `Operation scope '${op.scope}' is not valid. Valid values: ${SUPPORTED_OPERATION_SCOPES.join(", ")}.`
2661
- });
2662
- }
2663
- if (op.confirmation !== void 0) {
2664
- const conf = op.confirmation;
2665
- if (conf.approvers !== void 0 && (typeof conf.approvers !== "number" || conf.approvers < 1)) {
2666
- errors.push({
2667
- path: `${prefix}.confirmation.approvers`,
2668
- message: "Confirmation approvers must be at least 1."
2669
- });
2670
- }
2671
- }
2672
- }
2673
- function validateConsumerDeclaration(input, entity) {
2674
- const errors = [];
2675
- if (!input || typeof input !== "object") {
2676
- return { valid: false, errors: [{ path: "", message: "Consumer declaration must be an object." }] };
2677
- }
2678
- const decl = input;
2679
- if (!decl["consumer"] || typeof decl["consumer"] !== "string" || decl["consumer"].trim() === "") {
2680
- errors.push({ path: "consumer", message: "Consumer name is required." });
2681
- }
2682
- if (!decl["entity"] || typeof decl["entity"] !== "string" || decl["entity"].trim() === "") {
2683
- errors.push({ path: "entity", message: "Entity name is required." });
2684
- }
2685
- if (entity) {
2686
- const knownFields = new Set(Object.keys(entity.fields));
2687
- const knownOps = new Set(Object.keys(entity.operations ?? {}));
2688
- const entityName = entity.entity;
2689
- validateFieldRefs("list.fields", decl["list"], knownFields, entityName, errors);
2690
- validateFieldRefs("detail.fields", decl["detail"], knownFields, entityName, errors);
2691
- const forms = decl["forms"];
2692
- if (forms && typeof forms === "object") {
2693
- validateFieldRefs("forms.create.fields", forms["create"], knownFields, entityName, errors);
2694
- validateFieldRefs("forms.update.fields", forms["update"], knownFields, entityName, errors);
2695
- validateFieldRefs("forms.clone.fields", forms["clone"], knownFields, entityName, errors);
2696
- }
2697
- const actions = decl["actions"];
2698
- if (actions && typeof actions === "object") {
2699
- validateOperationRefs("actions.row", actions["row"], knownOps, entityName, errors);
2700
- validateOperationRefs("actions.bulk", actions["bulk"], knownOps, entityName, errors);
2701
- validateOperationRefs("actions.global", actions["global"], knownOps, entityName, errors);
2702
- }
2703
- }
2704
- return { valid: errors.length === 0, errors };
2705
- }
2706
- function validateFieldRefs(path, container, knownFields, entityName, errors) {
2707
- if (!container || typeof container !== "object") return;
2708
- const obj = container;
2709
- const fields = obj["fields"];
2710
- if (!Array.isArray(fields)) return;
2711
- for (let i = 0; i < fields.length; i++) {
2712
- const name = fields[i];
2713
- if (!knownFields.has(name)) {
2714
- errors.push({
2715
- path: `${path}[${i}]`,
2716
- message: `Field '${name}' does not exist in entity '${entityName}'.`
2717
- });
2718
- }
2719
- }
2720
- }
2721
- function validateOperationRefs(path, refs, knownOps, entityName, errors) {
2722
- if (!Array.isArray(refs)) return;
2723
- for (let i = 0; i < refs.length; i++) {
2724
- const id = refs[i];
2725
- if (!knownOps.has(id)) {
2726
- errors.push({
2727
- path: `${path}[${i}]`,
2728
- message: `Operation '${id}' does not exist in entity '${entityName}'.`
2729
- });
2730
- }
2731
- }
2732
- }
2733
-
2734
3295
  // src/governance/GovernanceEventType.ts
2735
3296
  var GOVERNANCE_EVENT_TYPES = {
2736
3297
  OPERATION_EXECUTED: "governance.operation.executed",
@@ -2827,6 +3388,7 @@ export {
2827
3388
  PolicyEngine,
2828
3389
  RbacEngine,
2829
3390
  ReportGenerator,
3391
+ compileDeclarations,
2830
3392
  createMaestro,
2831
3393
  createMaestroFromIntrospection,
2832
3394
  createMaestroHttpHandlers,
@@ -2843,6 +3405,7 @@ export {
2843
3405
  loadMaestroConfig,
2844
3406
  mergeIntrospectionWithOverrides,
2845
3407
  parseQueryInput,
3408
+ resolveConsumerProjections,
2846
3409
  tableNameToEntityId,
2847
3410
  tableNameToLabel,
2848
3411
  validateConsumerDeclaration,