nucleus-core-ts 0.10.147 → 0.10.151

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 (204) hide show
  1. package/LICENSE +48 -48
  2. package/dist/.build-ok +1 -1
  3. package/dist/fe/components/AuthorizationPage/labels.js +13 -13
  4. package/dist/fe/components/Captcha/labels.js +17 -17
  5. package/dist/fe/components/ChatPanel/helpers.js +17 -17
  6. package/dist/fe/components/ChatPanel/labels.js +16 -16
  7. package/dist/fe/components/DataTable/labels.js +16 -16
  8. package/dist/fe/components/DevicesPage/components/DeviceCard.js +15 -15
  9. package/dist/fe/components/DevicesPage/components/DevicesPage.js +9 -9
  10. package/dist/fe/components/DevicesPage/labels.js +12 -12
  11. package/dist/fe/components/ForgotPasswordPage/labels.js +8 -8
  12. package/dist/fe/components/FormBuilder/components/FormField.js +5 -5
  13. package/dist/fe/components/FormBuilder/utils/fieldMapping.js +9 -9
  14. package/dist/fe/components/IntegrationsPage/components/ConnectionAuthFields.js +8 -8
  15. package/dist/fe/components/IntegrationsPage/components/ConnectionBasicFields.js +3 -3
  16. package/dist/fe/components/IntegrationsPage/components/ConnectionExport.js +23 -23
  17. package/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +22 -22
  18. package/dist/fe/components/IntegrationsPage/components/ConnectionList.js +7 -7
  19. package/dist/fe/components/IntegrationsPage/components/ConnectionOauthFields.js +6 -6
  20. package/dist/fe/components/IntegrationsPage/components/ConnectionSettingsTab.js +24 -24
  21. package/dist/fe/components/IntegrationsPage/components/ConnectionSwitcher.js +19 -19
  22. package/dist/fe/components/IntegrationsPage/components/ConnectionTabs.js +15 -15
  23. package/dist/fe/components/IntegrationsPage/components/ConnectionTest.js +21 -21
  24. package/dist/fe/components/IntegrationsPage/components/ConnectionTransfer.js +18 -18
  25. package/dist/fe/components/IntegrationsPage/components/CredentialFields.js +19 -19
  26. package/dist/fe/components/IntegrationsPage/components/CredentialSlotRow.js +8 -8
  27. package/dist/fe/components/IntegrationsPage/components/EndpointForm.js +7 -7
  28. package/dist/fe/components/IntegrationsPage/components/EndpointFormFields.js +12 -12
  29. package/dist/fe/components/IntegrationsPage/components/EndpointList.js +7 -7
  30. package/dist/fe/components/IntegrationsPage/components/EndpointSample.js +36 -36
  31. package/dist/fe/components/IntegrationsPage/components/EndpointsTab.js +29 -29
  32. package/dist/fe/components/IntegrationsPage/components/FieldMappingRow.js +7 -7
  33. package/dist/fe/components/IntegrationsPage/components/FieldMappingRows.js +6 -6
  34. package/dist/fe/components/IntegrationsPage/components/IntegrationsPage.js +60 -60
  35. package/dist/fe/components/IntegrationsPage/components/MappingAccountFields.js +15 -15
  36. package/dist/fe/components/IntegrationsPage/components/MappingEditor.js +9 -9
  37. package/dist/fe/components/IntegrationsPage/components/MappingList.js +23 -23
  38. package/dist/fe/components/IntegrationsPage/components/MappingPasswordField.js +12 -12
  39. package/dist/fe/components/IntegrationsPage/components/MappingScheduleFields.js +23 -23
  40. package/dist/fe/components/IntegrationsPage/components/MappingWriteModeFields.js +31 -31
  41. package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +56 -56
  42. package/dist/fe/components/IntegrationsPage/components/PaginationFields.js +11 -11
  43. package/dist/fe/components/IntegrationsPage/components/ParamBindingRows.js +15 -15
  44. package/dist/fe/components/IntegrationsPage/components/Primitives.js +28 -28
  45. package/dist/fe/components/IntegrationsPage/components/ReferenceCheckRows.js +9 -9
  46. package/dist/fe/components/IntegrationsPage/components/RunConfirmStep.js +6 -6
  47. package/dist/fe/components/IntegrationsPage/components/RunHistory.js +6 -6
  48. package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +9 -9
  49. package/dist/fe/components/IntegrationsPage/components/RunPanel.js +40 -40
  50. package/dist/fe/components/IntegrationsPage/components/RunPasswordField.js +18 -18
  51. package/dist/fe/components/IntegrationsPage/components/RunPlanIssues.js +6 -6
  52. package/dist/fe/components/IntegrationsPage/components/RunPlanReferences.js +12 -12
  53. package/dist/fe/components/IntegrationsPage/components/RunPlanSuggestions.js +11 -11
  54. package/dist/fe/components/IntegrationsPage/components/RunPlanSummary.js +6 -6
  55. package/dist/fe/components/IntegrationsPage/components/RunProgressBar.js +6 -6
  56. package/dist/fe/components/IntegrationsPage/components/RunRevertConflicts.js +6 -6
  57. package/dist/fe/components/IntegrationsPage/components/Skeletons.js +7 -7
  58. package/dist/fe/components/IntegrationsPage/components/authOptions.js +4 -4
  59. package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +27 -27
  60. package/dist/fe/components/IntegrationsPage/components/credentialSlots.js +25 -25
  61. package/dist/fe/components/IntegrationsPage/components/deleteIsConfirmed.js +9 -9
  62. package/dist/fe/components/IntegrationsPage/components/fieldMappingOptions.js +4 -4
  63. package/dist/fe/components/IntegrationsPage/components/formSupport.js +34 -34
  64. package/dist/fe/components/IntegrationsPage/components/jsonArray.js +14 -14
  65. package/dist/fe/components/IntegrationsPage/components/jsonObjectField.js +15 -15
  66. package/dist/fe/components/IntegrationsPage/components/mappingDraft.js +10 -10
  67. package/dist/fe/components/IntegrationsPage/components/mappingStaleness.js +22 -22
  68. package/dist/fe/components/IntegrationsPage/components/nextRuns.js +8 -8
  69. package/dist/fe/components/IntegrationsPage/components/passwordIsNeeded.js +13 -13
  70. package/dist/fe/components/IntegrationsPage/components/pathParams.js +6 -6
  71. package/dist/fe/components/IntegrationsPage/components/runFormat.js +18 -18
  72. package/dist/fe/components/IntegrationsPage/components/settleOwnership.js +18 -18
  73. package/dist/fe/components/IntegrationsPage/components/useInfiniteList.js +8 -8
  74. package/dist/fe/components/IntegrationsPage/store/index.js +20 -20
  75. package/dist/fe/components/IntegrationsPage/store/upsertInto.js +12 -12
  76. package/dist/fe/components/IntegrationsPage/text/context.js +6 -6
  77. package/dist/fe/components/IntegrationsPage/text/index.js +18 -18
  78. package/dist/fe/components/IntegrationsPage/theme/index.js +29 -29
  79. package/dist/fe/components/IntegrationsPage/types/records.js +10 -10
  80. package/dist/fe/components/IntegrationsPage/types/results.js +3 -3
  81. package/dist/fe/components/NotificationCenter/labels.js +17 -17
  82. package/dist/fe/components/ProfilePage/labels.js +10 -10
  83. package/dist/fe/components/ResetPasswordPage/labels.js +4 -4
  84. package/dist/fe/components/SetPasswordPage/labels.js +11 -11
  85. package/dist/fe/components/UsersPage/labels.js +14 -14
  86. package/dist/fe/utils/columnUtils.js +24 -24
  87. package/dist/fe/utils/searchFold.js +36 -36
  88. package/dist/index.js +6 -6
  89. package/dist/server.js +6 -6
  90. package/dist/src/Client/Proxy/authz.js +4 -4
  91. package/dist/src/Client/Proxy/httpProxy.js +136 -136
  92. package/dist/src/Client/Proxy/largeBody.js +29 -29
  93. package/dist/src/Client/Proxy/utils.js +32 -32
  94. package/dist/src/Client/Proxy/wsProxy.js +59 -59
  95. package/dist/src/Managers/Redis/index.d.ts +14 -0
  96. package/dist/src/Nucleus/routes/auth/admin/impersonationStore.d.ts +3 -4
  97. package/dist/src/Nucleus/routes/auth/refresh/issuedPair.d.ts +67 -0
  98. package/dist/src/Nucleus/routes/auth/refresh/recordIssued.d.ts +15 -6
  99. package/dist/src/Nucleus/routes/auth/refresh/refreshChain.d.ts +56 -0
  100. package/dist/src/Services/Auth/SessionStore/utils.d.ts +2 -0
  101. package/dist/src/Shared/cron.js +71 -71
  102. package/dist/src/Shared/redactSecrets.js +156 -156
  103. package/dist/src/Shared/redaction/bare.js +61 -61
  104. package/dist/src/Shared/redaction/floor.js +48 -48
  105. package/dist/src/Shared/redaction/freeText.js +70 -70
  106. package/dist/src/Shared/redaction/json.js +30 -30
  107. package/dist/src/Shared/redaction/lists.js +23 -23
  108. package/dist/src/Shared/redaction/markers.js +28 -28
  109. package/dist/src/Shared/redaction/names.js +49 -49
  110. package/dist/src/Shared/redaction/pairs.js +54 -54
  111. package/dist/src/Shared/redaction/secretString.js +29 -29
  112. package/dist/src/Shared/redaction/structured.js +52 -52
  113. package/dist/src/Shared/redaction/walk.js +25 -25
  114. package/infra/scripts/generate-project.ts +766 -766
  115. package/infra/templates/README.md +57 -57
  116. package/infra/templates/backend/.dockerignore +9 -9
  117. package/infra/templates/backend/.env.example +15 -15
  118. package/infra/templates/backend/README.md +28 -28
  119. package/infra/templates/backend/azure-pipelines.yml +135 -135
  120. package/infra/templates/backend/k8s/Dockerfile +31 -31
  121. package/infra/templates/backend/k8s/configmap.yaml +26 -26
  122. package/infra/templates/backend/k8s/deployment.yaml +85 -85
  123. package/infra/templates/backend/k8s/pvc.yaml +12 -12
  124. package/infra/templates/backend/k8s/service.yaml +13 -13
  125. package/infra/templates/backend/tsconfig.json +22 -22
  126. package/infra/templates/frontend/.dockerignore +10 -10
  127. package/infra/templates/frontend/.env.example +8 -8
  128. package/infra/templates/frontend/.env.local.example +10 -10
  129. package/infra/templates/frontend/README.md +69 -69
  130. package/infra/templates/frontend/app/[tablename]/page.tsx +37 -37
  131. package/infra/templates/frontend/app/_components/AuthWrapper/index.tsx +63 -63
  132. package/infra/templates/frontend/app/authorization/page.tsx +43 -43
  133. package/infra/templates/frontend/app/change-password/page.tsx +23 -23
  134. package/infra/templates/frontend/app/devices/page.tsx +127 -127
  135. package/infra/templates/frontend/app/forgot-password/page.tsx +20 -20
  136. package/infra/templates/frontend/app/globals.css +28 -28
  137. package/infra/templates/frontend/app/layout.tsx +35 -35
  138. package/infra/templates/frontend/app/login/page.tsx +33 -33
  139. package/infra/templates/frontend/app/page.tsx +565 -565
  140. package/infra/templates/frontend/app/profile/page.tsx +92 -92
  141. package/infra/templates/frontend/app/register/page.tsx +27 -27
  142. package/infra/templates/frontend/app/reset-password/page.tsx +60 -60
  143. package/infra/templates/frontend/app/set-password/page.tsx +43 -43
  144. package/infra/templates/frontend/app/ui-docs/page.tsx +52 -52
  145. package/infra/templates/frontend/app/users/page.tsx +52 -52
  146. package/infra/templates/frontend/app/verify-email/page.tsx +41 -41
  147. package/infra/templates/frontend/app/verify-magic-link/page.tsx +46 -46
  148. package/infra/templates/frontend/azure-pipelines.yml +137 -137
  149. package/infra/templates/frontend/code-and-design-principles.md +438 -438
  150. package/infra/templates/frontend/k8s/Dockerfile +50 -50
  151. package/infra/templates/frontend/k8s/configmap.yaml +18 -18
  152. package/infra/templates/frontend/k8s/deployment.yaml +72 -72
  153. package/infra/templates/frontend/k8s/ingress.yaml +36 -36
  154. package/infra/templates/frontend/k8s/service.yaml +14 -14
  155. package/infra/templates/frontend/lib/api/endpoints.ts +37 -37
  156. package/infra/templates/frontend/lib/api/factory.ts +44 -44
  157. package/infra/templates/frontend/lib/api/hook.ts +10 -10
  158. package/infra/templates/frontend/next.config.ts +19 -19
  159. package/infra/templates/frontend/package.json +34 -34
  160. package/infra/templates/frontend/postcss.config.mjs +5 -5
  161. package/infra/templates/frontend/server.ts +114 -114
  162. package/infra/templates/frontend/tsconfig.json +23 -23
  163. package/package.json +1 -1
  164. package/public/components/CTA.tsx +25 -25
  165. package/public/components/Capabilities.tsx +67 -67
  166. package/public/components/Features.tsx +107 -107
  167. package/public/components/Footer.tsx +58 -58
  168. package/public/components/Hero.tsx +51 -51
  169. package/public/components/Managers.tsx +55 -55
  170. package/public/components/Nav.tsx +91 -91
  171. package/public/components/Stack.tsx +36 -36
  172. package/public/components/docs/Docs.tsx +60 -60
  173. package/public/components/docs/DocsContent.tsx +176 -176
  174. package/public/components/docs/DocsRightSidebar.tsx +53 -53
  175. package/public/components/docs/DocsSidebar.tsx +54 -54
  176. package/public/components/docs/content/authentication.ts +836 -836
  177. package/public/components/docs/content/authorization.ts +268 -268
  178. package/public/components/docs/content/backupRestore.ts +173 -173
  179. package/public/components/docs/content/clientModule.ts +287 -287
  180. package/public/components/docs/content/entityCrud.ts +682 -682
  181. package/public/components/docs/content/monitoring.ts +313 -313
  182. package/public/components/docs/content/multiTenant.ts +269 -269
  183. package/public/components/docs/content/oauthApiKeys.ts +325 -325
  184. package/public/components/docs/content/pubsub.ts +131 -131
  185. package/public/components/docs/content/storageCdn.ts +171 -171
  186. package/public/components/docs/content/verification.ts +289 -289
  187. package/public/components/docs/docsData.ts +113 -113
  188. package/public/components/docs/index.ts +6 -6
  189. package/public/components/index.ts +9 -9
  190. package/public/docs.css +629 -629
  191. package/public/index.html +81 -81
  192. package/public/index.tsx +42 -42
  193. package/public/store.ts +76 -76
  194. package/public/styles.css +858 -858
  195. package/schemas/config.nucleus.json +2651 -2651
  196. package/scripts/audit-purge-noise.ts +120 -120
  197. package/scripts/build.ts +582 -582
  198. package/scripts/generate-system-tables-types.ts +18 -18
  199. package/scripts/generate-types-schema.test.ts +73 -73
  200. package/scripts/generate-types-schema.ts +1284 -1284
  201. package/scripts/packagedScripts.test.ts +87 -87
  202. package/scripts/publish.ts +276 -276
  203. package/scripts/skillCitations.test.ts +107 -107
  204. package/scripts/verify-publishable.ts +40 -40
