narraeon 0.7.0 → 0.7.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.
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="color-scheme" content="dark" />
7
7
  <title>Narraeon</title>
8
- <script type="module" crossorigin src="/assets/index-Ro7dnSsC.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DkamBH6c.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BUCslLEK.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "narraeon",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "A local-first workspace for living worlds.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,108 +0,0 @@
1
- import { mkdir, open, readdir, readFile, rm, rmdir } from "node:fs/promises";
2
- import { join, resolve } from "node:path";
3
- /**
4
- * One service process owns a data directory (ADR-0044). The lock is a pid
5
- * file: a live owner refuses startup, and a dead owner's file is replaced.
6
- */
7
- const lockFileName = "server.pid";
8
- /** Persistent lock directories left by the retired cross-process world locks. */
9
- const legacyLockPaths = [
10
- "runtime-locks",
11
- join("operations", "world-locks"),
12
- join("operations", "authority-locks"),
13
- join("operations", "correction-claims"),
14
- ];
15
- export class DataRootBusyError extends Error {
16
- ownerPid;
17
- constructor(ownerPid) {
18
- super(`Another Narraeon server (pid ${ownerPid}) is already using this data directory`);
19
- this.name = "DataRootBusyError";
20
- this.ownerPid = ownerPid;
21
- }
22
- }
23
- export async function acquireDataRootLock(dataRoot) {
24
- const root = resolve(dataRoot);
25
- await mkdir(root, { recursive: true, mode: 0o700 });
26
- const path = join(root, lockFileName);
27
- for (let attempt = 0; attempt < 3; attempt += 1) {
28
- try {
29
- const handle = await open(path, "wx", 0o600);
30
- try {
31
- await handle.writeFile(`${process.pid}\n`, "utf8");
32
- }
33
- finally {
34
- await handle.close();
35
- }
36
- await removeLegacyLocks(root);
37
- return {
38
- release: async () => {
39
- if ((await readOwnerPid(path)) === process.pid)
40
- await rm(path, { force: true });
41
- },
42
- };
43
- }
44
- catch (error) {
45
- if (!isNodeError(error) || error.code !== "EEXIST")
46
- throw error;
47
- const owner = await readOwnerPid(path);
48
- if (owner !== null && processIsAlive(owner))
49
- throw new DataRootBusyError(owner);
50
- await rm(path, { force: true });
51
- }
52
- }
53
- throw new Error("Could not acquire the data-directory startup lock");
54
- }
55
- async function readOwnerPid(path) {
56
- let text;
57
- try {
58
- text = await readFile(path, "utf8");
59
- }
60
- catch (error) {
61
- if (isNodeError(error) && error.code === "ENOENT")
62
- return null;
63
- throw error;
64
- }
65
- const pid = Number(text.trim());
66
- return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
67
- }
68
- function processIsAlive(pid) {
69
- try {
70
- process.kill(pid, 0);
71
- return true;
72
- }
73
- catch (error) {
74
- return !(isNodeError(error) && error.code === "ESRCH");
75
- }
76
- }
77
- async function removeLegacyLocks(root) {
78
- for (const relative of legacyLockPaths)
79
- await rm(join(root, relative), { recursive: true, force: true });
80
- await removeLegacyOperationReceipts(root);
81
- }
82
- /**
83
- * Operation receipts written before #76 (including archive import receipts) have
84
- * no readers. The world-revision epoch markers in the same directory are kept.
85
- */
86
- async function removeLegacyOperationReceipts(root) {
87
- const directory = join(root, "operations");
88
- const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
89
- if (isNodeError(error) && error.code === "ENOENT")
90
- return null;
91
- throw error;
92
- });
93
- if (entries === null)
94
- return;
95
- for (const entry of entries)
96
- if (entry.isFile() && entry.name.endsWith(".json"))
97
- await rm(join(directory, entry.name), { force: true });
98
- try {
99
- await rmdir(directory);
100
- }
101
- catch (error) {
102
- if (!isNodeError(error) || error.code !== "ENOTEMPTY")
103
- throw error;
104
- }
105
- }
106
- function isNodeError(error) {
107
- return error instanceof Error && "code" in error;
108
- }