amalgm 0.1.208 → 0.1.210
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.
|
@@ -17,7 +17,7 @@ const CLAIM_LIMIT = 16;
|
|
|
17
17
|
const CLAIM_LEASE_MS = 30_000;
|
|
18
18
|
const RETRY_MS = 1_000;
|
|
19
19
|
const REAUTHORIZE_MS = 30_000;
|
|
20
|
-
const REPO_OBJECT_REQUEST_TIMEOUT_MS =
|
|
20
|
+
const REPO_OBJECT_REQUEST_TIMEOUT_MS = 15_000;
|
|
21
21
|
|
|
22
22
|
function createSharedRealtimeTunnel({
|
|
23
23
|
send,
|
|
@@ -475,35 +475,45 @@ function createSharedRealtimeTunnel({
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
function handleRepoObjectStored(frame) {
|
|
478
|
-
const object = repoUploadInflight.get(frame.request_id)
|
|
478
|
+
const object = repoUploadInflight.get(frame.request_id)
|
|
479
|
+
|| [...repoUploadInflight.values()].find((entry) => entry.sha256 === frame.sha256);
|
|
479
480
|
if (!object || object.sha256 !== frame.sha256) return;
|
|
480
481
|
void localStateJson('POST', '/state/repo-objects/uploads/ack', {
|
|
481
482
|
resourceId: object.resourceId,
|
|
482
483
|
sha256: object.sha256,
|
|
483
484
|
})
|
|
484
485
|
.then(() => {
|
|
485
|
-
repoUploadInflight
|
|
486
|
+
for (const [requestId, entry] of repoUploadInflight) {
|
|
487
|
+
if (entry.sha256 === object.sha256) repoUploadInflight.delete(requestId);
|
|
488
|
+
}
|
|
486
489
|
scheduleRepoObjects();
|
|
487
490
|
})
|
|
488
491
|
.catch(() => {
|
|
489
|
-
repoUploadInflight
|
|
492
|
+
for (const [requestId, entry] of repoUploadInflight) {
|
|
493
|
+
if (entry.sha256 === object.sha256) repoUploadInflight.delete(requestId);
|
|
494
|
+
}
|
|
490
495
|
scheduleRepoObjects(RETRY_MS);
|
|
491
496
|
});
|
|
492
497
|
}
|
|
493
498
|
|
|
494
499
|
function handleRepoObjectData(frame) {
|
|
495
|
-
const object = repoDownloadInflight.get(frame.request_id)
|
|
500
|
+
const object = repoDownloadInflight.get(frame.request_id)
|
|
501
|
+
|| [...repoDownloadInflight.values()].find((entry) => entry.sha256 === frame.sha256);
|
|
496
502
|
if (!object || object.sha256 !== frame.sha256) return;
|
|
497
503
|
void localStateJson('POST', '/state/repo-objects/downloads/receive', {
|
|
498
504
|
resourceId: object.resourceId,
|
|
499
505
|
sha256: object.sha256,
|
|
500
506
|
dataBase64: frame.data_b64,
|
|
501
507
|
}).then(() => {
|
|
502
|
-
repoDownloadInflight
|
|
508
|
+
for (const [requestId, entry] of repoDownloadInflight) {
|
|
509
|
+
if (entry.sha256 === object.sha256) repoDownloadInflight.delete(requestId);
|
|
510
|
+
}
|
|
503
511
|
scheduleRepoObjects();
|
|
504
512
|
}).catch((error) => {
|
|
505
513
|
warn(`repo object ${object.sha256} was not stored locally: ${error?.message || error}`);
|
|
506
|
-
repoDownloadInflight
|
|
514
|
+
for (const [requestId, entry] of repoDownloadInflight) {
|
|
515
|
+
if (entry.sha256 === object.sha256) repoDownloadInflight.delete(requestId);
|
|
516
|
+
}
|
|
507
517
|
scheduleRepoObjects(RETRY_MS);
|
|
508
518
|
});
|
|
509
519
|
}
|
package/package.json
CHANGED
|
@@ -127,6 +127,7 @@ test('repo object chunks retry when an acknowledgement is lost', async () => {
|
|
|
127
127
|
const uploadBytes = Buffer.from('retry this content-addressed chunk');
|
|
128
128
|
const uploadSha = crypto.createHash('sha256').update(uploadBytes).digest('hex');
|
|
129
129
|
const resourceId = 'shr_cp1abcdefghijklmnopqrstuvwx';
|
|
130
|
+
let acknowledgements = 0;
|
|
130
131
|
const server = http.createServer((req, res) => {
|
|
131
132
|
req.resume();
|
|
132
133
|
req.on('end', () => {
|
|
@@ -147,6 +148,9 @@ test('repo object chunks retry when an acknowledgement is lost', async () => {
|
|
|
147
148
|
}));
|
|
148
149
|
} else if (req.url === '/state/repo-objects/downloads/claim') {
|
|
149
150
|
res.end('{"download":null}');
|
|
151
|
+
} else if (req.url === '/state/repo-objects/uploads/ack') {
|
|
152
|
+
acknowledgements += 1;
|
|
153
|
+
res.end('{"changed":true,"completed":true}');
|
|
150
154
|
} else {
|
|
151
155
|
res.end('{}');
|
|
152
156
|
}
|
|
@@ -173,6 +177,13 @@ test('repo object chunks retry when an acknowledgement is lost', async () => {
|
|
|
173
177
|
const puts = frames.filter((frame) => frame.type === 'repo.object.put');
|
|
174
178
|
assert.notEqual(puts[0].request_id, puts[1].request_id);
|
|
175
179
|
assert.equal(puts[0].sha256, puts[1].sha256);
|
|
180
|
+
tunnel.handleFrame({
|
|
181
|
+
type: 'repo.object.stored',
|
|
182
|
+
request_id: puts[0].request_id,
|
|
183
|
+
sha256: uploadSha,
|
|
184
|
+
bytes: uploadBytes.length,
|
|
185
|
+
});
|
|
186
|
+
await until(() => acknowledgements === 1, 'the late upload acknowledgement');
|
|
176
187
|
} finally {
|
|
177
188
|
tunnel.stop();
|
|
178
189
|
server.close();
|
|
@@ -137,6 +137,34 @@ test('only a value different from both the materialized baseline and the rail is
|
|
|
137
137
|
branches: [{ name: 'main', oid: oidA }],
|
|
138
138
|
},
|
|
139
139
|
), false);
|
|
140
|
+
assert.equal(hasLocalContribution(
|
|
141
|
+
{
|
|
142
|
+
head: { branch: 'main', oid: oidB, detached: false },
|
|
143
|
+
refs: [{ name: 'main', oid: oidB }],
|
|
144
|
+
},
|
|
145
|
+
remoteAdvance,
|
|
146
|
+
baseline,
|
|
147
|
+
null,
|
|
148
|
+
{
|
|
149
|
+
positionVersion: 10,
|
|
150
|
+
head: { branch: 'main', oid: oidB, detached: false },
|
|
151
|
+
branches: [{ name: 'main', oid: oidB, version: 10 }],
|
|
152
|
+
},
|
|
153
|
+
), false);
|
|
154
|
+
assert.equal(hasLocalContribution(
|
|
155
|
+
{
|
|
156
|
+
head: { branch: 'main', oid: oidB, detached: false },
|
|
157
|
+
refs: [{ name: 'main', oid: oidB }],
|
|
158
|
+
},
|
|
159
|
+
remoteAdvance,
|
|
160
|
+
baseline,
|
|
161
|
+
null,
|
|
162
|
+
{
|
|
163
|
+
positionVersion: null,
|
|
164
|
+
head: { branch: 'main', oid: oidB, detached: false },
|
|
165
|
+
branches: [{ name: 'main', oid: oidB, version: null }],
|
|
166
|
+
},
|
|
167
|
+
), true);
|
|
140
168
|
assert.equal(hasLocalContribution(
|
|
141
169
|
local,
|
|
142
170
|
plan,
|
|
@@ -148,8 +176,8 @@ test('only a value different from both the materialized baseline and the rail is
|
|
|
148
176
|
{
|
|
149
177
|
head: { branch: 'main', oid: oidA, detached: false },
|
|
150
178
|
branches: [
|
|
151
|
-
{ name: 'main', oid: oidA },
|
|
152
|
-
{ name: 'feature', oid: oidB },
|
|
179
|
+
{ name: 'main', oid: oidA, version: 10 },
|
|
180
|
+
{ name: 'feature', oid: oidB, version: 10 },
|
|
153
181
|
],
|
|
154
182
|
},
|
|
155
183
|
), true);
|
|
@@ -272,6 +300,13 @@ test('a repo project appends immutable machine records', async () => {
|
|
|
272
300
|
assert.equal(entries[0].cards[0].positionMutationId, first.mutationId);
|
|
273
301
|
assert.equal(entries[0].cards[0].positionVersion, null);
|
|
274
302
|
assert.equal(publishedCardFor(refreshed.cards[0]), null);
|
|
303
|
+
const awaitingNumber = publishWall(repoKey);
|
|
304
|
+
assert.equal(awaitingNumber.published, false);
|
|
305
|
+
assert.equal(awaitingNumber.reason, 'unchanged');
|
|
306
|
+
assert.equal(
|
|
307
|
+
require('../workspace/reducer').localCardMayMaterialize(null, awaitingNumber),
|
|
308
|
+
false,
|
|
309
|
+
);
|
|
275
310
|
|
|
276
311
|
acknowledgeCloudMutation(project.resourceId, first.mutationId, {
|
|
277
312
|
version: 1,
|
|
@@ -144,6 +144,27 @@ function parseWallLine(line) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function sealCardVersions(project, card) {
|
|
148
|
+
return {
|
|
149
|
+
...card,
|
|
150
|
+
positionVersion: validVersion(card.positionVersion)
|
|
151
|
+
? card.positionVersion
|
|
152
|
+
: officialVersion(project.resourceId, card.positionMutationId),
|
|
153
|
+
branches: (card.branches || []).map((ref) => ({
|
|
154
|
+
...ref,
|
|
155
|
+
version: validVersion(ref.version)
|
|
156
|
+
? ref.version
|
|
157
|
+
: officialVersion(project.resourceId, ref.mutationId),
|
|
158
|
+
})),
|
|
159
|
+
checkpoints: (card.checkpoints || []).map((checkpoint) => ({
|
|
160
|
+
...checkpoint,
|
|
161
|
+
version: validVersion(checkpoint.version)
|
|
162
|
+
? checkpoint.version
|
|
163
|
+
: officialVersion(project.resourceId, checkpoint.mutationId),
|
|
164
|
+
})),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
147
168
|
function readProjectWall(project) {
|
|
148
169
|
if (!fs.existsSync(project.file)) return [];
|
|
149
170
|
const entries = new Map();
|
|
@@ -154,24 +175,7 @@ function readProjectWall(project) {
|
|
|
154
175
|
entries.set(entry.machine, {
|
|
155
176
|
...entry,
|
|
156
177
|
resourceId: project.resourceId,
|
|
157
|
-
cards: entry.cards.map((card) => (
|
|
158
|
-
...card,
|
|
159
|
-
positionVersion: validVersion(card.positionVersion)
|
|
160
|
-
? card.positionVersion
|
|
161
|
-
: officialVersion(project.resourceId, card.positionMutationId),
|
|
162
|
-
branches: (card.branches || []).map((ref) => ({
|
|
163
|
-
...ref,
|
|
164
|
-
version: validVersion(ref.version)
|
|
165
|
-
? ref.version
|
|
166
|
-
: officialVersion(project.resourceId, ref.mutationId),
|
|
167
|
-
})),
|
|
168
|
-
checkpoints: (card.checkpoints || []).map((checkpoint) => ({
|
|
169
|
-
...checkpoint,
|
|
170
|
-
version: validVersion(checkpoint.version)
|
|
171
|
-
? checkpoint.version
|
|
172
|
-
: officialVersion(project.resourceId, checkpoint.mutationId),
|
|
173
|
-
})),
|
|
174
|
-
})),
|
|
178
|
+
cards: entry.cards.map((card) => sealCardVersions(project, card)),
|
|
175
179
|
});
|
|
176
180
|
}
|
|
177
181
|
return [...entries.values()];
|
|
@@ -231,12 +235,20 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
|
|
|
231
235
|
const atBaseline = baseline
|
|
232
236
|
&& card.head?.branch === baseline.branch
|
|
233
237
|
&& card.head?.oid === baseline.baseOid;
|
|
234
|
-
|
|
238
|
+
const alreadyPublished = validVersion(previousCard?.positionVersion)
|
|
239
|
+
&& samePosition(card, previousCard);
|
|
240
|
+
const reversesPublishedSharedPosition = previousCard
|
|
241
|
+
&& validVersion(previousCard.positionVersion)
|
|
242
|
+
&& samePosition(previousCard, plan.card)
|
|
243
|
+
&& !alreadyPublished;
|
|
244
|
+
if ((!onRail && !atBaseline && !alreadyPublished) || reversesPublishedSharedPosition) return true;
|
|
235
245
|
|
|
236
246
|
const currentRefs = oidMap(card.refs);
|
|
237
247
|
const railRefs = oidMap(plan.branches);
|
|
238
248
|
const baselineRefs = oidMap(baseline?.branches);
|
|
239
|
-
const previousRefs =
|
|
249
|
+
const previousRefs = new Map(
|
|
250
|
+
(previousCard?.branches || previousCard?.refs || []).map((ref) => [ref.name, ref]),
|
|
251
|
+
);
|
|
240
252
|
for (const name of new Set([
|
|
241
253
|
...currentRefs.keys(),
|
|
242
254
|
...baselineRefs.keys(),
|
|
@@ -245,13 +257,16 @@ function hasLocalContribution(card, plan, baseline, checkpoint = null, previousC
|
|
|
245
257
|
const current = currentRefs.get(name) ?? null;
|
|
246
258
|
const shared = railRefs.get(name) ?? null;
|
|
247
259
|
const previous = baselineRefs.get(name) ?? null;
|
|
248
|
-
const
|
|
249
|
-
const
|
|
260
|
+
const publishedRef = previousRefs.get(name);
|
|
261
|
+
const published = publishedRef?.oid ?? null;
|
|
262
|
+
const matchesBaseline = baselineRefs.has(name) && current === previous;
|
|
263
|
+
const matchesPublished = validVersion(publishedRef?.version) && current === published;
|
|
264
|
+
const reversesPublishedSharedValue = validVersion(publishedRef?.version)
|
|
250
265
|
&& published === shared
|
|
251
266
|
&& current !== published;
|
|
252
267
|
if (
|
|
253
268
|
current !== shared
|
|
254
|
-
&& (!
|
|
269
|
+
&& ((!matchesBaseline && !matchesPublished) || reversesPublishedSharedValue)
|
|
255
270
|
) return true;
|
|
256
271
|
}
|
|
257
272
|
|
|
@@ -513,12 +528,13 @@ function publishProject(project, rawCards) {
|
|
|
513
528
|
const reducer = require('./reducer');
|
|
514
529
|
const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
|
|
515
530
|
const baseline = reducer.readRepoState(project.repoKey);
|
|
516
|
-
const
|
|
531
|
+
const previousCards = (previousEntry?.cards || []).map((card) => sealCardVersions(project, card));
|
|
532
|
+
const previousReplicas = new Set(previousCards.map((card) => card.replica));
|
|
517
533
|
const currentReplicas = new Set(rawCards.map((card) => card.replica));
|
|
518
534
|
const removed = [...previousReplicas].some((replica) => !currentReplicas.has(replica));
|
|
519
535
|
const contributes = rawCards.some((card) => {
|
|
520
536
|
const checkpoint = readCheckpoint(machineId(), project.repoKey);
|
|
521
|
-
const previousCard =
|
|
537
|
+
const previousCard = previousCards.find((entry) => entry.replica === card.replica);
|
|
522
538
|
return hasLocalContribution(card, sharedPlan, baseline, checkpoint, previousCard)
|
|
523
539
|
|| hasObjectUpgrade(card, sharedPlan);
|
|
524
540
|
});
|
|
@@ -529,7 +545,7 @@ function publishProject(project, rawCards) {
|
|
|
529
545
|
const mutationId = `mut_project_${crypto.randomBytes(16).toString('base64url')}`;
|
|
530
546
|
const cards = stampCards(
|
|
531
547
|
rawCards,
|
|
532
|
-
|
|
548
|
+
previousCards,
|
|
533
549
|
sharedPlan,
|
|
534
550
|
baseline,
|
|
535
551
|
mutationId,
|