nucleus-core-ts 0.9.781 → 0.9.783

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/client.js +1 -1
  2. package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.d.ts +2 -0
  3. package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +85 -0
  4. package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.d.ts +2 -0
  5. package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +165 -0
  6. package/dist/fe/components/IntegrationsPage/components/ConnectionForm.d.ts +10 -0
  7. package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +112 -0
  8. package/dist/fe/components/IntegrationsPage/components/ConnectionList.d.ts +9 -0
  9. package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +184 -0
  10. package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.d.ts +2 -0
  11. package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +131 -0
  12. package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.d.ts +21 -0
  13. package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +49 -0
  14. package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.d.ts +12 -0
  15. package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +95 -0
  16. package/dist/fe/components/IntegrationsPage/components/ConnectionTest.d.ts +9 -0
  17. package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +116 -0
  18. package/dist/fe/components/IntegrationsPage/components/CredentialFields.d.ts +9 -0
  19. package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +171 -0
  20. package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.d.ts +13 -0
  21. package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +63 -0
  22. package/dist/fe/components/IntegrationsPage/components/EndpointForm.d.ts +11 -0
  23. package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +166 -0
  24. package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.d.ts +19 -0
  25. package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +162 -0
  26. package/dist/fe/components/IntegrationsPage/components/EndpointList.d.ts +12 -0
  27. package/dist/fe/components/IntegrationsPage/components/EndpointList.js +205 -0
  28. package/dist/fe/components/IntegrationsPage/components/EndpointSample.d.ts +6 -0
  29. package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +204 -0
  30. package/dist/fe/components/IntegrationsPage/components/EndpointsTab.d.ts +5 -0
  31. package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +101 -0
  32. package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.d.ts +17 -0
  33. package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +179 -0
  34. package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.d.ts +11 -0
  35. package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +115 -0
  36. package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.d.ts +2 -0
  37. package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +175 -0
  38. package/dist/fe/components/IntegrationsPage/components/MappingEditor.d.ts +15 -0
  39. package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +193 -0
  40. package/dist/fe/components/IntegrationsPage/components/MappingList.d.ts +11 -0
  41. package/dist/fe/components/IntegrationsPage/components/MappingList.js +191 -0
  42. package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.d.ts +19 -0
  43. package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +150 -0
  44. package/dist/fe/components/IntegrationsPage/components/MappingsTab.d.ts +5 -0
  45. package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +154 -0
  46. package/dist/fe/components/IntegrationsPage/components/PaginationFields.d.ts +9 -0
  47. package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +218 -0
  48. package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.d.ts +24 -0
  49. package/dist/fe/components/IntegrationsPage/components/ParamBindingRow.js +180 -0
  50. package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.d.ts +33 -0
  51. package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +100 -0
  52. package/dist/fe/components/IntegrationsPage/components/Primitives.d.ts +65 -0
  53. package/dist/fe/components/IntegrationsPage/components/Primitives.js +94 -0
  54. package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.d.ts +10 -0
  55. package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +48 -0
  56. package/dist/fe/components/IntegrationsPage/components/RunHistory.d.ts +10 -0
  57. package/dist/fe/components/IntegrationsPage/components/RunHistory.js +124 -0
  58. package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.d.ts +7 -0
  59. package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +129 -0
  60. package/dist/fe/components/IntegrationsPage/components/RunPanel.d.ts +9 -0
  61. package/dist/fe/components/IntegrationsPage/components/RunPanel.js +160 -0
  62. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.d.ts +5 -0
  63. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +43 -0
  64. package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.d.ts +5 -0
  65. package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +92 -0
  66. package/dist/fe/components/IntegrationsPage/components/RunProgressBar.d.ts +8 -0
  67. package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +77 -0
  68. package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.d.ts +13 -0
  69. package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +65 -0
  70. package/dist/fe/components/IntegrationsPage/components/RunsTab.d.ts +13 -0
  71. package/dist/fe/components/IntegrationsPage/components/RunsTab.js +39 -0
  72. package/dist/fe/components/IntegrationsPage/components/Skeletons.d.ts +9 -0
  73. package/dist/fe/components/IntegrationsPage/components/Skeletons.js +139 -0
  74. package/dist/fe/components/IntegrationsPage/components/authOptions.d.ts +29 -0
  75. package/dist/fe/components/IntegrationsPage/components/authOptions.js +71 -0
  76. package/dist/fe/components/IntegrationsPage/components/connectionDraft.d.ts +64 -0
  77. package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +112 -0
  78. package/dist/fe/components/IntegrationsPage/components/credentialSlots.d.ts +25 -0
  79. package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +67 -0
  80. package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.d.ts +16 -0
  81. package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +41 -0
  82. package/dist/fe/components/IntegrationsPage/components/formSupport.d.ts +19 -0
  83. package/dist/fe/components/IntegrationsPage/components/formSupport.js +25 -0
  84. package/dist/fe/components/IntegrationsPage/components/mappingDraft.d.ts +40 -0
  85. package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +44 -0
  86. package/dist/fe/components/IntegrationsPage/components/pathParams.d.ts +9 -0
  87. package/dist/fe/components/IntegrationsPage/components/pathParams.js +15 -0
  88. package/dist/fe/components/IntegrationsPage/components/runFormat.d.ts +33 -0
  89. package/dist/fe/components/IntegrationsPage/components/runFormat.js +85 -0
  90. package/dist/fe/components/IntegrationsPage/components/useInfiniteList.d.ts +40 -0
  91. package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +110 -0
  92. package/dist/fe/components/IntegrationsPage/index.d.ts +18 -0
  93. package/dist/fe/components/IntegrationsPage/index.js +11 -0
  94. package/dist/fe/components/IntegrationsPage/store/index.d.ts +2 -0
  95. package/dist/fe/components/IntegrationsPage/store/index.js +124 -0
  96. package/dist/fe/components/IntegrationsPage/store/state.d.ts +80 -0
  97. package/dist/fe/components/IntegrationsPage/store/state.js +32 -0
  98. package/dist/fe/components/IntegrationsPage/theme/index.d.ts +103 -0
  99. package/dist/fe/components/IntegrationsPage/theme/index.js +113 -0
  100. package/dist/fe/components/IntegrationsPage/types/actions.d.ts +91 -0
  101. package/dist/fe/components/IntegrationsPage/types/actions.js +1 -0
  102. package/dist/fe/components/IntegrationsPage/types/index.d.ts +3 -0
  103. package/dist/fe/components/IntegrationsPage/types/index.js +3 -0
  104. package/dist/fe/components/IntegrationsPage/types/records.d.ts +139 -0
  105. package/dist/fe/components/IntegrationsPage/types/records.js +11 -0
  106. package/dist/fe/components/IntegrationsPage/types/results.d.ts +52 -0
  107. package/dist/fe/components/IntegrationsPage/types/results.js +4 -0
  108. package/dist/fe/index.d.ts +2 -0
  109. package/dist/fe/index.js +1 -0
  110. package/dist/index.js +14 -14
  111. package/dist/src/Client/ApiCaller/system-tables.d.ts +505 -0
  112. package/dist/src/Client/Proxy/wsProxy.js +25 -1
  113. package/dist/src/ElysiaPlugin/routes/backup/types.d.ts +3 -1
  114. package/dist/src/ElysiaPlugin/routes/integrations/index.d.ts +49 -0
  115. package/dist/src/ElysiaPlugin/routes/integrations/inspect.d.ts +87 -0
  116. package/dist/src/ElysiaPlugin/routes/integrations/inspectRoutes.d.ts +41 -0
  117. package/dist/src/ElysiaPlugin/routes/integrations/repository.d.ts +88 -0
  118. package/dist/src/ElysiaPlugin/routes/integrations/types.d.ts +22 -0
  119. package/dist/src/ElysiaPlugin/routes/pubsub/frame.d.ts +37 -0
  120. package/dist/src/ElysiaPlugin/routes/pubsub/frame.test.d.ts +1 -0
  121. package/dist/src/Services/Backup/BackupService.d.ts +16 -8
  122. package/dist/src/Services/Backup/types.d.ts +25 -0
  123. package/dist/src/Services/Integrations/bindings.d.ts +116 -0
  124. package/dist/src/Services/Integrations/bindings.test.d.ts +1 -0
  125. package/dist/src/Services/Integrations/collect.d.ts +68 -0
  126. package/dist/src/Services/Integrations/collect.test.d.ts +1 -0
  127. package/dist/src/Services/Integrations/fetcher.d.ts +100 -0
  128. package/dist/src/Services/Integrations/fetcher.test.d.ts +1 -0
  129. package/dist/src/Services/Integrations/lock.d.ts +77 -0
  130. package/dist/src/Services/Integrations/lock.test.d.ts +1 -0
  131. package/dist/src/Services/Integrations/mapping.d.ts +63 -0
  132. package/dist/src/Services/Integrations/mapping.test.d.ts +1 -0
  133. package/dist/src/Services/Integrations/oauth.d.ts +89 -0
  134. package/dist/src/Services/Integrations/oauth.test.d.ts +1 -0
  135. package/dist/src/Services/Integrations/pagination.d.ts +97 -0
  136. package/dist/src/Services/Integrations/pagination.test.d.ts +1 -0
  137. package/dist/src/Services/Integrations/plan.d.ts +74 -0
  138. package/dist/src/Services/Integrations/plan.test.d.ts +1 -0
  139. package/dist/src/Services/Integrations/revert.d.ts +78 -0
  140. package/dist/src/Services/Integrations/revert.test.d.ts +1 -0
  141. package/dist/src/Services/Integrations/runner.d.ts +126 -0
  142. package/dist/src/Services/Integrations/runner.test.d.ts +1 -0
  143. package/dist/src/Services/Integrations/types.d.ts +208 -0
  144. package/dist/src/Services/Integrations/writer.d.ts +105 -0
  145. package/dist/src/Services/Integrations/writer.test.d.ts +1 -0
  146. package/dist/src/Utils/cron.d.ts +65 -0
  147. package/dist/src/Utils/cron.test.d.ts +1 -0
  148. package/dist/src/types.d.ts +67 -1
  149. package/package.json +1 -1
  150. package/schemas/config.nucleus.json +43 -0
  151. package/schemas/table.schema.json +2 -1
  152. package/scripts/generate-schema.test.ts +78 -0
  153. package/scripts/generate-schema.ts +43 -5
  154. package/src/system.tables.json +362 -0
