@forgeax/engine-import 0.1.6 → 0.1.7
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/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/source-package-publication.integration.test.d.ts +2 -0
- package/dist/__tests__/source-package-publication.integration.test.d.ts.map +1 -0
- package/dist/index.mjs +17 -0
- package/dist/index.mjs.map +1 -1
- package/dist/source-package-publication.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/source-package-publication.integration.test.ts +75 -0
- package/src/source-package-publication.ts +31 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-package-publication.d.ts","sourceRoot":"","sources":["../src/source-package-publication.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,2BAA2B,EAChC,oBAAoB,EACpB,KAAK,OAAO,EAGb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,gBAAgB,EAEtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,4BAA4B,CAAC;AAQpC,UAAU,sBAAuB,SAAQ,2BAA2B;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,yBAAyB,CAAC;CAC7C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,SAAS,YAAY,EAAE,CAAC;IAClD,QAAQ,CAAC,WAAW,EAAE,SAAS,YAAY,EAAE,CAAC;IAC9C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE;QACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;CACzC;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE3F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,sBAAsB,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;CACrC;AAED,MAAM,MAAM,6BAA6B,GACrC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAA;CAAE,GAClE;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,sBAAsB,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"source-package-publication.d.ts","sourceRoot":"","sources":["../src/source-package-publication.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-import",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@forgeax/engine-ddc": "0.1.
|
|
31
|
-
"@forgeax/engine-ecs": "0.1.
|
|
32
|
-
"@forgeax/engine-geometry": "0.1.
|
|
33
|
-
"@forgeax/engine-pack": "0.1.
|
|
34
|
-
"@forgeax/engine-scene": "0.1.
|
|
35
|
-
"@forgeax/engine-types": "0.1.
|
|
30
|
+
"@forgeax/engine-ddc": "0.1.7",
|
|
31
|
+
"@forgeax/engine-ecs": "0.1.7",
|
|
32
|
+
"@forgeax/engine-geometry": "0.1.7",
|
|
33
|
+
"@forgeax/engine-pack": "0.1.7",
|
|
34
|
+
"@forgeax/engine-scene": "0.1.7",
|
|
35
|
+
"@forgeax/engine-types": "0.1.7",
|
|
36
36
|
"@noble/hashes": "^2.2.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
commitImportPublication,
|
|
7
|
+
discardImportPublication,
|
|
8
|
+
type ImportPublicationInput,
|
|
9
|
+
stageImportPublication,
|
|
10
|
+
} from '../source-package-publication.js';
|
|
11
|
+
|
|
12
|
+
const GUID = '019e3969-1d48-7c3b-ac24-6d68f457065f';
|
|
13
|
+
const KEY = 'a'.repeat(64);
|
|
14
|
+
|
|
15
|
+
function input(root: string, desiredKey = KEY): ImportPublicationInput {
|
|
16
|
+
return {
|
|
17
|
+
root,
|
|
18
|
+
guid: GUID,
|
|
19
|
+
desiredKey,
|
|
20
|
+
pack: {
|
|
21
|
+
schemaVersion: '2.0.0',
|
|
22
|
+
assets: [{ guid: GUID, kind: 'fixture', payload: { value: 'same' }, refs: [] }],
|
|
23
|
+
},
|
|
24
|
+
previousCatalog: [],
|
|
25
|
+
nextCatalog: [],
|
|
26
|
+
publishedGuids: [GUID],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('source package publication concurrency', () => {
|
|
31
|
+
const roots: string[] = [];
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('waits for an equivalent newer owner after a strict stale commit', async () => {
|
|
38
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
39
|
+
roots.push(root);
|
|
40
|
+
const publicationInput = input(root);
|
|
41
|
+
const first = await stageImportPublication(publicationInput);
|
|
42
|
+
const second = await stageImportPublication(publicationInput);
|
|
43
|
+
expect(first.ok).toBe(true);
|
|
44
|
+
expect(second.ok).toBe(true);
|
|
45
|
+
if (!first.ok || !second.ok) return;
|
|
46
|
+
|
|
47
|
+
const winner = (async () => {
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
49
|
+
return commitImportPublication(second.candidate);
|
|
50
|
+
})();
|
|
51
|
+
const loser = await commitImportPublication(first.candidate);
|
|
52
|
+
const winnerResult = await winner;
|
|
53
|
+
|
|
54
|
+
expect(loser).toMatchObject({ ok: true, head: { state: 'current', currentKey: KEY } });
|
|
55
|
+
expect(winnerResult).toMatchObject({ ok: true, head: { state: 'current', currentKey: KEY } });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('keeps a stale publication with a different desired key fail-closed', async () => {
|
|
59
|
+
const root = await mkdtemp(join(tmpdir(), 'forgeax-source-publication-'));
|
|
60
|
+
roots.push(root);
|
|
61
|
+
const first = await stageImportPublication(input(root, 'a'.repeat(64)));
|
|
62
|
+
const second = await stageImportPublication(input(root, 'b'.repeat(64)));
|
|
63
|
+
expect(first.ok).toBe(true);
|
|
64
|
+
expect(second.ok).toBe(true);
|
|
65
|
+
if (!first.ok || !second.ok) return;
|
|
66
|
+
|
|
67
|
+
const result = await commitImportPublication(first.candidate);
|
|
68
|
+
await discardImportPublication(second.candidate);
|
|
69
|
+
|
|
70
|
+
expect(result).toMatchObject({
|
|
71
|
+
ok: false,
|
|
72
|
+
error: { code: 'source-package-ddc-failed', detail: 'DDC lifecycle commit returned stale' },
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -83,6 +83,33 @@ function inspectHead(root: string, guid: string, desiredKey: string): Promise<Dd
|
|
|
83
83
|
return new DdcLifecycle(root).inspect(guid, desiredKey);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
const PUBLICATION_SETTLE_TIMEOUT_MS = 5_000;
|
|
87
|
+
const PUBLICATION_SETTLE_POLL_MS = 10;
|
|
88
|
+
|
|
89
|
+
async function inspectAfterSupersededCommit(candidate: StagedSourcePackageDdc): Promise<DdcHead> {
|
|
90
|
+
let head = await inspectHead(candidate.root, candidate.lease.guid, candidate.lease.desiredKey);
|
|
91
|
+
if (head.state === 'current' && head.currentKey === candidate.lease.desiredKey) return head;
|
|
92
|
+
|
|
93
|
+
// A newer owner may already have superseded this lease while it is still
|
|
94
|
+
// cooking. The strict DDC CAS correctly returns `stale` to this candidate;
|
|
95
|
+
// wait for that owner to publish before turning an equivalent publication
|
|
96
|
+
// into a user-visible import failure. The wait is bounded so a crashed
|
|
97
|
+
// owner never leaves the caller hanging and a different desired key still
|
|
98
|
+
// fails closed.
|
|
99
|
+
const deadline = Date.now() + PUBLICATION_SETTLE_TIMEOUT_MS;
|
|
100
|
+
while (
|
|
101
|
+
Date.now() < deadline &&
|
|
102
|
+
head.activeLease !== undefined &&
|
|
103
|
+
head.activeLease.attempt !== candidate.lease.attempt &&
|
|
104
|
+
head.activeLease.desiredKey === candidate.lease.desiredKey
|
|
105
|
+
) {
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, PUBLICATION_SETTLE_POLL_MS));
|
|
107
|
+
head = await inspectHead(candidate.root, candidate.lease.guid, candidate.lease.desiredKey);
|
|
108
|
+
if (head.state === 'current' && head.currentKey === candidate.lease.desiredKey) return head;
|
|
109
|
+
}
|
|
110
|
+
return head;
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
async function stageSourcePackageDdc(
|
|
87
114
|
input: SourcePackageDdcEntryInput,
|
|
88
115
|
): Promise<Result<StagedSourcePackageDdc, SourcePackageError>> {
|
|
@@ -106,6 +133,10 @@ async function commitSourcePackageDdc(
|
|
|
106
133
|
try {
|
|
107
134
|
const commit = await candidate.session.commitEntry(candidate, candidate.lease.desiredKey);
|
|
108
135
|
if (commit.result !== 'current') {
|
|
136
|
+
const head = await inspectAfterSupersededCommit(candidate);
|
|
137
|
+
if (head.state === 'current' && head.currentKey === candidate.lease.desiredKey) {
|
|
138
|
+
return ok(head);
|
|
139
|
+
}
|
|
109
140
|
return err(
|
|
110
141
|
sourcePackageError('source-package-ddc-failed', candidate.context, {
|
|
111
142
|
stage: 'ddc',
|