querysub 0.511.0 → 0.513.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "querysub",
3
- "version": "0.511.0",
3
+ "version": "0.513.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
package/spec.txt CHANGED
@@ -1,8 +1,3 @@
1
- todonext
2
- Make sure the AI updates the machine schema in a smart way. So we can actually have both the old and the new servers config and manage them correctly. make sure if we make multiple updates, it still remembers what the old config is. And make sure deploy all works correctly.
3
-
4
-
5
-
6
1
  Trigger values
7
2
  1) validStateComputer.ingestValuesAndValidStates
8
3
  1.5) authorityStorage.ingestValues
@@ -138,12 +138,9 @@ export class ServiceDetailPage extends qreact.Component {
138
138
  try {
139
139
  await action();
140
140
  } catch (error) {
141
- Querysub.localCommit(() => {
141
+ Querysub.commit(() => {
142
142
  this.state.saveError = error instanceof Error ? error.message : String(error);
143
143
  });
144
- } finally {
145
- // The write => read invalidation keys should also cover this, but explicitly re-request the config as well, so the UI always reflects what the server now has (only AFTER the action fully finishes, so we can't refetch a value from before our write committed)
146
- MachineServiceController(SocketFunction.browserNodeId()).getServiceConfig.refresh(selectedServiceIdParam.value);
147
144
  }
148
145
  });
149
146
  }
@@ -1,11 +1,14 @@
1
1
  import { SocketFunction } from "socket-function/SocketFunction";
2
2
  import { qreact } from "../../4-dom/qreact";
3
+ import { t } from "../../2-proxy/schema2";
4
+ import { Querysub } from "../../4-querysub/Querysub";
3
5
  import { DEFAULT_OVERLAP_TIME, MachineServiceController, MachineInfo, ServiceConfig } from "../machineSchema";
4
6
  import { showFullscreenModal } from "../../5-diagnostics/FullscreenModal";
5
7
  import { css } from "../../4-dom/css";
6
8
  import { formatTime } from "socket-function/src/formatting/format";
7
9
  import { formatDateJSX } from "../../misc/formatJSX";
8
10
  import { MachineController } from "../machineController";
11
+ import { deepCloneJSON } from "socket-function/src/misc";
9
12
  import { unique } from "../../misc";
10
13
  import { CommitModal } from "./CommitModal";
11
14
 
