nucleus-core-ts 0.9.802 → 0.9.804
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/fe/components/IntegrationsPage/components/ConnectionSwitcher.d.ts +13 -0
- package/dist/fe/components/IntegrationsPage/components/ConnectionSwitcher.js +51 -0
- package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +21 -19
- package/dist/fe/components/IntegrationsPage/components/EndpointList.js +8 -6
- package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +14 -12
- package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +3 -1
- package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +51 -56
- package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +5 -3
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.js +16 -13
- package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +3 -1
- package/dist/fe/components/IntegrationsPage/text/index.d.ts +72 -0
- package/dist/fe/components/IntegrationsPage/text/index.js +72 -2
- package/dist/fe/types/index.d.ts +14 -10
- package/package.json +1 -1
- package/scripts/generate-schema.test.ts +32 -1
- package/scripts/generate-schema.ts +19 -2
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The chosen connection, as one line instead of a column.
|
|
3
|
+
*
|
|
4
|
+
* The panel used to keep the connection list as a permanent rail, so the work
|
|
5
|
+
* happened in what was left — and the work is itself a list beside an editor.
|
|
6
|
+
* Three columns inside an admin page that already has a nav is how a form ends
|
|
7
|
+
* up past the right edge of the screen with the page scrolling sideways.
|
|
8
|
+
*
|
|
9
|
+
* A connection is CHOSEN once and then worked in. Once it is chosen the rail
|
|
10
|
+
* has nothing left to say that this line does not, so the rail goes and the
|
|
11
|
+
* whole width goes to the step.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ConnectionSwitcher(): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
6
|
+
import { integrationsPageTheme as theme } from '../theme';
|
|
7
|
+
/**
|
|
8
|
+
* The chosen connection, as one line instead of a column.
|
|
9
|
+
*
|
|
10
|
+
* The panel used to keep the connection list as a permanent rail, so the work
|
|
11
|
+
* happened in what was left — and the work is itself a list beside an editor.
|
|
12
|
+
* Three columns inside an admin page that already has a nav is how a form ends
|
|
13
|
+
* up past the right edge of the screen with the page scrolling sideways.
|
|
14
|
+
*
|
|
15
|
+
* A connection is CHOSEN once and then worked in. Once it is chosen the rail
|
|
16
|
+
* has nothing left to say that this line does not, so the rail goes and the
|
|
17
|
+
* whole width goes to the step.
|
|
18
|
+
*/ export function ConnectionSwitcher() {
|
|
19
|
+
const store = useIntegrationsStore();
|
|
20
|
+
const labels = useLabels();
|
|
21
|
+
const source = store.sources.find((candidate)=>candidate.id === store.selectedSourceId);
|
|
22
|
+
if (!source) return null;
|
|
23
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
24
|
+
className: cn('flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200/80 bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900'),
|
|
25
|
+
children: [
|
|
26
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
27
|
+
className: "flex min-w-0 flex-col",
|
|
28
|
+
children: [
|
|
29
|
+
/*#__PURE__*/ _jsx("span", {
|
|
30
|
+
className: cn(theme.field.label, 'text-slate-400 dark:text-slate-500'),
|
|
31
|
+
children: labels.connections.heading
|
|
32
|
+
}),
|
|
33
|
+
/*#__PURE__*/ _jsx("span", {
|
|
34
|
+
className: "truncate font-semibold text-base text-slate-900 dark:text-slate-100",
|
|
35
|
+
children: source.name
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx("span", {
|
|
38
|
+
className: cn(theme.list.itemMeta, 'mt-0.5'),
|
|
39
|
+
children: source.baseUrl
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
}),
|
|
43
|
+
/*#__PURE__*/ _jsx("button", {
|
|
44
|
+
className: theme.button.secondary,
|
|
45
|
+
onClick: ()=>store.selectSource(null),
|
|
46
|
+
type: "button",
|
|
47
|
+
children: labels.connections.change
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { useLabels } from '../text/context';
|
|
4
5
|
import { integrationsPageTheme } from '../theme';
|
|
5
6
|
import { describedBy } from './formSupport';
|
|
6
7
|
import { Field } from './Primitives';
|
|
@@ -18,15 +19,16 @@ const METHODS = [
|
|
|
18
19
|
'DELETE'
|
|
19
20
|
];
|
|
20
21
|
export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
22
|
+
const labels = useLabels();
|
|
21
23
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
22
24
|
children: [
|
|
23
25
|
/*#__PURE__*/ _jsxs("div", {
|
|
24
26
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
25
27
|
children: [
|
|
26
28
|
/*#__PURE__*/ _jsx(Field, {
|
|
27
|
-
hint:
|
|
29
|
+
hint: labels.endpoints.tagHint,
|
|
28
30
|
htmlFor: "endpoint-tag",
|
|
29
|
-
label:
|
|
31
|
+
label: labels.endpoints.tag,
|
|
30
32
|
children: /*#__PURE__*/ _jsx("input", {
|
|
31
33
|
"aria-describedby": "endpoint-tag-hint",
|
|
32
34
|
className: theme.field.input,
|
|
@@ -35,14 +37,14 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
35
37
|
onChange: (event)=>onChange({
|
|
36
38
|
tag: event.target.value
|
|
37
39
|
}),
|
|
38
|
-
placeholder:
|
|
40
|
+
placeholder: labels.endpoints.tagPlaceholder,
|
|
39
41
|
type: "text",
|
|
40
42
|
value: draft.tag
|
|
41
43
|
})
|
|
42
44
|
}),
|
|
43
45
|
/*#__PURE__*/ _jsx(Field, {
|
|
44
46
|
htmlFor: "endpoint-name",
|
|
45
|
-
label:
|
|
47
|
+
label: labels.endpoints.name,
|
|
46
48
|
children: /*#__PURE__*/ _jsx("input", {
|
|
47
49
|
className: theme.field.input,
|
|
48
50
|
disabled: disabled,
|
|
@@ -50,7 +52,7 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
50
52
|
onChange: (event)=>onChange({
|
|
51
53
|
name: event.target.value
|
|
52
54
|
}),
|
|
53
|
-
placeholder:
|
|
55
|
+
placeholder: labels.endpoints.namePlaceholder,
|
|
54
56
|
type: "text",
|
|
55
57
|
value: draft.name
|
|
56
58
|
})
|
|
@@ -64,7 +66,7 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
64
66
|
className: "sm:w-32",
|
|
65
67
|
children: /*#__PURE__*/ _jsx(Field, {
|
|
66
68
|
htmlFor: "endpoint-method",
|
|
67
|
-
label:
|
|
69
|
+
label: labels.endpoints.method,
|
|
68
70
|
children: /*#__PURE__*/ _jsx("select", {
|
|
69
71
|
className: theme.field.input,
|
|
70
72
|
disabled: disabled,
|
|
@@ -87,9 +89,9 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
87
89
|
/*#__PURE__*/ _jsx("div", {
|
|
88
90
|
className: "min-w-0 flex-1",
|
|
89
91
|
children: /*#__PURE__*/ _jsx(Field, {
|
|
90
|
-
hint:
|
|
92
|
+
hint: labels.endpoints.pathHint,
|
|
91
93
|
htmlFor: "endpoint-path",
|
|
92
|
-
label:
|
|
94
|
+
label: labels.endpoints.path,
|
|
93
95
|
children: /*#__PURE__*/ _jsx("input", {
|
|
94
96
|
"aria-describedby": "endpoint-path-hint",
|
|
95
97
|
className: theme.field.input,
|
|
@@ -98,7 +100,7 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
98
100
|
onChange: (event)=>onChange({
|
|
99
101
|
path: event.target.value
|
|
100
102
|
}),
|
|
101
|
-
placeholder:
|
|
103
|
+
placeholder: labels.endpoints.pathPlaceholder,
|
|
102
104
|
type: "text",
|
|
103
105
|
value: draft.path
|
|
104
106
|
})
|
|
@@ -107,9 +109,9 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
107
109
|
]
|
|
108
110
|
}),
|
|
109
111
|
/*#__PURE__*/ _jsx(Field, {
|
|
110
|
-
hint:
|
|
112
|
+
hint: labels.endpoints.rootPathHint,
|
|
111
113
|
htmlFor: "endpoint-root",
|
|
112
|
-
label:
|
|
114
|
+
label: labels.endpoints.rootPath,
|
|
113
115
|
children: /*#__PURE__*/ _jsx("input", {
|
|
114
116
|
"aria-describedby": "endpoint-root-hint",
|
|
115
117
|
className: theme.field.input,
|
|
@@ -118,16 +120,16 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
118
120
|
onChange: (event)=>onChange({
|
|
119
121
|
responseRootPath: event.target.value
|
|
120
122
|
}),
|
|
121
|
-
placeholder:
|
|
123
|
+
placeholder: labels.endpoints.rootPathPlaceholder,
|
|
122
124
|
type: "text",
|
|
123
125
|
value: draft.responseRootPath
|
|
124
126
|
})
|
|
125
127
|
}),
|
|
126
128
|
/*#__PURE__*/ _jsx(Field, {
|
|
127
129
|
error: errors?.queryParams,
|
|
128
|
-
hint:
|
|
130
|
+
hint: labels.endpoints.queryParamsHint,
|
|
129
131
|
htmlFor: "endpoint-query",
|
|
130
|
-
label:
|
|
132
|
+
label: labels.endpoints.queryParams,
|
|
131
133
|
children: /*#__PURE__*/ _jsx("textarea", {
|
|
132
134
|
"aria-describedby": describedBy('endpoint-query', true, Boolean(errors?.queryParams)),
|
|
133
135
|
className: cn(theme.field.input, 'h-auto min-h-16 py-2 font-mono text-xs'),
|
|
@@ -143,9 +145,9 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
143
145
|
}),
|
|
144
146
|
draft.method === 'GET' ? null : /*#__PURE__*/ _jsx(Field, {
|
|
145
147
|
error: errors?.requestBody,
|
|
146
|
-
hint:
|
|
148
|
+
hint: labels.endpoints.requestBodyHint,
|
|
147
149
|
htmlFor: "endpoint-body",
|
|
148
|
-
label:
|
|
150
|
+
label: labels.endpoints.requestBody,
|
|
149
151
|
children: /*#__PURE__*/ _jsx("textarea", {
|
|
150
152
|
"aria-describedby": describedBy('endpoint-body', true, Boolean(errors?.requestBody)),
|
|
151
153
|
className: cn(theme.field.input, 'h-auto min-h-20 py-2 font-mono text-xs'),
|
|
@@ -161,7 +163,7 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
161
163
|
}),
|
|
162
164
|
/*#__PURE__*/ _jsx(Field, {
|
|
163
165
|
htmlFor: "endpoint-description",
|
|
164
|
-
label:
|
|
166
|
+
label: labels.endpoints.description,
|
|
165
167
|
children: /*#__PURE__*/ _jsx("textarea", {
|
|
166
168
|
className: cn(theme.field.input, 'h-auto min-h-20 py-2'),
|
|
167
169
|
disabled: disabled,
|
|
@@ -169,7 +171,7 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
169
171
|
onChange: (event)=>onChange({
|
|
170
172
|
description: event.target.value
|
|
171
173
|
}),
|
|
172
|
-
placeholder:
|
|
174
|
+
placeholder: labels.endpoints.descriptionPlaceholder,
|
|
173
175
|
rows: 3,
|
|
174
176
|
value: draft.description
|
|
175
177
|
})
|
|
@@ -190,7 +192,7 @@ export function EndpointFormFields({ draft, disabled, errors, onChange }) {
|
|
|
190
192
|
}),
|
|
191
193
|
/*#__PURE__*/ _jsx("span", {
|
|
192
194
|
className: theme.field.label,
|
|
193
|
-
children:
|
|
195
|
+
children: labels.endpoints.enabled
|
|
194
196
|
})
|
|
195
197
|
]
|
|
196
198
|
})
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
4
|
import { cn } from '../../../utils/cn';
|
|
5
5
|
import { useIntegrationsStore } from '../store';
|
|
6
|
+
import { useLabels } from '../text/context';
|
|
6
7
|
import { integrationsPageTheme } from '../theme';
|
|
7
8
|
import { Badge, Field, Notice } from './Primitives';
|
|
8
9
|
import { ListSkeleton } from './Skeletons';
|
|
@@ -25,6 +26,7 @@ const METHOD_TONE = {
|
|
|
25
26
|
DELETE: 'danger'
|
|
26
27
|
};
|
|
27
28
|
export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloadToken = 0 }) {
|
|
29
|
+
const labels = useLabels();
|
|
28
30
|
const store = useIntegrationsStore();
|
|
29
31
|
const search = store.endpointsSearch;
|
|
30
32
|
const selectedId = store.selectedEndpointId;
|
|
@@ -91,7 +93,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
|
|
|
91
93
|
};
|
|
92
94
|
const firstLoad = list.loading && list.items.length === 0;
|
|
93
95
|
return /*#__PURE__*/ _jsxs("section", {
|
|
94
|
-
"aria-label":
|
|
96
|
+
"aria-label": labels.endpoints.heading,
|
|
95
97
|
className: theme.layout.aside,
|
|
96
98
|
children: [
|
|
97
99
|
/*#__PURE__*/ _jsxs("div", {
|
|
@@ -101,7 +103,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
|
|
|
101
103
|
children: [
|
|
102
104
|
/*#__PURE__*/ _jsx("h2", {
|
|
103
105
|
className: theme.header.title,
|
|
104
|
-
children:
|
|
106
|
+
children: labels.endpoints.heading
|
|
105
107
|
}),
|
|
106
108
|
/*#__PURE__*/ _jsx("p", {
|
|
107
109
|
className: theme.header.subtitle,
|
|
@@ -122,12 +124,12 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
|
|
|
122
124
|
}),
|
|
123
125
|
/*#__PURE__*/ _jsx(Field, {
|
|
124
126
|
htmlFor: "endpoint-search",
|
|
125
|
-
label:
|
|
127
|
+
label: labels.endpoints.searchLabel,
|
|
126
128
|
children: /*#__PURE__*/ _jsx("input", {
|
|
127
129
|
className: theme.field.input,
|
|
128
130
|
id: "endpoint-search",
|
|
129
131
|
onChange: (event)=>setTerm(event.target.value),
|
|
130
|
-
placeholder:
|
|
132
|
+
placeholder: labels.endpoints.searchPlaceholder,
|
|
131
133
|
type: "search",
|
|
132
134
|
value: term
|
|
133
135
|
})
|
|
@@ -136,7 +138,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
|
|
|
136
138
|
rows: 6
|
|
137
139
|
}) : null,
|
|
138
140
|
list.error ? /*#__PURE__*/ _jsxs(Notice, {
|
|
139
|
-
title:
|
|
141
|
+
title: labels.endpoints.loadFailed,
|
|
140
142
|
tone: "danger",
|
|
141
143
|
children: [
|
|
142
144
|
list.error,
|
|
@@ -198,7 +200,7 @@ export function EndpointList({ sourceId, actions, onNewEndpoint, onSelect, reloa
|
|
|
198
200
|
}),
|
|
199
201
|
list.loading && list.items.length > 0 ? /*#__PURE__*/ _jsx("p", {
|
|
200
202
|
className: theme.field.hint,
|
|
201
|
-
children:
|
|
203
|
+
children: labels.endpoints.loadingMore
|
|
202
204
|
}) : null
|
|
203
205
|
]
|
|
204
206
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from 'react';
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { integrationsPageTheme } from '../theme';
|
|
6
7
|
import { Badge, CodeBlock, Field, Notice, Stat } from './Primitives';
|
|
7
8
|
import { pathParamNames } from './pathParams';
|
|
@@ -15,6 +16,7 @@ import { CardSkeleton, StatGridSkeleton } from './Skeletons';
|
|
|
15
16
|
* their own block here rather than being folded into a generic "no data".
|
|
16
17
|
*/ const theme = integrationsPageTheme;
|
|
17
18
|
export function EndpointSample({ endpoint, actions }) {
|
|
19
|
+
const labels = useLabels();
|
|
18
20
|
const store = useIntegrationsStore();
|
|
19
21
|
const [values, setValues] = useState({});
|
|
20
22
|
// Kept in step with the render so an in-flight reply can ask what is selected
|
|
@@ -53,14 +55,14 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
53
55
|
children: [
|
|
54
56
|
/*#__PURE__*/ _jsx("h3", {
|
|
55
57
|
className: theme.card.title,
|
|
56
|
-
children:
|
|
58
|
+
children: labels.endpoints.sampleHeading
|
|
57
59
|
}),
|
|
58
60
|
/*#__PURE__*/ _jsx("button", {
|
|
59
61
|
className: theme.button.secondary,
|
|
60
62
|
disabled: store.sampling || unfilled > 0,
|
|
61
63
|
onClick: run,
|
|
62
64
|
type: "button",
|
|
63
|
-
children: store.sampling ? 'Sampling…' : '
|
|
65
|
+
children: store.sampling ? 'Sampling…' : '{labels.endpoints.sampleSend}'
|
|
64
66
|
})
|
|
65
67
|
]
|
|
66
68
|
}),
|
|
@@ -72,7 +74,7 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
72
74
|
children: [
|
|
73
75
|
/*#__PURE__*/ _jsx("legend", {
|
|
74
76
|
className: theme.field.label,
|
|
75
|
-
children:
|
|
77
|
+
children: labels.endpoints.samplePathValues
|
|
76
78
|
}),
|
|
77
79
|
/*#__PURE__*/ _jsx("p", {
|
|
78
80
|
className: theme.field.hint,
|
|
@@ -90,7 +92,7 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
90
92
|
...previous,
|
|
91
93
|
[name]: event.target.value
|
|
92
94
|
})),
|
|
93
|
-
placeholder:
|
|
95
|
+
placeholder: labels.endpoints.samplePlaceholder,
|
|
94
96
|
type: "text",
|
|
95
97
|
value: values[name] ?? ''
|
|
96
98
|
})
|
|
@@ -110,32 +112,32 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
110
112
|
className: theme.stat.grid,
|
|
111
113
|
children: [
|
|
112
114
|
/*#__PURE__*/ _jsx(Stat, {
|
|
113
|
-
label:
|
|
115
|
+
label: labels.endpoints.sampleStatus,
|
|
114
116
|
tone: sample.ok ? 'success' : 'danger',
|
|
115
117
|
value: sample.status
|
|
116
118
|
}),
|
|
117
119
|
/*#__PURE__*/ _jsx(Stat, {
|
|
118
|
-
label:
|
|
120
|
+
label: labels.endpoints.sampleRecords,
|
|
119
121
|
tone: sample.recordCount === 0 ? 'warning' : undefined,
|
|
120
122
|
value: sample.recordCount
|
|
121
123
|
}),
|
|
122
124
|
/*#__PURE__*/ _jsx(Stat, {
|
|
123
|
-
label:
|
|
125
|
+
label: labels.endpoints.sampleFields,
|
|
124
126
|
value: sample.fields.length
|
|
125
127
|
}),
|
|
126
128
|
/*#__PURE__*/ _jsx(Stat, {
|
|
127
|
-
label:
|
|
129
|
+
label: labels.endpoints.sampleBodyKeys,
|
|
128
130
|
value: sample.bodyKeys.length
|
|
129
131
|
})
|
|
130
132
|
]
|
|
131
133
|
}),
|
|
132
134
|
sample.error ? /*#__PURE__*/ _jsx(Notice, {
|
|
133
|
-
title:
|
|
135
|
+
title: labels.endpoints.sampleFailed,
|
|
134
136
|
tone: "danger",
|
|
135
137
|
children: sample.error
|
|
136
138
|
}) : null,
|
|
137
139
|
sample.recordCount === 0 ? /*#__PURE__*/ _jsxs(Notice, {
|
|
138
|
-
title:
|
|
140
|
+
title: labels.endpoints.sampleNoRecords,
|
|
139
141
|
tone: "warning",
|
|
140
142
|
children: [
|
|
141
143
|
/*#__PURE__*/ _jsx("p", {
|
|
@@ -166,7 +168,7 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
166
168
|
children: [
|
|
167
169
|
/*#__PURE__*/ _jsx("h4", {
|
|
168
170
|
className: theme.field.label,
|
|
169
|
-
children:
|
|
171
|
+
children: labels.endpoints.sampleFirstRecordFields
|
|
170
172
|
}),
|
|
171
173
|
/*#__PURE__*/ _jsx("ul", {
|
|
172
174
|
className: "flex flex-wrap gap-1.5",
|
|
@@ -184,7 +186,7 @@ export function EndpointSample({ endpoint, actions }) {
|
|
|
184
186
|
children: [
|
|
185
187
|
/*#__PURE__*/ _jsx("h4", {
|
|
186
188
|
className: theme.field.label,
|
|
187
|
-
children:
|
|
189
|
+
children: labels.endpoints.sampleRows
|
|
188
190
|
}),
|
|
189
191
|
/*#__PURE__*/ _jsx(CodeBlock, {
|
|
190
192
|
children: JSON.stringify(sample.sample, null, 2)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
4
|
import { useIntegrationsStore } from '../store';
|
|
5
|
+
import { useLabels } from '../text/context';
|
|
5
6
|
import { integrationsPageTheme } from '../theme';
|
|
6
7
|
import { EndpointForm } from './EndpointForm';
|
|
7
8
|
import { EndpointList } from './EndpointList';
|
|
@@ -14,6 +15,7 @@ import { EndpointSample } from './EndpointSample';
|
|
|
14
15
|
* is what makes switching selection a fresh form rather than a merged one.
|
|
15
16
|
*/ const theme = integrationsPageTheme;
|
|
16
17
|
export function EndpointsTab({ actions }) {
|
|
18
|
+
const labels = useLabels();
|
|
17
19
|
const store = useIntegrationsStore();
|
|
18
20
|
const sourceId = store.selectedSourceId;
|
|
19
21
|
const [selected, setSelected] = useState(null);
|
|
@@ -60,7 +62,7 @@ export function EndpointsTab({ actions }) {
|
|
|
60
62
|
if (!sourceId) {
|
|
61
63
|
return /*#__PURE__*/ _jsx("p", {
|
|
62
64
|
className: theme.list.empty,
|
|
63
|
-
children:
|
|
65
|
+
children: labels.endpoints.selectConnection
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
// Guarded against the store: the selection is the store's, and a record left
|
|
@@ -10,6 +10,7 @@ import { IntegrationsLabelsProvider } from '../text/context';
|
|
|
10
10
|
import { integrationsPageTheme } from '../theme';
|
|
11
11
|
import { ConnectionList } from './ConnectionList';
|
|
12
12
|
import { ConnectionSettingsTab } from './ConnectionSettingsTab';
|
|
13
|
+
import { ConnectionSwitcher } from './ConnectionSwitcher';
|
|
13
14
|
import { ConnectionTabs, connectionTabId, connectionTabPanelId } from './ConnectionTabs';
|
|
14
15
|
import { EndpointsTab } from './EndpointsTab';
|
|
15
16
|
import { MappingsTab } from './MappingsTab';
|
|
@@ -185,68 +186,62 @@ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect a
|
|
|
185
186
|
]
|
|
186
187
|
}) : null
|
|
187
188
|
}),
|
|
188
|
-
/*#__PURE__*/
|
|
189
|
-
className:
|
|
189
|
+
/*#__PURE__*/ _jsx("div", {
|
|
190
|
+
className: "flex min-w-0 flex-col gap-4 sm:gap-5",
|
|
190
191
|
ref: contentRef,
|
|
191
|
-
children:
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
children: [
|
|
205
|
-
/*#__PURE__*/ _jsx(ConnectionTabs, {
|
|
206
|
-
onChange: changeTab,
|
|
207
|
-
steps: steps,
|
|
208
|
-
value: tab
|
|
209
|
-
}),
|
|
210
|
-
/*#__PURE__*/ _jsxs("section", {
|
|
211
|
-
"aria-labelledby": connectionTabId(tab),
|
|
212
|
-
className: theme.layout.main,
|
|
213
|
-
id: connectionTabPanelId(tab),
|
|
214
|
-
role: "tabpanel",
|
|
215
|
-
children: [
|
|
216
|
-
tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
217
|
-
actions: actions,
|
|
218
|
-
onSaved: handleSourceSaved
|
|
219
|
-
}) : null,
|
|
220
|
-
tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
|
|
221
|
-
actions: actions
|
|
222
|
-
}) : null,
|
|
223
|
-
tab === 'mappings' ? /*#__PURE__*/ _jsx(MappingsTab, {
|
|
224
|
-
actions: actions
|
|
225
|
-
}) : null,
|
|
226
|
-
tab === 'runs' ? /*#__PURE__*/ _jsx(RunsTab, {
|
|
227
|
-
actions: actions
|
|
228
|
-
}) : null
|
|
229
|
-
]
|
|
230
|
-
})
|
|
231
|
-
]
|
|
232
|
-
}) : // Not a dead end. With nothing selected the only useful thing on this
|
|
233
|
-
// screen is creating a connection, so that is what it shows — the
|
|
234
|
-
// previous message told an operator to select from a list that is
|
|
235
|
-
// empty on a fresh install, with no way to add the first one.
|
|
236
|
-
/*#__PURE__*/ _jsxs(_Fragment, {
|
|
192
|
+
children: hasConnection ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
193
|
+
children: [
|
|
194
|
+
/*#__PURE__*/ _jsx(ConnectionSwitcher, {}),
|
|
195
|
+
/*#__PURE__*/ _jsx(ConnectionTabs, {
|
|
196
|
+
onChange: changeTab,
|
|
197
|
+
steps: steps,
|
|
198
|
+
value: tab
|
|
199
|
+
}),
|
|
200
|
+
/*#__PURE__*/ _jsxs("section", {
|
|
201
|
+
"aria-labelledby": connectionTabId(tab),
|
|
202
|
+
className: theme.layout.main,
|
|
203
|
+
id: connectionTabPanelId(tab),
|
|
204
|
+
role: "tabpanel",
|
|
237
205
|
children: [
|
|
238
|
-
/*#__PURE__*/ _jsx(
|
|
239
|
-
className: theme.list.empty,
|
|
240
|
-
children: "Select a connection on the left, or set up a new one here."
|
|
241
|
-
}),
|
|
242
|
-
/*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
206
|
+
tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
243
207
|
actions: actions,
|
|
244
208
|
onSaved: handleSourceSaved
|
|
245
|
-
})
|
|
209
|
+
}) : null,
|
|
210
|
+
tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
|
|
211
|
+
actions: actions
|
|
212
|
+
}) : null,
|
|
213
|
+
tab === 'mappings' ? /*#__PURE__*/ _jsx(MappingsTab, {
|
|
214
|
+
actions: actions
|
|
215
|
+
}) : null,
|
|
216
|
+
tab === 'runs' ? /*#__PURE__*/ _jsx(RunsTab, {
|
|
217
|
+
actions: actions,
|
|
218
|
+
onSubscribeProgress: onSubscribeProgress
|
|
219
|
+
}) : null
|
|
246
220
|
]
|
|
247
221
|
})
|
|
248
|
-
|
|
249
|
-
|
|
222
|
+
]
|
|
223
|
+
}) : // Nothing chosen yet, so choosing IS the screen: the list and the
|
|
224
|
+
// blank form side by side, both with room.
|
|
225
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
226
|
+
className: theme.layout.split,
|
|
227
|
+
children: [
|
|
228
|
+
/*#__PURE__*/ _jsx("aside", {
|
|
229
|
+
"aria-label": labels.connections.heading,
|
|
230
|
+
className: theme.layout.aside,
|
|
231
|
+
children: /*#__PURE__*/ _jsx(ConnectionList, {
|
|
232
|
+
actions: actions,
|
|
233
|
+
reloadToken: sourcesReloadToken
|
|
234
|
+
})
|
|
235
|
+
}),
|
|
236
|
+
/*#__PURE__*/ _jsx("section", {
|
|
237
|
+
className: theme.layout.main,
|
|
238
|
+
children: /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
|
|
239
|
+
actions: actions,
|
|
240
|
+
onSaved: handleSourceSaved
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
]
|
|
244
|
+
})
|
|
250
245
|
})
|
|
251
246
|
]
|
|
252
247
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useLabels } from '../text/context';
|
|
3
4
|
import { integrationsPageTheme } from '../theme';
|
|
4
5
|
import { Field, Notice } from './Primitives';
|
|
5
6
|
/**
|
|
@@ -141,6 +142,7 @@ const SIZE_LABEL = 'Records per request';
|
|
|
141
142
|
]
|
|
142
143
|
};
|
|
143
144
|
export function PaginationFields({ idPrefix, value, disabled, onChange }) {
|
|
145
|
+
const labels = useLabels();
|
|
144
146
|
const write = (spec, entered)=>{
|
|
145
147
|
const next = {
|
|
146
148
|
...value
|
|
@@ -161,14 +163,14 @@ export function PaginationFields({ idPrefix, value, disabled, onChange }) {
|
|
|
161
163
|
className: "flex flex-col gap-3",
|
|
162
164
|
children: [
|
|
163
165
|
/*#__PURE__*/ _jsx(Notice, {
|
|
164
|
-
title:
|
|
166
|
+
title: labels.endpoints.paginationTitle,
|
|
165
167
|
tone: "warning",
|
|
166
168
|
children: "Nothing here is detected for you. Pick the wrong style and the import still succeeds — it reads the first page and never asks for the rest. In a destructive write mode the records it never saw count as missing, so the run deletes or deactivates them."
|
|
167
169
|
}),
|
|
168
170
|
/*#__PURE__*/ _jsx(Field, {
|
|
169
|
-
hint:
|
|
171
|
+
hint: labels.endpoints.paginationHint,
|
|
170
172
|
htmlFor: `${idPrefix}-kind`,
|
|
171
|
-
label:
|
|
173
|
+
label: labels.endpoints.paginationStyle,
|
|
172
174
|
children: /*#__PURE__*/ _jsx("select", {
|
|
173
175
|
"aria-describedby": `${idPrefix}-kind-hint`,
|
|
174
176
|
className: theme.field.input,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
|
+
import { useLabels } from '../text/context';
|
|
4
5
|
import { integrationsPageTheme as theme } from '../theme';
|
|
5
6
|
import { endpointLabel } from './formSupport';
|
|
6
7
|
import { Field } from './Primitives';
|
|
7
8
|
export function ParamBindingRow({ binding, onChange, onRemove, siblings, entities, disabled }) {
|
|
9
|
+
const labels = useLabels();
|
|
8
10
|
const rowId = useId();
|
|
9
11
|
const changeSource = (from)=>{
|
|
10
12
|
if (from === 'static') onChange({
|
|
@@ -40,15 +42,15 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
40
42
|
children: [
|
|
41
43
|
/*#__PURE__*/ _jsx("option", {
|
|
42
44
|
value: "static",
|
|
43
|
-
children:
|
|
45
|
+
children: labels.endpoints.bindStatic
|
|
44
46
|
}),
|
|
45
47
|
/*#__PURE__*/ _jsx("option", {
|
|
46
48
|
value: "endpoint",
|
|
47
|
-
children:
|
|
49
|
+
children: labels.endpoints.bindEndpoint
|
|
48
50
|
}),
|
|
49
51
|
/*#__PURE__*/ _jsx("option", {
|
|
50
52
|
value: "entity",
|
|
51
|
-
children:
|
|
53
|
+
children: labels.endpoints.bindEntity
|
|
52
54
|
})
|
|
53
55
|
]
|
|
54
56
|
})
|
|
@@ -67,16 +69,17 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
67
69
|
disabled: disabled,
|
|
68
70
|
onClick: onRemove,
|
|
69
71
|
type: "button",
|
|
70
|
-
children:
|
|
72
|
+
children: labels.common.remove
|
|
71
73
|
})
|
|
72
74
|
]
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
/** The half of a row that depends on which source was chosen. */ function ParamBindingValueFields({ binding, onChange, siblings, entities, disabled, rowId }) {
|
|
78
|
+
const labels = useLabels();
|
|
76
79
|
if (binding.from === 'static') {
|
|
77
80
|
return /*#__PURE__*/ _jsx(Field, {
|
|
78
81
|
htmlFor: `${rowId}-value`,
|
|
79
|
-
label:
|
|
82
|
+
label: labels.endpoints.bindValue,
|
|
80
83
|
children: /*#__PURE__*/ _jsx("input", {
|
|
81
84
|
className: theme.field.input,
|
|
82
85
|
disabled: disabled,
|
|
@@ -85,7 +88,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
85
88
|
...binding,
|
|
86
89
|
value: event.target.value
|
|
87
90
|
}),
|
|
88
|
-
placeholder:
|
|
91
|
+
placeholder: labels.endpoints.bindValuePlaceholder,
|
|
89
92
|
value: binding.value
|
|
90
93
|
})
|
|
91
94
|
});
|
|
@@ -94,9 +97,9 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
94
97
|
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
95
98
|
children: [
|
|
96
99
|
/*#__PURE__*/ _jsx(Field, {
|
|
97
|
-
hint:
|
|
100
|
+
hint: labels.endpoints.bindEndpointHint,
|
|
98
101
|
htmlFor: `${rowId}-endpoint`,
|
|
99
|
-
label:
|
|
102
|
+
label: labels.endpoints.bindEndpoint,
|
|
100
103
|
children: /*#__PURE__*/ _jsx("select", {
|
|
101
104
|
className: theme.field.input,
|
|
102
105
|
disabled: disabled,
|
|
@@ -114,7 +117,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
114
117
|
}),
|
|
115
118
|
/*#__PURE__*/ _jsx(Field, {
|
|
116
119
|
htmlFor: `${rowId}-field`,
|
|
117
|
-
label:
|
|
120
|
+
label: labels.endpoints.bindField,
|
|
118
121
|
children: /*#__PURE__*/ _jsx("input", {
|
|
119
122
|
className: theme.field.input,
|
|
120
123
|
disabled: disabled,
|
|
@@ -123,7 +126,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
123
126
|
...binding,
|
|
124
127
|
field: event.target.value
|
|
125
128
|
}),
|
|
126
|
-
placeholder:
|
|
129
|
+
placeholder: labels.endpoints.bindFieldPlaceholder,
|
|
127
130
|
value: binding.field
|
|
128
131
|
})
|
|
129
132
|
})
|
|
@@ -135,7 +138,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
135
138
|
children: [
|
|
136
139
|
/*#__PURE__*/ _jsx(Field, {
|
|
137
140
|
htmlFor: `${rowId}-entity`,
|
|
138
|
-
label:
|
|
141
|
+
label: labels.endpoints.bindTable,
|
|
139
142
|
children: /*#__PURE__*/ _jsx("select", {
|
|
140
143
|
className: theme.field.input,
|
|
141
144
|
disabled: disabled,
|
|
@@ -154,7 +157,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
154
157
|
}),
|
|
155
158
|
/*#__PURE__*/ _jsx(Field, {
|
|
156
159
|
htmlFor: `${rowId}-column`,
|
|
157
|
-
label:
|
|
160
|
+
label: labels.endpoints.bindColumn,
|
|
158
161
|
children: /*#__PURE__*/ _jsxs("select", {
|
|
159
162
|
className: theme.field.input,
|
|
160
163
|
disabled: disabled,
|
|
@@ -167,7 +170,7 @@ export function ParamBindingRow({ binding, onChange, onRemove, siblings, entitie
|
|
|
167
170
|
children: [
|
|
168
171
|
/*#__PURE__*/ _jsx("option", {
|
|
169
172
|
value: "",
|
|
170
|
-
children:
|
|
173
|
+
children: labels.endpoints.bindColumnEmpty
|
|
171
174
|
}),
|
|
172
175
|
(selected?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
|
|
173
176
|
value: field,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from 'react';
|
|
4
|
+
import { useLabels } from '../text/context';
|
|
4
5
|
import { integrationsPageTheme as theme } from '../theme';
|
|
5
6
|
import { ParamBindingRow } from './ParamBindingRow';
|
|
6
7
|
import { Notice } from './Primitives';
|
|
@@ -20,6 +21,7 @@ import { Notice } from './Primitives';
|
|
|
20
21
|
* - a column of one of our own tables, for "fetch the detail of everything I
|
|
21
22
|
* already have".
|
|
22
23
|
*/ export function ParamBindingRows({ required, value, onChange, siblings, entities, disabled = false }) {
|
|
24
|
+
const labels = useLabels();
|
|
23
25
|
const groupId = useId();
|
|
24
26
|
const bound = new Set(value.map((binding)=>binding.param));
|
|
25
27
|
const unbound = required.filter((name)=>!bound.has(name));
|
|
@@ -65,7 +67,7 @@ import { Notice } from './Primitives';
|
|
|
65
67
|
]
|
|
66
68
|
}) : null,
|
|
67
69
|
unbound.length > 0 ? /*#__PURE__*/ _jsxs(Notice, {
|
|
68
|
-
title:
|
|
70
|
+
title: labels.endpoints.bindingsTitle,
|
|
69
71
|
tone: "warning",
|
|
70
72
|
children: [
|
|
71
73
|
unbound.join(', '),
|
|
@@ -56,6 +56,8 @@ export type IntegrationsLabels = {
|
|
|
56
56
|
loadFailed: string;
|
|
57
57
|
retry: string;
|
|
58
58
|
off: string;
|
|
59
|
+
/** Returns to the chooser once a connection is being worked in. */
|
|
60
|
+
change: string;
|
|
59
61
|
};
|
|
60
62
|
runs: {
|
|
61
63
|
/** The one-line summary above the buttons. */
|
|
@@ -80,6 +82,75 @@ export type IntegrationsLabels = {
|
|
|
80
82
|
skipped: string;
|
|
81
83
|
dedupKey: string;
|
|
82
84
|
};
|
|
85
|
+
/** Step two: the calls this connection can make. */
|
|
86
|
+
endpoints: {
|
|
87
|
+
heading: string;
|
|
88
|
+
countOne: string;
|
|
89
|
+
countMany: (n: number) => string;
|
|
90
|
+
searchLabel: string;
|
|
91
|
+
searchPlaceholder: string;
|
|
92
|
+
create: string;
|
|
93
|
+
empty: string;
|
|
94
|
+
emptySearch: (term: string) => string;
|
|
95
|
+
loadFailed: string;
|
|
96
|
+
loadingMore: string;
|
|
97
|
+
selectConnection: string;
|
|
98
|
+
editTitle: string;
|
|
99
|
+
newTitle: string;
|
|
100
|
+
tag: string;
|
|
101
|
+
tagHint: string;
|
|
102
|
+
tagPlaceholder: string;
|
|
103
|
+
name: string;
|
|
104
|
+
namePlaceholder: string;
|
|
105
|
+
method: string;
|
|
106
|
+
path: string;
|
|
107
|
+
pathHint: string;
|
|
108
|
+
pathPlaceholder: string;
|
|
109
|
+
rootPath: string;
|
|
110
|
+
rootPathHint: string;
|
|
111
|
+
rootPathPlaceholder: string;
|
|
112
|
+
queryParams: string;
|
|
113
|
+
queryParamsHint: string;
|
|
114
|
+
requestBody: string;
|
|
115
|
+
requestBodyHint: string;
|
|
116
|
+
description: string;
|
|
117
|
+
descriptionPlaceholder: string;
|
|
118
|
+
enabled: string;
|
|
119
|
+
save: string;
|
|
120
|
+
paginationTitle: string;
|
|
121
|
+
paginationBody: string;
|
|
122
|
+
paginationStyle: string;
|
|
123
|
+
paginationHint: string;
|
|
124
|
+
bindingsTitle: string;
|
|
125
|
+
bindingsUnbound: (names: string) => string;
|
|
126
|
+
bindingsNone: string;
|
|
127
|
+
bindFrom: string;
|
|
128
|
+
bindStatic: string;
|
|
129
|
+
bindEndpoint: string;
|
|
130
|
+
bindEntity: string;
|
|
131
|
+
bindValue: string;
|
|
132
|
+
bindValuePlaceholder: string;
|
|
133
|
+
bindEndpointHint: string;
|
|
134
|
+
bindField: string;
|
|
135
|
+
bindFieldPlaceholder: string;
|
|
136
|
+
bindTable: string;
|
|
137
|
+
bindColumn: string;
|
|
138
|
+
bindColumnEmpty: string;
|
|
139
|
+
sampleHeading: string;
|
|
140
|
+
sampleSend: string;
|
|
141
|
+
sampleNothingYet: string;
|
|
142
|
+
samplePathValues: string;
|
|
143
|
+
samplePathValuesHint: string;
|
|
144
|
+
samplePlaceholder: string;
|
|
145
|
+
sampleStatus: string;
|
|
146
|
+
sampleRecords: string;
|
|
147
|
+
sampleFields: string;
|
|
148
|
+
sampleBodyKeys: string;
|
|
149
|
+
sampleFailed: string;
|
|
150
|
+
sampleNoRecords: string;
|
|
151
|
+
sampleFirstRecordFields: string;
|
|
152
|
+
sampleRows: string;
|
|
153
|
+
};
|
|
83
154
|
/** The connection form — the first screen anyone meets. */
|
|
84
155
|
connectionForm: {
|
|
85
156
|
sectionLabel: string;
|
|
@@ -118,6 +189,7 @@ export type IntegrationsLabels = {
|
|
|
118
189
|
saving: string;
|
|
119
190
|
somethingWentWrong: string;
|
|
120
191
|
dismiss: string;
|
|
192
|
+
remove: string;
|
|
121
193
|
};
|
|
122
194
|
};
|
|
123
195
|
/** What every app gets before it says otherwise. */
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
emptySearch: (term)=>`No connection matches “${term}”. Clear the search to see them all.`,
|
|
51
51
|
loadFailed: 'This list could not be loaded',
|
|
52
52
|
retry: 'Try again',
|
|
53
|
-
off: 'Off'
|
|
53
|
+
off: 'Off',
|
|
54
|
+
change: 'Change'
|
|
54
55
|
},
|
|
55
56
|
runs: {
|
|
56
57
|
summary: (target, writeMode, missing)=>`Writes into ${target} · ${writeMode} · missing rows: ${missing}`,
|
|
@@ -74,6 +75,74 @@
|
|
|
74
75
|
skipped: 'Skipped',
|
|
75
76
|
dedupKey: 'Dedup key'
|
|
76
77
|
},
|
|
78
|
+
endpoints: {
|
|
79
|
+
heading: 'Calls',
|
|
80
|
+
countOne: '1 call',
|
|
81
|
+
countMany: (n)=>`${n} calls`,
|
|
82
|
+
searchLabel: 'Search',
|
|
83
|
+
searchPlaceholder: 'Tag, name or path',
|
|
84
|
+
create: 'New call',
|
|
85
|
+
empty: 'No calls yet. A call is one request this connection can make — a method, a path, and where the records sit in the answer.',
|
|
86
|
+
emptySearch: (term)=>`No call matches “${term}”.`,
|
|
87
|
+
loadFailed: 'This list could not be loaded',
|
|
88
|
+
loadingMore: 'Loading more…',
|
|
89
|
+
selectConnection: 'Select a connection to see its calls.',
|
|
90
|
+
editTitle: 'Edit call',
|
|
91
|
+
newTitle: 'New call',
|
|
92
|
+
tag: 'Tag',
|
|
93
|
+
tagHint: 'Groups the calls of one API.',
|
|
94
|
+
tagPlaceholder: 'directory',
|
|
95
|
+
name: 'Name',
|
|
96
|
+
namePlaceholder: 'List people',
|
|
97
|
+
method: 'Method',
|
|
98
|
+
path: 'Path',
|
|
99
|
+
pathHint: '{brace} and :colon path parameters are supported — parameter bindings supply their values.',
|
|
100
|
+
pathPlaceholder: '/v1/people/{id}',
|
|
101
|
+
rootPath: 'Where the records sit',
|
|
102
|
+
rootPathHint: 'Dotted path to the array of records. Leave it empty when the array is the response root.',
|
|
103
|
+
rootPathPlaceholder: 'data.items',
|
|
104
|
+
queryParams: 'Fixed query parameters',
|
|
105
|
+
queryParamsHint: 'Sent with every call, as JSON. A parameter bound below overrides the same name here.',
|
|
106
|
+
requestBody: 'Request body',
|
|
107
|
+
requestBodyHint: 'The JSON body sent with this call. Some list endpoints are a POST whose body decides what comes back.',
|
|
108
|
+
description: 'Description',
|
|
109
|
+
descriptionPlaceholder: 'What this call returns, in one line',
|
|
110
|
+
enabled: 'Enabled — a disabled call is never made',
|
|
111
|
+
save: 'Save call',
|
|
112
|
+
paginationTitle: 'The paging style is chosen, not guessed',
|
|
113
|
+
paginationBody: 'Nothing here is detected for you. Pick the wrong style and the import still succeeds — it reads the first page and never asks for the rest. In a destructive write mode the records it never saw count as missing, so the run deletes or deactivates them.',
|
|
114
|
+
paginationStyle: 'Paging style',
|
|
115
|
+
paginationHint: 'Match this to how the API documents its own list endpoints.',
|
|
116
|
+
bindingsTitle: 'Parameter values',
|
|
117
|
+
bindingsUnbound: (names)=>`${names} — an import cannot run until every one of them is bound. Add a binding for each below.`,
|
|
118
|
+
bindingsNone: 'This call takes no parameters. Add a {name} token to its path if it needs one.',
|
|
119
|
+
bindFrom: 'Value from',
|
|
120
|
+
bindStatic: 'A fixed value',
|
|
121
|
+
bindEndpoint: 'A field of another call',
|
|
122
|
+
bindEntity: 'A column of one of our tables',
|
|
123
|
+
bindValue: 'Value',
|
|
124
|
+
bindValuePlaceholder: 'e.g. ACTIVE',
|
|
125
|
+
bindEndpointHint: 'Fetched first; this call is then made once per record it returns.',
|
|
126
|
+
bindField: 'Field',
|
|
127
|
+
bindFieldPlaceholder: 'e.g. name',
|
|
128
|
+
bindTable: 'From table',
|
|
129
|
+
bindColumn: 'Column',
|
|
130
|
+
bindColumnEmpty: 'Choose a column…',
|
|
131
|
+
sampleHeading: 'Try it',
|
|
132
|
+
sampleSend: 'Send one request',
|
|
133
|
+
sampleNothingYet: 'Nothing has been tried yet. One request is sent, read-only, and the shape it comes back with is what the field mapping is built from.',
|
|
134
|
+
samplePathValues: 'Example parameter values',
|
|
135
|
+
samplePathValuesHint: 'Used for this one request only. During a run each parameter takes its value from the binding configured for it.',
|
|
136
|
+
samplePlaceholder: 'example value',
|
|
137
|
+
sampleStatus: 'HTTP status',
|
|
138
|
+
sampleRecords: 'Records read',
|
|
139
|
+
sampleFields: 'Fields',
|
|
140
|
+
sampleBodyKeys: 'Body keys',
|
|
141
|
+
sampleFailed: 'The source answered with an error',
|
|
142
|
+
sampleNoRecords: 'The request succeeded and no records were read',
|
|
143
|
+
sampleFirstRecordFields: 'Fields on the first record',
|
|
144
|
+
sampleRows: 'Sample records'
|
|
145
|
+
},
|
|
77
146
|
connectionForm: {
|
|
78
147
|
sectionLabel: 'Connection details',
|
|
79
148
|
name: 'Name',
|
|
@@ -110,7 +179,8 @@
|
|
|
110
179
|
deleting: 'Deleting…',
|
|
111
180
|
saving: 'Saving…',
|
|
112
181
|
somethingWentWrong: 'Something went wrong',
|
|
113
|
-
dismiss: 'Dismiss error'
|
|
182
|
+
dismiss: 'Dismiss error',
|
|
183
|
+
remove: 'Remove'
|
|
114
184
|
}
|
|
115
185
|
};
|
|
116
186
|
/**
|
package/dist/fe/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { FilterCondition, SortCondition } from '../../src/types';
|
|
2
3
|
export type SortDirection = 'asc' | 'desc' | null;
|
|
3
4
|
export interface QueryParams {
|
|
4
5
|
page?: number;
|
|
@@ -10,15 +11,19 @@ export interface QueryParams {
|
|
|
10
11
|
filters?: string;
|
|
11
12
|
with?: string;
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Re-exported from the server's own definition, not redeclared.
|
|
16
|
+
*
|
|
17
|
+
* This file used to carry a NARROWER copy: `field` was required and the
|
|
18
|
+
* operator list stopped before `arrayOverlaps`, `arrayContains`, `arrayEmpty`,
|
|
19
|
+
* `or` and `and`. So a query the backend accepts — an `or` group, or a filter
|
|
20
|
+
* over an array column — was a type error on the client, and the two drifted
|
|
21
|
+
* every time an operator was added on one side only.
|
|
22
|
+
*
|
|
23
|
+
* Types flow from the server outward. A second definition of the same contract
|
|
24
|
+
* is a second thing to keep right.
|
|
25
|
+
*/
|
|
26
|
+
export type { FilterCondition, FilterOperator, SortCondition } from '../../src/types';
|
|
22
27
|
export interface PaginationMeta {
|
|
23
28
|
page: number;
|
|
24
29
|
limit: number;
|
|
@@ -229,4 +234,3 @@ export interface UseNucleusEntityReturn<T extends Record<string, unknown>> {
|
|
|
229
234
|
setCurrentPage: (page: number) => void;
|
|
230
235
|
refetch: () => void;
|
|
231
236
|
}
|
|
232
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.804",
|
|
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",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test'
|
|
2
|
-
import { selectEmittedTables } from './generate-schema'
|
|
2
|
+
import { generateColumnCode, selectEmittedTables } from './generate-schema'
|
|
3
3
|
|
|
4
4
|
const table = (table_name: string) => ({ table_name })
|
|
5
5
|
|
|
@@ -76,3 +76,34 @@ describe('selectEmittedTables', () => {
|
|
|
76
76
|
expect(selectEmittedTables([], sets({}))).toEqual([])
|
|
77
77
|
})
|
|
78
78
|
})
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* `numeric` and `decimal` are STRING-mode columns in drizzle — Postgres numeric
|
|
82
|
+
* is arbitrary precision and a JS number cannot carry it. The generator emitted
|
|
83
|
+
* `.default(0)` for them, producing a schema that ran and did not typecheck.
|
|
84
|
+
* Nobody reads a generated file, so only `tsc` ever objected — and the project's
|
|
85
|
+
* typecheck did not cover the folder it landed in.
|
|
86
|
+
*/
|
|
87
|
+
describe('numeric defaults are quoted', () => {
|
|
88
|
+
const emit = (col: Record<string, unknown>) =>
|
|
89
|
+
generateColumnCode(col as never, [], 'widgets')
|
|
90
|
+
|
|
91
|
+
it('quotes a numeric default, because the column reads as a string', () => {
|
|
92
|
+
expect(emit({ name: 'position_x', type: 'numeric', notNull: true, default: 0 })).toContain(
|
|
93
|
+
".default('0')"
|
|
94
|
+
)
|
|
95
|
+
expect(emit({ name: 'rate', type: 'decimal', default: 0.4 })).toContain(".default('0.4')")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('leaves a real number column alone', () => {
|
|
99
|
+
expect(emit({ name: 'count', type: 'integer', default: 0 })).toContain('.default(0)')
|
|
100
|
+
expect(emit({ name: 'ratio', type: 'real', default: 1.5 })).toContain('.default(1.5)')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('quotes it with a precision too, where the column carries options', () => {
|
|
104
|
+
const code = emit({ name: 'lat', type: 'numeric', precision: 10, scale: 8, default: 0 })
|
|
105
|
+
|
|
106
|
+
expect(code).toContain('precision: 10')
|
|
107
|
+
expect(code).toContain(".default('0')")
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -108,6 +108,18 @@ const BASE_COLUMNS: Column[] = [
|
|
|
108
108
|
{ name: 'updated_by', type: 'uuid' },
|
|
109
109
|
]
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Columns drizzle reads and writes as STRINGS.
|
|
113
|
+
*
|
|
114
|
+
* Postgres `numeric` is arbitrary precision, so drizzle refuses to round-trip
|
|
115
|
+
* it through a JS number and types the column as a string. A default therefore
|
|
116
|
+
* has to be quoted — `.default(0)` compiles to a schema that RUNS and does not
|
|
117
|
+
* typecheck, which is exactly the kind of defect a generated file hides.
|
|
118
|
+
*/
|
|
119
|
+
function isNumericType(type: string): boolean {
|
|
120
|
+
return type === 'numeric' || type === 'decimal'
|
|
121
|
+
}
|
|
122
|
+
|
|
111
123
|
const TYPE_MAP: Record<string, string> = {
|
|
112
124
|
integer: 'integer',
|
|
113
125
|
smallint: 'smallint',
|
|
@@ -200,7 +212,7 @@ export function selectEmittedTables<T extends { table_name: string }>(
|
|
|
200
212
|
)
|
|
201
213
|
}
|
|
202
214
|
|
|
203
|
-
function generateColumnCode(col: Column, allTables: string[], currentTable?: string): string {
|
|
215
|
+
export function generateColumnCode(col: Column, allTables: string[], currentTable?: string): string {
|
|
204
216
|
const drizzleType = TYPE_MAP[col.type] || 'text'
|
|
205
217
|
let code = ''
|
|
206
218
|
|
|
@@ -255,7 +267,12 @@ function generateColumnCode(col: Column, allTables: string[], currentTable?: str
|
|
|
255
267
|
} else if (typeof col.default === 'boolean') {
|
|
256
268
|
code += `.default(${col.default})`
|
|
257
269
|
} else if (typeof col.default === 'number') {
|
|
258
|
-
|
|
270
|
+
// `numeric`/`decimal` are STRING-mode columns in drizzle — Postgres numeric
|
|
271
|
+
// is arbitrary precision and a JS number cannot carry it — so a numeric
|
|
272
|
+
// default has to be quoted. Emitting the bare number produced a schema
|
|
273
|
+
// that ran fine and failed `tsc`, which is how it survived: the file is
|
|
274
|
+
// generated, so nobody reads it, and only a typecheck ever objected.
|
|
275
|
+
code += isNumericType(col.type) ? `.default('${col.default}')` : `.default(${col.default})`
|
|
259
276
|
} else if (Array.isArray(col.default)) {
|
|
260
277
|
code += `.default(sql\`'{}'\`)`
|
|
261
278
|
} else if (typeof col.default === 'object') {
|