nucleus-core-ts 0.9.828 → 0.9.830

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 (47) hide show
  1. package/dist/fe/components/IntegrationsPage/components/MappingScheduleFields.js +1 -1
  2. package/dist/fe/components/IntegrationsPage/components/ReferenceCheckRows.js +89 -0
  3. package/dist/fe/components/IntegrationsPage/components/RunPanel.js +8 -2
  4. package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.d.ts +16 -0
  5. package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.js +16 -0
  6. package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.test.js +45 -0
  7. package/dist/fe/components/IntegrationsPage/types/records.d.ts +10 -0
  8. package/dist/index.js +3 -3
  9. package/dist/nucleus.config.d.ts +6 -1
  10. package/dist/src/ElysiaPlugin/routes/auth/fetchUserRolesAndClaims.d.ts +1 -1
  11. package/dist/src/ElysiaPlugin/routes/storage/index.d.ts +2 -2
  12. package/dist/src/Services/Integrations/types.d.ts +17 -0
  13. package/dist/src/Services/Integrations/writer.d.ts +1 -1
  14. package/dist/src/Shared/cron.test.d.ts +1 -0
  15. package/package.json +1 -1
  16. package/src/system.tables.json +14 -10
  17. package/dist/src/Services/Integrations/bindings.js +0 -248
  18. package/dist/src/Services/Integrations/bindings.test.js +0 -693
  19. package/dist/src/Services/Integrations/collect.js +0 -223
  20. package/dist/src/Services/Integrations/collect.test.js +0 -581
  21. package/dist/src/Services/Integrations/fetcher.js +0 -200
  22. package/dist/src/Services/Integrations/fetcher.test.js +0 -462
  23. package/dist/src/Services/Integrations/lock.js +0 -119
  24. package/dist/src/Services/Integrations/lock.test.js +0 -158
  25. package/dist/src/Services/Integrations/mapping.js +0 -412
  26. package/dist/src/Services/Integrations/mapping.test.js +0 -837
  27. package/dist/src/Services/Integrations/oauth.js +0 -216
  28. package/dist/src/Services/Integrations/oauth.test.js +0 -445
  29. package/dist/src/Services/Integrations/pagination.js +0 -262
  30. package/dist/src/Services/Integrations/pagination.test.js +0 -466
  31. package/dist/src/Services/Integrations/plan.js +0 -428
  32. package/dist/src/Services/Integrations/plan.test.js +0 -1216
  33. package/dist/src/Services/Integrations/revert.js +0 -208
  34. package/dist/src/Services/Integrations/revert.test.js +0 -605
  35. package/dist/src/Services/Integrations/runner.js +0 -382
  36. package/dist/src/Services/Integrations/runner.test.js +0 -653
  37. package/dist/src/Services/Integrations/scheduler.js +0 -114
  38. package/dist/src/Services/Integrations/scheduler.test.js +0 -95
  39. package/dist/src/Services/Integrations/transfer.js +0 -239
  40. package/dist/src/Services/Integrations/transfer.test.js +0 -195
  41. package/dist/src/Services/Integrations/types.js +0 -9
  42. package/dist/src/Services/Integrations/writer.js +0 -471
  43. package/dist/src/Services/Integrations/writer.test.js +0 -925
  44. /package/dist/{src/Services/Integrations/cron.test.d.ts → fe/components/IntegrationsPage/components/passwordIsNeeded.test.d.ts} +0 -0
  45. /package/dist/src/{Services/Integrations → Shared}/cron.d.ts +0 -0
  46. /package/dist/src/{Services/Integrations → Shared}/cron.js +0 -0
  47. /package/dist/src/{Services/Integrations → Shared}/cron.test.js +0 -0
@@ -1,7 +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 { useId } from 'react';
4
- import { describeCron, parseCron } from '../../../../src/Services/Integrations/cron';
4
+ import { describeCron, parseCron } from '../../../../src/Shared/cron';
5
5
  import { integrationsPageTheme } from '../theme';
6
6
  import { Field, Notice } from './Primitives';
