@forgeax/engine-ddc 0.1.2
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/LICENSE +202 -0
- package/README.md +136 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/concurrency.integration.test.d.ts +2 -0
- package/dist/__tests__/concurrency.integration.test.d.ts.map +1 -0
- package/dist/__tests__/consumer-path.integration.test.d.ts +2 -0
- package/dist/__tests__/consumer-path.integration.test.d.ts.map +1 -0
- package/dist/__tests__/crash-recovery.integration.test.d.ts +2 -0
- package/dist/__tests__/crash-recovery.integration.test.d.ts.map +1 -0
- package/dist/__tests__/entry-store.integration.test.d.ts +2 -0
- package/dist/__tests__/entry-store.integration.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/key.unit.test.d.ts +2 -0
- package/dist/__tests__/key.unit.test.d.ts.map +1 -0
- package/dist/__tests__/layout.unit.test.d.ts +2 -0
- package/dist/__tests__/layout.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/multiprocess-gc.integration.test.d.ts +2 -0
- package/dist/__tests__/multiprocess-gc.integration.test.d.ts.map +1 -0
- package/dist/__tests__/multiprocess-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/multiprocess-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/multiprocess-worker.d.ts +2 -0
- package/dist/__tests__/multiprocess-worker.d.ts.map +1 -0
- package/dist/__tests__/status-root-kind-owner.test-d.d.ts +2 -0
- package/dist/__tests__/status-root-kind-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/status.unit.test.d.ts +2 -0
- package/dist/__tests__/status.unit.test.d.ts.map +1 -0
- package/dist/entry-store.d.ts +52 -0
- package/dist/entry-store.d.ts.map +1 -0
- package/dist/entry-store.mjs +339 -0
- package/dist/entry-store.mjs.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.mjs +109 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/gc.d.ts +13 -0
- package/dist/gc.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +896 -0
- package/dist/index.mjs.map +1 -0
- package/dist/key.d.ts +13 -0
- package/dist/key.d.ts.map +1 -0
- package/dist/key.mjs +38 -0
- package/dist/key.mjs.map +1 -0
- package/dist/layout.d.ts +51 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.mjs +68 -0
- package/dist/layout.mjs.map +1 -0
- package/dist/lifecycle.d.ts +55 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/runtime-scope.d.ts +10 -0
- package/dist/runtime-scope.d.ts.map +1 -0
- package/dist/status.d.ts +39 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.mjs +20 -0
- package/dist/status.mjs.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/concurrency.integration.test.ts +53 -0
- package/src/__tests__/consumer-path.integration.test.ts +61 -0
- package/src/__tests__/crash-recovery.integration.test.ts +84 -0
- package/src/__tests__/entry-store.integration.test.ts +83 -0
- package/src/__tests__/errors.unit.test.ts +54 -0
- package/src/__tests__/key.unit.test.ts +49 -0
- package/src/__tests__/layout.unit.test.ts +50 -0
- package/src/__tests__/lifecycle.unit.test.ts +258 -0
- package/src/__tests__/multiprocess-gc.integration.test.ts +36 -0
- package/src/__tests__/multiprocess-lifecycle.integration.test.ts +109 -0
- package/src/__tests__/multiprocess-worker.ts +41 -0
- package/src/__tests__/status-root-kind-owner.test-d.ts +42 -0
- package/src/__tests__/status.unit.test.ts +56 -0
- package/src/entry-store.ts +307 -0
- package/src/errors.ts +164 -0
- package/src/gc.ts +34 -0
- package/src/index.ts +65 -0
- package/src/key.ts +46 -0
- package/src/layout.ts +132 -0
- package/src/lifecycle.ts +549 -0
- package/src/runtime-scope.ts +87 -0
- package/src/status.ts +58 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { DdcStoreError } from './errors.js';
|
|
5
|
+
|
|
6
|
+
export interface DdcRuntimeScope {
|
|
7
|
+
readonly scopeId: string;
|
|
8
|
+
readonly scopeHash: string;
|
|
9
|
+
readonly root: string;
|
|
10
|
+
readonly metadataPath: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ScopeMetadata {
|
|
14
|
+
readonly schemaVersion: 'forgeax-ddc-scope/v2';
|
|
15
|
+
readonly scopeId: string;
|
|
16
|
+
readonly scopeHash: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function scopeHash(scopeId: string): string {
|
|
20
|
+
return createHash('sha256').update(scopeId, 'utf8').digest('hex');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function runtimeScopeHash(scopeId: string): string {
|
|
24
|
+
return scopeHash(scopeId);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function createRuntimeScope(root: string, scopeId: string): Promise<DdcRuntimeScope> {
|
|
28
|
+
if (scopeId.length === 0) {
|
|
29
|
+
throw new DdcStoreError({
|
|
30
|
+
code: 'ddc-scope-mismatch',
|
|
31
|
+
detail: 'runtime scope id must not be empty',
|
|
32
|
+
expected: 'a non-empty original scope id',
|
|
33
|
+
actual: scopeId,
|
|
34
|
+
rootKind: 'project-ddc',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const hash = scopeHash(scopeId);
|
|
38
|
+
const scopeRoot = join(root, 'scopes', hash);
|
|
39
|
+
const metadataPath = join(scopeRoot, 'scope.json');
|
|
40
|
+
await mkdir(scopeRoot, { recursive: true });
|
|
41
|
+
const expected: ScopeMetadata = {
|
|
42
|
+
schemaVersion: 'forgeax-ddc-scope/v2',
|
|
43
|
+
scopeId,
|
|
44
|
+
scopeHash: hash,
|
|
45
|
+
};
|
|
46
|
+
try {
|
|
47
|
+
const actual = JSON.parse(await readFile(metadataPath, 'utf8')) as Partial<ScopeMetadata>;
|
|
48
|
+
if (
|
|
49
|
+
actual.schemaVersion !== expected.schemaVersion ||
|
|
50
|
+
actual.scopeId !== expected.scopeId ||
|
|
51
|
+
actual.scopeHash !== expected.scopeHash
|
|
52
|
+
) {
|
|
53
|
+
throw new DdcStoreError({
|
|
54
|
+
code: 'ddc-scope-mismatch',
|
|
55
|
+
detail: 'runtime scope metadata does not match the requested scope',
|
|
56
|
+
expected,
|
|
57
|
+
actual,
|
|
58
|
+
rootKind: 'project-ddc',
|
|
59
|
+
scope: scopeId,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error instanceof DdcStoreError) throw error;
|
|
64
|
+
const temporary = `${metadataPath}.${process.pid}.tmp`;
|
|
65
|
+
await writeFile(temporary, JSON.stringify(expected));
|
|
66
|
+
await rename(temporary, metadataPath);
|
|
67
|
+
}
|
|
68
|
+
return { scopeId, scopeHash: hash, root: scopeRoot, metadataPath };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function assertRuntimeScope(
|
|
72
|
+
root: string,
|
|
73
|
+
scope: Pick<DdcRuntimeScope, 'scopeId' | 'scopeHash'>,
|
|
74
|
+
): Promise<DdcRuntimeScope> {
|
|
75
|
+
const expectedHash = scopeHash(scope.scopeId);
|
|
76
|
+
if (scope.scopeHash !== expectedHash) {
|
|
77
|
+
throw new DdcStoreError({
|
|
78
|
+
code: 'ddc-scope-mismatch',
|
|
79
|
+
detail: 'runtime scope hash does not match the original scope id',
|
|
80
|
+
expected: expectedHash,
|
|
81
|
+
actual: scope.scopeHash,
|
|
82
|
+
rootKind: 'project-ddc',
|
|
83
|
+
scope: scope.scopeId,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return createRuntimeScope(root, scope.scopeId);
|
|
87
|
+
}
|
package/src/status.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { DdcErrorRootKind } from './errors.js';
|
|
2
|
+
|
|
3
|
+
export const DDC_STATUS_SCHEMA = 'forgeax-ddc-status/v2' as const;
|
|
4
|
+
|
|
5
|
+
export type DdcStatusHealth = 'ready' | 'blocked' | 'missing' | 'stale' | 'failed';
|
|
6
|
+
export type DdcStatusLayerKind = 'build' | 'project' | 'runtime';
|
|
7
|
+
export type DdcStatusRootKind = DdcErrorRootKind;
|
|
8
|
+
|
|
9
|
+
export interface DdcRecoveryAction {
|
|
10
|
+
readonly kind: 'inspect' | 'allocate-generation' | 'retry' | 'cold-rebuild' | 'prune';
|
|
11
|
+
readonly executable: boolean;
|
|
12
|
+
readonly exactTarget?: string;
|
|
13
|
+
readonly reason?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DdcStatusLayer {
|
|
17
|
+
readonly kind: DdcStatusLayerKind;
|
|
18
|
+
readonly owner: 'engine-ddc' | 'engine-pack' | 'plugin' | 'runtime';
|
|
19
|
+
readonly rootKind: DdcStatusRootKind;
|
|
20
|
+
readonly scopeId?: string;
|
|
21
|
+
readonly current?: number | string;
|
|
22
|
+
readonly lastKnownGood?: number | string;
|
|
23
|
+
readonly protection: readonly string[];
|
|
24
|
+
readonly actions: readonly DdcRecoveryAction[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface DdcStatus {
|
|
28
|
+
readonly schemaVersion: typeof DDC_STATUS_SCHEMA;
|
|
29
|
+
readonly health: DdcStatusHealth;
|
|
30
|
+
readonly layers: readonly DdcStatusLayer[];
|
|
31
|
+
readonly gameDir?: string;
|
|
32
|
+
readonly projectDdcRoot?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type BrowserDdcRecoveryAction = Omit<DdcRecoveryAction, 'exactTarget'>;
|
|
36
|
+
export type BrowserDdcStatusLayer = Omit<DdcStatusLayer, 'actions'> & {
|
|
37
|
+
readonly actions: readonly BrowserDdcRecoveryAction[];
|
|
38
|
+
};
|
|
39
|
+
export type BrowserDdcStatus = Omit<DdcStatus, 'gameDir' | 'projectDdcRoot' | 'layers'> & {
|
|
40
|
+
readonly layers: readonly BrowserDdcStatusLayer[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function serializeDdcStatus(status: DdcStatus): string {
|
|
44
|
+
return JSON.stringify(status);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function projectDdcStatusForBrowser(status: DdcStatus): BrowserDdcStatus {
|
|
48
|
+
return {
|
|
49
|
+
schemaVersion: DDC_STATUS_SCHEMA,
|
|
50
|
+
health: status.health,
|
|
51
|
+
layers: status.layers.map((layer) => ({
|
|
52
|
+
...layer,
|
|
53
|
+
actions: layer.actions.map(({ exactTarget: _exactTarget, ...action }) => action),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const toBrowserDdcStatus = projectDdcStatusForBrowser;
|