@@ -45,19 +45,19 @@ export function draftFromSource(source) {
45
45
  timeoutMs: source.timeoutMs == null ? '' : String(source.timeoutMs)
46
46
  };
47
47
  }
48
- /**
49
- * The boxes a SAVED record should correct, and only those.
50
- *
51
- * After an edit the form has to show what was actually stored — the server
52
- * trims, defaults and ignores things, and the boxes went on showing what was
53
- * typed. But re-deriving the whole draft from the reply is not safe: a
54
- * field-scoped patch claim strips columns out of the response, and
55
- * {@link draftFromSource} substitutes a default for every column it does not
56
- * find. A reply that legitimately omits `name` would therefore blank the name
57
- * box — trading a stale screen for a destroyed one.
58
- *
59
- * So this reads the reply key by key and says nothing about a key that is not
60
- * there. What it returns is a patch, applied over the draft the operator has.
48
+ /**
49
+ * The boxes a SAVED record should correct, and only those.
50
+ *
51
+ * After an edit the form has to show what was actually stored — the server
52
+ * trims, defaults and ignores things, and the boxes went on showing what was
53
+ * typed. But re-deriving the whole draft from the reply is not safe: a
54
+ * field-scoped patch claim strips columns out of the response, and
55
+ * {@link draftFromSource} substitutes a default for every column it does not
56
+ * find. A reply that legitimately omits `name` would therefore blank the name
57
+ * box — trading a stale screen for a destroyed one.
58
+ *
59
+ * So this reads the reply key by key and says nothing about a key that is not
60
+ * there. What it returns is a patch, applied over the draft the operator has.
61
61
  */ export function draftPatchFromSource(source) {
62
62
  const full = draftFromSource(source);
63
63
  const patch = {};
@@ -96,13 +96,13 @@ export function usesQueryParam(authType) {
96
96
  export function usesOauth(authType) {
97
97
  return authType === 'oauth2';
98
98
  }
99
- /**
100
- * Rebuilds the record.
101
- *
102
- * Fields the chosen auth type does not use are sent as null rather than left
103
- * alone. Switching a connection from OAuth to a bearer token and leaving the old
104
- * token URL behind is how a source ends up carrying settings nobody can see in
105
- * the form but the server still reads.
99
+ /**
100
+ * Rebuilds the record.
101
+ *
102
+ * Fields the chosen auth type does not use are sent as null rather than left
103
+ * alone. Switching a connection from OAuth to a bearer token and leaving the old
104
+ * token URL behind is how a source ends up carrying settings nobody can see in
105
+ * the form but the server still reads.
106
106
  */ export function draftToSource(draft, id) {
107
107
  const oauth = usesOauth(draft.authType);
108
108
  const timeout = Number.parseInt(draft.timeoutMs.trim(), 10);
@@ -139,13 +139,13 @@ export function usesOauth(authType) {
139
139
  }
140
140
  const SLUG_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
141
141
  const DIGITS_PATTERN = /^\d+$/;
142
- /**
143
- * What must be filled in before a save is worth attempting.
144
- *
145
- * The base URL is checked for a scheme and nothing else. It is deliberately not
146
- * checked for https, for a public host or for a resolvable name: an in-cluster
147
- * service on plain http is a normal target, and a form that rejected it would be
148
- * rejecting a working setup.
142
+ /**
143
+ * What must be filled in before a save is worth attempting.
144
+ *
145
+ * The base URL is checked for a scheme and nothing else. It is deliberately not
146
+ * checked for https, for a public host or for a resolvable name: an in-cluster
147
+ * service on plain http is a normal target, and a form that rejected it would be
148
+ * rejecting a working setup.
149
149
  */ export function validateConnectionDraft(draft) {
150
150
  const errors = {};
151
151
  if (draft.name.trim().length === 0) errors.name = 'Give the connection a name.';
@@ -14,24 +14,24 @@
14
14
  if (slot === 'oauthClientSecret') return source.oauthClientSecretHint ?? null;
15
15
  return source.oauthPasswordHint ?? null;
16
16
  }
17
- /**
18
- * Every name a server might return a preview under.
19
- *
20
- * This list was wrong, and the way it was wrong is worth keeping: it accepted
21
- * the slot name and the hint column, on the reasoning that a server would use
22
- * one or the other. Nucleus' own route uses NEITHER for the OAuth pair — it
23
- * answers `clientSecret` and `password`. So a saved password came back under a
24
- * name nothing looked for, {@link hintFromResult} said "the reply mentioned no
25
- * such slot", and the screen went on showing the record it had loaded BEFORE
26
- * the save: "Not set", under the words "Saved". Refreshing fixed it, which is
27
- * the tell — the value was in the database the whole time.
28
- *
29
- * `token` was unaffected, because for that one slot the guess happened to
30
- * match. One correct name out of three is what let this ship.
31
- *
32
- * The guard against it happening again is not a longer list: it is
33
- * `credentialSlots.contract.test.ts`, which reads the server route and fails if
34
- * it ever emits a name this does not accept.
17
+ /**
18
+ * Every name a server might return a preview under.
19
+ *
20
+ * This list was wrong, and the way it was wrong is worth keeping: it accepted
21
+ * the slot name and the hint column, on the reasoning that a server would use
22
+ * one or the other. Nucleus' own route uses NEITHER for the OAuth pair — it
23
+ * answers `clientSecret` and `password`. So a saved password came back under a
24
+ * name nothing looked for, {@link hintFromResult} said "the reply mentioned no
25
+ * such slot", and the screen went on showing the record it had loaded BEFORE
26
+ * the save: "Not set", under the words "Saved". Refreshing fixed it, which is
27
+ * the tell — the value was in the database the whole time.
28
+ *
29
+ * `token` was unaffected, because for that one slot the guess happened to
30
+ * match. One correct name out of three is what let this ship.
31
+ *
32
+ * The guard against it happening again is not a longer list: it is
33
+ * `credentialSlots.contract.test.ts`, which reads the server route and fails if
34
+ * it ever emits a name this does not accept.
35
35
  */ const HINT_KEYS = {
36
36
  token: [
37
37
  'token',
@@ -49,13 +49,13 @@
49
49
  'oauthPasswordHint'
50
50
  ]
51
51
  };
52
- /**
53
- * The record columns a set of new masks writes to.
54
- *
55
- * The counterpart of {@link storedHint}: that reads a hint off the record, this
56
- * puts one back. Both exist because the screen answers from local state OR from
57
- * the record, and if only one of the two is updated after a save the answer
58
- * depends on whether the component happens to have been remounted since.
52
+ /**
53
+ * The record columns a set of new masks writes to.
54
+ *
55
+ * The counterpart of {@link storedHint}: that reads a hint off the record, this
56
+ * puts one back. Both exist because the screen answers from local state OR from
57
+ * the record, and if only one of the two is updated after a save the answer
58
+ * depends on whether the component happens to have been remounted since.
59
59
  */ export function hintColumnsFor(hints) {
60
60
  const columns = {};
61
61
  if ('token' in hints) columns.authTokenHint = hints.token ?? null;
@@ -1,12 +1,12 @@
1
- /**
2
- * Whether what the operator typed matches the connection they are deleting.
3
- *
4
- * Kept out of the component so the rule can be tested: it is the only thing
5
- * standing between a stray click and a connection's calls, mappings and run
6
- * history. Trimmed on both sides because a name pasted out of the list carries
7
- * a trailing space often enough that refusing it reads as a bug, and compared
8
- * exactly otherwise — two connections called "HR" and "HR 2" must not confirm
9
- * for each other.
1
+ /**
2
+ * Whether what the operator typed matches the connection they are deleting.
3
+ *
4
+ * Kept out of the component so the rule can be tested: it is the only thing
5
+ * standing between a stray click and a connection's calls, mappings and run
6
+ * history. Trimmed on both sides because a name pasted out of the list carries
7
+ * a trailing space often enough that refusing it reads as a bug, and compared
8
+ * exactly otherwise — two connections called "HR" and "HR 2" must not confirm
9
+ * for each other.
10
10
  */ export function deleteIsConfirmed(name, typed) {
11
11
  const target = name.trim();
12
12
  if (target.length === 0) return false;
@@ -1,7 +1,7 @@
1
- /**
2
- * The vocabulary of a field mapping row, kept out of the components so the
3
- * header, the row and the editor cannot disagree about what a column is called
4
- * or how wide it is.
1
+ /**
2
+ * The vocabulary of a field mapping row, kept out of the components so the
3
+ * header, the row and the editor cannot disagree about what a column is called
4
+ * or how wide it is.
5
5
  */ /** A source that reads from the record the PARENT endpoint produced. */ export const PARENT_PREFIX = '__parent.';
6
6
  /** One template for the header and every row, so the columns cannot drift apart. */ export const FIELD_MAPPING_GRID = 'grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-[1fr_1fr_1fr_1fr_1fr_auto]';
7
7
  export const FIELD_MAPPING_COLUMNS = [
@@ -1,13 +1,13 @@
1
- /**
2
- * The small things every form in this panel needs and none of them deserves a file.
3
- */ /**
4
- * How an endpoint should read in a list someone has to choose from.
5
- *
6
- * The parameter-binding picker showed `tag`, which groups the endpoints of one
7
- * API — so a source whose endpoints are all tagged `corporate-portal` offered
8
- * three identical options and no way to tell which one supplied the value.
9
- * The name is what distinguishes them; the path is what distinguishes them when
10
- * nobody typed a name, since it is the one field that cannot be blank.
1
+ /**
2
+ * The small things every form in this panel needs and none of them deserves a file.
3
+ */ /**
4
+ * How an endpoint should read in a list someone has to choose from.
5
+ *
6
+ * The parameter-binding picker showed `tag`, which groups the endpoints of one
7
+ * API — so a source whose endpoints are all tagged `corporate-portal` offered
8
+ * three identical options and no way to tell which one supplied the value.
9
+ * The name is what distinguishes them; the path is what distinguishes them when
10
+ * nobody typed a name, since it is the one field that cannot be blank.
11
11
  */ /** Same caps as the engine's `flattenKeys`: this feeds a dropdown, not an analysis. */ const MAX_FIELDS_LISTED = 200;
12
12
  const MAX_FIELD_DEPTH = 4;
13
13
  function walkKeys(value, prefix, out, depth) {
@@ -27,18 +27,18 @@ function walkKeys(value, prefix, out, depth) {
27
27
  }
28
28
  return out;
29
29
  }
30
- /**
31
- * The field names an endpoint's STORED sample reports.
32
- *
33
- * A sample is persisted on the endpoint precisely so the field list survives a
34
- * reload, but the mapping editor was reading only the sample taken in this
35
- * browser session. Come back to a connection tomorrow and it said "sample the
36
- * endpoint first" about an endpoint it had already sampled and saved — so the
37
- * source names had to be typed from memory, which is the one thing the sample
38
- * exists to prevent.
39
- *
40
- * Mirrors the engine's `flattenKeys` rather than calling it: that lives in the
41
- * server bundle, which this kit cannot import.
30
+ /**
31
+ * The field names an endpoint's STORED sample reports.
32
+ *
33
+ * A sample is persisted on the endpoint precisely so the field list survives a
34
+ * reload, but the mapping editor was reading only the sample taken in this
35
+ * browser session. Come back to a connection tomorrow and it said "sample the
36
+ * endpoint first" about an endpoint it had already sampled and saved — so the
37
+ * source names had to be typed from memory, which is the one thing the sample
38
+ * exists to prevent.
39
+ *
40
+ * Mirrors the engine's `flattenKeys` rather than calling it: that lives in the
41
+ * server bundle, which this kit cannot import.
42
42
  */ export function fieldsOfStoredSample(sample) {
43
43
  const first = sample?.records?.[0];
44
44
  if (!first || typeof first !== 'object') return [];
@@ -51,24 +51,24 @@ export function endpointLabel(endpoint) {
51
51
  if (path) return `${endpoint.method} ${path}`;
52
52
  return endpoint.tag;
53
53
  }
54
- /**
55
- * The ids a control should point `aria-describedby` at.
56
- *
57
- * {@link Field} renders its hint as `<id>-hint` and its error as `<id>-error`,
58
- * so pointing at an id that was not rendered would leave a screen reader
59
- * announcing nothing — the ids are named here only when they exist.
54
+ /**
55
+ * The ids a control should point `aria-describedby` at.
56
+ *
57
+ * {@link Field} renders its hint as `<id>-hint` and its error as `<id>-error`,
58
+ * so pointing at an id that was not rendered would leave a screen reader
59
+ * announcing nothing — the ids are named here only when they exist.
60
60
  */ export function describedBy(id, hasHint, hasError) {
61
61
  if (hasHint && hasError) return `${id}-hint ${id}-error`;
62
62
  if (hasHint) return `${id}-hint`;
63
63
  if (hasError) return `${id}-error`;
64
64
  return undefined;
65
65
  }
66
- /**
67
- * Whatever a rejected action carries, as a sentence.
68
- *
69
- * The server's own wording is kept verbatim wherever there is one: it is the
70
- * only text that knows WHICH setting was wrong, and a friendlier replacement
71
- * would be a friendlier way of saying nothing.
66
+ /**
67
+ * Whatever a rejected action carries, as a sentence.
68
+ *
69
+ * The server's own wording is kept verbatim wherever there is one: it is the
70
+ * only text that knows WHICH setting was wrong, and a friendlier replacement
71
+ * would be a friendlier way of saying nothing.
72
72
  */ export function errorText(reason, fallback) {
73
73
  if (reason instanceof Error && reason.message.length > 0) return reason.message;
74
74
  if (typeof reason === 'string' && reason.length > 0) return reason;
@@ -1,17 +1,17 @@
1
- /**
2
- * Reading a jsonb column that is supposed to hold a list.
3
- *
4
- * It is not always a list. The schema generator wrote every object-shaped
5
- * default — including `[]` — into Postgres as `'{}'`, which is an empty OBJECT.
6
- * So every row that never had a value reads back as `{}`, and the first
7
- * `for…of` over it throws "object is not iterable". That is every existing row
8
- * the moment a new column is added to a table already in use, which is exactly
9
- * when nobody is looking: the column is new, nothing has filled it in, and the
10
- * screen that reads it dies on the next click.
11
- *
12
- * The generator is fixed. This exists because the rows it already wrote are
13
- * still there, and because a value crossing the wire from a database is not
14
- * something a component should have to trust.
1
+ /**
2
+ * Reading a jsonb column that is supposed to hold a list.
3
+ *
4
+ * It is not always a list. The schema generator wrote every object-shaped
5
+ * default — including `[]` — into Postgres as `'{}'`, which is an empty OBJECT.
6
+ * So every row that never had a value reads back as `{}`, and the first
7
+ * `for…of` over it throws "object is not iterable". That is every existing row
8
+ * the moment a new column is added to a table already in use, which is exactly
9
+ * when nobody is looking: the column is new, nothing has filled it in, and the
10
+ * screen that reads it dies on the next click.
11
+ *
12
+ * The generator is fixed. This exists because the rows it already wrote are
13
+ * still there, and because a value crossing the wire from a database is not
14
+ * something a component should have to trust.
15
15
  */ export function asArray(value) {
16
16
  return Array.isArray(value) ? value : [];
17
17
  }
@@ -1,18 +1,18 @@
1
- /**
2
- * A JSON object typed into a form field.
3
- *
4
- * Query parameters and a request body are `Record<string, unknown>` on the wire
5
- * — an endpoint may need `{"employeeTypes": ["A", "S"]}`, which no key/value
6
- * grid of strings can express. So the field takes JSON, and the only question
7
- * is whether a form is allowed to save what it cannot parse. It is not: a body
8
- * saved as broken text fails at RUN time, against a live service, with an error
9
- * from somewhere else entirely.
10
- */ /**
11
- * Parses the text of a JSON-object field.
12
- *
13
- * Empty is `null` rather than `{}` — "nothing configured" and "an empty object
14
- * configured" read the same on screen but not in a request, and the column is
15
- * nullable precisely so the difference survives.
1
+ /**
2
+ * A JSON object typed into a form field.
3
+ *
4
+ * Query parameters and a request body are `Record<string, unknown>` on the wire
5
+ * — an endpoint may need `{"employeeTypes": ["A", "S"]}`, which no key/value
6
+ * grid of strings can express. So the field takes JSON, and the only question
7
+ * is whether a form is allowed to save what it cannot parse. It is not: a body
8
+ * saved as broken text fails at RUN time, against a live service, with an error
9
+ * from somewhere else entirely.
10
+ */ /**
11
+ * Parses the text of a JSON-object field.
12
+ *
13
+ * Empty is `null` rather than `{}` — "nothing configured" and "an empty object
14
+ * configured" read the same on screen but not in a request, and the column is
15
+ * nullable precisely so the difference survives.
16
16
  */ export function parseJsonObject(text) {
17
17
  const trimmed = text.trim();
18
18
  if (trimmed === '') return {
@@ -1,10 +1,10 @@
1
1
  import { asArray } from './jsonArray';
2
2
  import { missingRecordFieldsIncomplete } from './MappingWriteModeFields';
3
- /**
4
- * Upsert and ignore: the two choices that cannot lose a row by themselves.
5
- *
6
- * `targetEntity` is filled in when the operator arrived from the table itself —
7
- * they have already said which one, and asking again is the panel forgetting.
3
+ /**
4
+ * Upsert and ignore: the two choices that cannot lose a row by themselves.
5
+ *
6
+ * `targetEntity` is filled in when the operator arrived from the table itself —
7
+ * they have already said which one, and asking again is the panel forgetting.
8
8
  */ export function blankMappingDraft(endpointId, targetEntity = '') {
9
9
  return {
10
10
  endpointId,
@@ -42,11 +42,11 @@ import { missingRecordFieldsIncomplete } from './MappingWriteModeFields';
42
42
  enabled: mapping.enabled
43
43
  };
44
44
  }
45
- /**
46
- * Everything standing between this draft and a run that does what it says.
47
- *
48
- * Reported as a list rather than a boolean because "Save is greyed out" with no
49
- * reason is the single most common way a form wastes somebody's afternoon.
45
+ /**
46
+ * Everything standing between this draft and a run that does what it says.
47
+ *
48
+ * Reported as a list rather than a boolean because "Save is greyed out" with no
49
+ * reason is the single most common way a form wastes somebody's afternoon.
50
50
  */ export function mappingDraftIssues(draft) {
51
51
  const issues = [];
52
52
  if (draft.name.trim() === '') issues.push('Give the mapping a name.');
@@ -1,31 +1,31 @@
1
- /**
2
- * Whether a mapping is actually keeping its table up to date.
3
- *
4
- * The list already said "Last run: 2026-08-02 12:51". Next to four siblings that
5
- * all ran the previous night, that line is the difference between a working
6
- * integration and a table two days out of date — and it takes arithmetic to see
7
- * it, so nobody did. What happened instead: a chief executive was missing from
8
- * the directory, a director's title was the one he had held in July, and people
9
- * who had left were still listed. All four were the same fact, and the screen
10
- * that knew it showed a timestamp.
11
- *
12
- * The cause underneath was smaller still: that mapping had no schedule. Its
13
- * siblings ran nightly; it only ever ran when somebody pressed a button, and
14
- * nobody had pressed it since Sunday. Nothing on the screen said so.
15
- *
16
- * So this answers two questions the timestamp cannot: does anything run this on
17
- * its own, and is what it wrote still recent. Both are properties of the row, so
18
- * they belong on the row.
1
+ /**
2
+ * Whether a mapping is actually keeping its table up to date.
3
+ *
4
+ * The list already said "Last run: 2026-08-02 12:51". Next to four siblings that
5
+ * all ran the previous night, that line is the difference between a working
6
+ * integration and a table two days out of date — and it takes arithmetic to see
7
+ * it, so nobody did. What happened instead: a chief executive was missing from
8
+ * the directory, a director's title was the one he had held in July, and people
9
+ * who had left were still listed. All four were the same fact, and the screen
10
+ * that knew it showed a timestamp.
11
+ *
12
+ * The cause underneath was smaller still: that mapping had no schedule. Its
13
+ * siblings ran nightly; it only ever ran when somebody pressed a button, and
14
+ * nobody had pressed it since Sunday. Nothing on the screen said so.
15
+ *
16
+ * So this answers two questions the timestamp cannot: does anything run this on
17
+ * its own, and is what it wrote still recent. Both are properties of the row, so
18
+ * they belong on the row.
19
19
  */ const HOUR = 60 * 60 * 1000;
20
20
  export function mappingStaleness(question) {
21
21
  const unscheduled = question.scheduleEnabled !== true;
22
22
  const staleAfter = (question.staleAfterHours ?? 24) * HOUR;
23
23
  const parsed = question.lastRunAt ? Date.parse(question.lastRunAt) : Number.NaN;
24
24
  if (!Number.isFinite(parsed)) {
25
- /*
26
- * Never run is only worth a warning when nothing is going to run it.
27
- * A mapping created five minutes ago with a schedule on it is not a
28
- * problem — it is a mapping waiting for tonight.
25
+ /*
26
+ * Never run is only worth a warning when nothing is going to run it.
27
+ * A mapping created five minutes ago with a schedule on it is not a
28
+ * problem — it is a mapping waiting for tonight.
29
29
  */ return {
30
30
  unscheduled,
31
31
  neverRun: true,
@@ -1,12 +1,12 @@
1
1
  import { nextFireTimes } from '../../../../src/Shared/cron';
2
- /**
3
- * A fixed, unambiguous format rather than the reader's locale.
4
- *
5
- * `toLocaleString()` here would render the server's locale on the server and
6
- * the browser's on the client — two different strings for one row, and a
7
- * hydration tear. It would also print 8/5/2026, which in Turkey reads as the
8
- * 8th of May. Year-first with the zone spelled out cannot be misread in either
9
- * direction.
2
+ /**
3
+ * A fixed, unambiguous format rather than the reader's locale.
4
+ *
5
+ * `toLocaleString()` here would render the server's locale on the server and
6
+ * the browser's on the client — two different strings for one row, and a
7
+ * hydration tear. It would also print 8/5/2026, which in Turkey reads as the
8
+ * 8th of May. Year-first with the zone spelled out cannot be misread in either
9
+ * direction.
10
10
  */ function partsIn(when, timeZone) {
11
11
  const parts = new Intl.DateTimeFormat('en-CA', {
12
12
  timeZone,
@@ -1,16 +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.
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
14
  */ export function passwordIsNeeded(mapping) {
15
15
  return Boolean(mapping.passwordTargetColumn) || mapping.accountRule != null;
16
16
  }
@@ -1,9 +1,9 @@
1
- /**
2
- * Reading the parameter names out of an endpoint path.
3
- *
4
- * Shared by the sample screen, which asks for a value by hand, and the binding
5
- * editor, which asks where the value should come from on a real run. One reader
6
- * so the two screens can never disagree about what a path needs.
1
+ /**
2
+ * Reading the parameter names out of an endpoint path.
3
+ *
4
+ * Shared by the sample screen, which asks for a value by hand, and the binding
5
+ * editor, which asks where the value should come from on a real run. One reader
6
+ * so the two screens can never disagree about what a path needs.
7
7
  */ const PARAM_PATTERN = /\{([^{}]+)\}|:([A-Za-z_][A-Za-z0-9_]*)/g;
8
8
  /** `{brace}` and `:colon` are both accepted, so both are read back. */ export function pathParamNames(path) {
9
9
  const names = [];
@@ -1,9 +1,9 @@
1
- /**
2
- * The small conversions the run screens share.
3
- *
4
- * Nothing here touches the server or React — it turns a record into the words an
5
- * operator reads, and it does so the SAME way in every place, so a duration on a
6
- * history row cannot disagree with the one on the panel above it.
1
+ /**
2
+ * The small conversions the run screens share.
3
+ *
4
+ * Nothing here touches the server or React — it turns a record into the words an
5
+ * operator reads, and it does so the SAME way in every place, so a duration on a
6
+ * history row cannot disagree with the one on the panel above it.
7
7
  */ /** A run's outcome, coloured. Reverted is deliberately calm: nothing is wrong with it. */ export const RUN_STATUS_TONE = {
8
8
  queued: 'neutral',
9
9
  running: 'info',
@@ -18,12 +18,12 @@ export function errorText(error, fallback) {
18
18
  function pad(value) {
19
19
  return String(value).padStart(2, '0');
20
20
  }
21
- /**
22
- * A moment, in UTC.
23
- *
24
- * Deliberately not locale-formatted: `toLocaleString` reads the server's zone on
25
- * the server and the browser's on the client, so the two renders differ and the
26
- * row tears on hydration. UTC is the same number everywhere, and it is labelled.
21
+ /**
22
+ * A moment, in UTC.
23
+ *
24
+ * Deliberately not locale-formatted: `toLocaleString` reads the server's zone on
25
+ * the server and the browser's on the client, so the two renders differ and the
26
+ * row tears on hydration. UTC is the same number everywhere, and it is labelled.
27
27
  */ export function formatMoment(value) {
28
28
  if (!value) return '—';
29
29
  const date = new Date(value);
@@ -65,12 +65,12 @@ export function formatDuration(startedAt, finishedAt) {
65
65
  if (run.errorCount > 0) parts.push(`${run.errorCount} failed`);
66
66
  return parts.join(' · ');
67
67
  }
68
- /**
69
- * What a destructive run takes away, counted wherever there is a plan to count from.
70
- *
71
- * The mapping alone can say WHAT will happen; only the plan can say to how many
72
- * rows. Both wordings exist because an operator is allowed to skip the preview,
73
- * and a confirmation that says "some rows" is not a confirmation.
68
+ /**
69
+ * What a destructive run takes away, counted wherever there is a plan to count from.
70
+ *
71
+ * The mapping alone can say WHAT will happen; only the plan can say to how many
72
+ * rows. Both wordings exist because an operator is allowed to skip the preview,
73
+ * and a confirmation that says "some rows" is not a confirmation.
74
74
  */ export function describeLosses(mapping, plan) {
75
75
  const lines = [];
76
76
  const target = mapping.targetEntity;
@@ -1,21 +1,21 @@
1
- /**
2
- * Who owns a busy flag when a late answer arrives.
3
- *
4
- * Every request in this panel sets a shared "in progress" flag on the way in and
5
- * clears it on the way out. That breaks the moment an answer is DISCARDED —
6
- * because the operator moved on, or because a newer request replaced this one —
7
- * since a bare `return` skips the clearing too and the flag stays set with
8
- * nobody left to unset it. The panel then shows a skeleton for a request that
9
- * has already finished and will never be written.
10
- *
11
- * Two questions, and they are not the same question:
12
- *
13
- * superseded — a NEWER request of the same kind has started. It set the flag
14
- * after this one did and will clear it when it settles, so this
15
- * answer must leave the flag strictly alone.
16
- * movedOn — the operator is looking at something else. Nobody else is
17
- * coming: this request is the last one out and must clear the
18
- * flag before dropping its answer.
1
+ /**
2
+ * Who owns a busy flag when a late answer arrives.
3
+ *
4
+ * Every request in this panel sets a shared "in progress" flag on the way in and
5
+ * clears it on the way out. That breaks the moment an answer is DISCARDED —
6
+ * because the operator moved on, or because a newer request replaced this one —
7
+ * since a bare `return` skips the clearing too and the flag stays set with
8
+ * nobody left to unset it. The panel then shows a skeleton for a request that
9
+ * has already finished and will never be written.
10
+ *
11
+ * Two questions, and they are not the same question:
12
+ *
13
+ * superseded — a NEWER request of the same kind has started. It set the flag
14
+ * after this one did and will clear it when it settles, so this
15
+ * answer must leave the flag strictly alone.
16
+ * movedOn — the operator is looking at something else. Nobody else is
17
+ * coming: this request is the last one out and must clear the
18
+ * flag before dropping its answer.
19
19
  */ export function settlementOf(args) {
20
20
  const superseded = args.latest !== args.mine;
21
21
  const movedOn = args.selectedNow !== args.requestedFor;
@@ -3,14 +3,14 @@ import { useEffect, useEffectEvent, useRef, useState } from 'react';
3
3
  const DEFAULT_PAGE_SIZE = 20;
4
4
  export function useInfiniteList(options) {
5
5
  const pageSize = options.pageSize ?? DEFAULT_PAGE_SIZE;
6
- /**
7
- * The caller's deps as ONE value.
8
- *
9
- * Spreading them into the dependency array made its LENGTH depend on the
10
- * caller, and React requires that length to be constant across renders — an
11
- * array that grows or shrinks silently stops the effect re-running when it
12
- * should. Collapsing to a string keeps one slot no matter how many things the
13
- * caller watches.
6
+ /**
7
+ * The caller's deps as ONE value.
8
+ *
9
+ * Spreading them into the dependency array made its LENGTH depend on the
10
+ * caller, and React requires that length to be constant across renders — an
11
+ * array that grows or shrinks silently stops the effect re-running when it
12
+ * should. Collapsing to a string keeps one slot no matter how many things the
13
+ * caller watches.
14
14
  */ const depsKey = JSON.stringify(options.deps);
15
15
  const [items, setItems] = useState([]);
16
16
  const [total, setTotal] = useState(0);