@@ -39,6 +42,9 @@ export const buttonStyle = css.pad2(12, 8).button.bord2(0, 0, 20).fontWeight("bo
39
42
  export class UpdateButtons extends qreact.Component<{
40
43
  services: ServiceConfig[];
41
44
  }> {
45
+ state = t.state({
46
+ isDeploying: t.type(false),
47
+ });
42
48
  render() {
43
49
  let controller = MachineServiceController(SocketFunction.browserNodeId());
44
50
  const gitInfo = controller.getGitInfo();
@@ -94,18 +100,31 @@ export class UpdateButtons extends qreact.Component<{
94
100
  {outdatedServices.length > 0 && gitInfo &&
95
101
  <button
96
102
  className={buttonStyle.hsl(120, 70, 90)}
97
- onClick={async () => {
98
- for (let service of outdatedServices) {
99
- service.parameters.gitRef = gitInfo.latestRef;
103
+ disabled={this.state.isDeploying}
104
+ onClick={() => {
105
+ this.state.isDeploying = true;
106
+ // Props are synchronized state, so everything the async work needs is cloned into plain locals HERE, in the synced part
107
+ let toDeploy = outdatedServices.map(service => {
108
+ let updated = deepCloneJSON(service);
109
+ updated.parameters.gitRef = gitInfo.latestRef;
100
110
  // Release with the configured overlap, so the new instances are running before the old ones shut down
101
- let overlap = service.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
102
- service.parameters.releaseTime = Date.now() + overlap;
103
- }
104
- await controller.setServiceConfigs.promise(outdatedServices);
111
+ let overlap = updated.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
112
+ updated.parameters.releaseTime = Date.now() + overlap;
113
+ return updated;
114
+ });
115
+ Querysub.onCommitFinished(async () => {
116
+ try {
117
+ await controller.setServiceConfigs.promise(toDeploy);
118
+ } finally {
119
+ Querysub.commit(() => {
120
+ this.state.isDeploying = false;
121
+ });
122
+ }
123
+ });
105
124
  }}
106
125
  >
107
126
  <div>
108
- {bigEmoji("🎯")} <span>Deploy & Restart All ({outdatedServices.length}) Services</span>
127
+ {bigEmoji("🎯")} <span>{this.state.isDeploying && "⏳ Deploying..." || `Deploy & Restart All (${outdatedServices.length}) Services`}</span>
109
128
  </div>
110
129
  <div className={css.hbox(5)}>
111
130
  <b>New</b>
@@ -124,6 +143,9 @@ export class UpdateButtons extends qreact.Component<{
124
143
  export class UpdateServiceButtons extends qreact.Component<{
125
144
  service: ServiceConfig;
126
145
  }> {
146
+ state = t.state({
147
+ isDeploying: t.type(false),
148
+ });
127
149
  render() {
128
150
  let controller = MachineServiceController(SocketFunction.browserNodeId());
129
151
  const gitInfo = controller.getGitInfo();
@@ -134,16 +156,28 @@ export class UpdateServiceButtons extends qreact.Component<{
134
156
  return <>
135
157
  {gitRef !== gitInfo.latestRef && <button
136
158
  className={buttonStyle.hsl(120, 70, 90).alignSelf("stretch")}
137
- onClick={async () => {
138
- this.props.service.parameters.gitRef = gitInfo.latestRef;
159
+ disabled={this.state.isDeploying}
160
+ onClick={() => {
161
+ this.state.isDeploying = true;
162
+ // Props are synchronized state, so everything the async work needs is cloned into plain locals HERE, in the synced part
163
+ let service = deepCloneJSON(this.props.service);
164
+ service.parameters.gitRef = gitInfo.latestRef;
139
165
  // Release with the configured overlap, so the new instances are running before the old ones shut down
140
- let overlap = this.props.service.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
141
- this.props.service.parameters.releaseTime = Date.now() + overlap;
142
- await controller.setServiceConfigs.promise([this.props.service]);
166
+ let overlap = service.parameters.overlapTime || DEFAULT_OVERLAP_TIME;
167
+ service.parameters.releaseTime = Date.now() + overlap;
168
+ Querysub.onCommitFinished(async () => {
169
+ try {
170
+ await controller.setServiceConfigs.promise([service]);
171
+ } finally {
172
+ Querysub.commit(() => {
173
+ this.state.isDeploying = false;
174
+ });
175
+ }
176
+ });
143
177
  }}
144
178
  >
145
179
  <div>
146
- {bigEmoji("🎯", -4)} <span>Update to New & Restart</span>
180
+ {bigEmoji("🎯", -4)} <span>{this.state.isDeploying && "⏳ Deploying..." || "Update to New & Restart"}</span>
147
181
  </div>
148
182
  <div className={css.hbox(5)}>
149
183
  <b>New</b>
@@ -307,7 +307,8 @@ export function getSyncedController<T extends {
307
307
  promise.then(
308
308
  result => {
309
309
  logFinished();
310
- Querysub.commitLocal(() => {
310
+ // commit, NOT commitLocal, as this runs from a promise callback (an async context), where commitLocal writes don't reliably commit / trigger re-renders (which made the write => read invalidation flaky)
311
+ Querysub.commit(() => {
311
312
  obj.result = atomicObjectWriteNoFreeze({ result });
312
313
  obj.promise = undefined;
313
314
  invalidateReaders();
@@ -315,7 +316,7 @@ export function getSyncedController<T extends {
315
316
  },
316
317
  error => {
317
318
  logFinished();
318
- Querysub.commitLocal(() => {
319
+ Querysub.commit(() => {
319
320
  obj.result = atomicObjectWriteNoFreeze({ error });
320
321
  obj.promise = undefined;
321
322
  invalidateReaders();