7
7
  /**
@@ -191,6 +191,95 @@ export function ReferenceCheckRows({ rows, targetFields, targets, disabled, onCh
191
191
  })
192
192
  ]
193
193
  }),
194
+ row.onMissing === 'create' ? /*#__PURE__*/ _jsxs("div", {
195
+ className: "mt-3 flex flex-col gap-2",
196
+ children: [
197
+ /*#__PURE__*/ _jsxs("div", {
198
+ className: "flex flex-wrap items-center justify-between gap-2",
199
+ children: [
200
+ /*#__PURE__*/ _jsx("span", {
201
+ className: theme.field.label,
202
+ children: "Also fill on the values it creates"
203
+ }),
204
+ /*#__PURE__*/ _jsx("button", {
205
+ className: theme.button.ghost,
206
+ disabled: disabled || !referenced,
207
+ onClick: ()=>patch(index, {
208
+ createValues: [
209
+ ...row.createValues ?? [],
210
+ {
211
+ column: '',
212
+ from: ''
213
+ }
214
+ ]
215
+ }),
216
+ type: "button",
217
+ children: "Add a column"
218
+ })
219
+ ]
220
+ }),
221
+ (row.createValues ?? []).length === 0 ? /*#__PURE__*/ _jsx("p", {
222
+ className: theme.field.hint,
223
+ children: `A new ${row.entity || 'row'} carries only the value itself. The record that needed it usually has the rest — its code, its parent — and those can be filled here.`
224
+ }) : null,
225
+ (row.createValues ?? []).map((pair, pairIndex)=>/*#__PURE__*/ _jsxs("div", {
226
+ className: "grid grid-cols-1 gap-2 sm:grid-cols-[1fr_1fr_auto]",
227
+ children: [
228
+ /*#__PURE__*/ _jsxs("select", {
229
+ className: theme.field.input,
230
+ disabled: disabled,
231
+ onChange: (event)=>patch(index, {
232
+ createValues: (row.createValues ?? []).with(pairIndex, {
233
+ ...pair,
234
+ column: event.target.value
235
+ })
236
+ }),
237
+ value: pair.column,
238
+ children: [
239
+ /*#__PURE__*/ _jsx("option", {
240
+ value: "",
241
+ children: "Column to fill…"
242
+ }),
243
+ (referenced?.fields ?? []).map((field)=>/*#__PURE__*/ _jsx("option", {
244
+ value: field,
245
+ children: field
246
+ }, field))
247
+ ]
248
+ }),
249
+ /*#__PURE__*/ _jsxs("select", {
250
+ className: theme.field.input,
251
+ disabled: disabled,
252
+ onChange: (event)=>patch(index, {
253
+ createValues: (row.createValues ?? []).with(pairIndex, {
254
+ ...pair,
255
+ from: event.target.value
256
+ })
257
+ }),
258
+ value: pair.from,
259
+ children: [
260
+ /*#__PURE__*/ _jsx("option", {
261
+ value: "",
262
+ children: "…from this record's"
263
+ }),
264
+ targetFields.map((field)=>/*#__PURE__*/ _jsx("option", {
265
+ value: field,
266
+ children: field
267
+ }, field))
268
+ ]
269
+ }),
270
+ /*#__PURE__*/ _jsx("button", {
271
+ className: theme.button.ghost,
272
+ disabled: disabled,
273
+ onClick: ()=>patch(index, {
274
+ createValues: (row.createValues ?? []).toSpliced(pairIndex, 1)
275
+ }),
276
+ type: "button",
277
+ children: "Remove"
278
+ })
279
+ ]
280
+ }, pairIndex))
281
+ ]
282
+ }) : null,
194
283
  /*#__PURE__*/ _jsxs("div", {
195
284
  className: "mt-2 flex items-center justify-between gap-3",
196
285
  children: [
@@ -5,6 +5,7 @@ import { useIntegrationsStore } from '../store';
5
5
  import { useLabels } from '../text/context';
6
6
  import { integrationsPageTheme } from '../theme';
7
7
  import { Notice } from './Primitives';
8
+ import { passwordIsNeeded } from './passwordIsNeeded';
8
9
  import { RunConfirmStep } from './RunConfirmStep';
9
10
  import { MIN_SHARED_PASSWORD, RunPasswordField } from './RunPasswordField';
10
11
  import { RunPlanReferences } from './RunPlanReferences';
@@ -35,7 +36,12 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
35
36
  // here, once, and goes no further than this run.
36
37
  const accountRule = mapping.accountRule ?? null;
37
38
  const passwordColumn = mapping.passwordTargetColumn ?? '';
38
- const needsPassword = passwordColumn !== '' || accountRule !== null;
39
+ // ONE answer, used by the field, the button and the request alike. Asked three
40
+ // times in three places, they drifted: the field appeared and the button
41
+ // enabled, while the request still tested the older condition and sent no
42
+ // password at all — so every import that created logins was refused, by a
43
+ // message the app then replaced with "the import did not start".
44
+ const needsPassword = passwordIsNeeded(mapping);
39
45
  const passwordReady = !needsPassword || sharedPassword.length >= MIN_SHARED_PASSWORD;
40
46
  const activeRunId = store.activeRunId;
41
47
  const plan = store.plan;
@@ -53,7 +59,7 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
53
59
  const start = (confirm, referenceMode)=>{
54
60
  store.setError(null);
55
61
  setStarting(true);
56
- actions.startRun(mapping.id, confirm, passwordColumn ? sharedPassword : undefined, referenceMode).then((result)=>{
62
+ actions.startRun(mapping.id, confirm, needsPassword ? sharedPassword : undefined, referenceMode).then((result)=>{
57
63
  setStarting(false);
58
64
  // The server decides what counts as destructive; the panel only asks.
59
65
  if (result.needsConfirm) {
@@ -0,0 +1,16 @@
1
+ import type { IntegrationMapping } from '../types';
2
+ /**
3
+ * Does starting this import require a password?
4
+ *
5
+ * Two ways it can: the mapping writes one into a column of the record's own
6
+ * table, or it creates a login alongside each record. Either way the operator
7
+ * types it once, on the run screen, and it goes no further than that run.
8
+ *
9
+ * A function rather than three expressions, because it WAS three: the field that
10
+ * asks for the password, the button that starts the import, and the request that
11
+ * carries it each decided for themselves. Two were updated when accounts were
12
+ * added and one was not — so the field appeared, the button enabled, and the
13
+ * request sent nothing. Every import that created logins was refused for a
14
+ * missing password the operator had typed.
15
+ */
16
+ export declare function passwordIsNeeded(mapping: IntegrationMapping): boolean;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Does starting this import require a password?
3
+ *
4
+ * Two ways it can: the mapping writes one into a column of the record's own
5
+ * table, or it creates a login alongside each record. Either way the operator
6
+ * types it once, on the run screen, and it goes no further than that run.
7
+ *
8
+ * A function rather than three expressions, because it WAS three: the field that
9
+ * asks for the password, the button that starts the import, and the request that
10
+ * carries it each decided for themselves. Two were updated when accounts were
11
+ * added and one was not — so the field appeared, the button enabled, and the
12
+ * request sent nothing. Every import that created logins was refused for a
13
+ * missing password the operator had typed.
14
+ */ export function passwordIsNeeded(mapping) {
15
+ return Boolean(mapping.passwordTargetColumn) || mapping.accountRule != null;
16
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'bun:test';
2
+ import { passwordIsNeeded } from './passwordIsNeeded';
3
+ /**
4
+ * The field that ASKS for the password, the button that starts the import and
5
+ * the request that CARRIES it each decided this for themselves. Two were updated
6
+ * when accounts were added and one was not: the field appeared, the button
7
+ * enabled, and the request sent nothing — so every import that created logins
8
+ * was refused for a password the operator had typed.
9
+ */ const mapping = (over)=>({
10
+ id: 'm1',
11
+ name: 'x',
12
+ targetEntity: 'employees',
13
+ ...over
14
+ });
15
+ describe('does this import need a password', ()=>{
16
+ it('yes, when it writes one into the record own table', ()=>{
17
+ expect(passwordIsNeeded(mapping({
18
+ passwordTargetColumn: 'passwordHash'
19
+ }))).toBe(true);
20
+ });
21
+ it('yes, when it creates a login alongside each record', ()=>{
22
+ // The case that was missed. Nothing about the record's own table says so.
23
+ expect(passwordIsNeeded(mapping({
24
+ accountRule: {
25
+ entity: 'users',
26
+ idColumn: 'userId',
27
+ matchColumn: 'email',
28
+ matchFrom: 'email',
29
+ passwordColumn: 'password'
30
+ }
31
+ }))).toBe(true);
32
+ });
33
+ it('no, for an import that creates no accounts at all', ()=>{
34
+ expect(passwordIsNeeded(mapping({}))).toBe(false);
35
+ expect(passwordIsNeeded(mapping({
36
+ passwordTargetColumn: null,
37
+ accountRule: null
38
+ }))).toBe(false);
39
+ });
40
+ it('treats an empty column name as no column', ()=>{
41
+ expect(passwordIsNeeded(mapping({
42
+ passwordTargetColumn: ''
43
+ }))).toBe(false);
44
+ });
45
+ });
@@ -111,6 +111,16 @@ export type ReferenceCheckValue = {
111
111
  /** Column in that entity to match against. */
112
112
  entityColumn: string;
113
113
  onMissing: 'create' | 'skipRecord' | 'ignore';
114
+ /**
115
+ * Other columns to fill when this run CREATES the missing value.
116
+ *
117
+ * A department created from nothing but its own name is a row somebody has to
118
+ * finish by hand — and the record that needed it usually carries the rest.
119
+ */
120
+ createValues?: {
121
+ column: string;
122
+ from: string;
123
+ }[];
114
124
  };
115
125
  /**
116
126
  * A login created alongside each imported record.