nucleus-core-ts 0.9.850 → 0.9.851

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.
@@ -25,10 +25,21 @@ export function ConnectionTest({ actions, source, loading = false }) {
25
25
  setResult(null);
26
26
  setTesting(false);
27
27
  });
28
- useEffect(()=>{
28
+ /**
29
+ * A verdict describes the settings it was given, not the connection's name.
30
+ *
31
+ * Keyed on the id alone, "Reachable" survived editing the base URL and saving:
32
+ * the record object is replaced but its id is not, so this never re-ran and a
33
+ * green badge sat under an address that had never been called. Everything the
34
+ * test actually depends on is in the key.
35
+ */ useEffect(()=>{
29
36
  clear();
30
37
  }, [
31
- source?.id
38
+ source?.id,
39
+ source?.baseUrl,
40
+ source?.authType,
41
+ source?.tokenUrl,
42
+ source?.tlsVerify
32
43
  ]);
33
44
  const handleTest = ()=>{
34
45
  if (!source) return;
@@ -26,7 +26,15 @@ export function EndpointSample({ endpoint, actions }) {
26
26
  const selectedRef = useRef(store.selectedEndpointId);
27
27
  selectedRef.current = store.selectedEndpointId;
28
28
  const params = pathParamNames(endpoint.path);
29
- const sample = store.sample;
29
+ /**
30
+ * Only a sample taken of THIS call.
31
+ *
32
+ * The card is keyed per selection, so switching calls removes it and a request
33
+ * still in flight settles with nowhere to report to — except the shared slot,
34
+ * which it writes to regardless. Before the tag, that answer appeared under
35
+ * whichever call was open when it landed, and the mapping built next took its
36
+ * field list from a call that had never returned those fields.
37
+ */ const sample = store.sampleEndpointId === endpoint.id ? store.sample : null;
30
38
  let unfilled = 0;
31
39
  for (const name of params){
32
40
  if (!(values[name] ?? '').trim()) unfilled += 1;
@@ -60,11 +68,11 @@ export function EndpointSample({ endpoint, actions }) {
60
68
  store.setError(null);
61
69
  actions.sampleEndpoint(requestedFor, params.length > 0 ? values : undefined).then((result)=>{
62
70
  if (settleElsewhere(mine, requestedFor)) return;
63
- store.setSample(result);
71
+ store.setSample(result, requestedFor);
64
72
  if (!result.ok && result.error) store.setError(result.error);
65
73
  }).catch((error)=>{
66
74
  if (settleElsewhere(mine, requestedFor)) return;
67
- store.setSample(null);
75
+ store.setSample(null, requestedFor);
68
76
  store.setError(error instanceof Error ? error.message : 'Sampling the endpoint failed.');
69
77
  });
70
78
  };
@@ -196,7 +196,10 @@ export function MappingsTab({ actions }) {
196
196
  // to and the editor matches it against the mapping's own choice. The editor
197
197
  // falls back to the endpoint's STORED sample — which is why the sample is
198
198
  // persisted at all, and what a connection revisited tomorrow has.
199
- const freshSample = store.selectedEndpointId && store.sample ? {
199
+ const freshSample = // The tag must match: a field list offered here becomes the mapping, and a
200
+ // sample left over from another call would name fields the chosen one does
201
+ // not return.
202
+ store.selectedEndpointId && store.sample && store.sampleEndpointId === store.selectedEndpointId ? {
200
203
  endpointId: store.selectedEndpointId,
201
204
  fields: store.sample.fields ?? []
202
205
  } : null;
@@ -52,6 +52,7 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
52
52
  store.mappingsLoading = false;
53
53
  store.runsLoading = false;
54
54
  store.sample = null;
55
+ store.sampleEndpointId = null;
55
56
  store.sampling = false;
56
57
  store.plan = null;
57
58
  store.planning = false;
@@ -100,6 +101,7 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
100
101
  // The sample describes ONE endpoint; carrying it over would offer the
101
102
  // wrong field list to the next mapping.
102
103
  store.sample = null;
104
+ store.sampleEndpointId = null;
103
105
  // And the WAIT for a sample belongs to the endpoint that was left, not to
104
106
  // the one arrived at. Clearing the record without clearing the flag left
105
107
  // the next endpoint showing a skeleton for a request that was never going
@@ -157,8 +159,9 @@ export const { useStore: useIntegrationsStore } = createStore(initial, {
157
159
  setRunsLoading: (store)=>(loading)=>{
158
160
  store.runsLoading = loading;
159
161
  },
160
- setSample: (store)=>(sample)=>batch(()=>{
162
+ setSample: (store)=>(sample, endpointId)=>batch(()=>{
161
163
  store.sample = sample;
164
+ store.sampleEndpointId = sample ? endpointId : null;
162
165
  store.sampling = false;
163
166
  }),
164
167
  setSampling: (store)=>(sampling)=>{
@@ -45,6 +45,19 @@ export type State = {
45
45
  runsLoading: boolean;
46
46
  /** The real shape of the selected endpoint's response, once sampled. */
47
47
  sample: EndpointSampleValue | null;
48
+ /**
49
+ * The call the sample in hand describes.
50
+ *
51
+ * A sample is a field list, and a field list is what the next mapping is built
52
+ * from — so one written against the wrong call produces a mapping of fields
53
+ * that call never returns. The card is keyed per selection and therefore
54
+ * REMOVED when the operator clicks another call, which means a request still
55
+ * in flight settles into a component that no longer exists and writes to this
56
+ * shared slot anyway. Tagging is what makes that write harmless: it lands
57
+ * under the call it was asked for, and whoever is showing a different one
58
+ * simply does not read it.
59
+ */
60
+ sampleEndpointId: string | null;
48
61
  sampling: boolean;
49
62
  /** What a run would do. Cleared whenever the mapping changes underneath it. */
50
63
  plan: RunPlanValue | null;
@@ -114,7 +127,7 @@ export type Actions = {
114
127
  selectMapping: (id: string | null) => void;
115
128
  setRuns: (items: IntegrationRun[], total: number) => void;
116
129
  setRunsLoading: (loading: boolean) => void;
117
- setSample: (sample: EndpointSampleValue | null) => void;
130
+ setSample: (sample: EndpointSampleValue | null, endpointId: string | null) => void;
118
131
  setSampling: (sampling: boolean) => void;
119
132
  setPlan: (plan: RunPlanValue | null) => void;
120
133
  setPlanning: (planning: boolean) => void;
@@ -21,6 +21,7 @@ export const initial = {
21
21
  runsTotal: 0,
22
22
  runsLoading: false,
23
23
  sample: null,
24
+ sampleEndpointId: null,
24
25
  sampling: false,
25
26
  plan: null,
26
27
  planning: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nucleus-core-ts",
3
- "version": "0.9.850",
3
+ "version": "0.9.851",
4
4
  "description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
5
5
  "author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
6
6
  "license": "SEE LICENSE IN LICENSE",