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

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`);
@@ -1131,25 +1167,451 @@ 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
+ function validateEntityDeclaration(input) {
1195
+ const errors = [];
1196
+ if (!input || typeof input !== "object") {
1197
+ return { valid: false, errors: [{ path: "", message: "Entity declaration must be an object." }] };
1198
+ }
1199
+ const decl = input;
1200
+ if (!decl["entity"] || typeof decl["entity"] !== "string" || decl["entity"].trim() === "") {
1201
+ errors.push({ path: "entity", message: "Entity name is required." });
1202
+ }
1203
+ const fields = decl["fields"];
1204
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) {
1205
+ errors.push({ path: "fields", message: "Entity must declare at least one field." });
1206
+ } else {
1207
+ const fieldMap = fields;
1208
+ if (Object.keys(fieldMap).length === 0) {
1209
+ errors.push({ path: "fields", message: "Entity must declare at least one field." });
1210
+ } else {
1211
+ for (const [name, rawField] of Object.entries(fieldMap)) {
1212
+ validateFieldDeclaration(name, rawField, errors);
1213
+ }
1214
+ }
1215
+ }
1216
+ const operations = decl["operations"];
1217
+ if (operations !== void 0) {
1218
+ if (typeof operations !== "object" || Array.isArray(operations)) {
1219
+ errors.push({ path: "operations", message: "Operations must be an object." });
1220
+ } else {
1221
+ const opMap = operations;
1222
+ for (const [id, rawOp] of Object.entries(opMap)) {
1223
+ validateOperationDeclaration(id, rawOp, errors);
1224
+ }
1225
+ }
1226
+ }
1227
+ return { valid: errors.length === 0, errors };
1228
+ }
1229
+ function validateFieldDeclaration(name, rawField, errors) {
1230
+ const prefix = `fields[${name}]`;
1231
+ if (!rawField || typeof rawField !== "object") {
1232
+ errors.push({ path: prefix, message: `Field '${name}' must be an object.` });
1233
+ return;
1234
+ }
1235
+ const field = rawField;
1236
+ if (!field.type) {
1237
+ errors.push({ path: `${prefix}.type`, message: "Field type is required." });
1238
+ } else if (!SUPPORTED_FIELD_TYPES.includes(field.type)) {
1239
+ errors.push({
1240
+ path: `${prefix}.type`,
1241
+ message: `Field type '${field.type}' is not supported. Supported types: ${SUPPORTED_FIELD_TYPES.join(", ")}.`
1242
+ });
1243
+ }
1244
+ if (field.sensitive === true && field.exportable === true) {
1245
+ errors.push({
1246
+ path: prefix,
1247
+ message: `Field '${name}' cannot be both sensitive and exportable.`
1248
+ });
1249
+ }
1250
+ }
1251
+ function validateOperationDeclaration(id, rawOp, errors) {
1252
+ const prefix = `operations[${id}]`;
1253
+ if (!rawOp || typeof rawOp !== "object") {
1254
+ errors.push({ path: prefix, message: `Operation '${id}' must be an object.` });
1255
+ return;
1256
+ }
1257
+ const op = rawOp;
1258
+ if (!op.label || typeof op.label !== "string" || op.label.trim() === "") {
1259
+ errors.push({ path: `${prefix}.label`, message: "Operation label is required." });
1260
+ }
1261
+ if (op.risk !== void 0 && !SUPPORTED_OPERATIONAL_RISKS.includes(op.risk)) {
1262
+ errors.push({
1263
+ path: `${prefix}.risk`,
1264
+ message: `Operation risk '${op.risk}' is not valid. Valid values: ${SUPPORTED_OPERATIONAL_RISKS.join(", ")}.`
1265
+ });
1266
+ }
1267
+ if (op.scope !== void 0 && !SUPPORTED_OPERATION_SCOPES.includes(op.scope)) {
1268
+ errors.push({
1269
+ path: `${prefix}.scope`,
1270
+ message: `Operation scope '${op.scope}' is not valid. Valid values: ${SUPPORTED_OPERATION_SCOPES.join(", ")}.`
1271
+ });
1272
+ }
1273
+ if (op.confirmation !== void 0) {
1274
+ const conf = op.confirmation;
1275
+ if (conf.approvers !== void 0 && (typeof conf.approvers !== "number" || conf.approvers < 1)) {
1276
+ errors.push({
1277
+ path: `${prefix}.confirmation.approvers`,
1278
+ message: "Confirmation approvers must be at least 1."
1279
+ });
1280
+ }
1281
+ }
1282
+ }
1283
+ function validateConsumerDeclaration(input, entity) {
1284
+ const errors = [];
1285
+ if (!input || typeof input !== "object") {
1286
+ return { valid: false, errors: [{ path: "", message: "Consumer declaration must be an object." }] };
1287
+ }
1288
+ const decl = input;
1289
+ if (!decl["consumer"] || typeof decl["consumer"] !== "string" || decl["consumer"].trim() === "") {
1290
+ errors.push({ path: "consumer", message: "Consumer name is required." });
1291
+ }
1292
+ if (!decl["entity"] || typeof decl["entity"] !== "string" || decl["entity"].trim() === "") {
1293
+ errors.push({ path: "entity", message: "Entity name is required." });
1294
+ }
1295
+ if (entity) {
1296
+ const knownFields = new Set(Object.keys(entity.fields));
1297
+ const knownOps = new Set(Object.keys(entity.operations ?? {}));
1298
+ const entityName = entity.entity;
1299
+ validateFieldRefs("list.fields", decl["list"], knownFields, entityName, errors);
1300
+ validateFieldRefs("detail.fields", decl["detail"], knownFields, entityName, errors);
1301
+ const forms = decl["forms"];
1302
+ if (forms && typeof forms === "object") {
1303
+ validateFieldRefs("forms.create.fields", forms["create"], knownFields, entityName, errors);
1304
+ validateFieldRefs("forms.update.fields", forms["update"], knownFields, entityName, errors);
1305
+ validateFieldRefs("forms.clone.fields", forms["clone"], knownFields, entityName, errors);
1306
+ }
1307
+ const actions = decl["actions"];
1308
+ if (actions && typeof actions === "object") {
1309
+ validateOperationRefs("actions.row", actions["row"], knownOps, entityName, errors);
1310
+ validateOperationRefs("actions.bulk", actions["bulk"], knownOps, entityName, errors);
1311
+ validateOperationRefs("actions.global", actions["global"], knownOps, entityName, errors);
1312
+ }
1313
+ }
1314
+ return { valid: errors.length === 0, errors };
1315
+ }
1316
+ function validateFieldRefs(path, container, knownFields, entityName, errors) {
1317
+ if (!container || typeof container !== "object") return;
1318
+ const obj = container;
1319
+ const fields = obj["fields"];
1320
+ if (!Array.isArray(fields)) return;
1321
+ for (let i = 0; i < fields.length; i++) {
1322
+ const name = fields[i];
1323
+ if (!knownFields.has(name)) {
1324
+ errors.push({
1325
+ path: `${path}[${i}]`,
1326
+ message: `Field '${name}' does not exist in entity '${entityName}'.`
1327
+ });
1328
+ }
1329
+ }
1330
+ }
1331
+ function validateOperationRefs(path, refs, knownOps, entityName, errors) {
1332
+ if (!Array.isArray(refs)) return;
1333
+ for (let i = 0; i < refs.length; i++) {
1334
+ const id = refs[i];
1335
+ if (!knownOps.has(id)) {
1336
+ errors.push({
1337
+ path: `${path}[${i}]`,
1338
+ message: `Operation '${id}' does not exist in entity '${entityName}'.`
1339
+ });
1340
+ }
1341
+ }
1342
+ }
1343
+
1344
+ // src/declarative/DeclarativeMetadataCompiler.ts
1345
+ function capitalizeFirst(name) {
1346
+ if (!name) return name;
1347
+ return name.charAt(0).toUpperCase() + name.slice(1);
1348
+ }
1349
+ function fieldDeclToFieldSchema(name, field) {
1350
+ return {
1351
+ name,
1352
+ label: field.label ?? capitalizeFirst(name),
1353
+ type: field.type,
1354
+ required: field.required,
1355
+ readonly: field.readonly,
1356
+ // FieldDeclaration uses visible (default true); FieldSchema uses hidden (default false)
1357
+ hidden: field.visible === false ? true : void 0,
1358
+ sensitive: field.sensitive,
1359
+ searchable: field.searchable,
1360
+ sortable: field.sortable,
1361
+ filterable: field.filterable,
1362
+ exportable: field.exportable,
1363
+ description: field.description
1364
+ };
1365
+ }
1366
+ function opDeclToStubOperationDef(entityId, opKey, op) {
1367
+ const id = `${entityId}.${opKey}`;
1368
+ return {
1369
+ id,
1370
+ label: op.label,
1371
+ description: op.description,
1372
+ entity: entityId,
1373
+ scope: op.scope ?? "record",
1374
+ requiredPermission: op.permission,
1375
+ requiresConfirmation: op.confirmation?.required,
1376
+ execute: async () => {
1377
+ throw new MaestroError(
1378
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1379
+ `Operation '${id}' declared but has no registered implementation. Provide an OperationDef with id '${id}' in config.operations.`
1380
+ );
1381
+ }
1382
+ };
1383
+ }
1384
+ function entityDeclToEntitySchema(decl, datasource) {
1385
+ const primaryKeyEntry = Object.entries(decl.fields).find(([, f]) => f.primary);
1386
+ const primaryKey = primaryKeyEntry ? primaryKeyEntry[0] : "id";
1387
+ const singularLabel = decl.label ?? capitalizeFirst(decl.entity);
1388
+ const pluralLabel = decl.pluralLabel ?? decl.label ?? capitalizeFirst(decl.entity);
1389
+ const fields = Object.entries(decl.fields).map(
1390
+ ([name, field]) => fieldDeclToFieldSchema(name, field)
1391
+ );
1392
+ return {
1393
+ id: decl.entity,
1394
+ label: { singular: singularLabel, plural: pluralLabel },
1395
+ description: decl.description,
1396
+ source: {
1397
+ datasource,
1398
+ table: decl.entity,
1399
+ primaryKey
1400
+ },
1401
+ fields
1402
+ };
1403
+ }
1404
+ function compileDeclarations(config, options) {
1405
+ const errors = [];
1406
+ const datasource = options.defaultDatasource ?? (options.datasourceIds.length === 1 ? options.datasourceIds[0] : void 0);
1407
+ if (options.datasourceIds.length > 1 && !datasource) {
1408
+ errors.push({
1409
+ path: "declarations.defaultDatasource",
1410
+ message: "Multiple datasources are configured. Set declarations.defaultDatasource to specify which one to use for declared entities."
1411
+ });
1412
+ }
1413
+ const entityDeclarationMap = /* @__PURE__ */ new Map();
1414
+ const compiledEntities = [];
1415
+ const compiledOperations = [];
1416
+ for (const decl of config.entities) {
1417
+ const entityId = typeof decl?.entity === "string" ? decl.entity : "?";
1418
+ const prefix = `declarations.entities[${entityId}]`;
1419
+ const validation = validateEntityDeclaration(decl);
1420
+ if (!validation.valid) {
1421
+ for (const err of validation.errors) {
1422
+ errors.push({ path: `${prefix}.${err.path}`.replace(/\.$/, ""), message: err.message });
1423
+ }
1424
+ continue;
1425
+ }
1426
+ if (entityDeclarationMap.has(decl.entity)) {
1427
+ errors.push({
1428
+ path: prefix,
1429
+ message: `Duplicate entity '${decl.entity}' in declarations.entities.`
1430
+ });
1431
+ continue;
1432
+ }
1433
+ entityDeclarationMap.set(decl.entity, decl);
1434
+ if (datasource) {
1435
+ compiledEntities.push(entityDeclToEntitySchema(decl, datasource));
1436
+ for (const [opKey, opDecl] of Object.entries(decl.operations ?? {})) {
1437
+ compiledOperations.push(opDeclToStubOperationDef(decl.entity, opKey, opDecl));
1438
+ }
1439
+ }
1440
+ }
1441
+ const consumerNames = /* @__PURE__ */ new Set();
1442
+ const validConsumers = [];
1443
+ for (const consumer of config.consumers ?? []) {
1444
+ const consumerId = typeof consumer?.consumer === "string" ? consumer.consumer : "?";
1445
+ const prefix = `declarations.consumers[${consumerId}]`;
1446
+ const entityDecl = entityDeclarationMap.get(consumer?.entity ?? "");
1447
+ if (consumer?.entity && !entityDecl) {
1448
+ errors.push({
1449
+ path: `${prefix}.entity`,
1450
+ message: `Entity '${consumer.entity}' referenced by consumer '${consumerId}' is not declared in declarations.entities.`
1451
+ });
1452
+ continue;
1453
+ }
1454
+ const validation = validateConsumerDeclaration(consumer, entityDecl);
1455
+ if (!validation.valid) {
1456
+ for (const err of validation.errors) {
1457
+ errors.push({ path: `${prefix}.${err.path}`.replace(/\.$/, ""), message: err.message });
1458
+ }
1459
+ continue;
1460
+ }
1461
+ if (consumerNames.has(consumer.consumer)) {
1462
+ errors.push({
1463
+ path: prefix,
1464
+ message: `Duplicate consumer '${consumer.consumer}' in declarations.consumers.`
1465
+ });
1466
+ continue;
1467
+ }
1468
+ consumerNames.add(consumer.consumer);
1469
+ validConsumers.push(consumer);
1470
+ }
1471
+ return {
1472
+ valid: errors.length === 0,
1473
+ errors,
1474
+ entities: compiledEntities,
1475
+ operations: compiledOperations,
1476
+ consumers: validConsumers
1477
+ };
1478
+ }
1479
+ function resolveConsumerProjections(consumers, metadata) {
1480
+ const results = [];
1481
+ for (const consumer of consumers) {
1482
+ const entityMeta = metadata.entities.find((e) => e.id === consumer.entity);
1483
+ if (!entityMeta) continue;
1484
+ const allFields = entityMeta.fields;
1485
+ const entityOps = metadata.operations.filter((o) => o.entity === consumer.entity);
1486
+ const resolveFields = (names) => {
1487
+ if (!names || names.length === 0) return [...allFields];
1488
+ return names.map((name) => allFields.find((f) => f.name === name)).filter((f) => f !== void 0);
1489
+ };
1490
+ const resolveOps = (keys, defaultScope) => {
1491
+ if (!keys || keys.length === 0) {
1492
+ return defaultScope ? entityOps.filter((o) => o.scope === defaultScope) : [...entityOps];
1493
+ }
1494
+ return keys.map((key) => {
1495
+ const fullId = `${consumer.entity}.${key}`;
1496
+ return entityOps.find((o) => o.id === fullId);
1497
+ }).filter((o) => o !== void 0);
1498
+ };
1499
+ results.push({
1500
+ consumer: consumer.consumer,
1501
+ entity: consumer.entity,
1502
+ fields: {
1503
+ list: resolveFields(consumer.list?.fields),
1504
+ detail: resolveFields(consumer.detail?.fields),
1505
+ create: resolveFields(consumer.forms?.create?.fields),
1506
+ update: resolveFields(consumer.forms?.update?.fields),
1507
+ clone: resolveFields(consumer.forms?.clone?.fields)
1508
+ },
1509
+ operations: {
1510
+ row: resolveOps(consumer.actions?.row, "record"),
1511
+ bulk: resolveOps(consumer.actions?.bulk, "bulk"),
1512
+ global: resolveOps(consumer.actions?.global, "global")
1513
+ }
1514
+ });
1515
+ }
1516
+ return results;
1517
+ }
1518
+
1134
1519
  // src/engine/createMaestro.ts
1520
+ function bindAndValidateOperations(stubs, configOps) {
1521
+ const errors = [];
1522
+ const configOpMap = /* @__PURE__ */ new Map();
1523
+ for (const op of configOps) {
1524
+ if (configOpMap.has(op.id)) {
1525
+ errors.push({
1526
+ path: `operations[${op.id}]`,
1527
+ message: `Duplicate operation id '${op.id}' in config.operations. Each operation must have a unique id.`
1528
+ });
1529
+ } else {
1530
+ configOpMap.set(op.id, op);
1531
+ }
1532
+ }
1533
+ const stubIds = new Set(stubs.map((s) => s.id));
1534
+ const bound = [];
1535
+ for (const stub of stubs) {
1536
+ const impl = configOpMap.get(stub.id);
1537
+ if (!impl) {
1538
+ errors.push({
1539
+ path: `declarations.entities[${stub.entity}].operations`,
1540
+ message: `Declared operation '${stub.id}' has no implementation. Provide an OperationDef with id '${stub.id}' in config.operations.`
1541
+ });
1542
+ continue;
1543
+ }
1544
+ if (impl.entity !== void 0 && impl.entity !== stub.entity) {
1545
+ errors.push({
1546
+ path: `operations[${impl.id}].entity`,
1547
+ message: `Operation '${impl.id}' is declared for entity '${stub.entity}' but config.operations specifies entity '${impl.entity}'.`
1548
+ });
1549
+ continue;
1550
+ }
1551
+ bound.push(impl);
1552
+ }
1553
+ for (const op of configOpMap.values()) {
1554
+ if (!stubIds.has(op.id)) {
1555
+ bound.push(op);
1556
+ }
1557
+ }
1558
+ return { operations: bound, errors };
1559
+ }
1135
1560
  function createMaestro(config) {
1136
- const validation = validateMaestroConfig(config);
1561
+ let extraEntities = [];
1562
+ let declaredOperations = [];
1563
+ let compiledConsumers = [];
1564
+ if (config.declarations) {
1565
+ const datasourceIds = Object.keys(config.datasources ?? {});
1566
+ const defaultDatasource = config.declarations.defaultDatasource ?? (datasourceIds.length === 1 ? datasourceIds[0] : void 0);
1567
+ const compilation = compileDeclarations(config.declarations, {
1568
+ datasourceIds,
1569
+ defaultDatasource
1570
+ });
1571
+ if (!compilation.valid) {
1572
+ const messages = compilation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
1573
+ throw new MaestroError(
1574
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1575
+ `Invalid declarative configuration: ${messages}`
1576
+ );
1577
+ }
1578
+ extraEntities = compilation.entities;
1579
+ declaredOperations = compilation.operations;
1580
+ compiledConsumers = compilation.consumers;
1581
+ }
1582
+ const mergedEntities = [
1583
+ ...config.entities ?? [],
1584
+ ...extraEntities
1585
+ ];
1586
+ const binding = bindAndValidateOperations(declaredOperations, config.operations ?? []);
1587
+ if (binding.errors.length > 0) {
1588
+ const messages = binding.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
1589
+ throw new MaestroError("CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */, `Invalid operation bindings: ${messages}`);
1590
+ }
1591
+ const mergedOperations = binding.operations;
1592
+ const mergedConfig = {
1593
+ ...config,
1594
+ entities: mergedEntities,
1595
+ operations: mergedOperations
1596
+ };
1597
+ const validation = validateMaestroConfig(mergedConfig);
1137
1598
  if (!validation.valid) {
1138
1599
  const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
1139
1600
  throw new MaestroError("CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */, `Invalid Maestro configuration: ${messages}`);
1140
1601
  }
1141
1602
  const metadataEngine = new MetadataEngine();
1142
- const metadata = metadataEngine.normalize(config);
1603
+ const metadata = metadataEngine.normalize(mergedConfig);
1604
+ const consumers = resolveConsumerProjections(compiledConsumers, metadata);
1143
1605
  const datasources = new DatasourceRegistry();
1144
1606
  for (const [id, provider] of Object.entries(config.datasources)) {
1145
1607
  datasources.register(id, provider);
1146
1608
  }
1147
1609
  const operations = new OperationRegistry();
1148
- for (const operation of config.operations ?? []) {
1610
+ for (const operation of mergedOperations) {
1149
1611
  operations.register(operation);
1150
1612
  }
1151
1613
  const audit = config.audit ? new AuditRecorder(config.audit) : void 0;
1152
- return new MaestroEngine(metadata, datasources, operations, audit);
1614
+ return new MaestroEngine(metadata, datasources, operations, audit, consumers);
1153
1615
  }
1154
1616
 
1155
1617
  // src/introspection/utils.ts
@@ -2557,180 +3019,6 @@ var InMemoryConfirmationRepository = class {
2557
3019
  }
2558
3020
  };
2559
3021
 
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
3022
  // src/governance/GovernanceEventType.ts
2735
3023
  var GOVERNANCE_EVENT_TYPES = {
2736
3024
  OPERATION_EXECUTED: "governance.operation.executed",
@@ -2827,6 +3115,7 @@ export {
2827
3115
  PolicyEngine,
2828
3116
  RbacEngine,
2829
3117
  ReportGenerator,
3118
+ compileDeclarations,
2830
3119
  createMaestro,
2831
3120
  createMaestroFromIntrospection,
2832
3121
  createMaestroHttpHandlers,
@@ -2843,6 +3132,7 @@ export {
2843
3132
  loadMaestroConfig,
2844
3133
  mergeIntrospectionWithOverrides,
2845
3134
  parseQueryInput,
3135
+ resolveConsumerProjections,
2846
3136
  tableNameToEntityId,
2847
3137
  tableNameToLabel,
2848
3138
  validateConsumerDeclaration,