@yugoo-design/dsh-design-studio-dsh 0.1.2 → 0.2.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/README.md +438 -3
- package/lib/authoring/panel-components.json +10790 -0
- package/lib/authoring/studio-creators/material.json +1 -0
- package/lib/authoring-agent-vc786tS0.js +648 -0
- package/lib/authoring-agent.d.ts +24 -0
- package/lib/authoring-agent.js +2 -0
- package/lib/authoring-agent.types-0S0xnlDW.d.ts +87 -0
- package/lib/authoring-entry.d.ts +20 -0
- package/lib/authoring-entry.js +77 -0
- package/lib/authoring-host-m8-GmXIx.js +348 -0
- package/lib/authoring-host.d.ts +5 -0
- package/lib/authoring-host.js +2 -0
- package/lib/authoring-host.types-BZt9l0xK.d.ts +77 -0
- package/lib/authoring-reference-store.d.ts +39 -0
- package/lib/authoring-reference-store.js +385 -0
- package/lib/authoring-service-WClXJGik.js +347 -0
- package/lib/authoring-service.d.ts +121 -0
- package/lib/authoring-service.js +2 -0
- package/lib/authoring-session.d.ts +5 -0
- package/lib/authoring-session.js +204 -0
- package/lib/authoring-session.types-C6HosIWU.d.ts +58 -0
- package/lib/authoring-sources-BzRuEuub.d.ts +17 -0
- package/lib/authoring-sources.d.ts +2 -0
- package/lib/authoring-sources.js +95 -0
- package/lib/authoring-workbench.d.ts +15 -0
- package/lib/authoring-workbench.js +782 -0
- package/lib/authoring.page-rpc-CYYWsmxc.d.ts +30 -0
- package/lib/authoring.page-rpc.d.ts +2 -0
- package/lib/authoring.page-rpc.js +55 -0
- package/lib/authoring.remote-client.d.ts +2557 -0
- package/lib/authoring.remote-client.js +435 -0
- package/lib/client-adapter-BmIzQcMT.d.ts +65 -0
- package/lib/client-adapter.d.ts +2 -64
- package/lib/client.js +1 -2
- package/lib/css-assets/dsh-design-studio-page/9178281a0695f79ff8bff8eca529231b67f896dc1c12c8dbaaad24b32dd3fc07.eot +0 -0
- package/lib/css-assets/dsh-design-studio-page/b366faf39f6bcc48f10b10f2860e5ebe57091cb2a15beeed8a0977e24560bbba.svg +224 -0
- package/lib/css-assets/dsh-design-studio-page/manifest.json +20 -0
- package/lib/index.js +4 -105
- package/lib/kit-files-DKBlBOG8.js +159 -0
- package/lib/page-client.js +4 -23849
- package/lib/page-path-BkPiqrUH.d.ts +25 -0
- package/lib/page-path.d.ts +2 -2
- package/lib/page-path.js +70 -2
- package/lib/page-rpc-DXbf4seT.d.ts +24 -0
- package/lib/page-rpc.d.ts +2 -15
- package/lib/page-rpc.js +9 -4
- package/lib/page.d.ts +5 -3
- package/lib/page.js +219 -2
- package/lib/remote-method-ab_lHwWN.js +16 -0
- package/lib/typert.host.d.ts +827 -2
- package/lib/typert.host.js +2 -1
- package/lib/typert.remote-client-CWz-anTr.d.ts +37 -0
- package/lib/typert.remote-client.d.ts +1 -36
- package/licenses/dsh-distribution.json +8644 -149
- package/package.json +73 -10
- package/lib/page-path-wn8e_fid.d.ts +0 -14
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import { constants } from "node:fs";
|
|
5
|
+
//#region src/authoring-reference-store.ts
|
|
6
|
+
/** Host-private native addresses only; sessionQuery and S1 retain their authorities. */
|
|
7
|
+
const STUDIO_AUTHORING_REFERENCE_LIMITS = Object.freeze({
|
|
8
|
+
references: 256,
|
|
9
|
+
bytes: 524288,
|
|
10
|
+
paths: 64
|
|
11
|
+
});
|
|
12
|
+
const NAMESPACE = "studio-authoring-references";
|
|
13
|
+
const FORMAT = "studio-authoring-reference/v1";
|
|
14
|
+
const OWNER = `${FORMAT}\n`;
|
|
15
|
+
const HASH = /^[a-f0-9]{64}$/u;
|
|
16
|
+
const RECORD = /^[a-f0-9]{64}\.json$/u;
|
|
17
|
+
const TEMP = /^\.[a-f0-9-]{36}\.tmp$/u;
|
|
18
|
+
const WINDOWS = process.platform === "win32";
|
|
19
|
+
function fail(message) {
|
|
20
|
+
throw new Error(`Studio authoring references: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
const hash = (text) => createHash("sha256").update(text).digest("hex");
|
|
23
|
+
const missing = (error) => error?.code === "ENOENT";
|
|
24
|
+
const same = (a, b) => a.dev === b.dev && a.ino === b.ino;
|
|
25
|
+
function object(value, required, optional = []) {
|
|
26
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail("expected a plain JSON object");
|
|
27
|
+
const record = value;
|
|
28
|
+
if (required.some((key) => !Object.hasOwn(record, key)) || Object.keys(record).some((key) => !required.includes(key) && !optional.includes(key))) fail("unexpected or missing fields");
|
|
29
|
+
return record;
|
|
30
|
+
}
|
|
31
|
+
function text(value, bytes = 1024) {
|
|
32
|
+
if (typeof value !== "string" || !value || !value.isWellFormed() || Buffer.byteLength(value) > bytes || /[\u0000-\u001f\u007f]/u.test(value)) fail("invalid or oversized identity/path text");
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function integer(value, minimum) {
|
|
36
|
+
if (!Number.isSafeInteger(value) || value < minimum) fail("invalid reference integer");
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/** Storage shape/budgets only. Native identity, source grants and pins are checked by S4/H2. */
|
|
40
|
+
function reference(value) {
|
|
41
|
+
const r = object(value, [
|
|
42
|
+
"sessionId",
|
|
43
|
+
"parentSessionId",
|
|
44
|
+
"invocationId",
|
|
45
|
+
"epoch",
|
|
46
|
+
"messageId",
|
|
47
|
+
"afterSeq",
|
|
48
|
+
"scope"
|
|
49
|
+
]);
|
|
50
|
+
const s = object(r.scope, [
|
|
51
|
+
"id",
|
|
52
|
+
"expectedRevision",
|
|
53
|
+
"allowedPaths"
|
|
54
|
+
], ["bundle"]);
|
|
55
|
+
if (!Array.isArray(s.allowedPaths) || !s.allowedPaths.length || s.allowedPaths.length > STUDIO_AUTHORING_REFERENCE_LIMITS.paths) fail("invalid allowed-path count");
|
|
56
|
+
const bundle = Object.hasOwn(s, "bundle") ? object(s.bundle, ["name", "version"]) : void 0;
|
|
57
|
+
return {
|
|
58
|
+
sessionId: text(r.sessionId),
|
|
59
|
+
parentSessionId: text(r.parentSessionId),
|
|
60
|
+
invocationId: text(r.invocationId),
|
|
61
|
+
epoch: text(r.epoch),
|
|
62
|
+
messageId: text(r.messageId),
|
|
63
|
+
afterSeq: integer(r.afterSeq, -1),
|
|
64
|
+
scope: {
|
|
65
|
+
id: text(s.id),
|
|
66
|
+
expectedRevision: integer(s.expectedRevision, 0),
|
|
67
|
+
allowedPaths: Array.from(s.allowedPaths, (path) => text(path, 4096)),
|
|
68
|
+
...bundle ? { bundle: {
|
|
69
|
+
name: text(bundle.name),
|
|
70
|
+
version: text(bundle.version)
|
|
71
|
+
} } : {}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function keyOf(sessionId, epoch) {
|
|
76
|
+
return hash(JSON.stringify([sessionId, epoch]));
|
|
77
|
+
}
|
|
78
|
+
function encode(ref) {
|
|
79
|
+
const result = JSON.stringify({
|
|
80
|
+
format: FORMAT,
|
|
81
|
+
key: keyOf(ref.sessionId, ref.epoch),
|
|
82
|
+
digest: hash(JSON.stringify(ref)),
|
|
83
|
+
reference: ref
|
|
84
|
+
}) + "\n";
|
|
85
|
+
if (Buffer.byteLength(result) > STUDIO_AUTHORING_REFERENCE_LIMITS.bytes) fail("reference exceeds JSON byte budget");
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function decode(bytes, key) {
|
|
89
|
+
const raw = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
90
|
+
const envelope = object(JSON.parse(raw), [
|
|
91
|
+
"format",
|
|
92
|
+
"key",
|
|
93
|
+
"digest",
|
|
94
|
+
"reference"
|
|
95
|
+
]);
|
|
96
|
+
const ref = reference(envelope.reference);
|
|
97
|
+
if (envelope.format !== FORMAT || envelope.key !== key || !HASH.test(String(envelope.digest)) || keyOf(ref.sessionId, ref.epoch) !== key || encode(ref) !== raw) fail("reference integrity mismatch");
|
|
98
|
+
return ref;
|
|
99
|
+
}
|
|
100
|
+
function privateMode(info, directory) {
|
|
101
|
+
if ((directory ? !info.isDirectory() : !info.isFile()) || info.isSymbolicLink() || !WINDOWS && ((info.mode & 4095) !== (directory ? 448 : 384) || process.getuid !== void 0 && info.uid !== process.getuid())) fail("storage must remain private, owned and physical");
|
|
102
|
+
}
|
|
103
|
+
async function optionalStat(path) {
|
|
104
|
+
try {
|
|
105
|
+
return await fs.lstat(path);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (missing(error)) return;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function directories(path) {
|
|
112
|
+
const root = parse(path).root, parts = path.slice(root.length).split(/[\\/]/u).filter(Boolean);
|
|
113
|
+
const result = [];
|
|
114
|
+
let current = root;
|
|
115
|
+
for (const part of ["", ...parts]) {
|
|
116
|
+
current = join(current, part);
|
|
117
|
+
const info = await fs.lstat(current);
|
|
118
|
+
if (!info.isDirectory() || info.isSymbolicLink()) fail("directory path traverses a symlink or non-directory");
|
|
119
|
+
result.push({
|
|
120
|
+
path: current,
|
|
121
|
+
info
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (await fs.realpath(path) !== path) fail("stateRoot must be a canonical physical path");
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
async function readPrivate(path, limit, expected, sync = false) {
|
|
128
|
+
const before = await optionalStat(path);
|
|
129
|
+
if (!before) return;
|
|
130
|
+
privateMode(before, false);
|
|
131
|
+
if (before.nlink !== 1 || before.size > limit || expected && !same(before, expected)) fail("unsafe, replaced or oversized reference file");
|
|
132
|
+
const access = WINDOWS && sync ? constants.O_RDWR : constants.O_RDONLY;
|
|
133
|
+
const handle = await fs.open(path, access | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
134
|
+
try {
|
|
135
|
+
const opened = await handle.stat();
|
|
136
|
+
privateMode(opened, false);
|
|
137
|
+
if (!same(before, opened) || opened.nlink !== 1 || opened.size > limit) fail("reference file changed while opening");
|
|
138
|
+
const bytes = Buffer.alloc(limit + 1);
|
|
139
|
+
let length = 0;
|
|
140
|
+
while (length < bytes.length) {
|
|
141
|
+
const { bytesRead } = await handle.read(bytes, length, bytes.length - length, null);
|
|
142
|
+
if (!bytesRead) break;
|
|
143
|
+
length += bytesRead;
|
|
144
|
+
}
|
|
145
|
+
if (length > limit) fail("reference exceeds JSON byte budget");
|
|
146
|
+
if (sync) await handle.sync();
|
|
147
|
+
const after = await handle.stat(), named = await fs.lstat(path);
|
|
148
|
+
privateMode(after, false);
|
|
149
|
+
privateMode(named, false);
|
|
150
|
+
if (!same(opened, named) || !same(opened, after) || after.nlink !== 1 || named.nlink !== 1 || opened.size !== after.size || opened.mtimeMs !== after.mtimeMs || opened.ctimeMs !== after.ctimeMs || after.size !== named.size || after.mtimeMs !== named.mtimeMs || after.ctimeMs !== named.ctimeMs) fail("reference file changed while reading");
|
|
151
|
+
return {
|
|
152
|
+
bytes: bytes.subarray(0, length),
|
|
153
|
+
info: after
|
|
154
|
+
};
|
|
155
|
+
} finally {
|
|
156
|
+
await handle.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function syncDirectory(path, expected) {
|
|
160
|
+
if (WINDOWS) {
|
|
161
|
+
const info = await fs.lstat(path);
|
|
162
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !same(info, expected)) fail("directory replaced before publication");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const handle = await fs.open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_DIRECTORY);
|
|
166
|
+
try {
|
|
167
|
+
const info = await handle.stat();
|
|
168
|
+
if (!info.isDirectory() || !same(info, expected)) fail("directory replaced before sync");
|
|
169
|
+
await handle.sync();
|
|
170
|
+
} finally {
|
|
171
|
+
await handle.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function preparePrivateDirectory(stateRoot) {
|
|
175
|
+
if (typeof stateRoot !== "string" || !isAbsolute(stateRoot) || resolve(stateRoot) !== stateRoot) fail("explicit absolute canonical stateRoot required");
|
|
176
|
+
const parents = await directories(dirname(stateRoot));
|
|
177
|
+
await fs.mkdir(stateRoot, { mode: 448 }).catch((error) => {
|
|
178
|
+
if (error.code !== "EEXIST") throw error;
|
|
179
|
+
});
|
|
180
|
+
const stateInfo = await fs.lstat(stateRoot);
|
|
181
|
+
privateMode(stateInfo, true);
|
|
182
|
+
const chain = await directories(stateRoot);
|
|
183
|
+
if (!same(chain.at(-1).info, stateInfo) || parents.some((entry, i) => !same(entry.info, chain[i].info))) fail("stateRoot changed during initialization");
|
|
184
|
+
return {
|
|
185
|
+
stateRoot,
|
|
186
|
+
stateInfo,
|
|
187
|
+
parents,
|
|
188
|
+
chain
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async function verifyDirectories(anchors) {
|
|
192
|
+
for (const entry of anchors) {
|
|
193
|
+
const info = await fs.lstat(entry.path);
|
|
194
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !same(info, entry.info) || !WINDOWS && (info.mode !== entry.info.mode || info.uid !== entry.info.uid)) fail("directory identity or permissions changed");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/** Prepare one Host-owned private leaf, without creating a store or repairing permissions.
|
|
198
|
+
* The canonical physical parent must already exist. Failures can leave the leaf;
|
|
199
|
+
* POSIX retries complete both sync barriers. Windows uses inherited user access
|
|
200
|
+
* and physical-path checks, without a directory-flush or power-loss guarantee. */
|
|
201
|
+
async function prepareStudioAuthoringDirectory(path) {
|
|
202
|
+
const { stateRoot, stateInfo, parents, chain } = await preparePrivateDirectory(path);
|
|
203
|
+
await verifyDirectories(chain);
|
|
204
|
+
await syncDirectory(stateRoot, stateInfo);
|
|
205
|
+
await syncDirectory(dirname(stateRoot), parents.at(-1).info);
|
|
206
|
+
await verifyDirectories(chain);
|
|
207
|
+
return stateRoot;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Explicit private stateRoot only. The marker owns this namespace, not S2 or the
|
|
211
|
+
* root. No permission repair, stale-lock reclamation, SDK import or native action.
|
|
212
|
+
* Path/handle checks reject detected replacement; they do not sandbox arbitrary
|
|
213
|
+
* same-user mutation between filesystem syscalls. POSIX requires directory sync;
|
|
214
|
+
* Windows uses ordinary user-directory access and file publication, without a
|
|
215
|
+
* directory-flush requirement or cross-platform power-loss guarantee.
|
|
216
|
+
*/
|
|
217
|
+
async function createStudioAuthoringReferenceStore(options) {
|
|
218
|
+
const { stateRoot, stateInfo, parents, chain } = await preparePrivateDirectory(options?.stateRoot);
|
|
219
|
+
const directory = join(stateRoot, NAMESPACE), owner = join(directory, ".owner");
|
|
220
|
+
let created = false;
|
|
221
|
+
await fs.mkdir(directory, { mode: 448 }).then(() => {
|
|
222
|
+
created = true;
|
|
223
|
+
}).catch((error) => {
|
|
224
|
+
if (error.code !== "EEXIST") throw error;
|
|
225
|
+
});
|
|
226
|
+
const directoryInfo = await fs.lstat(directory);
|
|
227
|
+
privateMode(directoryInfo, true);
|
|
228
|
+
if (created) {
|
|
229
|
+
const handle = await fs.open(owner, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, 384);
|
|
230
|
+
try {
|
|
231
|
+
await handle.writeFile(OWNER);
|
|
232
|
+
} finally {
|
|
233
|
+
await handle.close();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const ownership = await readPrivate(owner, Buffer.byteLength(OWNER));
|
|
237
|
+
if (!ownership || ownership.bytes.toString() !== OWNER) fail("namespace ownership conflict; existing content preserved");
|
|
238
|
+
const anchors = [...chain, {
|
|
239
|
+
path: directory,
|
|
240
|
+
info: directoryInfo
|
|
241
|
+
}];
|
|
242
|
+
const guard = async () => {
|
|
243
|
+
await verifyDirectories(anchors);
|
|
244
|
+
const marker = await readPrivate(owner, Buffer.byteLength(OWNER), ownership.info);
|
|
245
|
+
if (!marker || marker.bytes.toString() !== OWNER) fail("namespace ownership changed");
|
|
246
|
+
};
|
|
247
|
+
await guard();
|
|
248
|
+
const retainedOwner = await readPrivate(owner, Buffer.byteLength(OWNER), ownership.info, true);
|
|
249
|
+
if (!retainedOwner || retainedOwner.bytes.toString() !== OWNER) fail("namespace ownership changed");
|
|
250
|
+
await guard();
|
|
251
|
+
await syncDirectory(directory, directoryInfo);
|
|
252
|
+
await syncDirectory(stateRoot, stateInfo);
|
|
253
|
+
await syncDirectory(dirname(stateRoot), parents.at(-1).info);
|
|
254
|
+
await guard();
|
|
255
|
+
const known = /* @__PURE__ */ new Map();
|
|
256
|
+
const load = async (key, durable = false) => {
|
|
257
|
+
const file = await readPrivate(join(directory, `${key}.json`), STUDIO_AUTHORING_REFERENCE_LIMITS.bytes, known.get(key)?.info, durable);
|
|
258
|
+
if (!file) {
|
|
259
|
+
if (known.has(key)) fail("retained reference disappeared");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const ref = decode(file.bytes, key), digest = hash(encode(ref)), prior = known.get(key);
|
|
263
|
+
if (prior && prior.digest !== digest) fail("immutable reference changed");
|
|
264
|
+
if (known.size >= STUDIO_AUTHORING_REFERENCE_LIMITS.references && !prior) fail("reference count exceeds budget");
|
|
265
|
+
known.set(key, {
|
|
266
|
+
info: file.info,
|
|
267
|
+
digest
|
|
268
|
+
});
|
|
269
|
+
return ref;
|
|
270
|
+
};
|
|
271
|
+
const keys = async () => {
|
|
272
|
+
const names = [];
|
|
273
|
+
let entries = 0;
|
|
274
|
+
const dir = await fs.opendir(directory);
|
|
275
|
+
for await (const entry of dir) {
|
|
276
|
+
if (++entries > STUDIO_AUTHORING_REFERENCE_LIMITS.references + 16) fail("directory exceeds entry budget");
|
|
277
|
+
if (RECORD.test(entry.name)) names.push(entry.name.slice(0, -5));
|
|
278
|
+
else if (entry.name === ".owner" || entry.name === ".write.lock" || TEMP.test(entry.name)) {
|
|
279
|
+
const info = await fs.lstat(join(directory, entry.name));
|
|
280
|
+
privateMode(info, entry.name === ".write.lock");
|
|
281
|
+
if (entry.name !== ".write.lock" && info.nlink !== 1) fail("unsafe namespace entry");
|
|
282
|
+
} else fail("unexpected namespace entry");
|
|
283
|
+
}
|
|
284
|
+
if (names.length > STUDIO_AUTHORING_REFERENCE_LIMITS.references) fail("reference count exceeds budget");
|
|
285
|
+
return names.sort();
|
|
286
|
+
};
|
|
287
|
+
let lane = Promise.resolve();
|
|
288
|
+
const retain = async (input) => {
|
|
289
|
+
const ref = reference(input), bytes = encode(ref), key = keyOf(ref.sessionId, ref.epoch);
|
|
290
|
+
const operation = lane.then(async () => {
|
|
291
|
+
await guard();
|
|
292
|
+
const lock = join(directory, ".write.lock");
|
|
293
|
+
try {
|
|
294
|
+
await fs.mkdir(lock, { mode: 448 });
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (error.code === "EEXIST") fail("store is being retained elsewhere; no stale lock is reclaimed");
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
const lockInfo = await fs.lstat(lock);
|
|
300
|
+
privateMode(lockInfo, true);
|
|
301
|
+
let temporary;
|
|
302
|
+
try {
|
|
303
|
+
await guard();
|
|
304
|
+
const existing = await load(key, true);
|
|
305
|
+
if (existing) {
|
|
306
|
+
if (encode(existing) !== bytes) fail("conflicting native address; retained reference preserved");
|
|
307
|
+
await syncDirectory(directory, directoryInfo);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if ((await keys()).length >= STUDIO_AUTHORING_REFERENCE_LIMITS.references) fail("reference count budget reached");
|
|
311
|
+
const path = join(directory, `.${randomUUID()}.tmp`);
|
|
312
|
+
const handle = await fs.open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
313
|
+
try {
|
|
314
|
+
temporary = {
|
|
315
|
+
path,
|
|
316
|
+
info: await handle.stat()
|
|
317
|
+
};
|
|
318
|
+
privateMode(temporary.info, false);
|
|
319
|
+
await handle.writeFile(bytes, "utf8");
|
|
320
|
+
await handle.sync();
|
|
321
|
+
} finally {
|
|
322
|
+
await handle.close();
|
|
323
|
+
}
|
|
324
|
+
await guard();
|
|
325
|
+
const current = await fs.lstat(path);
|
|
326
|
+
privateMode(current, false);
|
|
327
|
+
if (!same(current, temporary.info) || current.nlink !== 1) fail("temporary reference replaced");
|
|
328
|
+
await fs.link(path, join(directory, `${key}.json`));
|
|
329
|
+
} finally {
|
|
330
|
+
if (temporary) {
|
|
331
|
+
await guard();
|
|
332
|
+
const info = await optionalStat(temporary.path);
|
|
333
|
+
if (info) {
|
|
334
|
+
privateMode(info, false);
|
|
335
|
+
if (!same(info, temporary.info)) fail("temporary reference changed; preserved");
|
|
336
|
+
await fs.unlink(temporary.path);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
await guard();
|
|
340
|
+
const info = await fs.lstat(lock);
|
|
341
|
+
privateMode(info, true);
|
|
342
|
+
if (!same(info, lockInfo)) fail("write lock changed; preserved");
|
|
343
|
+
await fs.rmdir(lock);
|
|
344
|
+
await syncDirectory(directory, directoryInfo);
|
|
345
|
+
}
|
|
346
|
+
const retained = await load(key);
|
|
347
|
+
if (!retained || encode(retained) !== bytes) fail("retained address verification failed");
|
|
348
|
+
await guard();
|
|
349
|
+
});
|
|
350
|
+
lane = operation.catch(() => {});
|
|
351
|
+
return operation;
|
|
352
|
+
};
|
|
353
|
+
const read = async (input) => {
|
|
354
|
+
const request = object(input, [
|
|
355
|
+
"parentSessionId",
|
|
356
|
+
"sessionId",
|
|
357
|
+
"epoch"
|
|
358
|
+
]);
|
|
359
|
+
const parent = text(request.parentSessionId), key = keyOf(text(request.sessionId), text(request.epoch));
|
|
360
|
+
await guard();
|
|
361
|
+
const ref = await load(key);
|
|
362
|
+
if (ref && ref.parentSessionId !== parent) fail("reference belongs to another exact parent");
|
|
363
|
+
await guard();
|
|
364
|
+
return ref;
|
|
365
|
+
};
|
|
366
|
+
const list = async (input) => {
|
|
367
|
+
const parent = text(object(input, ["parentSessionId"]).parentSessionId);
|
|
368
|
+
await guard();
|
|
369
|
+
const records = [];
|
|
370
|
+
for (const key of await keys()) {
|
|
371
|
+
const ref = await load(key);
|
|
372
|
+
if (!ref) fail("reference disappeared during list");
|
|
373
|
+
if (ref.parentSessionId === parent) records.push(ref);
|
|
374
|
+
}
|
|
375
|
+
await guard();
|
|
376
|
+
return records;
|
|
377
|
+
};
|
|
378
|
+
return Object.freeze({
|
|
379
|
+
retain,
|
|
380
|
+
read,
|
|
381
|
+
list
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
export { STUDIO_AUTHORING_REFERENCE_LIMITS, createStudioAuthoringReferenceStore, prepareStudioAuthoringDirectory };
|