@maykonpaulo/maestro-core 0.2.1 → 0.3.0-next.1

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
@@ -70,6 +70,52 @@ var ConsoleLogger = class {
70
70
  }
71
71
  };
72
72
 
73
+ // src/logging/ConsoleStructuredLogger.ts
74
+ var ConsoleStructuredLogger = class {
75
+ constructor(minLevel = "info") {
76
+ this.minLevel = minLevel;
77
+ }
78
+ minLevel;
79
+ debug(entry) {
80
+ this.log("debug", entry);
81
+ }
82
+ info(entry) {
83
+ this.log("info", entry);
84
+ }
85
+ warn(entry) {
86
+ this.log("warn", entry);
87
+ }
88
+ error(entry) {
89
+ this.log("error", entry);
90
+ }
91
+ log(level, entry) {
92
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.minLevel]) return;
93
+ const output = {
94
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
95
+ level
96
+ };
97
+ if (entry.correlationId !== void 0) output["correlationId"] = entry.correlationId;
98
+ if (entry.operation !== void 0) output["operation"] = entry.operation;
99
+ if (entry.entity !== void 0) output["entity"] = entry.entity;
100
+ if (entry.entityId !== void 0) output["entityId"] = entry.entityId;
101
+ if (entry.actor !== void 0) output["actor"] = entry.actor;
102
+ if (entry.durationMs !== void 0) output["durationMs"] = entry.durationMs;
103
+ if (entry.status !== void 0) output["status"] = entry.status;
104
+ if (entry.severity !== void 0) output["severity"] = entry.severity;
105
+ if (entry.message !== void 0) output["message"] = entry.message;
106
+ if (entry.metadata !== void 0) output["metadata"] = entry.metadata;
107
+ if (entry.error !== void 0) output["error"] = this.serializeError(entry.error);
108
+ const consoleFn = level === "error" ? console.error : level === "warn" ? console.warn : level === "debug" ? console.debug : console.info;
109
+ consoleFn(JSON.stringify(output));
110
+ }
111
+ serializeError(error) {
112
+ if (error instanceof Error) {
113
+ return { name: error.name, message: error.message };
114
+ }
115
+ return error;
116
+ }
117
+ };
118
+
73
119
  // src/audit/InMemoryAuditRepository.ts
74
120
  var InMemoryAuditRepository = class {
75
121
  store = /* @__PURE__ */ new Map();
@@ -85,6 +131,12 @@ var InMemoryAuditRepository = class {
85
131
  if (filter.action !== void 0) {
86
132
  events = events.filter((e) => e.action === filter.action);
87
133
  }
134
+ if (filter.entity !== void 0) {
135
+ events = events.filter((e) => e.entity === filter.entity);
136
+ }
137
+ if (filter.entityId !== void 0) {
138
+ events = events.filter((e) => e.entityId === filter.entityId);
139
+ }
88
140
  if (filter.resourceType !== void 0) {
89
141
  events = events.filter((e) => e.resource?.type === filter.resourceType);
90
142
  }
@@ -107,6 +159,9 @@ var InMemoryAuditRepository = class {
107
159
  }
108
160
  return events;
109
161
  }
162
+ async query(filter) {
163
+ return this.list(filter);
164
+ }
110
165
  };
111
166
 
112
167
  // src/audit/AuditRecorder.ts
@@ -124,6 +179,8 @@ var AuditRecorder = class {
124
179
  actor: input.actor,
125
180
  level: input.level ?? "info",
126
181
  resource: input.resource,
182
+ entity: input.entity,
183
+ entityId: input.entityId,
127
184
  before: input.before,
128
185
  after: input.after,
129
186
  metadata: input.metadata,
@@ -232,15 +289,2388 @@ var InMemoryEventBus = class {
232
289
  this.handlers.get(eventType)?.delete(handler);
233
290
  }
234
291
  };
