@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.
@@ -1 +1 @@
1
- {"version":3,"file":"source-package-publication.d.ts","sourceRoot":"","sources":["../src/source-package-publication.ts"],"names":[],"mappings":"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"}
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.6",
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.6",
31
- "@forgeax/engine-ecs": "0.1.6",
32
- "@forgeax/engine-geometry": "0.1.6",
33
- "@forgeax/engine-pack": "0.1.6",
34
- "@forgeax/engine-scene": "0.1.6",
35
- "@forgeax/engine-types": "0.1.6",
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',