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.
@@ -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: "Groups the endpoints of one API.",
29
+ hint: labels.endpoints.tagHint,
28
30
  htmlFor: "endpoint-tag",
29
- label: "Tag",
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: "directory",
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: "Name",
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: "List people",
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: "Method",
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: "{brace} and :colon path parameters are supported — parameter bindings supply their values.",
92
+ hint: labels.endpoints.pathHint,
91
93
  htmlFor: "endpoint-path",
92
- label: "Path",
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: "/v1/people/{id}",
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: "Dotted path to the array of records. Leave it empty when the array is the response root.",
112
+ hint: labels.endpoints.rootPathHint,
111
113
  htmlFor: "endpoint-root",
112
- label: "Response root path",
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: "data.items",
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: "Sent with every call, as JSON. A parameter bound below overrides the same name here.",
130
+ hint: labels.endpoints.queryParamsHint,
129
131
  htmlFor: "endpoint-query",
130
- label: "Fixed query parameters",
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: "The JSON body sent with this call. Some list endpoints are a POST whose body decides what comes back.",
148
+ hint: labels.endpoints.requestBodyHint,
147
149
  htmlFor: "endpoint-body",
148
- label: "Request body",
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: "Description",
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: "What this call returns, in one line",
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: "Enabled — a disabled endpoint is never called"
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": "Endpoints",
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: "Endpoints"
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: "Search",
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: "Tag, name or path",
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: "This list could not be loaded",
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: "Loading more…"
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: "Response sample"
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…' : 'Send one request'
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: "Example path parameter values"
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: "example value",
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: "HTTP status",
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: "Records read",
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: "Fields",
125
+ label: labels.endpoints.sampleFields,
124
126
  value: sample.fields.length
125
127
  }),
126
128
  /*#__PURE__*/ _jsx(Stat, {
127
- label: "Body keys",
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: "The source answered with an error",
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: "The request succeeded and no records were read",
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: "Fields on the first record"
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: "Sample records"
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: "Select a connection to see its endpoints."
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__*/ _jsxs("div", {
189
- className: theme.layout.split,
189
+ /*#__PURE__*/ _jsx("div", {
190
+ className: "flex min-w-0 flex-col gap-4 sm:gap-5",
190
191
  ref: contentRef,
191
- children: [
192
- /*#__PURE__*/ _jsx("aside", {
193
- "aria-label": labels.connections.heading,
194
- className: theme.layout.aside,
195
- children: /*#__PURE__*/ _jsx(ConnectionList, {
196
- actions: actions,
197
- reloadToken: sourcesReloadToken
198
- })
199
- }),
200
- /*#__PURE__*/ _jsx("section", {
201
- "aria-label": "Connection detail",
202
- className: theme.layout.main,
203
- children: hasConnection ? /*#__PURE__*/ _jsxs(_Fragment, {
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("p", {
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: "The pagination profile is chosen, not guessed",
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: "Match this to how the API documents its own list endpoints.",
171
+ hint: labels.endpoints.paginationHint,
170
172
  htmlFor: `${idPrefix}-kind`,
171
- label: "Pagination style",
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: "A fixed value"
45
+ children: labels.endpoints.bindStatic
44
46
  }),
45
47
  /*#__PURE__*/ _jsx("option", {
46
48
  value: "endpoint",
47
- children: "A field of another endpoint"
49
+ children: labels.endpoints.bindEndpoint
48
50
  }),
49
51
  /*#__PURE__*/ _jsx("option", {
50
52
  value: "entity",
51
- children: "A column of one of our tables"
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: "Remove"
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: "Value",
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: "e.g. ACTIVE",
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: "Fetched first; this endpoint is then called once per record it returns.",
100
+ hint: labels.endpoints.bindEndpointHint,
98
101
  htmlFor: `${rowId}-endpoint`,
99
- label: "From endpoint",
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: "Field",
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: "e.g. name",
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: "From table",
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: "Column",
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: "Choose a column…"
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: "These parameters have no value yet",
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
  /**
@@ -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
- export interface FilterCondition {
14
- field: string;
15
- operator: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in' | 'notIn' | 'isNull' | 'isNotNull';
16
- value: unknown;
17
- }
18
- export interface SortCondition {
19
- field: string;
20
- direction: 'asc' | 'desc';
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.802",
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
- code += `.default(${col.default})`
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') {