292
+
293
+ // src/metadata/EntityCapabilities.ts
294
+ var DEFAULT_CAPABILITIES = {
295
+ list: true,
296
+ detail: true,
297
+ create: false,
298
+ update: false,
299
+ clone: false,
300
+ delete: false,
301
+ softDelete: false,
302
+ export: false,
303
+ bulkActions: false
304
+ };
305
+
306
+ // src/datasource/DatasourceRegistry.ts
307
+ var DatasourceRegistry = class {
308
+ registry = /* @__PURE__ */ new Map();
309
+ register(id, provider) {
310
+ this.registry.set(id, provider);
311
+ }
312
+ get(id) {
313
+ const provider = this.registry.get(id);
314
+ if (!provider) {
315
+ throw new MaestroError(
316
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
317
+ `Datasource '${id}' not found. Registered datasources: [${this.ids().join(", ")}]`
318
+ );
319
+ }
320
+ return provider;
321
+ }
322
+ has(id) {
323
+ return this.registry.has(id);
324
+ }
325
+ ids() {
326
+ return Array.from(this.registry.keys());
327
+ }
328
+ };
329
+
330
+ // src/datasource/InMemoryDatasourceProvider.ts
331
+ import { randomUUID as randomUUID2 } from "crypto";
332
+ var InMemoryDatasourceProvider = class {
333
+ tables = /* @__PURE__ */ new Map();
334
+ seed(table, records, primaryKey = "id") {
335
+ if (!this.tables.has(table)) {
336
+ this.tables.set(table, /* @__PURE__ */ new Map());
337
+ }
338
+ const store = this.tables.get(table);
339
+ for (const record of records) {
340
+ const id = String(record[primaryKey] ?? randomUUID2());
341
+ store.set(id, { ...record, [primaryKey]: id });
342
+ }
343
+ }
344
+ async list(context) {
345
+ const store = this.getStore(context.table);
346
+ let records = Array.from(store.values());
347
+ if (context.search?.term) {
348
+ const term = context.search.term.toLowerCase();
349
+ records = records.filter(
350
+ (r) => context.search.fields.some(
351
+ (field) => String(r[field] ?? "").toLowerCase().includes(term)
352
+ )
353
+ );
354
+ }
355
+ if (context.filters?.length) {
356
+ records = this.applyFilters(records, context.filters);
357
+ }
358
+ if (context.sort?.length) {
359
+ records = this.applySort(records, context.sort);
360
+ }
361
+ const total = records.length;
362
+ if (context.pagination) {
363
+ const p = context.pagination;
364
+ if (p.strategy === "page") {
365
+ const start = (p.page - 1) * p.pageSize;
366
+ records = records.slice(start, start + p.pageSize);
367
+ return { records, total, page: p.page, pageSize: p.pageSize, totalPages: Math.ceil(total / p.pageSize) };
368
+ }
369
+ if (p.strategy === "offset") {
370
+ records = records.slice(p.offset, p.offset + p.limit);
371
+ }
372
+ if (p.strategy === "cursor") {
373
+ const from = p.cursor ? parseInt(p.cursor, 10) : 0;
374
+ const sliced = records.slice(from, from + p.limit);
375
+ const nextIndex = from + p.limit;
376
+ return { records: sliced, total, nextCursor: nextIndex < total ? String(nextIndex) : void 0 };
377
+ }
378
+ }
379
+ return { records, total };
380
+ }
381
+ async findById(context) {
382
+ return this.getStore(context.table).get(context.id) ?? null;
383
+ }
384
+ async create(context) {
385
+ const store = this.getStore(context.table);
386
+ const id = String(context.data[context.primaryKey] ?? randomUUID2());
387
+ const record = { ...context.data, [context.primaryKey]: id };
388
+ store.set(id, record);
389
+ return record;
390
+ }
391
+ async update(context) {
392
+ const store = this.getStore(context.table);
393
+ const existing = store.get(context.id);
394
+ if (!existing) {
395
+ throw new MaestroError(
396
+ "NOT_FOUND" /* NOT_FOUND */,
397
+ `Record '${context.id}' not found in table '${context.table}'.`
398
+ );
399
+ }
400
+ const updated = { ...existing, ...context.data, [context.primaryKey]: context.id };
401
+ store.set(context.id, updated);
402
+ return updated;
403
+ }
404
+ async delete(context) {
405
+ const store = this.getStore(context.table);
406
+ if (!store.has(context.id)) {
407
+ throw new MaestroError(
408
+ "NOT_FOUND" /* NOT_FOUND */,
409
+ `Record '${context.id}' not found in table '${context.table}'.`
410
+ );
411
+ }
412
+ store.delete(context.id);
413
+ }
414
+ async count(context) {
415
+ const result = await this.list({ ...context, pagination: void 0 });
416
+ return result.total;
417
+ }
418
+ getStore(table) {
419
+ if (!this.tables.has(table)) {
420
+ this.tables.set(table, /* @__PURE__ */ new Map());
421
+ }
422
+ return this.tables.get(table);
423
+ }
424
+ applyFilters(records, filters) {
425
+ return records.filter((record) => filters.every((f) => this.matchFilter(record, f)));
426
+ }
427
+ matchFilter(record, filter) {
428
+ const val = record[filter.field];
429
+ switch (filter.operator) {
430
+ case "equals":
431
+ return val === filter.value;
432
+ case "notEquals":
433
+ return val !== filter.value;
434
+ case "contains":
435
+ return String(val ?? "").toLowerCase().includes(String(filter.value ?? "").toLowerCase());
436
+ case "startsWith":
437
+ return String(val ?? "").startsWith(String(filter.value ?? ""));
438
+ case "endsWith":
439
+ return String(val ?? "").endsWith(String(filter.value ?? ""));
440
+ case "in":
441
+ return Array.isArray(filter.value) && filter.value.includes(val);
442
+ case "notIn":
443
+ return Array.isArray(filter.value) && !filter.value.includes(val);
444
+ case "gt":
445
+ return val > filter.value;
446
+ case "gte":
447
+ return val >= filter.value;
448
+ case "lt":
449
+ return val < filter.value;
450
+ case "lte":
451
+ return val <= filter.value;
452
+ case "between": {
453
+ const [min, max] = filter.value;
454
+ return val >= min && val <= max;
455
+ }
456
+ case "isNull":
457
+ return val === null || val === void 0;
458
+ case "isNotNull":
459
+ return val !== null && val !== void 0;
460
+ case "isTrue":
461
+ return val === true;
462
+ case "isFalse":
463
+ return val === false;
464
+ default:
465
+ return true;
466
+ }
467
+ }
468
+ applySort(records, sort) {
469
+ return [...records].sort((a, b) => {
470
+ for (const { field, direction } of sort) {
471
+ const aVal = a[field];
472
+ const bVal = b[field];
473
+ let cmp = 0;
474
+ if (aVal === null || aVal === void 0) cmp = 1;
475
+ else if (bVal === null || bVal === void 0) cmp = -1;
476
+ else if (aVal < bVal) cmp = -1;
477
+ else if (aVal > bVal) cmp = 1;
478
+ if (cmp !== 0) return direction === "desc" ? -cmp : cmp;
479
+ }
480
+ return 0;
481
+ });
482
+ }
483
+ };
484
+
485
+ // src/operation/OperationRegistry.ts
486
+ var OperationRegistry = class {
487
+ operations = /* @__PURE__ */ new Map();
488
+ register(operation) {
489
+ if (this.operations.has(operation.id)) {
490
+ throw new MaestroError(
491
+ "CONFLICT" /* CONFLICT */,
492
+ `Operation '${operation.id}' is already registered.`
493
+ );
494
+ }
495
+ this.operations.set(operation.id, operation);
496
+ }
497
+ get(id) {
498
+ const op = this.operations.get(id);
499
+ if (!op) {
500
+ throw new MaestroError("NOT_FOUND" /* NOT_FOUND */, `Operation '${id}' not found.`);
501
+ }
502
+ return op;
503
+ }
504
+ find(id) {
505
+ return this.operations.get(id);
506
+ }
507
+ forEntity(entityId, scope) {
508
+ return Array.from(this.operations.values()).filter(
509
+ (op) => op.entity === entityId && (scope === void 0 || op.scope === scope)
510
+ );
511
+ }
512
+ all() {
513
+ return Array.from(this.operations.values());
514
+ }
515
+ };
516
+
517
+ // src/schema/SchemaValidator.ts
518
+ function validateMaestroConfig(config) {
519
+ const errors = [];
520
+ if (!config.datasources || Object.keys(config.datasources).length === 0) {
521
+ errors.push({ path: "datasources", message: "At least one datasource is required." });
522
+ }
523
+ if (!config.entities || config.entities.length === 0) {
524
+ errors.push({ path: "entities", message: "At least one entity is required." });
525
+ }
526
+ const entityIds = /* @__PURE__ */ new Set();
527
+ for (const entity of config.entities ?? []) {
528
+ validateEntity(entity, config, entityIds, errors);
529
+ if (entity.id) entityIds.add(entity.id);
530
+ }
531
+ for (const relation of config.relations ?? []) {
532
+ validateRelation(relation, entityIds, errors);
533
+ }
534
+ return { valid: errors.length === 0, errors };
535
+ }
536
+ function validateEntity(entity, config, existingIds, errors) {
537
+ const prefix = `entities[${entity.id ?? "?"}]`;
538
+ if (!entity.id) {
539
+ errors.push({ path: prefix, message: "Entity id is required." });
540
+ return;
541
+ }
542
+ if (existingIds.has(entity.id)) {
543
+ errors.push({ path: prefix, message: `Duplicate entity id '${entity.id}'.` });
544
+ }
545
+ if (!entity.label?.singular || !entity.label?.plural) {
546
+ errors.push({ path: `${prefix}.label`, message: "Entity label.singular and label.plural are required." });
547
+ }
548
+ if (!entity.source?.datasource) {
549
+ errors.push({ path: `${prefix}.source.datasource`, message: "Entity source.datasource is required." });
550
+ } else if (!config.datasources[entity.source.datasource]) {
551
+ errors.push({
552
+ path: `${prefix}.source.datasource`,
553
+ message: `Datasource '${entity.source.datasource}' referenced by entity '${entity.id}' is not configured.`
554
+ });
555
+ }
556
+ if (!entity.source?.table) {
557
+ errors.push({ path: `${prefix}.source.table`, message: "Entity source.table is required." });
558
+ }
559
+ if (!entity.fields || entity.fields.length === 0) {
560
+ errors.push({ path: `${prefix}.fields`, message: "Entity must have at least one field." });
561
+ return;
562
+ }
563
+ const fieldNames = /* @__PURE__ */ new Set();
564
+ for (const field of entity.fields) {
565
+ const fp = `${prefix}.fields[${field.name ?? "?"}]`;
566
+ if (!field.name) {
567
+ errors.push({ path: fp, message: "Field name is required." });
568
+ continue;
569
+ }
570
+ if (fieldNames.has(field.name)) {
571
+ errors.push({ path: fp, message: `Duplicate field name '${field.name}'.` });
572
+ } else {
573
+ fieldNames.add(field.name);
574
+ }
575
+ if (!field.label) {
576
+ errors.push({ path: `${fp}.label`, message: "Field label is required." });
577
+ }
578
+ if (!field.type) {
579
+ errors.push({ path: `${fp}.type`, message: "Field type is required." });
580
+ }
581
+ }
582
+ }
583
+ function validateRelation(relation, entityIds, errors) {
584
+ const prefix = `relations[${relation.id ?? "?"}]`;
585
+ if (!relation.id) {
586
+ errors.push({ path: prefix, message: "Relation id is required." });
587
+ }
588
+ if (!relation.from?.entity) {
589
+ errors.push({ path: `${prefix}.from.entity`, message: "Relation from.entity is required." });
590
+ } else if (!entityIds.has(relation.from.entity)) {
591
+ errors.push({ path: `${prefix}.from.entity`, message: `Entity '${relation.from.entity}' not found.` });
592
+ }
593
+ if (!relation.to?.entity) {
594
+ errors.push({ path: `${prefix}.to.entity`, message: "Relation to.entity is required." });
595
+ } else if (!entityIds.has(relation.to.entity)) {
596
+ errors.push({ path: `${prefix}.to.entity`, message: `Entity '${relation.to.entity}' not found.` });
597
+ }
598
+ }
599
+
600
+ // src/engine/MetadataEngine.ts
601
+ var STRING_OPERATORS = [
602
+ "equals",
603
+ "notEquals",
604
+ "contains",
605
+ "startsWith",
606
+ "endsWith",
607
+ "in",
608
+ "notIn",
609
+ "isNull",
610
+ "isNotNull"
611
+ ];
612
+ var NUMERIC_OPERATORS = [
613
+ "equals",
614
+ "notEquals",
615
+ "gt",
616
+ "gte",
617
+ "lt",
618
+ "lte",
619
+ "between",
620
+ "in",
621
+ "notIn",
622
+ "isNull",
623
+ "isNotNull"
624
+ ];
625
+ var DATE_OPERATORS = [
626
+ "equals",
627
+ "notEquals",
628
+ "gt",
629
+ "gte",
630
+ "lt",
631
+ "lte",
632
+ "between",
633
+ "isNull",
634
+ "isNotNull"
635
+ ];
636
+ var ENUM_OPERATORS = ["equals", "notEquals", "in", "notIn", "isNull", "isNotNull"];
637
+ var BOOLEAN_OPERATORS = ["isTrue", "isFalse", "isNull", "isNotNull"];
638
+ var NULL_OPERATORS = ["isNull", "isNotNull"];
639
+ function filterOperatorsForType(type) {
640
+ switch (type) {
641
+ case "string":
642
+ case "text":
643
+ case "email":
644
+ case "phone":
645
+ case "url":
646
+ case "document":
647
+ return STRING_OPERATORS;
648
+ case "number":
649
+ case "integer":
650
+ case "decimal":
651
+ case "currency":
652
+ return NUMERIC_OPERATORS;
653
+ case "boolean":
654
+ return BOOLEAN_OPERATORS;
655
+ case "date":
656
+ case "datetime":
657
+ case "time":
658
+ return DATE_OPERATORS;
659
+ case "enum":
660
+ return ENUM_OPERATORS;
661
+ case "uuid":
662
+ return ENUM_OPERATORS;
663
+ case "json":
664
+ case "array":
665
+ case "relation":
666
+ return NULL_OPERATORS;
667
+ default:
668
+ return ["equals", "notEquals", "isNull", "isNotNull"];
669
+ }
670
+ }
671
+ function buildContextActions(entity, operations) {
672
+ const caps = { ...DEFAULT_CAPABILITIES, ...entity.capabilities };
673
+ const actions = [];
674
+ if (caps.update) {
675
+ actions.push({ id: "edit", label: "Edit", permission: `entity.${entity.id}.update` });
676
+ }
677
+ if (caps.clone) {
678
+ actions.push({ id: "clone", label: "Clone", permission: `entity.${entity.id}.clone` });
679
+ }
680
+ if (caps.softDelete) {
681
+ actions.push({
682
+ id: "soft-delete",
683
+ label: "Deactivate",
684
+ permission: `entity.${entity.id}.softDelete`,
685
+ condition: "when-active",
686
+ style: "warning"
687
+ });
688
+ actions.push({
689
+ id: "restore",
690
+ label: "Restore",
691
+ permission: `entity.${entity.id}.restore`,
692
+ condition: "when-inactive"
693
+ });
694
+ }
695
+ if (caps.delete) {
696
+ actions.push({
697
+ id: "hard-delete",
698
+ label: "Delete permanently",
699
+ permission: `entity.${entity.id}.delete`,
700
+ style: "danger"
701
+ });
702
+ }
703
+ for (const op of operations) {
704
+ if (op.entity === entity.id && op.scope === "record") {
705
+ actions.push({
706
+ id: op.id,
707
+ label: op.label,
708
+ permission: op.requiredPermission ?? `operation.${op.id}`,
709
+ operationId: op.id
710
+ });
711
+ }
712
+ }
713
+ return actions;
714
+ }
715
+ function buildBulkOperations(entity, operations) {
716
+ return operations.filter((op) => op.entity === entity.id && op.scope === "bulk").map((op) => op.id);
717
+ }
718
+ var MetadataEngine = class {
719
+ normalize(config) {
720
+ const operations = config.operations ?? [];
721
+ const entities = config.entities.map((e) => this.normalizeEntity(e, operations));
722
+ const relations = config.relations ?? [];
723
+ const entityRelationMap = /* @__PURE__ */ new Map();
724
+ for (const rel of relations) {
725
+ const addToMap = (entityId) => {
726
+ if (!entityRelationMap.has(entityId)) entityRelationMap.set(entityId, []);
727
+ entityRelationMap.get(entityId).push(rel.id);
728
+ };
729
+ addToMap(rel.from.entity);
730
+ if (rel.to.entity !== rel.from.entity) addToMap(rel.to.entity);
731
+ }
732
+ const enrichedEntities = entities.map((e) => ({
733
+ ...e,
734
+ relations: entityRelationMap.get(e.id) ?? []
735
+ }));
736
+ const normalizedOps = operations.map((op) => ({
737
+ id: op.id,
738
+ label: op.label,
739
+ description: op.description,
740
+ entity: op.entity,
741
+ scope: op.scope,
742
+ requiresConfirmation: op.requiresConfirmation ?? false,
743
+ confirmationMessage: op.confirmationMessage,
744
+ requiresTypedConfirmation: op.requiresTypedConfirmation ?? false,
745
+ requiredPermission: op.requiredPermission
746
+ }));
747
+ return {
748
+ entities: enrichedEntities,
749
+ relations,
750
+ operations: normalizedOps,
751
+ policies: config.policies ?? { roles: {} }
752
+ };
753
+ }
754
+ normalizeEntity(schema, operations) {
755
+ const capabilities = { ...DEFAULT_CAPABILITIES, ...schema.capabilities };
756
+ const primaryKey = schema.source.primaryKey ?? "id";
757
+ const displayField = schema.displayField ?? schema.fields.find((f) => !f.hidden)?.name ?? primaryKey;
758
+ return {
759
+ id: schema.id,
760
+ label: schema.label,
761
+ description: schema.description,
762
+ datasource: schema.source.datasource,
763
+ table: schema.source.table,
764
+ primaryKey,
765
+ displayField,
766
+ fields: schema.fields.map((f) => this.normalizeField(f)),
767
+ capabilities,
768
+ softDelete: schema.softDelete,
769
+ defaultSort: schema.defaultSort,
770
+ search: schema.search,
771
+ relations: [],
772
+ contextActions: buildContextActions(schema, operations),
773
+ bulkOperations: buildBulkOperations(schema, operations),
774
+ exportConfig: schema.exportConfig
775
+ };
776
+ }
777
+ normalizeField(schema) {
778
+ return {
779
+ name: schema.name,
780
+ label: schema.label,
781
+ type: schema.type,
782
+ required: schema.required ?? false,
783
+ readonly: schema.readonly ?? false,
784
+ hidden: schema.hidden ?? false,
785
+ sensitive: schema.sensitive ?? false,
786
+ searchable: schema.searchable ?? false,
787
+ sortable: schema.sortable ?? false,
788
+ filterable: schema.filterable ?? false,
789
+ exportable: schema.exportable ?? true,
790
+ filterOperators: schema.filterOperators ?? filterOperatorsForType(schema.type),
791
+ description: schema.description,
792
+ enumOptions: schema.enumOptions,
793
+ relationEntity: schema.relationEntity,
794
+ mask: schema.mask,
795
+ list: {
796
+ visible: schema.list?.visible ?? !(schema.hidden ?? false),
797
+ width: schema.list?.width,
798
+ align: schema.list?.align,
799
+ order: schema.list?.order
800
+ },
801
+ detail: {
802
+ visible: schema.detail?.visible ?? !(schema.hidden ?? false),
803
+ group: schema.detail?.group,
804
+ order: schema.detail?.order
805
+ },
806
+ form: {
807
+ creatable: schema.form?.creatable ?? true,
808
+ editable: schema.form?.editable ?? !(schema.readonly ?? false),
809
+ cloneable: schema.form?.cloneable ?? !(schema.readonly ?? false),
810
+ placeholder: schema.form?.placeholder,
811
+ helpText: schema.form?.helpText,
812
+ section: schema.form?.section,
813
+ order: schema.form?.order
814
+ }
815
+ };
816
+ }
817
+ };
818
+
819
+ // src/engine/MaestroEngine.ts
820
+ import { randomUUID as randomUUID3 } from "crypto";
821
+
822
+ // src/export/CsvExportProvider.ts
823
+ var CsvExportProvider = class {
824
+ export(entity, records, options) {
825
+ const fields = this.resolveFields(entity, options);
826
+ const headers = fields.map((f) => f.label);
827
+ const rows = records.map(
828
+ (record) => fields.map((field) => this.formatValue(record[field.name], field, options))
829
+ );
830
+ const csvLines = [headers, ...rows].map(
831
+ (row) => row.map((cell) => this.escapeCsvCell(String(cell ?? ""))).join(",")
832
+ );
833
+ const bom = options.bom !== false ? "\uFEFF" : "";
834
+ const data = bom + csvLines.join("\r\n");
835
+ const datestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
836
+ const filename = `${entity.id}_export_${datestamp}.csv`;
837
+ return {
838
+ format: "csv",
839
+ data,
840
+ filename,
841
+ recordCount: records.length,
842
+ mimeType: "text/csv;charset=utf-8"
843
+ };
844
+ }
845
+ resolveFields(entity, options) {
846
+ let fields = entity.fields.filter((f) => f.exportable && !f.hidden);
847
+ if (options.includeFields?.length) {
848
+ const include = new Set(options.includeFields);
849
+ fields = fields.filter((f) => include.has(f.name));
850
+ }
851
+ if (options.excludeFields?.length) {
852
+ const exclude = new Set(options.excludeFields);
853
+ fields = fields.filter((f) => !exclude.has(f.name));
854
+ }
855
+ return fields;
856
+ }
857
+ formatValue(value, field, options) {
858
+ if (value === null || value === void 0) return "";
859
+ if (field.sensitive) return "***";
860
+ switch (field.type) {
861
+ case "boolean":
862
+ return value ? "Sim" : "N\xE3o";
863
+ case "date":
864
+ case "datetime": {
865
+ if (value instanceof Date) {
866
+ return options.dateFormat ? this.formatDate(value, options.dateFormat) : value.toISOString();
867
+ }
868
+ return String(value);
869
+ }
870
+ case "enum": {
871
+ const option = field.enumOptions?.find((o) => o.value === value);
872
+ return option ? option.label : String(value);
873
+ }
874
+ case "json":
875
+ return JSON.stringify(value);
876
+ default:
877
+ return String(value);
878
+ }
879
+ }
880
+ formatDate(date, format) {
881
+ return format.replace("YYYY", String(date.getFullYear())).replace("MM", String(date.getMonth() + 1).padStart(2, "0")).replace("DD", String(date.getDate()).padStart(2, "0")).replace("HH", String(date.getHours()).padStart(2, "0")).replace("mm", String(date.getMinutes()).padStart(2, "0")).replace("ss", String(date.getSeconds()).padStart(2, "0"));
882
+ }
883
+ escapeCsvCell(value) {
884
+ if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
885
+ return `"${value.replace(/"/g, '""')}"`;
886
+ }
887
+ return value;
888
+ }
889
+ };
890
+
891
+ // src/engine/MaestroEngine.ts
892
+ var MaestroEngine = class {
893
+ constructor(metadata, datasources, operations, audit) {
894
+ this.metadata = metadata;
895
+ this.datasources = datasources;
896
+ this.operations = operations;
897
+ this.audit = audit;
898
+ this.rbac = new RbacEngine(metadata.policies);
899
+ }
900
+ metadata;
901
+ datasources;
902
+ operations;
903
+ audit;
904
+ rbac;
905
+ getMetadata() {
906
+ return this.metadata;
907
+ }
908
+ async list(entityId, query, actor) {
909
+ const entity = this.requireEntity(entityId);
910
+ this.requireCapability(entity, "list", actor, `entity.${entityId}.list`);
911
+ const provider = this.datasources.get(entity.datasource);
912
+ const result = await provider.list({
913
+ table: entity.table,
914
+ primaryKey: entity.primaryKey,
915
+ filters: query.filters,
916
+ sort: query.sort ?? (entity.defaultSort ? [entity.defaultSort] : void 0),
917
+ pagination: query.pagination,
918
+ search: query.search
919
+ });
920
+ await this.recordAudit(`entity.${entityId}.list`, actor, { type: entityId, id: "*" }, "debug", {
921
+ total: result.total
922
+ });
923
+ return result;
924
+ }
925
+ async findById(entityId, id, actor) {
926
+ const entity = this.requireEntity(entityId);
927
+ this.requireCapability(entity, "detail", actor, `entity.${entityId}.detail`);
928
+ const provider = this.datasources.get(entity.datasource);
929
+ const record = await provider.findById({ table: entity.table, primaryKey: entity.primaryKey, id });
930
+ if (record) {
931
+ await this.recordAudit(`entity.${entityId}.detail`, actor, { type: entityId, id }, "debug");
932
+ }
933
+ return record;
934
+ }
935
+ async create(entityId, data, actor) {
936
+ const entity = this.requireEntity(entityId);
937
+ this.requireCapability(entity, "create", actor, `entity.${entityId}.create`);
938
+ const provider = this.datasources.get(entity.datasource);
939
+ const created = await provider.create({
940
+ table: entity.table,
941
+ primaryKey: entity.primaryKey,
942
+ data: { ...data, [entity.primaryKey]: data[entity.primaryKey] ?? randomUUID3() }
943
+ });
944
+ await this.recordAudit(
945
+ `entity.${entityId}.create`,
946
+ actor,
947
+ { type: entityId, id: String(created[entity.primaryKey]) },
948
+ "info",
949
+ void 0,
950
+ void 0,
951
+ created
952
+ );
953
+ return created;
954
+ }
955
+ async update(entityId, id, data, actor) {
956
+ const entity = this.requireEntity(entityId);
957
+ this.requireCapability(entity, "update", actor, `entity.${entityId}.update`);
958
+ const provider = this.datasources.get(entity.datasource);
959
+ const before = await provider.findById({ table: entity.table, primaryKey: entity.primaryKey, id });
960
+ const updated = await provider.update({ table: entity.table, primaryKey: entity.primaryKey, id, data });
961
+ await this.recordAudit(
962
+ `entity.${entityId}.update`,
963
+ actor,
964
+ { type: entityId, id },
965
+ "info",
966
+ void 0,
967
+ before ?? void 0,
968
+ updated
969
+ );
970
+ return updated;
971
+ }
972
+ async clone(entityId, id, actor) {
973
+ const entity = this.requireEntity(entityId);
974
+ this.requireCapability(entity, "clone", actor, `entity.${entityId}.clone`);
975
+ const provider = this.datasources.get(entity.datasource);
976
+ const source = await provider.findById({ table: entity.table, primaryKey: entity.primaryKey, id });
977
+ if (!source) {
978
+ throw new MaestroError("NOT_FOUND" /* NOT_FOUND */, `Record '${id}' not found in entity '${entityId}'.`);
979
+ }
980
+ const cloneableFields = entity.fields.filter((f) => f.form.cloneable && f.name !== entity.primaryKey);
981
+ const cloneData = {};
982
+ for (const field of cloneableFields) {
983
+ if (source[field.name] !== void 0) cloneData[field.name] = source[field.name];
984
+ }
985
+ const created = await provider.create({
986
+ table: entity.table,
987
+ primaryKey: entity.primaryKey,
988
+ data: { ...cloneData, [entity.primaryKey]: randomUUID3() }
989
+ });
990
+ await this.recordAudit(
991
+ `entity.${entityId}.clone`,
992
+ actor,
993
+ { type: entityId, id: String(created[entity.primaryKey]) },
994
+ "info",
995
+ { sourceId: id }
996
+ );
997
+ return created;
998
+ }
999
+ async softDelete(entityId, id, actor) {
1000
+ const entity = this.requireEntity(entityId);
1001
+ this.requireCapability(entity, "softDelete", actor, `entity.${entityId}.softDelete`);
1002
+ if (!entity.softDelete) {
1003
+ throw new MaestroError(
1004
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1005
+ `Entity '${entityId}' does not have softDelete configured.`
1006
+ );
1007
+ }
1008
+ const provider = this.datasources.get(entity.datasource);
1009
+ await provider.update({
1010
+ table: entity.table,
1011
+ primaryKey: entity.primaryKey,
1012
+ id,
1013
+ data: { [entity.softDelete.field]: entity.softDelete.inactiveValue }
1014
+ });
1015
+ await this.recordAudit(`entity.${entityId}.softDelete`, actor, { type: entityId, id }, "warn");
1016
+ }
1017
+ async restore(entityId, id, actor) {
1018
+ const entity = this.requireEntity(entityId);
1019
+ if (!entity.softDelete) {
1020
+ throw new MaestroError(
1021
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1022
+ `Entity '${entityId}' does not have softDelete configured.`
1023
+ );
1024
+ }
1025
+ const permission = `entity.${entityId}.restore`;
1026
+ if (!this.rbac.can(actor, permission)) {
1027
+ throw new MaestroError("PERMISSION_DENIED" /* PERMISSION_DENIED */, `Permission denied: ${permission}`);
1028
+ }
1029
+ const provider = this.datasources.get(entity.datasource);
1030
+ await provider.update({
1031
+ table: entity.table,
1032
+ primaryKey: entity.primaryKey,
1033
+ id,
1034
+ data: { [entity.softDelete.field]: entity.softDelete.activeValue }
1035
+ });
1036
+ await this.recordAudit(`entity.${entityId}.restore`, actor, { type: entityId, id }, "warn");
1037
+ }
1038
+ async hardDelete(entityId, id, actor) {
1039
+ const entity = this.requireEntity(entityId);
1040
+ this.requireCapability(entity, "delete", actor, `entity.${entityId}.delete`);
1041
+ const provider = this.datasources.get(entity.datasource);
1042
+ const before = await provider.findById({ table: entity.table, primaryKey: entity.primaryKey, id });
1043
+ await provider.delete({ table: entity.table, primaryKey: entity.primaryKey, id });
1044
+ await this.recordAudit(
1045
+ `entity.${entityId}.delete`,
1046
+ actor,
1047
+ { type: entityId, id },
1048
+ "warn",
1049
+ void 0,
1050
+ before ?? void 0
1051
+ );
1052
+ }
1053
+ async export(entityId, query, actor, format = "csv") {
1054
+ const entity = this.requireEntity(entityId);
1055
+ this.requireCapability(entity, "export", actor, `entity.${entityId}.export`);
1056
+ const provider = this.datasources.get(entity.datasource);
1057
+ const result = await provider.list({
1058
+ table: entity.table,
1059
+ primaryKey: entity.primaryKey,
1060
+ filters: query.filters,
1061
+ sort: query.sort,
1062
+ search: query.search
1063
+ });
1064
+ const exportProvider = new CsvExportProvider();
1065
+ const exportResult = exportProvider.export(entity, result.records, { format });
1066
+ await this.recordAudit(
1067
+ `entity.${entityId}.export`,
1068
+ actor,
1069
+ { type: entityId, id: "*" },
1070
+ "info",
1071
+ { recordCount: exportResult.recordCount, format }
1072
+ );
1073
+ return exportResult;
1074
+ }
1075
+ async executeOperation(operationId, context, actor) {
1076
+ const operation = this.operations.find(operationId);
1077
+ if (!operation) {
1078
+ throw new MaestroError("NOT_FOUND" /* NOT_FOUND */, `Operation '${operationId}' not found.`);
1079
+ }
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
+ );
1085
+ }
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
+ }
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.`);
1106
+ }
1107
+ return entity;
1108
+ }
1109
+ requireCapability(entity, capability, actor, permission) {
1110
+ if (!entity.capabilities[capability]) {
1111
+ throw new MaestroError(
1112
+ "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
1113
+ `Capability '${String(capability)}' is not enabled for entity '${entity.id}'.`
1114
+ );
1115
+ }
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
+ });
1131
+ }
1132
+ };
1133
+
1134
+ // src/engine/createMaestro.ts
1135
+ function createMaestro(config) {
1136
+ const validation = validateMaestroConfig(config);
1137
+ if (!validation.valid) {
1138
+ const messages = validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
1139
+ throw new MaestroError("CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */, `Invalid Maestro configuration: ${messages}`);
1140
+ }
1141
+ const metadataEngine = new MetadataEngine();
1142
+ const metadata = metadataEngine.normalize(config);
1143
+ const datasources = new DatasourceRegistry();
1144
+ for (const [id, provider] of Object.entries(config.datasources)) {
1145
+ datasources.register(id, provider);
1146
+ }
1147
+ const operations = new OperationRegistry();
1148
+ for (const operation of config.operations ?? []) {
1149
+ operations.register(operation);
1150
+ }
1151
+ const audit = config.audit ? new AuditRecorder(config.audit) : void 0;
1152
+ return new MaestroEngine(metadata, datasources, operations, audit);
1153
+ }
1154
+
1155
+ // src/introspection/utils.ts
1156
+ var DISPLAY_FIELD_PRIORITY = [
1157
+ "name",
1158
+ "title",
1159
+ "username",
1160
+ "email",
1161
+ "code",
1162
+ "slug",
1163
+ "label",
1164
+ "first_name",
1165
+ "firstname",
1166
+ "full_name",
1167
+ "fullname",
1168
+ "subject"
1169
+ ];
1170
+ var TIMESTAMP_PATTERN = /^(created_at|createdat|created_date|createddate|updated_at|updatedat|updated_date|updateddate|deleted_at|deletedat|modified_at|modifiedat)$/i;
1171
+ var SEARCH_CANDIDATE_PATTERN = /^(name|title|description|email|username|first_name|firstname|last_name|lastname|full_name|fullname|label|code|slug|reference|subject)$/i;
1172
+ function inferFieldType(nativeType, fieldName) {
1173
+ const nt = nativeType.toLowerCase().replace(/\(.*\)/, "").trim();
1174
+ const fn = fieldName.toLowerCase();
1175
+ if (["uuid", "uniqueidentifier"].includes(nt)) return "uuid";
1176
+ if (["boolean", "bool", "bit"].includes(nt)) return "boolean";
1177
+ if (nt === "date") return "date";
1178
+ if ([
1179
+ "timestamp",
1180
+ "timestamptz",
1181
+ "timestamp with time zone",
1182
+ "timestamp without time zone",
1183
+ "datetime",
1184
+ "datetime2"
1185
+ ].includes(nt))
1186
+ return "datetime";
1187
+ if (["time", "timetz", "time with time zone", "time without time zone"].includes(nt))
1188
+ return "time";
1189
+ if (["json", "jsonb"].includes(nt)) return "json";
1190
+ if (nt.includes("[]") || nt === "array") return "array";
1191
+ if ([
1192
+ "int",
1193
+ "int2",
1194
+ "int4",
1195
+ "int8",
1196
+ "int16",
1197
+ "integer",
1198
+ "bigint",
1199
+ "smallint",
1200
+ "tinyint",
1201
+ "mediumint",
1202
+ "serial",
1203
+ "bigserial",
1204
+ "smallserial"
1205
+ ].includes(nt))
1206
+ return "integer";
1207
+ if (["float", "float4", "float8", "double", "double precision", "real"].includes(nt))
1208
+ return "number";
1209
+ if (["decimal", "numeric", "money", "smallmoney"].includes(nt)) {
1210
+ if (/^(price|amount|value|total|cost|fee|salary|balance|revenue|profit|tax)$|_(price|amount|value|total|cost|fee)$/.test(
1211
+ fn
1212
+ ))
1213
+ return "currency";
1214
+ return "decimal";
1215
+ }
1216
+ if (["text", "mediumtext", "longtext", "clob"].includes(nt)) return "text";
1217
+ if (fn === "email" || fn.endsWith("_email")) return "email";
1218
+ if (fn === "phone" || fn === "telephone" || fn.endsWith("_phone") || fn.endsWith("_tel"))
1219
+ return "phone";
1220
+ if (fn === "url" || fn === "website" || fn.endsWith("_url") || fn.endsWith("_website"))
1221
+ return "url";
1222
+ if (/^(doc|document|cpf|cnpj|ssn|nif|rg)$|_(doc|document|cpf|cnpj|ssn|rg)$/.test(fn))
1223
+ return "document";
1224
+ return "string";
1225
+ }
1226
+ function isTimestampField(fieldName, fieldType) {
1227
+ return TIMESTAMP_PATTERN.test(fieldName) && (fieldType === "datetime" || fieldType === "date");
1228
+ }
1229
+ function isSoftDeleteCandidate(fieldName, fieldType, nullable) {
1230
+ const fn = fieldName.toLowerCase();
1231
+ if ((fn === "deleted_at" || fn === "deletedat") && fieldType === "datetime" && nullable)
1232
+ return true;
1233
+ if ((fn === "is_deleted" || fn === "isdeleted" || fn === "deleted") && fieldType === "boolean")
1234
+ return true;
1235
+ if ((fn === "is_active" || fn === "isactive" || fn === "active") && fieldType === "boolean")
1236
+ return true;
1237
+ return false;
1238
+ }
1239
+ function isSearchCandidate(fieldName, fieldType, isPrimaryKey) {
1240
+ if (isPrimaryKey) return false;
1241
+ if (!["string", "text", "email", "phone", "url"].includes(fieldType)) return false;
1242
+ return SEARCH_CANDIDATE_PATTERN.test(fieldName);
1243
+ }
1244
+ function tableNameToEntityId(table) {
1245
+ return table.replace(/([A-Z])/g, (match, c, offset) => offset > 0 ? `-${c}` : c).replace(/_/g, "-").replace(/--+/g, "-").toLowerCase();
1246
+ }
1247
+ function humanizeWord(word) {
1248
+ if (!word) return "";
1249
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
1250
+ }
1251
+ function tableNameToLabel(table) {
1252
+ const words = table.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim().split(/\s+/).filter(Boolean).map(humanizeWord);
1253
+ const plural = words.join(" ");
1254
+ const lastWord = words[words.length - 1];
1255
+ let singularLast;
1256
+ if (lastWord.toLowerCase().endsWith("ies")) {
1257
+ singularLast = lastWord.slice(0, -3) + "y";
1258
+ } else if (lastWord.toLowerCase().endsWith("ses") || lastWord.toLowerCase().endsWith("xes") || lastWord.toLowerCase().endsWith("ches") || lastWord.toLowerCase().endsWith("shes")) {
1259
+ singularLast = lastWord.slice(0, -2);
1260
+ } else if (lastWord.toLowerCase().endsWith("s") && !lastWord.toLowerCase().endsWith("ss")) {
1261
+ singularLast = lastWord.slice(0, -1);
1262
+ } else {
1263
+ singularLast = lastWord;
1264
+ }
1265
+ const singular = [...words.slice(0, -1), singularLast].join(" ");
1266
+ return { singular, plural };
1267
+ }
1268
+ function humanizeFieldName(name) {
1269
+ return name.replace(/([A-Z])/g, " $1").replace(/_/g, " ").trim().replace(/\b\w/g, (c) => c.toUpperCase());
1270
+ }
1271
+ function detectDisplayField(fields) {
1272
+ for (const priority of DISPLAY_FIELD_PRIORITY) {
1273
+ const match = fields.find((f) => f.name.toLowerCase() === priority && !f.isPrimaryKey);
1274
+ if (match) return match.name;
1275
+ }
1276
+ const fallback = fields.find(
1277
+ (f) => !f.isPrimaryKey && ["string", "text", "email"].includes(f.type)
1278
+ );
1279
+ return fallback?.name;
1280
+ }
1281
+
1282
+ // src/hybrid-loader/HybridLoader.ts
1283
+ function fieldFromIntrospection(field) {
1284
+ return {
1285
+ name: field.name,
1286
+ label: humanizeFieldName(field.name),
1287
+ type: field.type,
1288
+ required: !field.nullable && !field.isPrimaryKey,
1289
+ readonly: field.isPrimaryKey,
1290
+ hidden: field.isPrimaryKey,
1291
+ searchable: field.candidateForSearch ?? false,
1292
+ sortable: !field.isPrimaryKey && !field.isTimestamp,
1293
+ filterable: field.isIndexed ?? false,
1294
+ exportable: !field.isPrimaryKey
1295
+ };
1296
+ }
1297
+ function mergeFields(introspectedFields, overrideFields, strategy) {
1298
+ const overrideMap = new Map(overrideFields.map((f) => [f.name, f]));
1299
+ const introspectedMap = new Map(introspectedFields.map((f) => [f.name, f]));
1300
+ const result = [];
1301
+ for (const override of overrideFields) {
1302
+ const introspected = introspectedMap.get(override.name);
1303
+ if (introspected) {
1304
+ result.push(override);
1305
+ } else {
1306
+ result.push(override);
1307
+ }
1308
+ }
1309
+ if (strategy === "extend") {
1310
+ for (const introspected of introspectedFields) {
1311
+ if (!overrideMap.has(introspected.name)) {
1312
+ result.push(fieldFromIntrospection(introspected));
1313
+ }
1314
+ }
1315
+ }
1316
+ return result;
1317
+ }
1318
+ function detectSoftDelete(fields) {
1319
+ const PRIORITY = ["deleted_at", "deletedat", "is_deleted", "isdeleted", "deleted", "is_active", "isactive", "active"];
1320
+ for (const candidate of PRIORITY) {
1321
+ const field = fields.find((f) => f.name.toLowerCase() === candidate && f.candidateForSoftDelete);
1322
+ if (field) {
1323
+ const fn = field.name.toLowerCase();
1324
+ if (fn === "deleted_at" || fn === "deletedat") {
1325
+ return { field: field.name, activeValue: null, inactiveValue: "timestamp" };
1326
+ }
1327
+ if (fn === "is_deleted" || fn === "isdeleted" || fn === "deleted") {
1328
+ return { field: field.name, activeValue: false, inactiveValue: true };
1329
+ }
1330
+ return { field: field.name, activeValue: true, inactiveValue: false };
1331
+ }
1332
+ }
1333
+ return void 0;
1334
+ }
1335
+ function detectSearchConfig(fields) {
1336
+ const searchFields = fields.filter((f) => f.candidateForSearch).map((f) => f.name);
1337
+ return searchFields.length > 0 ? { fields: searchFields } : void 0;
1338
+ }
1339
+ function entityFromIntrospection(entity, datasource) {
1340
+ const pk = entity.fields.find((f) => f.isPrimaryKey)?.name ?? "id";
1341
+ const displayField = detectDisplayField(entity.fields);
1342
+ const softDelete = detectSoftDelete(entity.fields);
1343
+ const search = detectSearchConfig(entity.fields);
1344
+ return {
1345
+ id: tableNameToEntityId(entity.table),
1346
+ label: tableNameToLabel(entity.table),
1347
+ source: {
1348
+ datasource,
1349
+ table: entity.table,
1350
+ primaryKey: pk
1351
+ },
1352
+ displayField,
1353
+ fields: entity.fields.map(fieldFromIntrospection),
1354
+ ...softDelete && { softDelete },
1355
+ ...search && { search }
1356
+ };
1357
+ }
1358
+ function mergeEntity(introspected, override, strategy) {
1359
+ return {
1360
+ ...override,
1361
+ fields: mergeFields(introspected.fields, override.fields, strategy)
1362
+ };
1363
+ }
1364
+ function introspectedRelationToSchema(rel, entityIdByTable) {
1365
+ const fromEntity = entityIdByTable.get(rel.from.table);
1366
+ const toEntity = entityIdByTable.get(rel.to.table);
1367
+ if (!fromEntity || !toEntity) return null;
1368
+ return {
1369
+ id: rel.id,
1370
+ type: rel.type,
1371
+ from: { entity: fromEntity, field: rel.from.field },
1372
+ to: { entity: toEntity, field: rel.to.field },
1373
+ label: `${tableNameToLabel(rel.to.table).plural}`
1374
+ };
1375
+ }
1376
+ function mergeIntrospectionWithOverrides(options) {
1377
+ const { introspection, overrides, datasources, defaultDatasource, strategy = "extend" } = options;
1378
+ const overrideByTable = /* @__PURE__ */ new Map();
1379
+ const overrideById = /* @__PURE__ */ new Map();
1380
+ for (const entity of overrides.entities) {
1381
+ overrideByTable.set(entity.source.table, entity);
1382
+ overrideById.set(entity.id, entity);
1383
+ }
1384
+ const entityIdByTable = /* @__PURE__ */ new Map();
1385
+ const mergedEntities = [];
1386
+ const handledOverrideIds = /* @__PURE__ */ new Set();
1387
+ for (const introspectedEntity of introspection.entities) {
1388
+ const override = overrideByTable.get(introspectedEntity.table) ?? overrideById.get(tableNameToEntityId(introspectedEntity.table));
1389
+ if (override) {
1390
+ handledOverrideIds.add(override.id);
1391
+ mergedEntities.push(mergeEntity(introspectedEntity, override, strategy));
1392
+ entityIdByTable.set(introspectedEntity.table, override.id);
1393
+ } else {
1394
+ const generated = entityFromIntrospection(introspectedEntity, defaultDatasource);
1395
+ mergedEntities.push(generated);
1396
+ entityIdByTable.set(introspectedEntity.table, generated.id);
1397
+ }
1398
+ }
1399
+ for (const override of overrides.entities) {
1400
+ if (!handledOverrideIds.has(override.id)) {
1401
+ mergedEntities.push(override);
1402
+ }
1403
+ }
1404
+ const overrideRelationIds = new Set(overrides.relations.map((r) => r.id));
1405
+ const mergedRelations = [...overrides.relations];
1406
+ for (const rel of introspection.relations) {
1407
+ if (!overrideRelationIds.has(rel.id)) {
1408
+ const schema = introspectedRelationToSchema(rel, entityIdByTable);
1409
+ if (schema) mergedRelations.push(schema);
1410
+ }
1411
+ }
1412
+ return {
1413
+ datasources,
1414
+ entities: mergedEntities,
1415
+ relations: mergedRelations,
1416
+ policies: overrides.policies,
1417
+ operations: options.operations,
1418
+ audit: options.audit,
1419
+ logger: options.logger
1420
+ };
1421
+ }
1422
+
1423
+ // src/engine/createMaestroFromIntrospection.ts
1424
+ async function createMaestroFromIntrospection(options) {
1425
+ const introspection = await options.provider.introspect();
1426
+ const overrides = options.overrides ?? { entities: [], relations: [] };
1427
+ const datasources = options.datasources ?? {};
1428
+ const defaultDatasource = options.defaultDatasource ?? "";
1429
+ const config = mergeIntrospectionWithOverrides({
1430
+ introspection,
1431
+ overrides,
1432
+ datasources,
1433
+ defaultDatasource,
1434
+ strategy: options.mergeStrategy ?? "extend",
1435
+ operations: options.operations,
1436
+ audit: options.audit,
1437
+ logger: options.logger
1438
+ });
1439
+ if (options.policies) {
1440
+ config.policies = options.policies;
1441
+ }
1442
+ return createMaestro(config);
1443
+ }
1444
+
1445
+ // src/http/QueryStringParser.ts
1446
+ var VALID_OPERATORS = /* @__PURE__ */ new Set([
1447
+ "equals",
1448
+ "notEquals",
1449
+ "contains",
1450
+ "startsWith",
1451
+ "endsWith",
1452
+ "in",
1453
+ "notIn",
1454
+ "gt",
1455
+ "gte",
1456
+ "lt",
1457
+ "lte",
1458
+ "between",
1459
+ "isNull",
1460
+ "isNotNull",
1461
+ "isTrue",
1462
+ "isFalse"
1463
+ ]);
1464
+ function parseStr(val) {
1465
+ return Array.isArray(val) ? val[0] : val;
1466
+ }
1467
+ function parseNum(val) {
1468
+ const s = parseStr(val);
1469
+ if (!s) return void 0;
1470
+ const n = Number(s);
1471
+ return Number.isNaN(n) ? void 0 : n;
1472
+ }
1473
+ function parseFilterValue(operator, value) {
1474
+ if (value === void 0) return void 0;
1475
+ if (operator === "in" || operator === "notIn") return value.split(",");
1476
+ if (operator === "between") {
1477
+ const [from, to] = value.split(",");
1478
+ return [from, to];
1479
+ }
1480
+ return value;
1481
+ }
1482
+ function parseQueryInput(query) {
1483
+ const result = {};
1484
+ const filterParam = query["filter"] ?? query["filters"];
1485
+ if (filterParam) {
1486
+ const rawFilters = Array.isArray(filterParam) ? filterParam : [filterParam];
1487
+ const filters = [];
1488
+ for (const raw of rawFilters) {
1489
+ const colonIdx = raw.indexOf(":");
1490
+ if (colonIdx === -1) continue;
1491
+ const field = raw.slice(0, colonIdx);
1492
+ const rest = raw.slice(colonIdx + 1);
1493
+ const colonIdx2 = rest.indexOf(":");
1494
+ const op = colonIdx2 === -1 ? rest : rest.slice(0, colonIdx2);
1495
+ const valueRaw = colonIdx2 === -1 ? void 0 : rest.slice(colonIdx2 + 1);
1496
+ if (!field || !VALID_OPERATORS.has(op)) continue;
1497
+ const operator = op;
1498
+ filters.push({ field, operator, value: parseFilterValue(operator, valueRaw) });
1499
+ }
1500
+ if (filters.length > 0) result.filters = filters;
1501
+ }
1502
+ const sortParam = query["sort"];
1503
+ if (sortParam) {
1504
+ const rawSorts = Array.isArray(sortParam) ? sortParam : [sortParam];
1505
+ const sort = [];
1506
+ for (const raw of rawSorts) {
1507
+ const colonIdx = raw.indexOf(":");
1508
+ const field = colonIdx === -1 ? raw : raw.slice(0, colonIdx);
1509
+ const direction = colonIdx === -1 ? "asc" : raw.slice(colonIdx + 1);
1510
+ if (!field) continue;
1511
+ sort.push({ field, direction: direction === "desc" ? "desc" : "asc" });
1512
+ }
1513
+ if (sort.length > 0) result.sort = sort;
1514
+ }
1515
+ const page = parseNum(query["page"]);
1516
+ const pageSize = parseNum(query["pageSize"]);
1517
+ const offset = parseNum(query["offset"]);
1518
+ const limit = parseNum(query["limit"]);
1519
+ const cursor = parseStr(query["cursor"]);
1520
+ if (page !== void 0) {
1521
+ const pagination = { strategy: "page", page, pageSize: pageSize ?? 20 };
1522
+ result.pagination = pagination;
1523
+ } else if (offset !== void 0) {
1524
+ const pagination = { strategy: "offset", offset, limit: limit ?? 20 };
1525
+ result.pagination = pagination;
1526
+ } else if (cursor !== void 0) {
1527
+ const pagination = { strategy: "cursor", cursor, limit: limit ?? 20 };
1528
+ result.pagination = pagination;
1529
+ }
1530
+ const searchTerm = parseStr(query["search"]);
1531
+ if (searchTerm) {
1532
+ const searchFields = parseStr(query["searchFields"]);
1533
+ result.search = {
1534
+ term: searchTerm,
1535
+ fields: searchFields ? searchFields.split(",") : []
1536
+ };
1537
+ }
1538
+ return result;
1539
+ }
1540
+
1541
+ // src/http/createMaestroHttpHandlers.ts
1542
+ var HTTP_STATUS = {
1543
+ ["PERMISSION_DENIED" /* PERMISSION_DENIED */]: 403,
1544
+ ["NOT_FOUND" /* NOT_FOUND */]: 404,
1545
+ ["CONFLICT" /* CONFLICT */]: 409,
1546
+ ["VALIDATION_ERROR" /* VALIDATION_ERROR */]: 400,
1547
+ ["CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */]: 400,
1548
+ ["INTERNAL_ERROR" /* INTERNAL_ERROR */]: 500
1549
+ };
1550
+ function errorResponse(err) {
1551
+ if (err instanceof MaestroError) {
1552
+ return {
1553
+ status: HTTP_STATUS[err.code] ?? 500,
1554
+ body: { error: err.code, message: err.message }
1555
+ };
1556
+ }
1557
+ const message = err instanceof Error ? err.message : "Internal server error";
1558
+ return { status: 500, body: { error: "INTERNAL_ERROR", message } };
1559
+ }
1560
+ function createMaestroHttpHandlers(engine, options) {
1561
+ const resolve = options.actorResolver;
1562
+ const list = async (req) => {
1563
+ try {
1564
+ const { actor } = await resolve(req);
1565
+ const result = await engine.list(req.params["entity"], parseQueryInput(req.query), actor);
1566
+ return { status: 200, body: result };
1567
+ } catch (err) {
1568
+ return errorResponse(err);
1569
+ }
1570
+ };
1571
+ const findById = async (req) => {
1572
+ try {
1573
+ const { actor } = await resolve(req);
1574
+ const record = await engine.findById(req.params["entity"], req.params["id"], actor);
1575
+ if (!record) {
1576
+ return { status: 404, body: { error: "NOT_FOUND", message: `Record '${req.params["id"]}' not found.` } };
1577
+ }
1578
+ return { status: 200, body: record };
1579
+ } catch (err) {
1580
+ return errorResponse(err);
1581
+ }
1582
+ };
1583
+ const create = async (req) => {
1584
+ try {
1585
+ const { actor } = await resolve(req);
1586
+ const created = await engine.create(req.params["entity"], req.body, actor);
1587
+ return { status: 201, body: created };
1588
+ } catch (err) {
1589
+ return errorResponse(err);
1590
+ }
1591
+ };
1592
+ const update = async (req) => {
1593
+ try {
1594
+ const { actor } = await resolve(req);
1595
+ const updated = await engine.update(
1596
+ req.params["entity"],
1597
+ req.params["id"],
1598
+ req.body,
1599
+ actor
1600
+ );
1601
+ return { status: 200, body: updated };
1602
+ } catch (err) {
1603
+ return errorResponse(err);
1604
+ }
1605
+ };
1606
+ const clone = async (req) => {
1607
+ try {
1608
+ const { actor } = await resolve(req);
1609
+ const cloned = await engine.clone(req.params["entity"], req.params["id"], actor);
1610
+ return { status: 201, body: cloned };
1611
+ } catch (err) {
1612
+ return errorResponse(err);
1613
+ }
1614
+ };
1615
+ const softDelete = async (req) => {
1616
+ try {
1617
+ const { actor } = await resolve(req);
1618
+ await engine.softDelete(req.params["entity"], req.params["id"], actor);
1619
+ return { status: 204, body: null };
1620
+ } catch (err) {
1621
+ return errorResponse(err);
1622
+ }
1623
+ };
1624
+ const restore = async (req) => {
1625
+ try {
1626
+ const { actor } = await resolve(req);
1627
+ await engine.restore(req.params["entity"], req.params["id"], actor);
1628
+ return { status: 200, body: { success: true } };
1629
+ } catch (err) {
1630
+ return errorResponse(err);
1631
+ }
1632
+ };
1633
+ const hardDelete = async (req) => {
1634
+ try {
1635
+ const { actor } = await resolve(req);
1636
+ await engine.hardDelete(req.params["entity"], req.params["id"], actor);
1637
+ return { status: 204, body: null };
1638
+ } catch (err) {
1639
+ return errorResponse(err);
1640
+ }
1641
+ };
1642
+ const exportHandler = async (req) => {
1643
+ try {
1644
+ const { actor } = await resolve(req);
1645
+ const rawFormat = Array.isArray(req.query["format"]) ? req.query["format"][0] : req.query["format"];
1646
+ const format = rawFormat === "json" ? "json" : "csv";
1647
+ const exportResult = await engine.export(req.params["entity"], parseQueryInput(req.query), actor, format);
1648
+ return {
1649
+ status: 200,
1650
+ body: exportResult.data,
1651
+ headers: {
1652
+ "Content-Type": exportResult.mimeType,
1653
+ "Content-Disposition": `attachment; filename="${exportResult.filename}"`
1654
+ }
1655
+ };
1656
+ } catch (err) {
1657
+ return errorResponse(err);
1658
+ }
1659
+ };
1660
+ const executeOperation = async (req) => {
1661
+ try {
1662
+ const { actor, correlationId } = await resolve(req);
1663
+ const body = req.body ?? {};
1664
+ const result = await engine.executeOperation(
1665
+ req.params["operationId"],
1666
+ {
1667
+ actor,
1668
+ correlationId,
1669
+ entityId: body["entityId"],
1670
+ record: body["record"],
1671
+ input: body["input"]
1672
+ },
1673
+ actor
1674
+ );
1675
+ return { status: result.success ? 200 : 422, body: result };
1676
+ } catch (err) {
1677
+ return errorResponse(err);
1678
+ }
1679
+ };
1680
+ const getMetadata = async () => {
1681
+ return { status: 200, body: engine.getMetadata() };
1682
+ };
1683
+ const getEntityMetadata = async (req) => {
1684
+ const entityId = req.params["entity"];
1685
+ const meta = engine.getMetadata();
1686
+ const entity = meta.entities.find((e) => e.id === entityId);
1687
+ if (!entity) {
1688
+ return { status: 404, body: { error: "NOT_FOUND", message: `Entity '${entityId}' not found.` } };
1689
+ }
1690
+ return { status: 200, body: entity };
1691
+ };
1692
+ const health = async () => {
1693
+ return { status: 200, body: { status: "ok" } };
1694
+ };
1695
+ return {
1696
+ list,
1697
+ findById,
1698
+ create,
1699
+ update,
1700
+ clone,
1701
+ softDelete,
1702
+ restore,
1703
+ hardDelete,
1704
+ export: exportHandler,
1705
+ executeOperation,
1706
+ getMetadata,
1707
+ getEntityMetadata,
1708
+ health
1709
+ };
1710
+ }
1711
+
1712
+ // src/config-loader/MaestroFileLoader.ts
1713
+ import { readFile, readdir, access } from "fs/promises";
1714
+ import { join, extname } from "path";
1715
+ var defaultFileReader = {
1716
+ readFile: (p) => readFile(p, "utf-8"),
1717
+ readDir: async (p) => {
1718
+ const entries = await readdir(p, { withFileTypes: true });
1719
+ return entries.filter((e) => e.isFile()).map((e) => e.name);
1720
+ },
1721
+ exists: async (p) => {
1722
+ try {
1723
+ await access(p);
1724
+ return true;
1725
+ } catch {
1726
+ return false;
1727
+ }
1728
+ }
1729
+ };
1730
+ function parseContent(content, ext, yamlParser) {
1731
+ if (ext === ".yml" || ext === ".yaml") {
1732
+ if (!yamlParser) {
1733
+ throw new Error(
1734
+ "YAML files require a yamlParser option. Install js-yaml and pass { parse: jsYaml.load } as yamlParser."
1735
+ );
1736
+ }
1737
+ return yamlParser.parse(content);
1738
+ }
1739
+ return JSON.parse(content);
1740
+ }
1741
+ async function readAllInDir(dir, suffix, reader, yamlParser) {
1742
+ const exists = await reader.exists(dir);
1743
+ if (!exists) return [];
1744
+ const files = await reader.readDir(dir);
1745
+ const matched = files.filter(
1746
+ (f) => f.endsWith(`${suffix}.json`) || f.endsWith(`${suffix}.yml`) || f.endsWith(`${suffix}.yaml`)
1747
+ );
1748
+ const results = [];
1749
+ for (const f of matched) {
1750
+ const content = await reader.readFile(join(dir, f));
1751
+ results.push(parseContent(content, extname(f), yamlParser));
1752
+ }
1753
+ return results;
1754
+ }
1755
+ async function loadMaestroConfig(options) {
1756
+ const reader = options.fileReader ?? defaultFileReader;
1757
+ const { rootDir, yamlParser } = options;
1758
+ const [entitiesRaw, relationsRaw] = await Promise.all([
1759
+ readAllInDir(join(rootDir, "entities"), ".entity", reader, yamlParser),
1760
+ readAllInDir(join(rootDir, "relations"), ".relation", reader, yamlParser)
1761
+ ]);
1762
+ let policies;
1763
+ const policyCandidates = [
1764
+ "policies/rbac.policy.json",
1765
+ "policies/rbac.policy.yml",
1766
+ "policies/rbac.policy.yaml"
1767
+ ];
1768
+ for (const candidate of policyCandidates) {
1769
+ const fullPath = join(rootDir, candidate);
1770
+ if (await reader.exists(fullPath)) {
1771
+ const content = await reader.readFile(fullPath);
1772
+ policies = parseContent(content, extname(candidate), yamlParser);
1773
+ break;
1774
+ }
1775
+ }
1776
+ return {
1777
+ entities: entitiesRaw,
1778
+ relations: relationsRaw,
1779
+ policies
1780
+ };
1781
+ }
1782
+
1783
+ // src/config-generator/ConfigGenerator.ts
1784
+ function detectSoftDelete2(fields) {
1785
+ const PRIORITY = [
1786
+ "deleted_at",
1787
+ "deletedat",
1788
+ "is_deleted",
1789
+ "isdeleted",
1790
+ "deleted",
1791
+ "is_active",
1792
+ "isactive",
1793
+ "active"
1794
+ ];
1795
+ for (const candidate of PRIORITY) {
1796
+ const field = fields.find(
1797
+ (f) => f.name.toLowerCase() === candidate && f.candidateForSoftDelete
1798
+ );
1799
+ if (!field) continue;
1800
+ const fn = field.name.toLowerCase();
1801
+ if (fn === "deleted_at" || fn === "deletedat") {
1802
+ return { field: field.name, activeValue: null, inactiveValue: "timestamp" };
1803
+ }
1804
+ if (fn === "is_deleted" || fn === "isdeleted" || fn === "deleted") {
1805
+ return { field: field.name, activeValue: false, inactiveValue: true };
1806
+ }
1807
+ return { field: field.name, activeValue: true, inactiveValue: false };
1808
+ }
1809
+ return void 0;
1810
+ }
1811
+ function fieldToSchema(field) {
1812
+ return {
1813
+ name: field.name,
1814
+ label: humanizeFieldName(field.name),
1815
+ type: field.type,
1816
+ required: !field.nullable && !field.isPrimaryKey,
1817
+ readonly: field.isPrimaryKey,
1818
+ hidden: field.isPrimaryKey,
1819
+ searchable: field.candidateForSearch ?? false,
1820
+ sortable: !field.isPrimaryKey && !field.isTimestamp,
1821
+ filterable: field.isIndexed ?? false,
1822
+ exportable: !field.isPrimaryKey
1823
+ };
1824
+ }
1825
+ function generateEntityConfig(entity, datasource) {
1826
+ const pk = entity.fields.find((f) => f.isPrimaryKey)?.name ?? "id";
1827
+ const displayField = detectDisplayField(entity.fields);
1828
+ const softDelete = detectSoftDelete2(entity.fields);
1829
+ const searchFields = entity.fields.filter((f) => f.candidateForSearch).map((f) => f.name);
1830
+ return {
1831
+ id: tableNameToEntityId(entity.table),
1832
+ label: tableNameToLabel(entity.table),
1833
+ source: {
1834
+ datasource,
1835
+ table: entity.table,
1836
+ primaryKey: pk
1837
+ },
1838
+ ...displayField && { displayField },
1839
+ fields: entity.fields.map(fieldToSchema),
1840
+ ...softDelete && { softDelete },
1841
+ ...searchFields.length > 0 && { search: { fields: searchFields } }
1842
+ };
1843
+ }
1844
+ function generateRelationConfig(relation, entityIdByTable) {
1845
+ const fromEntity = entityIdByTable.get(relation.from.table);
1846
+ const toEntity = entityIdByTable.get(relation.to.table);
1847
+ if (!fromEntity || !toEntity) return null;
1848
+ return {
1849
+ id: relation.id,
1850
+ type: relation.type,
1851
+ from: { entity: fromEntity, field: relation.from.field },
1852
+ to: { entity: toEntity, field: relation.to.field },
1853
+ label: tableNameToLabel(relation.to.table).plural
1854
+ };
1855
+ }
1856
+ function generateAllConfigs(result, datasource) {
1857
+ const entities = {};
1858
+ const entityIdByTable = /* @__PURE__ */ new Map();
1859
+ for (const entity of result.entities) {
1860
+ const schema = generateEntityConfig(entity, datasource);
1861
+ entities[schema.id] = schema;
1862
+ entityIdByTable.set(entity.table, schema.id);
1863
+ }
1864
+ const relations = {};
1865
+ for (const relation of result.relations) {
1866
+ const schema = generateRelationConfig(relation, entityIdByTable);
1867
+ if (schema) relations[schema.id] = schema;
1868
+ }
1869
+ return { entities, relations };
1870
+ }
1871
+
1872
+ // src/diff/DiffEngine.ts
1873
+ var DiffEngine = class {
1874
+ compare(previous, current, options = {}) {
1875
+ const changes = [];
1876
+ const prevEntityMap = new Map(previous.entities.map((e) => [e.table, e]));
1877
+ const currEntityMap = new Map(current.entities.map((e) => [e.table, e]));
1878
+ for (const [table, entity] of currEntityMap) {
1879
+ if (!prevEntityMap.has(table)) {
1880
+ changes.push({
1881
+ category: "entity",
1882
+ kind: "entity_added",
1883
+ impact: "SAFE",
1884
+ entityTable: table,
1885
+ current: entity
1886
+ });
1887
+ }
1888
+ }
1889
+ for (const [table, entity] of prevEntityMap) {
1890
+ if (!currEntityMap.has(table)) {
1891
+ changes.push({
1892
+ category: "entity",
1893
+ kind: "entity_removed",
1894
+ impact: "BREAKING",
1895
+ entityTable: table,
1896
+ previous: entity
1897
+ });
1898
+ }
1899
+ }
1900
+ for (const [table, prevEntity] of prevEntityMap) {
1901
+ const currEntity = currEntityMap.get(table);
1902
+ if (!currEntity) continue;
1903
+ const prevLabel = options.previousEntityLabels?.get(table) ?? tableNameToLabel(prevEntity.table).singular;
1904
+ const currLabel = options.currentEntityLabels?.get(table) ?? tableNameToLabel(currEntity.table).singular;
1905
+ if (prevLabel !== currLabel) {
1906
+ changes.push({
1907
+ category: "entity",
1908
+ kind: "entity_label_changed",
1909
+ impact: "WARNING",
1910
+ entityTable: table,
1911
+ previous: prevLabel,
1912
+ current: currLabel
1913
+ });
1914
+ }
1915
+ const prevPk = prevEntity.fields.find((f) => f.isPrimaryKey)?.name;
1916
+ const currPk = currEntity.fields.find((f) => f.isPrimaryKey)?.name;
1917
+ if (prevPk !== currPk) {
1918
+ changes.push({
1919
+ category: "entity",
1920
+ kind: "entity_primary_key_changed",
1921
+ impact: "BREAKING",
1922
+ entityTable: table,
1923
+ previous: prevPk,
1924
+ current: currPk
1925
+ });
1926
+ }
1927
+ changes.push(...this.compareFields(table, prevEntity.fields, currEntity.fields));
1928
+ }
1929
+ const prevRelMap = new Map(previous.relations.map((r) => [r.id, r]));
1930
+ const currRelMap = new Map(current.relations.map((r) => [r.id, r]));
1931
+ for (const [id, rel] of currRelMap) {
1932
+ if (!prevRelMap.has(id)) {
1933
+ changes.push({
1934
+ category: "relation",
1935
+ kind: "relation_added",
1936
+ impact: "SAFE",
1937
+ relationId: id,
1938
+ current: rel
1939
+ });
1940
+ }
1941
+ }
1942
+ for (const [id, rel] of prevRelMap) {
1943
+ if (!currRelMap.has(id)) {
1944
+ changes.push({
1945
+ category: "relation",
1946
+ kind: "relation_removed",
1947
+ impact: "BREAKING",
1948
+ relationId: id,
1949
+ previous: rel
1950
+ });
1951
+ }
1952
+ }
1953
+ for (const [id, prevRel] of prevRelMap) {
1954
+ const currRel = currRelMap.get(id);
1955
+ if (!currRel) continue;
1956
+ if (prevRel.type !== currRel.type) {
1957
+ changes.push({
1958
+ category: "relation",
1959
+ kind: "relation_cardinality_changed",
1960
+ impact: "BREAKING",
1961
+ relationId: id,
1962
+ previous: prevRel.type,
1963
+ current: currRel.type
1964
+ });
1965
+ }
1966
+ if (prevRel.from.field !== currRel.from.field || prevRel.to.field !== currRel.to.field) {
1967
+ changes.push({
1968
+ category: "relation",
1969
+ kind: "relation_fields_changed",
1970
+ impact: "BREAKING",
1971
+ relationId: id,
1972
+ previous: { from: prevRel.from.field, to: prevRel.to.field },
1973
+ current: { from: currRel.from.field, to: currRel.to.field }
1974
+ });
1975
+ }
1976
+ }
1977
+ const summary = {
1978
+ total: changes.length,
1979
+ safe: changes.filter((c) => c.impact === "SAFE").length,
1980
+ warnings: changes.filter((c) => c.impact === "WARNING").length,
1981
+ breaking: changes.filter((c) => c.impact === "BREAKING").length
1982
+ };
1983
+ return {
1984
+ changes,
1985
+ summary,
1986
+ hasBreaking: summary.breaking > 0,
1987
+ hasWarnings: summary.warnings > 0
1988
+ };
1989
+ }
1990
+ compareFields(entityTable, prevFields, currFields) {
1991
+ const changes = [];
1992
+ const prevMap = new Map(prevFields.map((f) => [f.name, f]));
1993
+ const currMap = new Map(currFields.map((f) => [f.name, f]));
1994
+ for (const [name, field] of currMap) {
1995
+ if (!prevMap.has(name)) {
1996
+ changes.push({
1997
+ category: "field",
1998
+ kind: "field_added",
1999
+ impact: "SAFE",
2000
+ entityTable,
2001
+ fieldName: name,
2002
+ current: field
2003
+ });
2004
+ }
2005
+ }
2006
+ for (const [name, field] of prevMap) {
2007
+ if (!currMap.has(name)) {
2008
+ changes.push({
2009
+ category: "field",
2010
+ kind: "field_removed",
2011
+ impact: "BREAKING",
2012
+ entityTable,
2013
+ fieldName: name,
2014
+ previous: field
2015
+ });
2016
+ }
2017
+ }
2018
+ for (const [name, prevField] of prevMap) {
2019
+ const currField = currMap.get(name);
2020
+ if (!currField) continue;
2021
+ if (prevField.type !== currField.type) {
2022
+ changes.push({
2023
+ category: "field",
2024
+ kind: "field_type_changed",
2025
+ impact: "BREAKING",
2026
+ entityTable,
2027
+ fieldName: name,
2028
+ previous: prevField.type,
2029
+ current: currField.type
2030
+ });
2031
+ }
2032
+ if (prevField.nullable !== currField.nullable) {
2033
+ const impact = !prevField.nullable && currField.nullable ? "SAFE" : "BREAKING";
2034
+ changes.push({
2035
+ category: "field",
2036
+ kind: "field_nullable_changed",
2037
+ impact,
2038
+ entityTable,
2039
+ fieldName: name,
2040
+ previous: prevField.nullable,
2041
+ current: currField.nullable
2042
+ });
2043
+ }
2044
+ const wasIndexed = prevField.isIndexed ?? false;
2045
+ const isIndexed = currField.isIndexed ?? false;
2046
+ if (wasIndexed !== isIndexed) {
2047
+ if (isIndexed) {
2048
+ changes.push({
2049
+ category: "field",
2050
+ kind: "field_index_added",
2051
+ impact: "SAFE",
2052
+ entityTable,
2053
+ fieldName: name,
2054
+ previous: false,
2055
+ current: true
2056
+ });
2057
+ } else {
2058
+ changes.push({
2059
+ category: "field",
2060
+ kind: "field_index_removed",
2061
+ impact: "WARNING",
2062
+ entityTable,
2063
+ fieldName: name,
2064
+ previous: true,
2065
+ current: false
2066
+ });
2067
+ }
2068
+ }
2069
+ const prevMeta = {
2070
+ candidateForSearch: prevField.candidateForSearch ?? false,
2071
+ candidateForSoftDelete: prevField.candidateForSoftDelete ?? false
2072
+ };
2073
+ const currMeta = {
2074
+ candidateForSearch: currField.candidateForSearch ?? false,
2075
+ candidateForSoftDelete: currField.candidateForSoftDelete ?? false
2076
+ };
2077
+ if (prevMeta.candidateForSearch !== currMeta.candidateForSearch || prevMeta.candidateForSoftDelete !== currMeta.candidateForSoftDelete) {
2078
+ changes.push({
2079
+ category: "field",
2080
+ kind: "field_metadata_changed",
2081
+ impact: "WARNING",
2082
+ entityTable,
2083
+ fieldName: name,
2084
+ previous: prevMeta,
2085
+ current: currMeta
2086
+ });
2087
+ }
2088
+ }
2089
+ return changes;
2090
+ }
2091
+ };
2092
+
2093
+ // src/introspection-report/ReportGenerator.ts
2094
+ var ReportGenerator = class {
2095
+ generate(result, diff) {
2096
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
2097
+ const hasDiff = diff !== void 0;
2098
+ const stats = {
2099
+ entitiesDiscovered: result.entities.length,
2100
+ relationsDiscovered: result.relations.length,
2101
+ totalChanges: diff?.summary.total ?? 0,
2102
+ safeChanges: diff?.summary.safe ?? 0,
2103
+ warnings: diff?.summary.warnings ?? 0,
2104
+ breakingChanges: diff?.summary.breaking ?? 0
2105
+ };
2106
+ const changes = hasDiff ? diff.changes.map((c) => this.describeChange(c)) : [];
2107
+ const text = this.generateText(stats, changes, hasDiff);
2108
+ return { completedAt, stats, changes, hasDiff, text };
2109
+ }
2110
+ describeChange(change) {
2111
+ switch (change.category) {
2112
+ case "entity": {
2113
+ switch (change.kind) {
2114
+ case "entity_added":
2115
+ return {
2116
+ impact: "SAFE",
2117
+ description: `Entity "${change.entityTable}" added`
2118
+ };
2119
+ case "entity_removed":
2120
+ return {
2121
+ impact: "BREAKING",
2122
+ description: `Entity "${change.entityTable}" removed`,
2123
+ recommendation: `Ensure all consumers of "${change.entityTable}" are updated before deploying.`
2124
+ };
2125
+ case "entity_label_changed":
2126
+ return {
2127
+ impact: "WARNING",
2128
+ description: `Entity "${change.entityTable}" label changed: "${change.previous}" \u2192 "${change.current}"`,
2129
+ recommendation: "Update UI labels and documentation to reflect the new entity name."
2130
+ };
2131
+ case "entity_primary_key_changed":
2132
+ return {
2133
+ impact: "BREAKING",
2134
+ description: `Entity "${change.entityTable}" primary key changed: "${change.previous}" \u2192 "${change.current}"`,
2135
+ recommendation: "This is a destructive change. Review all queries and foreign key references before deploying."
2136
+ };
2137
+ }
2138
+ break;
2139
+ }
2140
+ case "field": {
2141
+ switch (change.kind) {
2142
+ case "field_added":
2143
+ return {
2144
+ impact: "SAFE",
2145
+ description: `Field "${change.entityTable}.${change.fieldName}" added`
2146
+ };
2147
+ case "field_removed":
2148
+ return {
2149
+ impact: "BREAKING",
2150
+ description: `Field "${change.entityTable}.${change.fieldName}" removed`,
2151
+ recommendation: `Remove all references to "${change.fieldName}" in "${change.entityTable}" before deploying.`
2152
+ };
2153
+ case "field_type_changed":
2154
+ return {
2155
+ impact: "BREAKING",
2156
+ description: `Field "${change.entityTable}.${change.fieldName}" changed type: ${change.previous} \u2192 ${change.current}`,
2157
+ recommendation: "Verify that existing data can be migrated to the new type and that all consumers handle the new type."
2158
+ };
2159
+ case "field_nullable_changed":
2160
+ if (!change.previous && change.current) {
2161
+ return {
2162
+ impact: "SAFE",
2163
+ description: `Field "${change.entityTable}.${change.fieldName}" is now optional (was required)`
2164
+ };
2165
+ }
2166
+ return {
2167
+ impact: "BREAKING",
2168
+ description: `Field "${change.entityTable}.${change.fieldName}" is now required (was optional)`,
2169
+ recommendation: "Ensure existing rows have values for this field before deploying the migration."
2170
+ };
2171
+ case "field_index_added":
2172
+ return {
2173
+ impact: "SAFE",
2174
+ description: `Index added to field "${change.entityTable}.${change.fieldName}"`
2175
+ };
2176
+ case "field_index_removed":
2177
+ return {
2178
+ impact: "WARNING",
2179
+ description: `Index removed from field "${change.entityTable}.${change.fieldName}"`,
2180
+ recommendation: "Removing indexes may degrade query performance. Review affected queries."
2181
+ };
2182
+ case "field_metadata_changed":
2183
+ return {
2184
+ impact: "WARNING",
2185
+ description: `Derived metadata changed for field "${change.entityTable}.${change.fieldName}"`
2186
+ };
2187
+ }
2188
+ break;
2189
+ }
2190
+ case "relation": {
2191
+ switch (change.kind) {
2192
+ case "relation_added":
2193
+ return {
2194
+ impact: "SAFE",
2195
+ description: `Relation "${change.relationId}" added`
2196
+ };
2197
+ case "relation_removed":
2198
+ return {
2199
+ impact: "BREAKING",
2200
+ description: `Relation "${change.relationId}" removed`,
2201
+ recommendation: `Update all code that references relation "${change.relationId}".`
2202
+ };
2203
+ case "relation_cardinality_changed":
2204
+ return {
2205
+ impact: "BREAKING",
2206
+ description: `Relation "${change.relationId}" cardinality changed: ${change.previous} \u2192 ${change.current}`,
2207
+ recommendation: "Verify data integrity and update all query logic that depends on this relation."
2208
+ };
2209
+ case "relation_fields_changed": {
2210
+ const prev = change.previous;
2211
+ const curr = change.current;
2212
+ return {
2213
+ impact: "BREAKING",
2214
+ description: `Relation "${change.relationId}" participating fields changed (from: ${prev?.from} \u2192 ${curr?.from}, to: ${prev?.to} \u2192 ${curr?.to})`,
2215
+ recommendation: "Review all JOIN logic and foreign key constraints for this relation."
2216
+ };
2217
+ }
2218
+ }
2219
+ break;
2220
+ }
2221
+ }
2222
+ return { impact: "WARNING", description: "Unknown change detected" };
2223
+ }
2224
+ generateText(stats, changes, hasDiff) {
2225
+ const lines = [];
2226
+ lines.push("Introspection completed");
2227
+ lines.push("");
2228
+ lines.push(`Entities discovered: ${stats.entitiesDiscovered}`);
2229
+ lines.push(`Relations discovered: ${stats.relationsDiscovered}`);
2230
+ if (!hasDiff) {
2231
+ lines.push("");
2232
+ lines.push("No previous snapshot available for comparison.");
2233
+ return lines.join("\n");
2234
+ }
2235
+ if (stats.totalChanges === 0) {
2236
+ lines.push("");
2237
+ lines.push("No changes detected since last snapshot.");
2238
+ return lines.join("\n");
2239
+ }
2240
+ lines.push("");
2241
+ lines.push(`Changes detected: ${stats.totalChanges}`);
2242
+ const safeChanges = changes.filter((c) => c.impact === "SAFE");
2243
+ const warnings = changes.filter((c) => c.impact === "WARNING");
2244
+ const breaking = changes.filter((c) => c.impact === "BREAKING");
2245
+ if (safeChanges.length > 0) {
2246
+ lines.push("");
2247
+ lines.push("SAFE:");
2248
+ for (const c of safeChanges) {
2249
+ lines.push(`- ${c.description}`);
2250
+ }
2251
+ }
2252
+ if (warnings.length > 0) {
2253
+ lines.push("");
2254
+ lines.push("WARNINGS:");
2255
+ for (const c of warnings) {
2256
+ lines.push(`- ${c.description}`);
2257
+ if (c.recommendation) lines.push(` \u2192 ${c.recommendation}`);
2258
+ }
2259
+ }
2260
+ if (breaking.length > 0) {
2261
+ lines.push("");
2262
+ lines.push("BREAKING:");
2263
+ for (const c of breaking) {
2264
+ lines.push(`- ${c.description}`);
2265
+ if (c.recommendation) lines.push(` \u2192 ${c.recommendation}`);
2266
+ }
2267
+ }
2268
+ return lines.join("\n");
2269
+ }
2270
+ };
2271
+
2272
+ // src/snapshot/InMemorySnapshotRepository.ts
2273
+ var InMemorySnapshotRepository = class {
2274
+ store = /* @__PURE__ */ new Map();
2275
+ async save(snapshot) {
2276
+ this.store.set(snapshot.id, snapshot);
2277
+ }
2278
+ async load(id) {
2279
+ return this.store.get(id) ?? null;
2280
+ }
2281
+ };
2282
+
2283
+ // src/introspection-runtime/IntrospectionRuntime.ts
2284
+ var _snapshotCounter = 0;
2285
+ function generateSnapshotId() {
2286
+ return `snapshot-${Date.now()}-${++_snapshotCounter}`;
2287
+ }
2288
+ var IntrospectionRuntime = class {
2289
+ constructor(snapshotRepository) {
2290
+ this.snapshotRepository = snapshotRepository;
2291
+ }
2292
+ snapshotRepository;
2293
+ lastProvider;
2294
+ async run(options) {
2295
+ this.lastProvider = options.provider;
2296
+ const introspectionResult = await options.provider.introspect();
2297
+ const snapshot = {
2298
+ id: generateSnapshotId(),
2299
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2300
+ result: introspectionResult
2301
+ };
2302
+ let diff;
2303
+ if (options.snapshotId && this.snapshotRepository) {
2304
+ const previous = await this.snapshotRepository.load(options.snapshotId);
2305
+ if (previous) {
2306
+ const engine = new DiffEngine();
2307
+ diff = engine.compare(previous.result, introspectionResult);
2308
+ }
2309
+ }
2310
+ const reportGenerator = new ReportGenerator();
2311
+ const report = reportGenerator.generate(introspectionResult, diff);
2312
+ const shouldSave = options.saveSnapshot !== false && this.snapshotRepository !== void 0;
2313
+ if (shouldSave) {
2314
+ await this.snapshotRepository.save(snapshot);
2315
+ }
2316
+ const overrides = options.overrides ?? { entities: [], relations: [] };
2317
+ const datasources = options.datasources ?? {};
2318
+ const defaultDatasource = options.defaultDatasource ?? "";
2319
+ const config = mergeIntrospectionWithOverrides({
2320
+ introspection: introspectionResult,
2321
+ overrides,
2322
+ datasources,
2323
+ defaultDatasource,
2324
+ strategy: options.mergeStrategy ?? "extend",
2325
+ operations: options.operations,
2326
+ audit: options.audit,
2327
+ logger: options.logger
2328
+ });
2329
+ if (options.policies) {
2330
+ config.policies = options.policies;
2331
+ }
2332
+ const metadataEngine = new MetadataEngine();
2333
+ const metadata = metadataEngine.normalize(config);
2334
+ return { metadata, diff, report, snapshot };
2335
+ }
2336
+ async saveSnapshot(snapshot) {
2337
+ if (!this.snapshotRepository) {
2338
+ throw new Error(
2339
+ "IntrospectionRuntime: no snapshotRepository configured. Pass one in the constructor."
2340
+ );
2341
+ }
2342
+ await this.snapshotRepository.save(snapshot);
2343
+ }
2344
+ async loadSnapshot(id) {
2345
+ if (!this.snapshotRepository) return null;
2346
+ return this.snapshotRepository.load(id);
2347
+ }
2348
+ async compareWithSnapshot(snapshotId, provider) {
2349
+ if (!this.snapshotRepository) return null;
2350
+ const resolvedProvider = provider ?? this.lastProvider;
2351
+ if (!resolvedProvider) {
2352
+ throw new Error(
2353
+ "IntrospectionRuntime.compareWithSnapshot: a provider must be supplied or run() must have been called first."
2354
+ );
2355
+ }
2356
+ const previous = await this.snapshotRepository.load(snapshotId);
2357
+ if (!previous) return null;
2358
+ const current = await resolvedProvider.introspect();
2359
+ const engine = new DiffEngine();
2360
+ return engine.compare(previous.result, current);
2361
+ }
2362
+ };
2363
+
2364
+ // src/correlation/CorrelationId.ts
2365
+ import { randomUUID as randomUUID4 } from "crypto";
2366
+ function generateCorrelationId() {
2367
+ return randomUUID4();
2368
+ }
2369
+
2370
+ // src/risk/MetadataRiskClassifier.ts
2371
+ var MetadataRiskClassifier = class {
2372
+ constructor(operationRiskMap = {}, defaultRisk = "LOW") {
2373
+ this.operationRiskMap = operationRiskMap;
2374
+ this.defaultRisk = defaultRisk;
2375
+ }
2376
+ operationRiskMap;
2377
+ defaultRisk;
2378
+ classify(input) {
2379
+ const fromMetadata = input.metadata?.["riskLevel"];
2380
+ if (isOperationalRisk(fromMetadata)) return fromMetadata;
2381
+ return this.operationRiskMap[input.operation] ?? this.defaultRisk;
2382
+ }
2383
+ };
2384
+ var RISK_LEVELS = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
2385
+ function isOperationalRisk(value) {
2386
+ return typeof value === "string" && RISK_LEVELS.includes(value);
2387
+ }
2388
+
2389
+ // src/authorization/ContextualAuthorizationEngine.ts
2390
+ var ContextualAuthorizationEngine = class {
2391
+ constructor(policy, criticalOperations = /* @__PURE__ */ new Set()) {
2392
+ this.criticalOperations = criticalOperations;
2393
+ this.rbac = new RbacEngine(policy);
2394
+ }
2395
+ criticalOperations;
2396
+ rbac;
2397
+ async evaluate(context) {
2398
+ if (!this.rbac.can(context.actor, context.operation)) {
2399
+ return { decision: "DENY", reason: `Actor lacks permission for operation: ${context.operation}` };
2400
+ }
2401
+ const risk = context.riskLevel ?? "LOW";
2402
+ if (risk === "CRITICAL" || this.criticalOperations.has(context.operation)) {
2403
+ return { decision: "REQUIRES_CONFIRMATION", reason: `Operation requires confirmation due to risk level: ${risk}` };
2404
+ }
2405
+ return { decision: "ALLOW" };
2406
+ }
2407
+ };
2408
+
2409
+ // src/policy/PolicyEngine.ts
2410
+ var DECISION_PRIORITY = {
2411
+ ALLOW: 0,
2412
+ REQUIRES_CONFIRMATION: 1,
2413
+ DENY: 2
2414
+ };
2415
+ var PolicyEngine = class {
2416
+ constructor(provider) {
2417
+ this.provider = provider;
2418
+ }
2419
+ provider;
2420
+ evaluate(context) {
2421
+ const violations = [];
2422
+ let decision = "ALLOW";
2423
+ let reason;
2424
+ for (const rule of this.provider.rules()) {
2425
+ const result = rule.evaluate(context);
2426
+ if (result === null) continue;
2427
+ if (DECISION_PRIORITY[result.decision] > DECISION_PRIORITY[decision]) {
2428
+ decision = result.decision;
2429
+ reason = result.reason;
2430
+ }
2431
+ if (result.decision === "DENY" || result.decision === "REQUIRES_CONFIRMATION") {
2432
+ violations.push({
2433
+ ruleId: rule.id,
2434
+ reason: result.reason,
2435
+ context,
2436
+ occurredAt: /* @__PURE__ */ new Date()
2437
+ });
2438
+ }
2439
+ }
2440
+ return { decision, violations, reason };
2441
+ }
2442
+ };
2443
+
2444
+ // src/policy/InMemoryPolicyProvider.ts
2445
+ var InMemoryPolicyProvider = class {
2446
+ _rules;
2447
+ constructor(rules = []) {
2448
+ this._rules = rules;
2449
+ }
2450
+ rules() {
2451
+ return this._rules;
2452
+ }
2453
+ };
2454
+
2455
+ // src/confirmation/ConfirmationEngine.ts
2456
+ import { randomUUID as randomUUID5 } from "crypto";
2457
+ var ConfirmationEngine = class {
2458
+ constructor(repository) {
2459
+ this.repository = repository;
2460
+ }
2461
+ repository;
2462
+ async request(input) {
2463
+ const requiredApprovals = input.requiredApprovals ?? 1;
2464
+ const request = {
2465
+ id: randomUUID5(),
2466
+ operation: input.operation,
2467
+ actor: input.actor,
2468
+ resource: input.resource,
2469
+ status: requiredApprovals > 1 ? "AWAITING_CONFIRMATION" : "REQUESTED",
2470
+ requiredApprovals,
2471
+ approvals: [],
2472
+ rejections: [],
2473
+ requestedAt: /* @__PURE__ */ new Date(),
2474
+ expiresAt: input.expiresAt,
2475
+ metadata: input.metadata,
2476
+ correlationId: input.correlationId
2477
+ };
2478
+ await this.repository.save(request);
2479
+ return request;
2480
+ }
2481
+ async approve(requestId, approver, comment) {
2482
+ const request = await this.findOrThrow(requestId);
2483
+ this.assertActive(request);
2484
+ const updated = {
2485
+ ...request,
2486
+ approvals: [...request.approvals, { approver, approvedAt: /* @__PURE__ */ new Date(), comment }]
2487
+ };
2488
+ const fullyApproved = updated.approvals.length >= updated.requiredApprovals;
2489
+ updated.status = fullyApproved ? "APPROVED" : "AWAITING_CONFIRMATION";
2490
+ await this.repository.save(updated);
2491
+ return updated;
2492
+ }
2493
+ async reject(requestId, approver, comment) {
2494
+ const request = await this.findOrThrow(requestId);
2495
+ this.assertActive(request);
2496
+ const updated = {
2497
+ ...request,
2498
+ status: "REJECTED",
2499
+ rejections: [...request.rejections, { approver, approvedAt: /* @__PURE__ */ new Date(), comment }]
2500
+ };
2501
+ await this.repository.save(updated);
2502
+ return updated;
2503
+ }
2504
+ async execute(requestId) {
2505
+ const request = await this.findOrThrow(requestId);
2506
+ if (request.status !== "APPROVED") {
2507
+ throw new MaestroError(
2508
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
2509
+ `Cannot execute confirmation request in status: ${request.status}`
2510
+ );
2511
+ }
2512
+ const updated = {
2513
+ ...request,
2514
+ status: "EXECUTED",
2515
+ executedAt: /* @__PURE__ */ new Date()
2516
+ };
2517
+ await this.repository.save(updated);
2518
+ return updated;
2519
+ }
2520
+ async getPending() {
2521
+ return this.repository.listPending();
2522
+ }
2523
+ async findOrThrow(requestId) {
2524
+ const request = await this.repository.findById(requestId);
2525
+ if (!request) {
2526
+ throw new MaestroError(
2527
+ "NOT_FOUND" /* NOT_FOUND */,
2528
+ `Confirmation request not found: ${requestId}`
2529
+ );
2530
+ }
2531
+ return request;
2532
+ }
2533
+ assertActive(request) {
2534
+ const terminal = ["APPROVED", "REJECTED", "EXECUTED", "EXPIRED"];
2535
+ if (terminal.includes(request.status)) {
2536
+ throw new MaestroError(
2537
+ "VALIDATION_ERROR" /* VALIDATION_ERROR */,
2538
+ `Confirmation request is already in terminal status: ${request.status}`
2539
+ );
2540
+ }
2541
+ }
2542
+ };
2543
+
2544
+ // src/confirmation/InMemoryConfirmationRepository.ts
2545
+ var InMemoryConfirmationRepository = class {
2546
+ store = /* @__PURE__ */ new Map();
2547
+ async save(request) {
2548
+ this.store.set(request.id, request);
2549
+ }
2550
+ async findById(id) {
2551
+ return this.store.get(id);
2552
+ }
2553
+ async listPending() {
2554
+ return Array.from(this.store.values()).filter(
2555
+ (r) => r.status === "REQUESTED" || r.status === "AWAITING_CONFIRMATION"
2556
+ );
2557
+ }
2558
+ };
2559
+
2560
+ // src/governance/GovernanceEventType.ts
2561
+ var GOVERNANCE_EVENT_TYPES = {
2562
+ OPERATION_EXECUTED: "governance.operation.executed",
2563
+ AUTHORIZATION_DENIED: "governance.authorization.denied",
2564
+ POLICY_TRIGGERED: "governance.policy.triggered",
2565
+ CONFIRMATION_REQUESTED: "governance.confirmation.requested",
2566
+ CONFIRMATION_APPROVED: "governance.confirmation.approved",
2567
+ CONFIRMATION_REJECTED: "governance.confirmation.rejected",
2568
+ AUDIT_RECORDED: "governance.audit.recorded"
2569
+ };
2570
+
2571
+ // src/governance/InMemoryGovernanceEventBus.ts
2572
+ import { randomUUID as randomUUID6 } from "crypto";
2573
+ var InMemoryGovernanceEventBus = class extends InMemoryEventBus {
2574
+ async publishGovernance(type, payload, correlationId) {
2575
+ const event = {
2576
+ id: randomUUID6(),
2577
+ type,
2578
+ occurredAt: /* @__PURE__ */ new Date(),
2579
+ payload,
2580
+ correlationId
2581
+ };
2582
+ await this.publish(event);
2583
+ return event;
2584
+ }
2585
+ };
2586
+
2587
+ // src/governance/DefaultGovernanceApi.ts
2588
+ var DefaultGovernanceApi = class {
2589
+ constructor(auditRepository, confirmationRepository, violationLog = []) {
2590
+ this.auditRepository = auditRepository;
2591
+ this.confirmationRepository = confirmationRepository;
2592
+ this.violationLog = violationLog;
2593
+ }
2594
+ auditRepository;
2595
+ confirmationRepository;
2596
+ violationLog;
2597
+ async getAuditTimeline(filter) {
2598
+ return this.auditRepository.list(filter);
2599
+ }
2600
+ async getCorrelationTrace(correlationId) {
2601
+ return this.auditRepository.list({ correlationId });
2602
+ }
2603
+ async getPendingConfirmations() {
2604
+ return this.confirmationRepository.listPending();
2605
+ }
2606
+ async getPolicyViolations(filter) {
2607
+ let violations = [...this.violationLog];
2608
+ if (filter?.ruleId !== void 0) {
2609
+ violations = violations.filter((v) => v.ruleId === filter.ruleId);
2610
+ }
2611
+ if (filter?.correlationId !== void 0) {
2612
+ violations = violations.filter((v) => v.context.correlationId === filter.correlationId);
2613
+ }
2614
+ if (filter?.from !== void 0) {
2615
+ const from = filter.from;
2616
+ violations = violations.filter((v) => v.occurredAt >= from);
2617
+ }
2618
+ if (filter?.to !== void 0) {
2619
+ const to = filter.to;
2620
+ violations = violations.filter((v) => v.occurredAt <= to);
2621
+ }
2622
+ return violations;
2623
+ }
2624
+ };
235
2625
  export {
236
2626
  AuditRecorder,
2627
+ ConfirmationEngine,
237
2628
  ConsoleLogger,
2629
+ ConsoleStructuredLogger,
2630
+ ContextualAuthorizationEngine,
2631
+ CsvExportProvider,
2632
+ DEFAULT_CAPABILITIES,
2633
+ DatasourceRegistry,
2634
+ DefaultGovernanceApi,
2635
+ DiffEngine,
238
2636
  ErrorCode,
2637
+ GOVERNANCE_EVENT_TYPES,
239
2638
  InMemoryAuditRepository,
240
2639
  InMemoryConfigProvider,
2640
+ InMemoryConfirmationRepository,
2641
+ InMemoryDatasourceProvider,
241
2642
  InMemoryEventBus,
242
2643
  InMemoryFeatureFlagProvider,
2644
+ InMemoryGovernanceEventBus,
2645
+ InMemoryPolicyProvider,
2646
+ InMemorySnapshotRepository,
2647
+ IntrospectionRuntime,
2648
+ MaestroEngine,
243
2649
  MaestroError,
244
- RbacEngine
2650
+ MetadataEngine,
2651
+ MetadataRiskClassifier,
2652
+ OperationRegistry,
2653
+ PolicyEngine,
2654
+ RbacEngine,
2655
+ ReportGenerator,
2656
+ createMaestro,
2657
+ createMaestroFromIntrospection,
2658
+ createMaestroHttpHandlers,
2659
+ detectDisplayField,
2660
+ generateAllConfigs,
2661
+ generateCorrelationId,
2662
+ generateEntityConfig,
2663
+ generateRelationConfig,
2664
+ humanizeFieldName,
2665
+ inferFieldType,
2666
+ isSearchCandidate,
2667
+ isSoftDeleteCandidate,
2668
+ isTimestampField,
2669
+ loadMaestroConfig,
2670
+ mergeIntrospectionWithOverrides,
2671
+ parseQueryInput,
2672
+ tableNameToEntityId,
2673
+ tableNameToLabel,
2674
+ validateMaestroConfig
245
2675
  };
246
2676
  //# sourceMappingURL=index.js.map