@living-architecture/riviere-cli 0.9.16 → 0.9.18

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.
Files changed (3) hide show
  1. package/dist/bin.js +406 -63
  2. package/dist/index.js +405 -62
  3. package/package.json +6 -6
package/dist/bin.js CHANGED
@@ -6802,6 +6802,14 @@ var DuplicateDomainError = class extends Error {
6802
6802
  this.domainName = domainName;
6803
6803
  }
6804
6804
  };
6805
+ var SourceConflictError = class extends Error {
6806
+ repository;
6807
+ constructor(repository) {
6808
+ super(`Source '${repository}' already exists with different values`);
6809
+ this.name = "SourceConflictError";
6810
+ this.repository = repository;
6811
+ }
6812
+ };
6805
6813
  var DomainNotFoundError = class extends Error {
6806
6814
  domainName;
6807
6815
  constructor(domainName) {
@@ -6829,6 +6837,18 @@ var DuplicateComponentError = class extends Error {
6829
6837
  this.componentId = componentId;
6830
6838
  }
6831
6839
  };
6840
+ var ComponentTypeMismatchError = class extends Error {
6841
+ componentId;
6842
+ existingType;
6843
+ incomingType;
6844
+ constructor(componentId, existingType, incomingType) {
6845
+ super(`Component '${componentId}' already exists as type '${existingType}'; cannot upsert as '${incomingType}'`);
6846
+ this.name = "ComponentTypeMismatchError";
6847
+ this.componentId = componentId;
6848
+ this.existingType = existingType;
6849
+ this.incomingType = incomingType;
6850
+ }
6851
+ };
6832
6852
  var ComponentNotFoundError = class extends Error {
6833
6853
  componentId;
6834
6854
  suggestions;
@@ -7725,17 +7745,250 @@ function validateRequiredProperties(customTypes, customTypeName, metadata) {
7725
7745
  assertRequiredPropertiesProvided(customTypes, customTypeName, metadata);
7726
7746
  }
7727
7747
 
7748
+ // ../riviere-builder/dist/platform/domain/collection-utils/deduplicate-strings.js
7749
+ function deduplicateStrings(existing, incoming) {
7750
+ const existingSet = new Set(existing);
7751
+ return incoming.filter((item) => !existingSet.has(item));
7752
+ }
7753
+
7754
+ // ../riviere-builder/dist/features/building/domain/enrichment/merge-behavior.js
7755
+ function mergeStringArray(existing, incoming) {
7756
+ const base = existing ?? [];
7757
+ return [...base, ...deduplicateStrings(base, incoming)];
7758
+ }
7759
+ function mergeBehavior(existing, incoming) {
7760
+ const base = existing ?? {};
7761
+ return {
7762
+ ...base,
7763
+ ...incoming.reads !== void 0 && { reads: mergeStringArray(base.reads, incoming.reads) },
7764
+ ...incoming.validates !== void 0 && { validates: mergeStringArray(base.validates, incoming.validates) },
7765
+ ...incoming.modifies !== void 0 && { modifies: mergeStringArray(base.modifies, incoming.modifies) },
7766
+ ...incoming.emits !== void 0 && { emits: mergeStringArray(base.emits, incoming.emits) }
7767
+ };
7768
+ }
7769
+
7770
+ // ../riviere-builder/dist/features/building/domain/enrichment/upsert-merge.js
7771
+ var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["id", "type", "name", "domain", "module"]);
7772
+ var CUSTOM_BASE_FIELDS = /* @__PURE__ */ new Set([
7773
+ "id",
7774
+ "type",
7775
+ "customTypeName",
7776
+ "name",
7777
+ "domain",
7778
+ "module",
7779
+ "description",
7780
+ "sourceLocation"
7781
+ ]);
7782
+ function mergeComponentForUpsert(existing, incoming, options, warnings) {
7783
+ const merged = { ...existing };
7784
+ for (const [field, incomingValue] of Object.entries(incoming)) {
7785
+ if (shouldSkipTopLevelField(field, incomingValue)) {
7786
+ continue;
7787
+ }
7788
+ mergeTopLevelField(merged, existing.id, field, incomingValue, options, warnings);
7789
+ }
7790
+ if (isCustomComponent(existing) && isCustomComponent(incoming)) {
7791
+ const mergedMetadata = mergeCustomMetadata(existing, incoming, options, warnings);
7792
+ for (const [key, value] of Object.entries(mergedMetadata)) {
7793
+ setField(merged, key, value);
7794
+ }
7795
+ }
7796
+ return merged;
7797
+ }
7798
+ function mergeTopLevelField(target, componentId, field, incomingValue, options, warnings) {
7799
+ if (Array.isArray(incomingValue)) {
7800
+ mergeArrayField(target, field, incomingValue);
7801
+ return;
7802
+ }
7803
+ if (field === "behavior" && isRecord(incomingValue)) {
7804
+ mergeBehaviorField(target, incomingValue);
7805
+ return;
7806
+ }
7807
+ if (isRecord(incomingValue) && shouldRecursivelyMergeTopLevelObject(field)) {
7808
+ const existingValue = getField(target, field);
7809
+ const existingRecord = isRecord(existingValue) ? existingValue : void 0;
7810
+ setField(target, field, mergeNestedObject(existingRecord, incomingValue, options, warnings, componentId, field));
7811
+ return;
7812
+ }
7813
+ mergeScalarLikeField(target, field, incomingValue, options, warnings, componentId);
7814
+ }
7815
+ function mergeCustomMetadata(existing, incoming, options, warnings) {
7816
+ const existingMetadata = extractCustomMetadata(existing);
7817
+ const incomingMetadata = extractCustomMetadata(incoming);
7818
+ return mergeNestedObject(existingMetadata, incomingMetadata, options, warnings, existing.id, "metadata");
7819
+ }
7820
+ function extractCustomMetadata(component) {
7821
+ const metadata = {};
7822
+ for (const [key, value] of Object.entries(component)) {
7823
+ if (CUSTOM_BASE_FIELDS.has(key)) {
7824
+ continue;
7825
+ }
7826
+ metadata[key] = value;
7827
+ }
7828
+ return metadata;
7829
+ }
7830
+ function shouldSkipTopLevelField(field, value) {
7831
+ return IDENTITY_FIELDS.has(field) || !isDefined(value);
7832
+ }
7833
+ function shouldRecursivelyMergeTopLevelObject(field) {
7834
+ return field !== "sourceLocation" && field !== "signature";
7835
+ }
7836
+ function mergeArrayField(target, field, incomingValue) {
7837
+ if (incomingValue.length === 0) {
7838
+ return;
7839
+ }
7840
+ const existingValue = getField(target, field);
7841
+ const existingArray = Array.isArray(existingValue) ? existingValue : [];
7842
+ setField(target, field, mergeArrayUnion(existingArray, incomingValue));
7843
+ }
7844
+ function mergeBehaviorField(target, incomingValue) {
7845
+ const existingValue = getField(target, "behavior");
7846
+ const existingBehavior = toOperationBehavior(existingValue);
7847
+ const incomingBehavior = toOperationBehaviorFromRecord(incomingValue);
7848
+ setField(target, "behavior", mergeBehavior(existingBehavior, incomingBehavior));
7849
+ }
7850
+ function mergeScalarLikeField(target, field, incomingValue, options, warnings, componentId) {
7851
+ const existingValue = getField(target, field);
7852
+ if (options?.noOverwrite && isDefined(existingValue)) {
7853
+ return;
7854
+ }
7855
+ maybePushScalarOverwriteWarning(warnings, componentId, field, existingValue, incomingValue);
7856
+ setField(target, field, incomingValue);
7857
+ }
7858
+ function maybePushScalarOverwriteWarning(warnings, componentId, field, existingValue, incomingValue) {
7859
+ if (!isPrimitive(existingValue) || !isPrimitive(incomingValue) || existingValue === incomingValue) {
7860
+ return;
7861
+ }
7862
+ warnings.push({
7863
+ code: "SCALAR_OVERWRITE",
7864
+ message: `Scalar field '${field}' on component '${componentId}' overwritten`,
7865
+ componentId,
7866
+ field,
7867
+ oldValue: existingValue,
7868
+ newValue: incomingValue
7869
+ });
7870
+ }
7871
+ function mergeNestedObject(existing, incoming, options, warnings, componentId, pathPrefix) {
7872
+ const merged = { ...existing ?? {} };
7873
+ for (const [field, incomingValue] of Object.entries(incoming)) {
7874
+ if (!isDefined(incomingValue)) {
7875
+ continue;
7876
+ }
7877
+ if (Array.isArray(incomingValue)) {
7878
+ mergeArrayField(merged, field, incomingValue);
7879
+ continue;
7880
+ }
7881
+ if (isRecord(incomingValue)) {
7882
+ const existingValue = merged[field];
7883
+ const existingRecord = isRecord(existingValue) ? existingValue : void 0;
7884
+ merged[field] = mergeNestedObject(existingRecord, incomingValue, options, warnings, componentId, `${pathPrefix}.${field}`);
7885
+ continue;
7886
+ }
7887
+ if (options?.noOverwrite && isDefined(merged[field])) {
7888
+ continue;
7889
+ }
7890
+ maybePushScalarOverwriteWarning(warnings, componentId, `${pathPrefix}.${field}`, merged[field], incomingValue);
7891
+ merged[field] = incomingValue;
7892
+ }
7893
+ return merged;
7894
+ }
7895
+ function toOperationBehavior(value) {
7896
+ if (!isRecord(value)) {
7897
+ return void 0;
7898
+ }
7899
+ return toOperationBehaviorFromRecord(value);
7900
+ }
7901
+ function toOperationBehaviorFromRecord(value) {
7902
+ const behavior = {};
7903
+ const reads = toStringArray(value["reads"]);
7904
+ if (reads !== void 0) {
7905
+ behavior.reads = reads;
7906
+ }
7907
+ const validates = toStringArray(value["validates"]);
7908
+ if (validates !== void 0) {
7909
+ behavior.validates = validates;
7910
+ }
7911
+ const modifies = toStringArray(value["modifies"]);
7912
+ if (modifies !== void 0) {
7913
+ behavior.modifies = modifies;
7914
+ }
7915
+ const emits = toStringArray(value["emits"]);
7916
+ if (emits !== void 0) {
7917
+ behavior.emits = emits;
7918
+ }
7919
+ return behavior;
7920
+ }
7921
+ function toStringArray(value) {
7922
+ if (!Array.isArray(value)) {
7923
+ return void 0;
7924
+ }
7925
+ return value.filter(isString);
7926
+ }
7927
+ function mergeArrayUnion(existing, incoming) {
7928
+ const merged = [...existing];
7929
+ const seen = new Set(existing.map((item) => toStableKey(item)));
7930
+ for (const item of incoming) {
7931
+ const key = toStableKey(item);
7932
+ if (seen.has(key)) {
7933
+ continue;
7934
+ }
7935
+ seen.add(key);
7936
+ merged.push(item);
7937
+ }
7938
+ return merged;
7939
+ }
7940
+ function getField(target, field) {
7941
+ return Reflect.get(target, field);
7942
+ }
7943
+ function setField(target, field, value) {
7944
+ Reflect.set(target, field, value);
7945
+ }
7946
+ function toStableKey(value) {
7947
+ if (isPrimitive(value)) {
7948
+ return `${typeof value}:${String(value)}`;
7949
+ }
7950
+ return `json:${JSON.stringify(value)}`;
7951
+ }
7952
+ function isDefined(value) {
7953
+ return value !== void 0 && value !== null;
7954
+ }
7955
+ function isPrimitive(value) {
7956
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
7957
+ }
7958
+ function isRecord(value) {
7959
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7960
+ }
7961
+ function isString(value) {
7962
+ return typeof value === "string";
7963
+ }
7964
+ function isCustomComponent(component) {
7965
+ return component.type === "Custom";
7966
+ }
7967
+
7728
7968
  // ../riviere-builder/dist/features/building/domain/construction/graph-construction.js
7729
7969
  var GraphConstruction = class {
7730
7970
  graph;
7731
- constructor(graph) {
7971
+ operationWarnings;
7972
+ constructor(graph, operationWarnings) {
7732
7973
  this.graph = graph;
7974
+ this.operationWarnings = operationWarnings;
7733
7975
  }
7734
7976
  addSource(source) {
7977
+ const existing = this.graph.metadata.sources.find((item) => item.repository === source.repository);
7978
+ if (existing) {
7979
+ if (areSourcesEqual(existing, source)) {
7980
+ return;
7981
+ }
7982
+ throw new SourceConflictError(source.repository);
7983
+ }
7735
7984
  this.graph.metadata.sources.push(source);
7736
7985
  }
7737
7986
  addDomain(input) {
7738
- if (this.graph.metadata.domains[input.name]) {
7987
+ const existing = this.graph.metadata.domains[input.name];
7988
+ if (existing) {
7989
+ if (existing.description === input.description && existing.systemType === input.systemType) {
7990
+ return;
7991
+ }
7739
7992
  throw new DuplicateDomainError(input.name);
7740
7993
  }
7741
7994
  this.graph.metadata.domains[input.name] = {
@@ -7744,9 +7997,62 @@ var GraphConstruction = class {
7744
7997
  };
7745
7998
  }
7746
7999
  addUI(input) {
8000
+ return this.registerComponent(this.buildUIComponent(input));
8001
+ }
8002
+ upsertUI(input, options) {
8003
+ return this.upsertTypedComponent(this.buildUIComponent(input), options);
8004
+ }
8005
+ addApi(input) {
8006
+ return this.registerComponent(this.buildAPIComponent(input));
8007
+ }
8008
+ upsertApi(input, options) {
8009
+ return this.upsertTypedComponent(this.buildAPIComponent(input), options);
8010
+ }
8011
+ addUseCase(input) {
8012
+ return this.registerComponent(this.buildUseCaseComponent(input));
8013
+ }
8014
+ upsertUseCase(input, options) {
8015
+ return this.upsertTypedComponent(this.buildUseCaseComponent(input), options);
8016
+ }
8017
+ addDomainOp(input) {
8018
+ return this.registerComponent(this.buildDomainOpComponent(input));
8019
+ }
8020
+ upsertDomainOp(input, options) {
8021
+ return this.upsertTypedComponent(this.buildDomainOpComponent(input), options);
8022
+ }
8023
+ addEvent(input) {
8024
+ return this.registerComponent(this.buildEventComponent(input));
8025
+ }
8026
+ upsertEvent(input, options) {
8027
+ return this.upsertTypedComponent(this.buildEventComponent(input), options);
8028
+ }
8029
+ addEventHandler(input) {
8030
+ return this.registerComponent(this.buildEventHandlerComponent(input));
8031
+ }
8032
+ upsertEventHandler(input, options) {
8033
+ return this.upsertTypedComponent(this.buildEventHandlerComponent(input), options);
8034
+ }
8035
+ defineCustomType(input) {
8036
+ const customTypes = this.graph.metadata.customTypes;
8037
+ if (customTypes[input.name]) {
8038
+ throw new CustomTypeAlreadyDefinedError(input.name);
8039
+ }
8040
+ customTypes[input.name] = {
8041
+ ...input.requiredProperties !== void 0 && { requiredProperties: input.requiredProperties },
8042
+ ...input.optionalProperties !== void 0 && { optionalProperties: input.optionalProperties },
8043
+ ...input.description !== void 0 && { description: input.description }
8044
+ };
8045
+ }
8046
+ addCustom(input) {
8047
+ return this.registerComponent(this.buildCustomComponent(input));
8048
+ }
8049
+ upsertCustom(input, options) {
8050
+ return this.upsertTypedComponent(this.buildCustomComponent(input), options);
8051
+ }
8052
+ buildUIComponent(input) {
7747
8053
  validateDomainExists(this.graph.metadata.domains, input.domain);
7748
8054
  const id = generateComponentId(input.domain, input.module, "ui", input.name);
7749
- const component = {
8055
+ return {
7750
8056
  id,
7751
8057
  type: "UI",
7752
8058
  name: input.name,
@@ -7756,12 +8062,11 @@ var GraphConstruction = class {
7756
8062
  sourceLocation: input.sourceLocation,
7757
8063
  ...input.description !== void 0 && { description: input.description }
7758
8064
  };
7759
- return this.registerComponent(component);
7760
8065
  }
7761
- addApi(input) {
8066
+ buildAPIComponent(input) {
7762
8067
  validateDomainExists(this.graph.metadata.domains, input.domain);
7763
8068
  const id = generateComponentId(input.domain, input.module, "api", input.name);
7764
- const component = {
8069
+ return {
7765
8070
  id,
7766
8071
  type: "API",
7767
8072
  name: input.name,
@@ -7774,12 +8079,11 @@ var GraphConstruction = class {
7774
8079
  ...input.operationName !== void 0 && { operationName: input.operationName },
7775
8080
  ...input.description !== void 0 && { description: input.description }
7776
8081
  };
7777
- return this.registerComponent(component);
7778
8082
  }
7779
- addUseCase(input) {
8083
+ buildUseCaseComponent(input) {
7780
8084
  validateDomainExists(this.graph.metadata.domains, input.domain);
7781
8085
  const id = generateComponentId(input.domain, input.module, "usecase", input.name);
7782
- const component = {
8086
+ return {
7783
8087
  id,
7784
8088
  type: "UseCase",
7785
8089
  name: input.name,
@@ -7788,12 +8092,11 @@ var GraphConstruction = class {
7788
8092
  sourceLocation: input.sourceLocation,
7789
8093
  ...input.description !== void 0 && { description: input.description }
7790
8094
  };
7791
- return this.registerComponent(component);
7792
8095
  }
7793
- addDomainOp(input) {
8096
+ buildDomainOpComponent(input) {
7794
8097
  validateDomainExists(this.graph.metadata.domains, input.domain);
7795
8098
  const id = generateComponentId(input.domain, input.module, "domainop", input.name);
7796
- const component = {
8099
+ return {
7797
8100
  id,
7798
8101
  type: "DomainOp",
7799
8102
  name: input.name,
@@ -7808,12 +8111,11 @@ var GraphConstruction = class {
7808
8111
  ...input.businessRules !== void 0 && { businessRules: input.businessRules },
7809
8112
  ...input.description !== void 0 && { description: input.description }
7810
8113
  };
7811
- return this.registerComponent(component);
7812
8114
  }
7813
- addEvent(input) {
8115
+ buildEventComponent(input) {
7814
8116
  validateDomainExists(this.graph.metadata.domains, input.domain);
7815
8117
  const id = generateComponentId(input.domain, input.module, "event", input.name);
7816
- const component = {
8118
+ return {
7817
8119
  id,
7818
8120
  type: "Event",
7819
8121
  name: input.name,
@@ -7824,12 +8126,11 @@ var GraphConstruction = class {
7824
8126
  ...input.eventSchema !== void 0 && { eventSchema: input.eventSchema },
7825
8127
  ...input.description !== void 0 && { description: input.description }
7826
8128
  };
7827
- return this.registerComponent(component);
7828
8129
  }
7829
- addEventHandler(input) {
8130
+ buildEventHandlerComponent(input) {
7830
8131
  validateDomainExists(this.graph.metadata.domains, input.domain);
7831
8132
  const id = generateComponentId(input.domain, input.module, "eventhandler", input.name);
7832
- const component = {
8133
+ return {
7833
8134
  id,
7834
8135
  type: "EventHandler",
7835
8136
  name: input.name,
@@ -7839,20 +8140,8 @@ var GraphConstruction = class {
7839
8140
  sourceLocation: input.sourceLocation,
7840
8141
  ...input.description !== void 0 && { description: input.description }
7841
8142
  };
7842
- return this.registerComponent(component);
7843
- }
7844
- defineCustomType(input) {
7845
- const customTypes = this.graph.metadata.customTypes;
7846
- if (customTypes[input.name]) {
7847
- throw new CustomTypeAlreadyDefinedError(input.name);
7848
- }
7849
- customTypes[input.name] = {
7850
- ...input.requiredProperties !== void 0 && { requiredProperties: input.requiredProperties },
7851
- ...input.optionalProperties !== void 0 && { optionalProperties: input.optionalProperties },
7852
- ...input.description !== void 0 && { description: input.description }
7853
- };
7854
8143
  }
7855
- addCustom(input) {
8144
+ buildCustomComponent(input) {
7856
8145
  validateDomainExists(this.graph.metadata.domains, input.domain);
7857
8146
  validateCustomType(this.graph.metadata.customTypes, input.customTypeName);
7858
8147
  validateRequiredProperties(this.graph.metadata.customTypes, input.customTypeName, input.metadata);
@@ -7868,7 +8157,7 @@ var GraphConstruction = class {
7868
8157
  ...input.description !== void 0 && { description: input.description },
7869
8158
  ...input.metadata
7870
8159
  };
7871
- return this.registerComponent(component);
8160
+ return component;
7872
8161
  }
7873
8162
  registerComponent(component) {
7874
8163
  if (this.graph.components.some((c) => c.id === component.id)) {
@@ -7877,7 +8166,33 @@ var GraphConstruction = class {
7877
8166
  this.graph.components.push(component);
7878
8167
  return component;
7879
8168
  }
8169
+ upsertTypedComponent(incoming, options) {
8170
+ const existingIndex = this.graph.components.findIndex((component) => component.id === incoming.id);
8171
+ if (existingIndex === -1) {
8172
+ this.graph.components.push(incoming);
8173
+ return {
8174
+ component: incoming,
8175
+ created: true
8176
+ };
8177
+ }
8178
+ const existing = this.graph.components[existingIndex];
8179
+ if (!isSameTypeComponent(existing, incoming)) {
8180
+ throw new ComponentTypeMismatchError(incoming.id, existing?.type ?? "unknown", incoming.type);
8181
+ }
8182
+ const merged = mergeComponentForUpsert(existing, incoming, options, this.operationWarnings);
8183
+ this.graph.components[existingIndex] = merged;
8184
+ return {
8185
+ component: merged,
8186
+ created: false
8187
+ };
8188
+ }
7880
8189
  };
8190
+ function areSourcesEqual(existing, incoming) {
8191
+ return existing.repository === incoming.repository && existing.commit === incoming.commit && existing.extractedAt === incoming.extractedAt;
8192
+ }
8193
+ function isSameTypeComponent(existing, incoming) {
8194
+ return existing?.type === incoming.type;
8195
+ }
7881
8196
 
7882
8197
  // ../riviere-builder/dist/features/building/domain/enrichment/enrichment-errors.js
7883
8198
  var InvalidEnrichmentTargetError = class extends Error {
@@ -7896,28 +8211,6 @@ function deduplicateStateTransitions(existing, incoming) {
7896
8211
  return incoming.filter((item) => !existing.some((e) => e.from === item.from && e.to === item.to && e.trigger === item.trigger));
7897
8212
  }
7898
8213
 
7899
- // ../riviere-builder/dist/platform/domain/collection-utils/deduplicate-strings.js
7900
- function deduplicateStrings(existing, incoming) {
7901
- const existingSet = new Set(existing);
7902
- return incoming.filter((item) => !existingSet.has(item));
7903
- }
7904
-
7905
- // ../riviere-builder/dist/features/building/domain/enrichment/merge-behavior.js
7906
- function mergeStringArray(existing, incoming) {
7907
- const base = existing ?? [];
7908
- return [...base, ...deduplicateStrings(base, incoming)];
7909
- }
7910
- function mergeBehavior(existing, incoming) {
7911
- const base = existing ?? {};
7912
- return {
7913
- ...base,
7914
- ...incoming.reads !== void 0 && { reads: mergeStringArray(base.reads, incoming.reads) },
7915
- ...incoming.validates !== void 0 && { validates: mergeStringArray(base.validates, incoming.validates) },
7916
- ...incoming.modifies !== void 0 && { modifies: mergeStringArray(base.modifies, incoming.modifies) },
7917
- ...incoming.emits !== void 0 && { emits: mergeStringArray(base.emits, incoming.emits) }
7918
- };
7919
- }
7920
-
7921
8214
  // ../riviere-builder/dist/features/building/domain/enrichment/graph-enrichment.js
7922
8215
  var GraphEnrichment = class {
7923
8216
  graph;
@@ -7957,14 +8250,27 @@ var GraphEnrichment = class {
7957
8250
  // ../riviere-builder/dist/features/building/domain/linking/graph-linking.js
7958
8251
  var GraphLinking = class {
7959
8252
  graph;
7960
- constructor(graph) {
8253
+ operationWarnings;
8254
+ constructor(graph, operationWarnings) {
7961
8255
  this.graph = graph;
8256
+ this.operationWarnings = operationWarnings;
7962
8257
  }
7963
8258
  link(input) {
7964
8259
  const sourceExists = this.graph.components.some((c) => c.id === input.from);
7965
8260
  if (!sourceExists) {
7966
8261
  throw createComponentNotFoundError(this.graph.components, input.from);
7967
8262
  }
8263
+ const duplicate = this.graph.links.find((link2) => link2.source === input.from && link2.target === input.to && link2.type === input.type);
8264
+ if (duplicate) {
8265
+ this.operationWarnings.push({
8266
+ code: "DUPLICATE_LINK_SKIPPED",
8267
+ message: `Duplicate link '${input.from}' -> '${input.to}' (${input.type ?? "unspecified"}) skipped`,
8268
+ source: input.from,
8269
+ target: input.to,
8270
+ ...input.type !== void 0 && { linkType: input.type }
8271
+ });
8272
+ return duplicate;
8273
+ }
7968
8274
  const link = {
7969
8275
  source: input.from,
7970
8276
  target: input.to,
@@ -7978,6 +8284,19 @@ var GraphLinking = class {
7978
8284
  if (!sourceExists) {
7979
8285
  throw createComponentNotFoundError(this.graph.components, input.from);
7980
8286
  }
8287
+ const duplicate = this.graph.externalLinks.find((link) => link.source === input.from && link.target.repository === input.target.repository && link.target.name === input.target.name && link.type === input.type);
8288
+ if (duplicate) {
8289
+ this.operationWarnings.push({
8290
+ code: "DUPLICATE_LINK_SKIPPED",
8291
+ message: `Duplicate external link '${input.from}' -> '${input.target.name}' (${input.type ?? "unspecified"}) skipped`,
8292
+ source: input.from,
8293
+ target: input.target.name,
8294
+ ...input.type !== void 0 && { linkType: input.type },
8295
+ ...input.target.repository !== void 0 && { targetRepository: input.target.repository },
8296
+ targetName: input.target.name
8297
+ });
8298
+ return duplicate;
8299
+ }
7981
8300
  const externalLink = {
7982
8301
  source: input.from,
7983
8302
  target: input.target,
@@ -22354,7 +22673,7 @@ function buildEventHandlerInfo(handler, eventByName) {
22354
22673
  }
22355
22674
 
22356
22675
  // ../riviere-query/dist/features/querying/queries/graph-validation.js
22357
- function isCustomComponent(component) {
22676
+ function isCustomComponent2(component) {
22358
22677
  return component.type === "Custom";
22359
22678
  }
22360
22679
  function validateGraph(graph) {
@@ -22386,7 +22705,7 @@ function validateCustomTypes(graph) {
22386
22705
  const errors = [];
22387
22706
  const customTypes = graph.metadata.customTypes;
22388
22707
  graph.components.forEach((component, index) => {
22389
- if (!isCustomComponent(component)) {
22708
+ if (!isCustomComponent2(component)) {
22390
22709
  return;
22391
22710
  }
22392
22711
  const customTypeName = component.customTypeName;
@@ -23138,11 +23457,13 @@ function validateGraph2(graph) {
23138
23457
  // ../riviere-builder/dist/features/building/domain/inspection/graph-inspection.js
23139
23458
  var GraphInspection = class {
23140
23459
  graph;
23141
- constructor(graph) {
23460
+ operationWarnings;
23461
+ constructor(graph, operationWarnings) {
23142
23462
  this.graph = graph;
23463
+ this.operationWarnings = operationWarnings;
23143
23464
  }
23144
23465
  warnings() {
23145
- return findWarnings(this.graph);
23466
+ return [...findWarnings(this.graph), ...this.operationWarnings];
23146
23467
  }
23147
23468
  stats() {
23148
23469
  return calculateStats(this.graph);
@@ -23181,10 +23502,11 @@ var RiviereBuilder = class _RiviereBuilder {
23181
23502
  constructor(graph, graphPath) {
23182
23503
  this.graph = graph;
23183
23504
  this.graphPath = graphPath;
23184
- this.construction = new GraphConstruction(graph);
23505
+ const operationWarnings = [];
23506
+ this.construction = new GraphConstruction(graph, operationWarnings);
23185
23507
  this.enrichment = new GraphEnrichment(graph);
23186
- this.linking = new GraphLinking(graph);
23187
- this.inspection = new GraphInspection(graph);
23508
+ this.linking = new GraphLinking(graph, operationWarnings);
23509
+ this.inspection = new GraphInspection(graph, operationWarnings);
23188
23510
  this.errorRecovery = new NearMatch(graph);
23189
23511
  }
23190
23512
  static resume(graph, graphPath = "") {
@@ -23292,6 +23614,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23292
23614
  addUI(input) {
23293
23615
  return this.delegate.construction.addUI(input);
23294
23616
  }
23617
+ upsertUI(input, options) {
23618
+ return this.delegate.construction.upsertUI(input, options);
23619
+ }
23295
23620
  /**
23296
23621
  * Adds an API component to the graph.
23297
23622
  *
@@ -23301,6 +23626,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23301
23626
  addApi(input) {
23302
23627
  return this.delegate.construction.addApi(input);
23303
23628
  }
23629
+ upsertApi(input, options) {
23630
+ return this.delegate.construction.upsertApi(input, options);
23631
+ }
23304
23632
  /**
23305
23633
  * Adds a UseCase component to the graph.
23306
23634
  *
@@ -23310,6 +23638,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23310
23638
  addUseCase(input) {
23311
23639
  return this.delegate.construction.addUseCase(input);
23312
23640
  }
23641
+ upsertUseCase(input, options) {
23642
+ return this.delegate.construction.upsertUseCase(input, options);
23643
+ }
23313
23644
  /**
23314
23645
  * Adds a DomainOp component to the graph.
23315
23646
  *
@@ -23319,6 +23650,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23319
23650
  addDomainOp(input) {
23320
23651
  return this.delegate.construction.addDomainOp(input);
23321
23652
  }
23653
+ upsertDomainOp(input, options) {
23654
+ return this.delegate.construction.upsertDomainOp(input, options);
23655
+ }
23322
23656
  /**
23323
23657
  * Adds an Event component to the graph.
23324
23658
  *
@@ -23328,6 +23662,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23328
23662
  addEvent(input) {
23329
23663
  return this.delegate.construction.addEvent(input);
23330
23664
  }
23665
+ upsertEvent(input, options) {
23666
+ return this.delegate.construction.upsertEvent(input, options);
23667
+ }
23331
23668
  /**
23332
23669
  * Adds an EventHandler component to the graph.
23333
23670
  *
@@ -23337,6 +23674,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23337
23674
  addEventHandler(input) {
23338
23675
  return this.delegate.construction.addEventHandler(input);
23339
23676
  }
23677
+ upsertEventHandler(input, options) {
23678
+ return this.delegate.construction.upsertEventHandler(input, options);
23679
+ }
23340
23680
  /**
23341
23681
  * Defines a custom component type for the graph.
23342
23682
  *
@@ -23354,6 +23694,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23354
23694
  addCustom(input) {
23355
23695
  return this.delegate.construction.addCustom(input);
23356
23696
  }
23697
+ upsertCustom(input, options) {
23698
+ return this.delegate.construction.upsertCustom(input, options);
23699
+ }
23357
23700
  /**
23358
23701
  * Enriches a DomainOp component with additional domain details.
23359
23702
  *
@@ -31325,7 +31668,7 @@ function parsePackageJson(pkg) {
31325
31668
  }
31326
31669
  function loadPackageJson() {
31327
31670
  if (true) {
31328
- return { version: "0.9.15" };
31671
+ return { version: "0.9.17" };
31329
31672
  }
31330
31673
  const require2 = createRequire2(import.meta.url);
31331
31674
  return parsePackageJson(require2("../../package.json"));
package/dist/index.js CHANGED
@@ -6801,6 +6801,14 @@ var DuplicateDomainError = class extends Error {
6801
6801
  this.domainName = domainName;
6802
6802
  }
6803
6803
  };
6804
+ var SourceConflictError = class extends Error {
6805
+ repository;
6806
+ constructor(repository) {
6807
+ super(`Source '${repository}' already exists with different values`);
6808
+ this.name = "SourceConflictError";
6809
+ this.repository = repository;
6810
+ }
6811
+ };
6804
6812
  var DomainNotFoundError = class extends Error {
6805
6813
  domainName;
6806
6814
  constructor(domainName) {
@@ -6828,6 +6836,18 @@ var DuplicateComponentError = class extends Error {
6828
6836
  this.componentId = componentId;
6829
6837
  }
6830
6838
  };
6839
+ var ComponentTypeMismatchError = class extends Error {
6840
+ componentId;
6841
+ existingType;
6842
+ incomingType;
6843
+ constructor(componentId, existingType, incomingType) {
6844
+ super(`Component '${componentId}' already exists as type '${existingType}'; cannot upsert as '${incomingType}'`);
6845
+ this.name = "ComponentTypeMismatchError";
6846
+ this.componentId = componentId;
6847
+ this.existingType = existingType;
6848
+ this.incomingType = incomingType;
6849
+ }
6850
+ };
6831
6851
  var ComponentNotFoundError = class extends Error {
6832
6852
  componentId;
6833
6853
  suggestions;
@@ -7724,17 +7744,250 @@ function validateRequiredProperties(customTypes, customTypeName, metadata) {
7724
7744
  assertRequiredPropertiesProvided(customTypes, customTypeName, metadata);
7725
7745
  }
7726
7746
 
7747
+ // ../riviere-builder/dist/platform/domain/collection-utils/deduplicate-strings.js
7748
+ function deduplicateStrings(existing, incoming) {
7749
+ const existingSet = new Set(existing);
7750
+ return incoming.filter((item) => !existingSet.has(item));
7751
+ }
7752
+
7753
+ // ../riviere-builder/dist/features/building/domain/enrichment/merge-behavior.js
7754
+ function mergeStringArray(existing, incoming) {
7755
+ const base = existing ?? [];
7756
+ return [...base, ...deduplicateStrings(base, incoming)];
7757
+ }
7758
+ function mergeBehavior(existing, incoming) {
7759
+ const base = existing ?? {};
7760
+ return {
7761
+ ...base,
7762
+ ...incoming.reads !== void 0 && { reads: mergeStringArray(base.reads, incoming.reads) },
7763
+ ...incoming.validates !== void 0 && { validates: mergeStringArray(base.validates, incoming.validates) },
7764
+ ...incoming.modifies !== void 0 && { modifies: mergeStringArray(base.modifies, incoming.modifies) },
7765
+ ...incoming.emits !== void 0 && { emits: mergeStringArray(base.emits, incoming.emits) }
7766
+ };
7767
+ }
7768
+
7769
+ // ../riviere-builder/dist/features/building/domain/enrichment/upsert-merge.js
7770
+ var IDENTITY_FIELDS = /* @__PURE__ */ new Set(["id", "type", "name", "domain", "module"]);
7771
+ var CUSTOM_BASE_FIELDS = /* @__PURE__ */ new Set([
7772
+ "id",
7773
+ "type",
7774
+ "customTypeName",
7775
+ "name",
7776
+ "domain",
7777
+ "module",
7778
+ "description",
7779
+ "sourceLocation"
7780
+ ]);
7781
+ function mergeComponentForUpsert(existing, incoming, options, warnings) {
7782
+ const merged = { ...existing };
7783
+ for (const [field, incomingValue] of Object.entries(incoming)) {
7784
+ if (shouldSkipTopLevelField(field, incomingValue)) {
7785
+ continue;
7786
+ }
7787
+ mergeTopLevelField(merged, existing.id, field, incomingValue, options, warnings);
7788
+ }
7789
+ if (isCustomComponent(existing) && isCustomComponent(incoming)) {
7790
+ const mergedMetadata = mergeCustomMetadata(existing, incoming, options, warnings);
7791
+ for (const [key, value] of Object.entries(mergedMetadata)) {
7792
+ setField(merged, key, value);
7793
+ }
7794
+ }
7795
+ return merged;
7796
+ }
7797
+ function mergeTopLevelField(target, componentId, field, incomingValue, options, warnings) {
7798
+ if (Array.isArray(incomingValue)) {
7799
+ mergeArrayField(target, field, incomingValue);
7800
+ return;
7801
+ }
7802
+ if (field === "behavior" && isRecord(incomingValue)) {
7803
+ mergeBehaviorField(target, incomingValue);
7804
+ return;
7805
+ }
7806
+ if (isRecord(incomingValue) && shouldRecursivelyMergeTopLevelObject(field)) {
7807
+ const existingValue = getField(target, field);
7808
+ const existingRecord = isRecord(existingValue) ? existingValue : void 0;
7809
+ setField(target, field, mergeNestedObject(existingRecord, incomingValue, options, warnings, componentId, field));
7810
+ return;
7811
+ }
7812
+ mergeScalarLikeField(target, field, incomingValue, options, warnings, componentId);
7813
+ }
7814
+ function mergeCustomMetadata(existing, incoming, options, warnings) {
7815
+ const existingMetadata = extractCustomMetadata(existing);
7816
+ const incomingMetadata = extractCustomMetadata(incoming);
7817
+ return mergeNestedObject(existingMetadata, incomingMetadata, options, warnings, existing.id, "metadata");
7818
+ }
7819
+ function extractCustomMetadata(component) {
7820
+ const metadata = {};
7821
+ for (const [key, value] of Object.entries(component)) {
7822
+ if (CUSTOM_BASE_FIELDS.has(key)) {
7823
+ continue;
7824
+ }
7825
+ metadata[key] = value;
7826
+ }
7827
+ return metadata;
7828
+ }
7829
+ function shouldSkipTopLevelField(field, value) {
7830
+ return IDENTITY_FIELDS.has(field) || !isDefined(value);
7831
+ }
7832
+ function shouldRecursivelyMergeTopLevelObject(field) {
7833
+ return field !== "sourceLocation" && field !== "signature";
7834
+ }
7835
+ function mergeArrayField(target, field, incomingValue) {
7836
+ if (incomingValue.length === 0) {
7837
+ return;
7838
+ }
7839
+ const existingValue = getField(target, field);
7840
+ const existingArray = Array.isArray(existingValue) ? existingValue : [];
7841
+ setField(target, field, mergeArrayUnion(existingArray, incomingValue));
7842
+ }
7843
+ function mergeBehaviorField(target, incomingValue) {
7844
+ const existingValue = getField(target, "behavior");
7845
+ const existingBehavior = toOperationBehavior(existingValue);
7846
+ const incomingBehavior = toOperationBehaviorFromRecord(incomingValue);
7847
+ setField(target, "behavior", mergeBehavior(existingBehavior, incomingBehavior));
7848
+ }
7849
+ function mergeScalarLikeField(target, field, incomingValue, options, warnings, componentId) {
7850
+ const existingValue = getField(target, field);
7851
+ if (options?.noOverwrite && isDefined(existingValue)) {
7852
+ return;
7853
+ }
7854
+ maybePushScalarOverwriteWarning(warnings, componentId, field, existingValue, incomingValue);
7855
+ setField(target, field, incomingValue);
7856
+ }
7857
+ function maybePushScalarOverwriteWarning(warnings, componentId, field, existingValue, incomingValue) {
7858
+ if (!isPrimitive(existingValue) || !isPrimitive(incomingValue) || existingValue === incomingValue) {
7859
+ return;
7860
+ }
7861
+ warnings.push({
7862
+ code: "SCALAR_OVERWRITE",
7863
+ message: `Scalar field '${field}' on component '${componentId}' overwritten`,
7864
+ componentId,
7865
+ field,
7866
+ oldValue: existingValue,
7867
+ newValue: incomingValue
7868
+ });
7869
+ }
7870
+ function mergeNestedObject(existing, incoming, options, warnings, componentId, pathPrefix) {
7871
+ const merged = { ...existing ?? {} };
7872
+ for (const [field, incomingValue] of Object.entries(incoming)) {
7873
+ if (!isDefined(incomingValue)) {
7874
+ continue;
7875
+ }
7876
+ if (Array.isArray(incomingValue)) {
7877
+ mergeArrayField(merged, field, incomingValue);
7878
+ continue;
7879
+ }
7880
+ if (isRecord(incomingValue)) {
7881
+ const existingValue = merged[field];
7882
+ const existingRecord = isRecord(existingValue) ? existingValue : void 0;
7883
+ merged[field] = mergeNestedObject(existingRecord, incomingValue, options, warnings, componentId, `${pathPrefix}.${field}`);
7884
+ continue;
7885
+ }
7886
+ if (options?.noOverwrite && isDefined(merged[field])) {
7887
+ continue;
7888
+ }
7889
+ maybePushScalarOverwriteWarning(warnings, componentId, `${pathPrefix}.${field}`, merged[field], incomingValue);
7890
+ merged[field] = incomingValue;
7891
+ }
7892
+ return merged;
7893
+ }
7894
+ function toOperationBehavior(value) {
7895
+ if (!isRecord(value)) {
7896
+ return void 0;
7897
+ }
7898
+ return toOperationBehaviorFromRecord(value);
7899
+ }
7900
+ function toOperationBehaviorFromRecord(value) {
7901
+ const behavior = {};
7902
+ const reads = toStringArray(value["reads"]);
7903
+ if (reads !== void 0) {
7904
+ behavior.reads = reads;
7905
+ }
7906
+ const validates = toStringArray(value["validates"]);
7907
+ if (validates !== void 0) {
7908
+ behavior.validates = validates;
7909
+ }
7910
+ const modifies = toStringArray(value["modifies"]);
7911
+ if (modifies !== void 0) {
7912
+ behavior.modifies = modifies;
7913
+ }
7914
+ const emits = toStringArray(value["emits"]);
7915
+ if (emits !== void 0) {
7916
+ behavior.emits = emits;
7917
+ }
7918
+ return behavior;
7919
+ }
7920
+ function toStringArray(value) {
7921
+ if (!Array.isArray(value)) {
7922
+ return void 0;
7923
+ }
7924
+ return value.filter(isString);
7925
+ }
7926
+ function mergeArrayUnion(existing, incoming) {
7927
+ const merged = [...existing];
7928
+ const seen = new Set(existing.map((item) => toStableKey(item)));
7929
+ for (const item of incoming) {
7930
+ const key = toStableKey(item);
7931
+ if (seen.has(key)) {
7932
+ continue;
7933
+ }
7934
+ seen.add(key);
7935
+ merged.push(item);
7936
+ }
7937
+ return merged;
7938
+ }
7939
+ function getField(target, field) {
7940
+ return Reflect.get(target, field);
7941
+ }
7942
+ function setField(target, field, value) {
7943
+ Reflect.set(target, field, value);
7944
+ }
7945
+ function toStableKey(value) {
7946
+ if (isPrimitive(value)) {
7947
+ return `${typeof value}:${String(value)}`;
7948
+ }
7949
+ return `json:${JSON.stringify(value)}`;
7950
+ }
7951
+ function isDefined(value) {
7952
+ return value !== void 0 && value !== null;
7953
+ }
7954
+ function isPrimitive(value) {
7955
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
7956
+ }
7957
+ function isRecord(value) {
7958
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7959
+ }
7960
+ function isString(value) {
7961
+ return typeof value === "string";
7962
+ }
7963
+ function isCustomComponent(component) {
7964
+ return component.type === "Custom";
7965
+ }
7966
+
7727
7967
  // ../riviere-builder/dist/features/building/domain/construction/graph-construction.js
7728
7968
  var GraphConstruction = class {
7729
7969
  graph;
7730
- constructor(graph) {
7970
+ operationWarnings;
7971
+ constructor(graph, operationWarnings) {
7731
7972
  this.graph = graph;
7973
+ this.operationWarnings = operationWarnings;
7732
7974
  }
7733
7975
  addSource(source) {
7976
+ const existing = this.graph.metadata.sources.find((item) => item.repository === source.repository);
7977
+ if (existing) {
7978
+ if (areSourcesEqual(existing, source)) {
7979
+ return;
7980
+ }
7981
+ throw new SourceConflictError(source.repository);
7982
+ }
7734
7983
  this.graph.metadata.sources.push(source);
7735
7984
  }
7736
7985
  addDomain(input) {
7737
- if (this.graph.metadata.domains[input.name]) {
7986
+ const existing = this.graph.metadata.domains[input.name];
7987
+ if (existing) {
7988
+ if (existing.description === input.description && existing.systemType === input.systemType) {
7989
+ return;
7990
+ }
7738
7991
  throw new DuplicateDomainError(input.name);
7739
7992
  }
7740
7993
  this.graph.metadata.domains[input.name] = {
@@ -7743,9 +7996,62 @@ var GraphConstruction = class {
7743
7996
  };
7744
7997
  }
7745
7998
  addUI(input) {
7999
+ return this.registerComponent(this.buildUIComponent(input));
8000
+ }
8001
+ upsertUI(input, options) {
8002
+ return this.upsertTypedComponent(this.buildUIComponent(input), options);
8003
+ }
8004
+ addApi(input) {
8005
+ return this.registerComponent(this.buildAPIComponent(input));
8006
+ }
8007
+ upsertApi(input, options) {
8008
+ return this.upsertTypedComponent(this.buildAPIComponent(input), options);
8009
+ }
8010
+ addUseCase(input) {
8011
+ return this.registerComponent(this.buildUseCaseComponent(input));
8012
+ }
8013
+ upsertUseCase(input, options) {
8014
+ return this.upsertTypedComponent(this.buildUseCaseComponent(input), options);
8015
+ }
8016
+ addDomainOp(input) {
8017
+ return this.registerComponent(this.buildDomainOpComponent(input));
8018
+ }
8019
+ upsertDomainOp(input, options) {
8020
+ return this.upsertTypedComponent(this.buildDomainOpComponent(input), options);
8021
+ }
8022
+ addEvent(input) {
8023
+ return this.registerComponent(this.buildEventComponent(input));
8024
+ }
8025
+ upsertEvent(input, options) {
8026
+ return this.upsertTypedComponent(this.buildEventComponent(input), options);
8027
+ }
8028
+ addEventHandler(input) {
8029
+ return this.registerComponent(this.buildEventHandlerComponent(input));
8030
+ }
8031
+ upsertEventHandler(input, options) {
8032
+ return this.upsertTypedComponent(this.buildEventHandlerComponent(input), options);
8033
+ }
8034
+ defineCustomType(input) {
8035
+ const customTypes = this.graph.metadata.customTypes;
8036
+ if (customTypes[input.name]) {
8037
+ throw new CustomTypeAlreadyDefinedError(input.name);
8038
+ }
8039
+ customTypes[input.name] = {
8040
+ ...input.requiredProperties !== void 0 && { requiredProperties: input.requiredProperties },
8041
+ ...input.optionalProperties !== void 0 && { optionalProperties: input.optionalProperties },
8042
+ ...input.description !== void 0 && { description: input.description }
8043
+ };
8044
+ }
8045
+ addCustom(input) {
8046
+ return this.registerComponent(this.buildCustomComponent(input));
8047
+ }
8048
+ upsertCustom(input, options) {
8049
+ return this.upsertTypedComponent(this.buildCustomComponent(input), options);
8050
+ }
8051
+ buildUIComponent(input) {
7746
8052
  validateDomainExists(this.graph.metadata.domains, input.domain);
7747
8053
  const id = generateComponentId(input.domain, input.module, "ui", input.name);
7748
- const component = {
8054
+ return {
7749
8055
  id,
7750
8056
  type: "UI",
7751
8057
  name: input.name,
@@ -7755,12 +8061,11 @@ var GraphConstruction = class {
7755
8061
  sourceLocation: input.sourceLocation,
7756
8062
  ...input.description !== void 0 && { description: input.description }
7757
8063
  };
7758
- return this.registerComponent(component);
7759
8064
  }
7760
- addApi(input) {
8065
+ buildAPIComponent(input) {
7761
8066
  validateDomainExists(this.graph.metadata.domains, input.domain);
7762
8067
  const id = generateComponentId(input.domain, input.module, "api", input.name);
7763
- const component = {
8068
+ return {
7764
8069
  id,
7765
8070
  type: "API",
7766
8071
  name: input.name,
@@ -7773,12 +8078,11 @@ var GraphConstruction = class {
7773
8078
  ...input.operationName !== void 0 && { operationName: input.operationName },
7774
8079
  ...input.description !== void 0 && { description: input.description }
7775
8080
  };
7776
- return this.registerComponent(component);
7777
8081
  }
7778
- addUseCase(input) {
8082
+ buildUseCaseComponent(input) {
7779
8083
  validateDomainExists(this.graph.metadata.domains, input.domain);
7780
8084
  const id = generateComponentId(input.domain, input.module, "usecase", input.name);
7781
- const component = {
8085
+ return {
7782
8086
  id,
7783
8087
  type: "UseCase",
7784
8088
  name: input.name,
@@ -7787,12 +8091,11 @@ var GraphConstruction = class {
7787
8091
  sourceLocation: input.sourceLocation,
7788
8092
  ...input.description !== void 0 && { description: input.description }
7789
8093
  };
7790
- return this.registerComponent(component);
7791
8094
  }
7792
- addDomainOp(input) {
8095
+ buildDomainOpComponent(input) {
7793
8096
  validateDomainExists(this.graph.metadata.domains, input.domain);
7794
8097
  const id = generateComponentId(input.domain, input.module, "domainop", input.name);
7795
- const component = {
8098
+ return {
7796
8099
  id,
7797
8100
  type: "DomainOp",
7798
8101
  name: input.name,
@@ -7807,12 +8110,11 @@ var GraphConstruction = class {
7807
8110
  ...input.businessRules !== void 0 && { businessRules: input.businessRules },
7808
8111
  ...input.description !== void 0 && { description: input.description }
7809
8112
  };
7810
- return this.registerComponent(component);
7811
8113
  }
7812
- addEvent(input) {
8114
+ buildEventComponent(input) {
7813
8115
  validateDomainExists(this.graph.metadata.domains, input.domain);
7814
8116
  const id = generateComponentId(input.domain, input.module, "event", input.name);
7815
- const component = {
8117
+ return {
7816
8118
  id,
7817
8119
  type: "Event",
7818
8120
  name: input.name,
@@ -7823,12 +8125,11 @@ var GraphConstruction = class {
7823
8125
  ...input.eventSchema !== void 0 && { eventSchema: input.eventSchema },
7824
8126
  ...input.description !== void 0 && { description: input.description }
7825
8127
  };
7826
- return this.registerComponent(component);
7827
8128
  }
7828
- addEventHandler(input) {
8129
+ buildEventHandlerComponent(input) {
7829
8130
  validateDomainExists(this.graph.metadata.domains, input.domain);
7830
8131
  const id = generateComponentId(input.domain, input.module, "eventhandler", input.name);
7831
- const component = {
8132
+ return {
7832
8133
  id,
7833
8134
  type: "EventHandler",
7834
8135
  name: input.name,
@@ -7838,20 +8139,8 @@ var GraphConstruction = class {
7838
8139
  sourceLocation: input.sourceLocation,
7839
8140
  ...input.description !== void 0 && { description: input.description }
7840
8141
  };
7841
- return this.registerComponent(component);
7842
- }
7843
- defineCustomType(input) {
7844
- const customTypes = this.graph.metadata.customTypes;
7845
- if (customTypes[input.name]) {
7846
- throw new CustomTypeAlreadyDefinedError(input.name);
7847
- }
7848
- customTypes[input.name] = {
7849
- ...input.requiredProperties !== void 0 && { requiredProperties: input.requiredProperties },
7850
- ...input.optionalProperties !== void 0 && { optionalProperties: input.optionalProperties },
7851
- ...input.description !== void 0 && { description: input.description }
7852
- };
7853
8142
  }
7854
- addCustom(input) {
8143
+ buildCustomComponent(input) {
7855
8144
  validateDomainExists(this.graph.metadata.domains, input.domain);
7856
8145
  validateCustomType(this.graph.metadata.customTypes, input.customTypeName);
7857
8146
  validateRequiredProperties(this.graph.metadata.customTypes, input.customTypeName, input.metadata);
@@ -7867,7 +8156,7 @@ var GraphConstruction = class {
7867
8156
  ...input.description !== void 0 && { description: input.description },
7868
8157
  ...input.metadata
7869
8158
  };
7870
- return this.registerComponent(component);
8159
+ return component;
7871
8160
  }
7872
8161
  registerComponent(component) {
7873
8162
  if (this.graph.components.some((c) => c.id === component.id)) {
@@ -7876,7 +8165,33 @@ var GraphConstruction = class {
7876
8165
  this.graph.components.push(component);
7877
8166
  return component;
7878
8167
  }
8168
+ upsertTypedComponent(incoming, options) {
8169
+ const existingIndex = this.graph.components.findIndex((component) => component.id === incoming.id);
8170
+ if (existingIndex === -1) {
8171
+ this.graph.components.push(incoming);
8172
+ return {
8173
+ component: incoming,
8174
+ created: true
8175
+ };
8176
+ }
8177
+ const existing = this.graph.components[existingIndex];
8178
+ if (!isSameTypeComponent(existing, incoming)) {
8179
+ throw new ComponentTypeMismatchError(incoming.id, existing?.type ?? "unknown", incoming.type);
8180
+ }
8181
+ const merged = mergeComponentForUpsert(existing, incoming, options, this.operationWarnings);
8182
+ this.graph.components[existingIndex] = merged;
8183
+ return {
8184
+ component: merged,
8185
+ created: false
8186
+ };
8187
+ }
7879
8188
  };
8189
+ function areSourcesEqual(existing, incoming) {
8190
+ return existing.repository === incoming.repository && existing.commit === incoming.commit && existing.extractedAt === incoming.extractedAt;
8191
+ }
8192
+ function isSameTypeComponent(existing, incoming) {
8193
+ return existing?.type === incoming.type;
8194
+ }
7880
8195
 
7881
8196
  // ../riviere-builder/dist/features/building/domain/enrichment/enrichment-errors.js
7882
8197
  var InvalidEnrichmentTargetError = class extends Error {
@@ -7895,28 +8210,6 @@ function deduplicateStateTransitions(existing, incoming) {
7895
8210
  return incoming.filter((item) => !existing.some((e) => e.from === item.from && e.to === item.to && e.trigger === item.trigger));
7896
8211
  }
7897
8212
 
7898
- // ../riviere-builder/dist/platform/domain/collection-utils/deduplicate-strings.js
7899
- function deduplicateStrings(existing, incoming) {
7900
- const existingSet = new Set(existing);
7901
- return incoming.filter((item) => !existingSet.has(item));
7902
- }
7903
-
7904
- // ../riviere-builder/dist/features/building/domain/enrichment/merge-behavior.js
7905
- function mergeStringArray(existing, incoming) {
7906
- const base = existing ?? [];
7907
- return [...base, ...deduplicateStrings(base, incoming)];
7908
- }
7909
- function mergeBehavior(existing, incoming) {
7910
- const base = existing ?? {};
7911
- return {
7912
- ...base,
7913
- ...incoming.reads !== void 0 && { reads: mergeStringArray(base.reads, incoming.reads) },
7914
- ...incoming.validates !== void 0 && { validates: mergeStringArray(base.validates, incoming.validates) },
7915
- ...incoming.modifies !== void 0 && { modifies: mergeStringArray(base.modifies, incoming.modifies) },
7916
- ...incoming.emits !== void 0 && { emits: mergeStringArray(base.emits, incoming.emits) }
7917
- };
7918
- }
7919
-
7920
8213
  // ../riviere-builder/dist/features/building/domain/enrichment/graph-enrichment.js
7921
8214
  var GraphEnrichment = class {
7922
8215
  graph;
@@ -7956,14 +8249,27 @@ var GraphEnrichment = class {
7956
8249
  // ../riviere-builder/dist/features/building/domain/linking/graph-linking.js
7957
8250
  var GraphLinking = class {
7958
8251
  graph;
7959
- constructor(graph) {
8252
+ operationWarnings;
8253
+ constructor(graph, operationWarnings) {
7960
8254
  this.graph = graph;
8255
+ this.operationWarnings = operationWarnings;
7961
8256
  }
7962
8257
  link(input) {
7963
8258
  const sourceExists = this.graph.components.some((c) => c.id === input.from);
7964
8259
  if (!sourceExists) {
7965
8260
  throw createComponentNotFoundError(this.graph.components, input.from);
7966
8261
  }
8262
+ const duplicate = this.graph.links.find((link2) => link2.source === input.from && link2.target === input.to && link2.type === input.type);
8263
+ if (duplicate) {
8264
+ this.operationWarnings.push({
8265
+ code: "DUPLICATE_LINK_SKIPPED",
8266
+ message: `Duplicate link '${input.from}' -> '${input.to}' (${input.type ?? "unspecified"}) skipped`,
8267
+ source: input.from,
8268
+ target: input.to,
8269
+ ...input.type !== void 0 && { linkType: input.type }
8270
+ });
8271
+ return duplicate;
8272
+ }
7967
8273
  const link = {
7968
8274
  source: input.from,
7969
8275
  target: input.to,
@@ -7977,6 +8283,19 @@ var GraphLinking = class {
7977
8283
  if (!sourceExists) {
7978
8284
  throw createComponentNotFoundError(this.graph.components, input.from);
7979
8285
  }
8286
+ const duplicate = this.graph.externalLinks.find((link) => link.source === input.from && link.target.repository === input.target.repository && link.target.name === input.target.name && link.type === input.type);
8287
+ if (duplicate) {
8288
+ this.operationWarnings.push({
8289
+ code: "DUPLICATE_LINK_SKIPPED",
8290
+ message: `Duplicate external link '${input.from}' -> '${input.target.name}' (${input.type ?? "unspecified"}) skipped`,
8291
+ source: input.from,
8292
+ target: input.target.name,
8293
+ ...input.type !== void 0 && { linkType: input.type },
8294
+ ...input.target.repository !== void 0 && { targetRepository: input.target.repository },
8295
+ targetName: input.target.name
8296
+ });
8297
+ return duplicate;
8298
+ }
7980
8299
  const externalLink = {
7981
8300
  source: input.from,
7982
8301
  target: input.target,
@@ -22353,7 +22672,7 @@ function buildEventHandlerInfo(handler, eventByName) {
22353
22672
  }
22354
22673
 
22355
22674
  // ../riviere-query/dist/features/querying/queries/graph-validation.js
22356
- function isCustomComponent(component) {
22675
+ function isCustomComponent2(component) {
22357
22676
  return component.type === "Custom";
22358
22677
  }
22359
22678
  function validateGraph(graph) {
@@ -22385,7 +22704,7 @@ function validateCustomTypes(graph) {
22385
22704
  const errors = [];
22386
22705
  const customTypes = graph.metadata.customTypes;
22387
22706
  graph.components.forEach((component, index) => {
22388
- if (!isCustomComponent(component)) {
22707
+ if (!isCustomComponent2(component)) {
22389
22708
  return;
22390
22709
  }
22391
22710
  const customTypeName = component.customTypeName;
@@ -23137,11 +23456,13 @@ function validateGraph2(graph) {
23137
23456
  // ../riviere-builder/dist/features/building/domain/inspection/graph-inspection.js
23138
23457
  var GraphInspection = class {
23139
23458
  graph;
23140
- constructor(graph) {
23459
+ operationWarnings;
23460
+ constructor(graph, operationWarnings) {
23141
23461
  this.graph = graph;
23462
+ this.operationWarnings = operationWarnings;
23142
23463
  }
23143
23464
  warnings() {
23144
- return findWarnings(this.graph);
23465
+ return [...findWarnings(this.graph), ...this.operationWarnings];
23145
23466
  }
23146
23467
  stats() {
23147
23468
  return calculateStats(this.graph);
@@ -23180,10 +23501,11 @@ var RiviereBuilder = class _RiviereBuilder {
23180
23501
  constructor(graph, graphPath) {
23181
23502
  this.graph = graph;
23182
23503
  this.graphPath = graphPath;
23183
- this.construction = new GraphConstruction(graph);
23504
+ const operationWarnings = [];
23505
+ this.construction = new GraphConstruction(graph, operationWarnings);
23184
23506
  this.enrichment = new GraphEnrichment(graph);
23185
- this.linking = new GraphLinking(graph);
23186
- this.inspection = new GraphInspection(graph);
23507
+ this.linking = new GraphLinking(graph, operationWarnings);
23508
+ this.inspection = new GraphInspection(graph, operationWarnings);
23187
23509
  this.errorRecovery = new NearMatch(graph);
23188
23510
  }
23189
23511
  static resume(graph, graphPath = "") {
@@ -23291,6 +23613,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23291
23613
  addUI(input) {
23292
23614
  return this.delegate.construction.addUI(input);
23293
23615
  }
23616
+ upsertUI(input, options) {
23617
+ return this.delegate.construction.upsertUI(input, options);
23618
+ }
23294
23619
  /**
23295
23620
  * Adds an API component to the graph.
23296
23621
  *
@@ -23300,6 +23625,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23300
23625
  addApi(input) {
23301
23626
  return this.delegate.construction.addApi(input);
23302
23627
  }
23628
+ upsertApi(input, options) {
23629
+ return this.delegate.construction.upsertApi(input, options);
23630
+ }
23303
23631
  /**
23304
23632
  * Adds a UseCase component to the graph.
23305
23633
  *
@@ -23309,6 +23637,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23309
23637
  addUseCase(input) {
23310
23638
  return this.delegate.construction.addUseCase(input);
23311
23639
  }
23640
+ upsertUseCase(input, options) {
23641
+ return this.delegate.construction.upsertUseCase(input, options);
23642
+ }
23312
23643
  /**
23313
23644
  * Adds a DomainOp component to the graph.
23314
23645
  *
@@ -23318,6 +23649,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23318
23649
  addDomainOp(input) {
23319
23650
  return this.delegate.construction.addDomainOp(input);
23320
23651
  }
23652
+ upsertDomainOp(input, options) {
23653
+ return this.delegate.construction.upsertDomainOp(input, options);
23654
+ }
23321
23655
  /**
23322
23656
  * Adds an Event component to the graph.
23323
23657
  *
@@ -23327,6 +23661,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23327
23661
  addEvent(input) {
23328
23662
  return this.delegate.construction.addEvent(input);
23329
23663
  }
23664
+ upsertEvent(input, options) {
23665
+ return this.delegate.construction.upsertEvent(input, options);
23666
+ }
23330
23667
  /**
23331
23668
  * Adds an EventHandler component to the graph.
23332
23669
  *
@@ -23336,6 +23673,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23336
23673
  addEventHandler(input) {
23337
23674
  return this.delegate.construction.addEventHandler(input);
23338
23675
  }
23676
+ upsertEventHandler(input, options) {
23677
+ return this.delegate.construction.upsertEventHandler(input, options);
23678
+ }
23339
23679
  /**
23340
23680
  * Defines a custom component type for the graph.
23341
23681
  *
@@ -23353,6 +23693,9 @@ var RiviereBuilder2 = class _RiviereBuilder {
23353
23693
  addCustom(input) {
23354
23694
  return this.delegate.construction.addCustom(input);
23355
23695
  }
23696
+ upsertCustom(input, options) {
23697
+ return this.delegate.construction.upsertCustom(input, options);
23698
+ }
23356
23699
  /**
23357
23700
  * Enriches a DomainOp component with additional domain details.
23358
23701
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@living-architecture/riviere-cli",
3
- "version": "0.9.16",
3
+ "version": "0.9.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -32,10 +32,10 @@
32
32
  "glob": "^11.0.2",
33
33
  "ts-morph": "^24.0.0",
34
34
  "yaml": "^2.7.0",
35
- "@living-architecture/riviere-builder": "0.7.12",
36
- "@living-architecture/riviere-extract-ts": "0.4.15",
37
- "@living-architecture/riviere-query": "0.6.12",
38
- "@living-architecture/riviere-schema": "0.6.12",
39
- "@living-architecture/riviere-extract-config": "0.5.12"
35
+ "@living-architecture/riviere-extract-config": "0.5.13",
36
+ "@living-architecture/riviere-schema": "0.6.13",
37
+ "@living-architecture/riviere-extract-ts": "0.4.16",
38
+ "@living-architecture/riviere-builder": "0.8.1",
39
+ "@living-architecture/riviere-query": "0.6.13"
40
40
  }
41
41
  }