@@ -0,0 +1,175 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useGSAP } from '@gsap/react';
4
+ import gsap from 'gsap';
5
+ import { useEffect, useEffectEvent, useRef } from 'react';
6
+ import { cn } from '../../../utils/cn';
7
+ import { useIntegrationsStore } from '../store';
8
+ import { integrationsPageTheme } from '../theme';
9
+ import { ConnectionList } from './ConnectionList';
10
+ import { ConnectionSettingsTab } from './ConnectionSettingsTab';
11
+ import { ConnectionTabs, connectionTabId, connectionTabPanelId } from './ConnectionTabs';
12
+ import { EndpointsTab } from './EndpointsTab';
13
+ import { MappingsTab } from './MappingsTab';
14
+ import { Notice } from './Primitives';
15
+ import { RunsTab } from './RunsTab';
16
+ /**
17
+ * The shell.
18
+ *
19
+ * It owns three things and nothing else: the selection rail, which section of the
20
+ * selected connection is on screen, and the one place an error is reported. Every
21
+ * call it needs arrives as an injected action, and each section fetches its own
22
+ * page from the server rather than being handed a list to slice.
23
+ */ gsap.registerPlugin(useGSAP);
24
+ const theme = integrationsPageTheme;
25
+ export function IntegrationsPage({ title = 'Integrations', subtitle = 'Connect an external service, describe the data you want and import it on your terms.', className, actions, onSubscribeProgress }) {
26
+ const store = useIntegrationsStore();
27
+ const containerRef = useRef(null);
28
+ const contentRef = useRef(null);
29
+ // One measured entrance. Anything longer makes an operator wait to read counts
30
+ // they came here to check.
31
+ useGSAP(()=>{
32
+ if (!contentRef.current) return;
33
+ gsap.fromTo(contentRef.current, {
34
+ opacity: 0,
35
+ y: 10
36
+ }, {
37
+ opacity: 1,
38
+ y: 0,
39
+ duration: 0.3,
40
+ ease: 'power2.out'
41
+ });
42
+ }, {
43
+ scope: containerRef
44
+ });
45
+ // Progress is pushed by the app, not polled here. The unsubscribe is returned
46
+ // straight back so a panel that closes mid-import stops receiving frames.
47
+ const subscribeProgress = useEffectEvent(()=>onSubscribeProgress?.((progress)=>{
48
+ store.setProgress(progress);
49
+ }));
50
+ useEffect(()=>{
51
+ if (!onSubscribeProgress) return;
52
+ return subscribeProgress();
53
+ }, [
54
+ onSubscribeProgress
55
+ ]);
56
+ const dismissError = ()=>{
57
+ store.setError(null);
58
+ };
59
+ const changeTab = (tab)=>{
60
+ store.setTab(tab);
61
+ };
62
+ const tab = store.tab;
63
+ const hasConnection = store.selectedSourceId !== null;
64
+ return /*#__PURE__*/ _jsxs("div", {
65
+ className: cn(theme.container, className),
66
+ ref: containerRef,
67
+ children: [
68
+ /*#__PURE__*/ _jsx("header", {
69
+ className: theme.header.wrapper,
70
+ children: /*#__PURE__*/ _jsxs("div", {
71
+ className: "flex min-w-0 flex-col gap-1",
72
+ children: [
73
+ /*#__PURE__*/ _jsx("h1", {
74
+ className: theme.header.title,
75
+ children: title
76
+ }),
77
+ subtitle ? /*#__PURE__*/ _jsx("p", {
78
+ className: theme.header.subtitle,
79
+ children: subtitle
80
+ }) : null
81
+ ]
82
+ })
83
+ }),
84
+ /*#__PURE__*/ _jsx("div", {
85
+ "aria-live": "assertive",
86
+ className: "contents",
87
+ role: "alert",
88
+ children: store.error ? /*#__PURE__*/ _jsxs("div", {
89
+ className: "relative",
90
+ children: [
91
+ /*#__PURE__*/ _jsx(Notice, {
92
+ title: "Something went wrong",
93
+ tone: "danger",
94
+ children: store.error
95
+ }),
96
+ /*#__PURE__*/ _jsx("button", {
97
+ "aria-label": "Dismiss error",
98
+ className: cn(theme.button.ghost, 'absolute top-1.5 right-1.5 h-8 w-8 px-0 sm:h-8'),
99
+ onClick: dismissError,
100
+ type: "button",
101
+ children: /*#__PURE__*/ _jsx("span", {
102
+ "aria-hidden": "true",
103
+ children: "✕"
104
+ })
105
+ })
106
+ ]
107
+ }) : null
108
+ }),
109
+ /*#__PURE__*/ _jsxs("div", {
110
+ className: theme.layout.split,
111
+ ref: contentRef,
112
+ children: [
113
+ /*#__PURE__*/ _jsx("aside", {
114
+ "aria-label": "Connections",
115
+ className: theme.layout.aside,
116
+ children: /*#__PURE__*/ _jsx(ConnectionList, {
117
+ actions: actions
118
+ })
119
+ }),
120
+ /*#__PURE__*/ _jsx("section", {
121
+ "aria-label": "Connection detail",
122
+ className: theme.layout.main,
123
+ children: hasConnection ? /*#__PURE__*/ _jsxs(_Fragment, {
124
+ children: [
125
+ /*#__PURE__*/ _jsx(ConnectionTabs, {
126
+ counts: {
127
+ endpoints: store.endpointsTotal,
128
+ mappings: store.mappingsTotal,
129
+ runs: store.runsTotal
130
+ },
131
+ onChange: changeTab,
132
+ value: tab
133
+ }),
134
+ /*#__PURE__*/ _jsxs("section", {
135
+ "aria-labelledby": connectionTabId(tab),
136
+ className: theme.layout.main,
137
+ id: connectionTabPanelId(tab),
138
+ role: "tabpanel",
139
+ children: [
140
+ tab === 'connection' ? /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
141
+ actions: actions
142
+ }) : null,
143
+ tab === 'endpoints' ? /*#__PURE__*/ _jsx(EndpointsTab, {
144
+ actions: actions
145
+ }) : null,
146
+ tab === 'mappings' ? /*#__PURE__*/ _jsx(MappingsTab, {
147
+ actions: actions
148
+ }) : null,
149
+ tab === 'runs' ? /*#__PURE__*/ _jsx(RunsTab, {
150
+ actions: actions
151
+ }) : null
152
+ ]
153
+ })
154
+ ]
155
+ }) : // Not a dead end. With nothing selected the only useful thing on this
156
+ // screen is creating a connection, so that is what it shows — the
157
+ // previous message told an operator to select from a list that is
158
+ // empty on a fresh install, with no way to add the first one.
159
+ /*#__PURE__*/ _jsxs(_Fragment, {
160
+ children: [
161
+ /*#__PURE__*/ _jsx("p", {
162
+ className: theme.list.empty,
163
+ children: "Select a connection on the left, or set up a new one here."
164
+ }),
165
+ /*#__PURE__*/ _jsx(ConnectionSettingsTab, {
166
+ actions: actions
167
+ })
168
+ ]
169
+ })
170
+ })
171
+ ]
172
+ })
173
+ ]
174
+ });
175
+ }
@@ -0,0 +1,15 @@
1
+ import { type MappingDraft, type MappingTarget } from './mappingDraft';
2
+ export type MappingEditorProps = {
3
+ /** Starting values. Read once — remount with a new `key` to load another. */
4
+ mapping: MappingDraft;
5
+ targets: MappingTarget[];
6
+ targetsLoading: boolean;
7
+ /** Field names the selected endpoint's last sample reported. */
8
+ sourceFields: string[];
9
+ saving: boolean;
10
+ deleting: boolean;
11
+ onSave: (draft: MappingDraft) => void;
12
+ onDelete: (id: string) => void;
13
+ onCancel: () => void;
14
+ };
15
+ export declare function MappingEditor({ mapping, targets, targetsLoading, sourceFields, saving, deleting, onSave, onDelete, onCancel, }: MappingEditorProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,193 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from 'react';
4
+ import { integrationsPageTheme } from '../theme';
5
+ import { FieldMappingRows } from './FieldMappingRows';
6
+ import { MappingWriteModeFields } from './MappingWriteModeFields';
7
+ import { mappingDraftIssues } from './mappingDraft';
8
+ import { Field } from './Primitives';
9
+ import { CardSkeleton } from './Skeletons';
10
+ /**
11
+ * Everything about one mapping, on one screen.
12
+ *
13
+ * The draft is local until Save: an import is not a text field where a stray
14
+ * keystroke is harmless, and half a mapping saved between two thoughts is a
15
+ * mapping that could be RUN in that state.
16
+ *
17
+ * Mount it with a `key` of the mapping id so switching rows starts a fresh
18
+ * draft instead of pouring one mapping's edits into another.
19
+ */ const theme = integrationsPageTheme;
20
+ export function MappingEditor({ mapping, targets, targetsLoading, sourceFields, saving, deleting, onSave, onDelete, onCancel }) {
21
+ const uid = useId();
22
+ const [draft, setDraft] = useState(mapping);
23
+ const patch = (next)=>{
24
+ setDraft((current)=>({
25
+ ...current,
26
+ ...next
27
+ }));
28
+ };
29
+ // Switching entity invalidates every column name chosen for the old one.
30
+ // Blanking them is louder than leaving a select pointing at a column that no
31
+ // longer exists, which renders empty and saves the stale name anyway.
32
+ const chooseTarget = (entity)=>{
33
+ const fields = targets.find((option)=>option.entity === entity)?.fields ?? [];
34
+ const keepDedup = fields.includes(draft.dedupTargetField ?? '');
35
+ patch({
36
+ targetEntity: entity,
37
+ dedupTargetField: keepDedup ? draft.dedupTargetField : null,
38
+ fieldMappings: draft.fieldMappings.map((row)=>fields.length === 0 || fields.includes(row.target) ? row : {
39
+ ...row,
40
+ target: ''
41
+ })
42
+ });
43
+ };
44
+ if (targetsLoading) return /*#__PURE__*/ _jsx(CardSkeleton, {});
45
+ const targetFields = targets.find((option)=>option.entity === draft.targetEntity)?.fields ?? [];
46
+ const issues = mappingDraftIssues(draft);
47
+ const busy = saving || deleting;
48
+ return /*#__PURE__*/ _jsxs("section", {
49
+ className: theme.card.wrapper,
50
+ children: [
51
+ /*#__PURE__*/ _jsx("header", {
52
+ className: theme.card.header,
53
+ children: /*#__PURE__*/ _jsx("h2", {
54
+ className: theme.card.title,
55
+ children: draft.id ? draft.name || 'Mapping' : 'New mapping'
56
+ })
57
+ }),
58
+ /*#__PURE__*/ _jsxs("div", {
59
+ className: theme.card.body,
60
+ children: [
61
+ /*#__PURE__*/ _jsxs("div", {
62
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
63
+ children: [
64
+ /*#__PURE__*/ _jsx(Field, {
65
+ htmlFor: `${uid}-name`,
66
+ label: "Name",
67
+ children: /*#__PURE__*/ _jsx("input", {
68
+ className: theme.field.input,
69
+ disabled: busy,
70
+ id: `${uid}-name`,
71
+ onChange: (event)=>patch({
72
+ name: event.target.value
73
+ }),
74
+ placeholder: "Staff directory import",
75
+ value: draft.name
76
+ })
77
+ }),
78
+ /*#__PURE__*/ _jsx(Field, {
79
+ hint: targets.length === 0 ? 'This app exposes no writable entities.' : undefined,
80
+ htmlFor: `${uid}-target`,
81
+ label: "Target entity",
82
+ children: /*#__PURE__*/ _jsxs("select", {
83
+ className: theme.field.input,
84
+ disabled: busy || targets.length === 0,
85
+ id: `${uid}-target`,
86
+ onChange: (event)=>chooseTarget(event.target.value),
87
+ value: draft.targetEntity,
88
+ children: [
89
+ /*#__PURE__*/ _jsx("option", {
90
+ value: "",
91
+ children: "Not set"
92
+ }),
93
+ targets.map((option)=>/*#__PURE__*/ _jsx("option", {
94
+ value: option.entity,
95
+ children: option.label
96
+ }, option.entity))
97
+ ]
98
+ })
99
+ })
100
+ ]
101
+ }),
102
+ /*#__PURE__*/ _jsx(Field, {
103
+ hint: targetFields.length === 0 ? 'Choose a target entity to list its columns.' : 'The column an incoming record is matched on. Without it nothing can be updated.',
104
+ htmlFor: `${uid}-dedup`,
105
+ label: "Deduplication column",
106
+ children: /*#__PURE__*/ _jsxs("select", {
107
+ "aria-describedby": `${uid}-dedup-hint`,
108
+ className: theme.field.input,
109
+ disabled: busy || targetFields.length === 0,
110
+ id: `${uid}-dedup`,
111
+ onChange: (event)=>patch({
112
+ dedupTargetField: event.target.value || null
113
+ }),
114
+ value: draft.dedupTargetField ?? '',
115
+ children: [
116
+ /*#__PURE__*/ _jsx("option", {
117
+ value: "",
118
+ children: "Not set"
119
+ }),
120
+ targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
121
+ value: field,
122
+ children: field
123
+ }, field))
124
+ ]
125
+ })
126
+ }),
127
+ /*#__PURE__*/ _jsx(MappingWriteModeFields, {
128
+ dedupConfigured: (draft.dedupTargetField ?? '').trim() !== '',
129
+ disabled: busy,
130
+ idPrefix: uid,
131
+ missingRecordColumn: draft.missingRecordColumn ?? '',
132
+ missingRecordPolicy: draft.missingRecordPolicy,
133
+ missingRecordValue: draft.missingRecordValue ?? '',
134
+ onMissingRecordColumnChange: (value)=>patch({
135
+ missingRecordColumn: value
136
+ }),
137
+ onMissingRecordPolicyChange: (policy)=>patch({
138
+ missingRecordPolicy: policy
139
+ }),
140
+ onMissingRecordValueChange: (value)=>patch({
141
+ missingRecordValue: value
142
+ }),
143
+ onWriteModeChange: (mode)=>patch({
144
+ writeMode: mode
145
+ }),
146
+ writeMode: draft.writeMode
147
+ }),
148
+ /*#__PURE__*/ _jsx(FieldMappingRows, {
149
+ disabled: busy,
150
+ onChange: (rows)=>patch({
151
+ fieldMappings: rows
152
+ }),
153
+ rows: draft.fieldMappings,
154
+ sourceFields: sourceFields,
155
+ targetFields: targetFields
156
+ }),
157
+ issues.length > 0 ? /*#__PURE__*/ _jsx("ul", {
158
+ className: theme.field.error,
159
+ children: issues.map((issue)=>/*#__PURE__*/ _jsx("li", {
160
+ children: issue
161
+ }, issue))
162
+ }) : null
163
+ ]
164
+ }),
165
+ /*#__PURE__*/ _jsxs("footer", {
166
+ className: theme.card.footer,
167
+ children: [
168
+ draft.id ? /*#__PURE__*/ _jsx("button", {
169
+ className: theme.button.danger,
170
+ disabled: busy,
171
+ onClick: ()=>onDelete(draft.id ?? ''),
172
+ type: "button",
173
+ children: deleting ? 'Deleting…' : 'Delete'
174
+ }) : null,
175
+ /*#__PURE__*/ _jsx("button", {
176
+ className: theme.button.secondary,
177
+ disabled: busy,
178
+ onClick: onCancel,
179
+ type: "button",
180
+ children: "Cancel"
181
+ }),
182
+ /*#__PURE__*/ _jsx("button", {
183
+ className: theme.button.primary,
184
+ disabled: busy || issues.length > 0,
185
+ onClick: ()=>onSave(draft),
186
+ type: "button",
187
+ children: saving ? 'Saving…' : 'Save mapping'
188
+ })
189
+ ]
190
+ })
191
+ ]
192
+ });
193
+ }
@@ -0,0 +1,11 @@
1
+ import type { IntegrationMapping, IntegrationsActions } from '../types';
2
+ export type MappingListProps = {
3
+ actions: IntegrationsActions;
4
+ /** The connection whose mappings these are. */
5
+ sourceId: string;
6
+ selectedId: string | null;
7
+ /** Bumped by the tab after a save or a delete so page one is fetched again. */
8
+ reloadToken: number;
9
+ onSelect: (mapping: IntegrationMapping) => void;
10
+ };
11
+ export declare function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect, }: MappingListProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,191 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useEffectEvent, useId, useState } from 'react';
4
+ import { cn } from '../../../utils/cn';
5
+ import { useIntegrationsStore } from '../store';
6
+ import { integrationsPageTheme } from '../theme';
7
+ import { Badge, Field, Notice } from './Primitives';
8
+ import { ListSkeleton } from './Skeletons';
9
+ import { useInfiniteList } from './useInfiniteList';
10
+ /**
11
+ * The mappings of the selected connection, one page at a time.
12
+ *
13
+ * Searching and paging are the server's job; this list only shows what came
14
+ * back. The write mode is on the row rather than only inside the editor because
15
+ * `replace` is the one setting worth spotting before opening anything.
16
+ */ const theme = integrationsPageTheme;
17
+ const PAGE_SIZE = 20;
18
+ const SEARCH_DEBOUNCE_MS = 300;
19
+ const WRITE_MODE_TONE = {
20
+ upsert: 'info',
21
+ append: 'neutral',
22
+ replace: 'danger'
23
+ };
24
+ function describeLastRun(value) {
25
+ if (!value) return 'Never run';
26
+ const when = new Date(value);
27
+ return Number.isNaN(when.getTime()) ? 'Never run' : `Last run ${when.toLocaleString()}`;
28
+ }
29
+ export function MappingList({ actions, sourceId, selectedId, reloadToken, onSelect }) {
30
+ const store = useIntegrationsStore();
31
+ const uid = useId();
32
+ const search = store.mappingsSearch;
33
+ const [term, setTerm] = useState(search);
34
+ // Typed characters become a request only once the typing stops; a fetch per
35
+ // keystroke would put page-1 answers in a race with each other.
36
+ const commitSearch = useEffectEvent(()=>{
37
+ store.setMappingsSearch(term);
38
+ });
39
+ useEffect(()=>{
40
+ const timer = setTimeout(commitSearch, SEARCH_DEBOUNCE_MS);
41
+ return ()=>clearTimeout(timer);
42
+ }, [
43
+ term
44
+ ]);
45
+ const list = useInfiniteList({
46
+ load: (page)=>{
47
+ // Without a connection there is nothing to scope by, and asking anyway
48
+ // would answer with every mapping in the deployment under this
49
+ // connection's name.
50
+ if (!sourceId) return Promise.resolve({
51
+ items: [],
52
+ total: 0
53
+ });
54
+ return actions.listMappings(sourceId, {
55
+ page,
56
+ limit: PAGE_SIZE,
57
+ search: search.length > 0 ? search : undefined,
58
+ sort: [
59
+ {
60
+ field: 'name',
61
+ direction: 'asc'
62
+ }
63
+ ]
64
+ }).then((result)=>({
65
+ items: result.items,
66
+ total: result.meta?.totalItems ?? result.items.length
67
+ }));
68
+ },
69
+ deps: [
70
+ sourceId,
71
+ search,
72
+ reloadToken
73
+ ],
74
+ pageSize: PAGE_SIZE
75
+ });
76
+ // Mirrored so the runs screen can name the selected mapping without fetching
77
+ // it again. The rows still come from, and only from, the server.
78
+ const publishItems = useEffectEvent(()=>{
79
+ store.setMappings(list.items, list.total);
80
+ });
81
+ const publishLoading = useEffectEvent(()=>{
82
+ store.setMappingsLoading(list.loading);
83
+ });
84
+ useEffect(()=>{
85
+ publishItems();
86
+ }, [
87
+ list.items,
88
+ list.total
89
+ ]);
90
+ useEffect(()=>{
91
+ publishLoading();
92
+ }, [
93
+ list.loading
94
+ ]);
95
+ const showSkeleton = list.loading && list.items.length === 0;
96
+ return /*#__PURE__*/ _jsxs("section", {
97
+ className: "flex flex-col gap-3",
98
+ children: [
99
+ /*#__PURE__*/ _jsx(Field, {
100
+ htmlFor: `${uid}-search`,
101
+ label: "Search mappings",
102
+ children: /*#__PURE__*/ _jsx("input", {
103
+ className: theme.field.input,
104
+ id: `${uid}-search`,
105
+ onChange: (event)=>setTerm(event.target.value),
106
+ placeholder: "Name or target entity",
107
+ type: "search",
108
+ value: term
109
+ })
110
+ }),
111
+ list.error ? /*#__PURE__*/ _jsxs(Notice, {
112
+ title: "This list could not be loaded",
113
+ tone: "danger",
114
+ children: [
115
+ list.error,
116
+ ' ',
117
+ /*#__PURE__*/ _jsx("button", {
118
+ className: theme.button.ghost,
119
+ onClick: list.reload,
120
+ type: "button",
121
+ children: "Try again"
122
+ })
123
+ ]
124
+ }) : null,
125
+ showSkeleton ? /*#__PURE__*/ _jsx(ListSkeleton, {
126
+ rows: 5
127
+ }) : list.items.length === 0 && !list.error ? /*#__PURE__*/ _jsx("p", {
128
+ className: theme.list.empty,
129
+ children: store.endpointsTotal === 0 ? 'No endpoints on this connection yet. A mapping reads from one, so add an endpoint first.' : 'No mappings match. A mapping is what turns a response into rows.'
130
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
131
+ children: [
132
+ /*#__PURE__*/ _jsxs("p", {
133
+ className: theme.list.itemMeta,
134
+ children: [
135
+ list.items.length,
136
+ " of ",
137
+ list.total,
138
+ " shown"
139
+ ]
140
+ }),
141
+ /*#__PURE__*/ _jsx("ul", {
142
+ className: theme.list.wrapper,
143
+ children: list.items.map((mapping)=>/*#__PURE__*/ _jsx("li", {
144
+ children: /*#__PURE__*/ _jsxs("button", {
145
+ "aria-current": mapping.id === selectedId ? 'true' : undefined,
146
+ className: cn(theme.list.item, mapping.id === selectedId ? theme.list.itemSelected : undefined),
147
+ onClick: ()=>onSelect(mapping),
148
+ type: "button",
149
+ children: [
150
+ /*#__PURE__*/ _jsx("span", {
151
+ className: theme.list.itemTitle,
152
+ children: mapping.name
153
+ }),
154
+ /*#__PURE__*/ _jsx("span", {
155
+ className: theme.list.itemMeta,
156
+ children: mapping.targetEntity || 'No target entity'
157
+ }),
158
+ /*#__PURE__*/ _jsxs("span", {
159
+ className: "mt-1 flex flex-wrap items-center gap-1.5",
160
+ children: [
161
+ /*#__PURE__*/ _jsx(Badge, {
162
+ tone: WRITE_MODE_TONE[mapping.writeMode],
163
+ children: mapping.writeMode
164
+ }),
165
+ mapping.enabled === false ? /*#__PURE__*/ _jsx(Badge, {
166
+ tone: "neutral",
167
+ children: "disabled"
168
+ }) : null,
169
+ /*#__PURE__*/ _jsx("span", {
170
+ className: theme.list.itemMeta,
171
+ children: describeLastRun(mapping.lastRunAt)
172
+ })
173
+ ]
174
+ })
175
+ ]
176
+ })
177
+ }, mapping.id))
178
+ })
179
+ ]
180
+ }),
181
+ /*#__PURE__*/ _jsx("div", {
182
+ className: theme.list.sentinel,
183
+ ref: list.sentinelRef
184
+ }),
185
+ list.loading && list.items.length > 0 ? /*#__PURE__*/ _jsx("p", {
186
+ className: theme.list.itemMeta,
187
+ children: "Loading more…"
188
+ }) : null
189
+ ]
190
+ });
191
+ }
@@ -0,0 +1,19 @@
1
+ import type { MissingRecordPolicyValue, WriteModeValue } from '../types';
2
+ /** Deactivate has nowhere to write its flag without both of these. */
3
+ export declare function missingRecordFieldsIncomplete(policy: MissingRecordPolicyValue, column: string, value: string): boolean;
4
+ export type MappingWriteModeFieldsProps = {
5
+ /** Namespaces the control ids so two editors on one screen cannot collide. */
6
+ idPrefix: string;
7
+ writeMode: WriteModeValue;
8
+ missingRecordPolicy: MissingRecordPolicyValue;
9
+ missingRecordColumn: string;
10
+ missingRecordValue: string;
11
+ /** Upsert is only the calmer option when there is something to match on. */
12
+ dedupConfigured: boolean;
13
+ disabled?: boolean;
14
+ onWriteModeChange: (mode: WriteModeValue) => void;
15
+ onMissingRecordPolicyChange: (policy: MissingRecordPolicyValue) => void;
16
+ onMissingRecordColumnChange: (value: string) => void;
17
+ onMissingRecordValueChange: (value: string) => void;
18
+ };
19
+ export declare function MappingWriteModeFields({ idPrefix, writeMode, missingRecordPolicy, missingRecordColumn, missingRecordValue, dedupConfigured, disabled, onWriteModeChange, onMissingRecordPolicyChange, onMissingRecordColumnChange, onMissingRecordValueChange, }: MappingWriteModeFieldsProps): import("react/jsx-runtime").JSX.Element;