@volter/world-core 3.0.155 → 3.0.157
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/attach.cjs +2 -0
- package/capability-decision.cjs +6 -4
- package/dist/attach.cjs +2 -0
- package/dist/capability-decision.cjs +6 -4
- package/dist/generated/pack-facts.json +204 -4
- package/dist/inject.cjs +150 -86
- package/dist/network-policy.cjs +11 -1
- package/dist/network-policy.d.cts +1 -0
- package/dist/pack-facts.d.cts +3 -0
- package/dist/required-credential-transport.cjs +29 -0
- package/dist/required-credential-transport.d.cts +3 -0
- package/dist/src/actions.d.ts +15 -0
- package/dist/src/actions.js +82 -21
- package/dist/src/ancestry.d.ts +5 -4
- package/dist/src/ancestry.js +53 -28
- package/dist/src/blob-store.d.ts +16 -0
- package/dist/src/blob-store.js +76 -1
- package/dist/src/changeset.js +1 -1
- package/dist/src/clickhouse/compression.d.ts +28 -0
- package/dist/src/clickhouse/compression.js +281 -0
- package/dist/src/clickhouse/index.d.ts +1 -0
- package/dist/src/clickhouse/index.js +1 -0
- package/dist/src/derived-core.d.ts +84 -7
- package/dist/src/derived-core.js +336 -61
- package/dist/src/derived-real.js +97 -15
- package/dist/src/derived.d.ts +5 -0
- package/dist/src/derived.js +3 -0
- package/dist/src/executor.d.ts +7 -0
- package/dist/src/executor.js +15 -1
- package/dist/src/fork.js +1 -1
- package/dist/src/git/lfs.d.ts +13 -3
- package/dist/src/git/lfs.js +41 -9
- package/dist/src/grpc-wire.js +2 -1
- package/dist/src/h2.js +63 -10
- package/dist/src/head.d.ts +5 -0
- package/dist/src/head.js +15 -0
- package/dist/src/history.d.ts +1 -1
- package/dist/src/history.js +7 -7
- package/dist/src/index.d.ts +5 -1
- package/dist/src/index.js +6 -1
- package/dist/src/live/ingest.js +9 -13
- package/dist/src/live/rtmp.js +7 -5
- package/dist/src/live/segmenter.js +7 -1
- package/dist/src/log.d.ts +23 -3
- package/dist/src/log.js +160 -37
- package/dist/src/machines.js +5 -1
- package/dist/src/managed-go-programs.d.ts +55 -0
- package/dist/src/managed-go-programs.js +133 -0
- package/dist/src/managed-original-inputs.d.ts +101 -0
- package/dist/src/managed-original-inputs.js +125 -0
- package/dist/src/mongodb/client.d.ts +8 -0
- package/dist/src/mongodb/client.js +27 -0
- package/dist/src/mongodb/index.d.ts +2 -0
- package/dist/src/mongodb/index.js +5 -0
- package/dist/src/mongodb/stream.d.ts +97 -0
- package/dist/src/mongodb/stream.js +206 -0
- package/dist/src/observe.js +1 -1
- package/dist/src/pack-fetch.d.ts +6 -1
- package/dist/src/pack-fetch.js +69 -14
- package/dist/src/pack-paths.d.ts +2 -1
- package/dist/src/pack-paths.js +6 -1
- package/dist/src/packRegistry.d.ts +10 -14
- package/dist/src/packRegistry.js +2 -13
- package/dist/src/protocol.d.ts +13 -0
- package/dist/src/protocol.js +13 -0
- package/dist/src/redis/engine.d.ts +10 -4
- package/dist/src/redis/engine.js +103 -32
- package/dist/src/redis/member-storage.js +20 -8
- package/dist/src/redis/service-times.d.ts +25 -0
- package/dist/src/redis/service-times.js +87 -0
- package/dist/src/redis/stream.d.ts +2 -1
- package/dist/src/redis/stream.js +109 -20
- package/dist/src/resource-blob.d.ts +4 -0
- package/dist/src/resource-blob.js +8 -1
- package/dist/src/runtime-state.d.ts +11 -0
- package/dist/src/runtime-state.js +13 -0
- package/dist/src/runtime.d.ts +7 -5
- package/dist/src/runtime.js +7 -4
- package/dist/src/serve-http.d.ts +2 -0
- package/dist/src/serve-http.js +73 -6
- package/dist/src/serve.d.ts +10 -0
- package/dist/src/serve.js +43 -3
- package/dist/src/sockets.d.ts +4 -1
- package/dist/src/sockets.js +59 -6
- package/dist/src/storage.d.ts +2 -1
- package/dist/src/storage.js +6 -3
- package/dist/src/vendor-call.js +58 -0
- package/dist/src/world-boot.d.ts +1 -0
- package/dist/src/world-boot.js +6 -0
- package/dist/src/world-clock.js +6 -2
- package/dist/src/world-store.d.ts +122 -7
- package/dist/src/world-store.js +959 -103
- package/dist/src/xml.d.ts +30 -0
- package/dist/src/xml.js +74 -0
- package/generated/pack-facts.json +204 -4
- package/inject.cjs +150 -86
- package/network-policy.cjs +11 -1
- package/network-policy.d.cts +1 -0
- package/pack-facts.d.cts +3 -0
- package/package.json +27 -1
- package/required-credential-transport.cjs +29 -0
- package/required-credential-transport.d.cts +3 -0
- package/src/actions.ts +78 -21
- package/src/ancestry.ts +46 -24
- package/src/blob-store.ts +59 -1
- package/src/changeset.ts +1 -1
- package/src/clickhouse/compression.ts +172 -0
- package/src/clickhouse/index.ts +2 -0
- package/src/derived-core.ts +356 -61
- package/src/derived-real.ts +80 -14
- package/src/derived.ts +9 -1
- package/src/executor.ts +17 -3
- package/src/fork.ts +1 -1
- package/src/git/lfs.ts +40 -9
- package/src/grpc-wire.ts +2 -1
- package/src/h2.ts +46 -5
- package/src/head.ts +15 -0
- package/src/history.ts +7 -7
- package/src/index.ts +7 -1
- package/src/live/ingest.ts +7 -3
- package/src/live/rtmp.ts +7 -5
- package/src/live/segmenter.ts +7 -1
- package/src/log.ts +147 -32
- package/src/machines.ts +5 -1
- package/src/managed-go-programs.ts +146 -0
- package/src/managed-original-inputs.ts +163 -0
- package/src/mongodb/client.ts +27 -0
- package/src/mongodb/index.ts +5 -0
- package/src/mongodb/stream.ts +223 -0
- package/src/observe.ts +1 -1
- package/src/pack-fetch.ts +66 -16
- package/src/pack-paths.ts +5 -1
- package/src/packRegistry.ts +3 -15
- package/src/protocol.ts +13 -0
- package/src/redis/engine.ts +100 -57
- package/src/redis/member-storage.ts +17 -9
- package/src/redis/service-times.ts +81 -0
- package/src/redis/stream.ts +66 -14
- package/src/resource-blob.ts +9 -1
- package/src/runtime-state.ts +14 -0
- package/src/runtime.ts +10 -3
- package/src/serve-http.ts +65 -6
- package/src/serve.ts +45 -3
- package/src/sockets.ts +39 -8
- package/src/storage.ts +5 -3
- package/src/vendor-call.ts +54 -0
- package/src/world-boot.ts +6 -0
- package/src/world-clock.ts +6 -2
- package/src/world-store.ts +768 -91
- package/src/xml.ts +71 -0
package/dist/src/history.js
CHANGED
|
@@ -108,14 +108,14 @@ export function saveHistoryView(directory, view) {
|
|
|
108
108
|
for (const dependency of dependencies)
|
|
109
109
|
commitParentPin(dependency.directory, file);
|
|
110
110
|
return id;
|
|
111
|
-
});
|
|
111
|
+
}, { operation: 'view pin publication' });
|
|
112
112
|
}
|
|
113
113
|
/** What an inherited history's content stands on, as file facts: every segment file the view names
|
|
114
114
|
* (and the view document itself) and each named directory's ancestry record (its generation). A read cache keyed by this sees an
|
|
115
115
|
* ancestor's retained history truncated, replaced or its directory re-created, which the full read
|
|
116
116
|
* refuses (`historyEntries`), instead of serving a tree folded from the history it replaced. */
|
|
117
|
-
export function inheritedHistoryKey(service, root) {
|
|
118
|
-
const parent = readBranchMeta(service, root)?.parent;
|
|
117
|
+
export function inheritedHistoryKey(service, root, meta) {
|
|
118
|
+
const parent = readBranchMeta(service, root, meta)?.parent;
|
|
119
119
|
if (!parent?.view)
|
|
120
120
|
return '';
|
|
121
121
|
const store = getActiveWorldStore();
|
|
@@ -170,7 +170,7 @@ export function captureHistory(service, root) {
|
|
|
170
170
|
descriptor.segments.push({ directory, generation, file, start: 0, count: entries.length, digest: historyDigest(entries) });
|
|
171
171
|
}
|
|
172
172
|
return { view: saveHistoryView(directory, descriptor), position: historyLength(descriptor.layout), descriptor };
|
|
173
|
-
});
|
|
173
|
+
}, { operation: 'capture' });
|
|
174
174
|
}
|
|
175
175
|
export function originHead(service, root) {
|
|
176
176
|
const raw = getActiveWorldStore().read(join(worldPaths(service, root).dir, 'origin-head.json'));
|
|
@@ -213,7 +213,7 @@ export function publishOriginHistory(service, root, remoteView, layout, entries)
|
|
|
213
213
|
const view = saveHistoryView(directory, { version: 1, owner: { directory, generation }, layout, segments });
|
|
214
214
|
store.writeAtomic(join(directory, 'origin-head.json'), JSON.stringify({ view, remoteView }));
|
|
215
215
|
return { view, appended };
|
|
216
|
-
});
|
|
216
|
+
}, { operation: 'origin fetch' });
|
|
217
217
|
}
|
|
218
218
|
/** A changeset binds the complete inherited view, including this root's observations. */
|
|
219
219
|
export function captureParentHistory(service, root) {
|
|
@@ -221,7 +221,7 @@ export function captureParentHistory(service, root) {
|
|
|
221
221
|
const head = captureHistory(service, root);
|
|
222
222
|
const position = head.position - head.descriptor.layout.branch;
|
|
223
223
|
return { view: saveHistoryView(worldPaths(service, root).dir, historyPrefix(head.descriptor, position)), position };
|
|
224
|
-
});
|
|
224
|
+
}, { operation: 'capture' });
|
|
225
225
|
}
|
|
226
226
|
/** Compare evaluated views, so removals, reordered rows and layout changes count as drift. */
|
|
227
227
|
export function historyChanges(service, before, after) {
|
|
@@ -295,7 +295,7 @@ export function historyAtInstant(service, instant, root, selectedView) {
|
|
|
295
295
|
}
|
|
296
296
|
const view = saveHistoryView(directory, { version: 1, owner: source.owner, layout, segments, ...(source.origin ? { origin: { ...source.origin, ...(keep.slice(0, source.origin.position).some(v => !v) ? { incomplete: true } : {}) } } : {}) });
|
|
297
297
|
return { view, position: historyLength(layout) };
|
|
298
|
-
});
|
|
298
|
+
}, { operation: 'capture' });
|
|
299
299
|
}
|
|
300
300
|
/** Replace only the tracked origin layer; inherited local layers keep their exact projection order. */
|
|
301
301
|
export function replaceHistoryOrigin(previous, origin, next) {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export { answerVendorErrors, RefusedWriteError } from './head.js';
|
|
|
40
40
|
export { GraphqlError, type GraphqlPart, type GraphqlResolver } from './graphql-wire.js';
|
|
41
41
|
export { VendorUnreachableError } from './vendor-call.js';
|
|
42
42
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from './request-scope.js';
|
|
43
|
-
export { serveHttp, serveStream, StreamListenerUnavailable, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.js';
|
|
43
|
+
export { serveHttp, serveStream, nodeBuiltin, StreamListenerUnavailable, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.js';
|
|
44
44
|
export type { TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.js';
|
|
45
45
|
export { privateTransportKey } from './private-transport.js';
|
|
46
46
|
export { siteLabel, siteUrlOf, twinSiteUrl, twinVendorUrl } from './twin-fetch.js';
|
|
@@ -58,6 +58,7 @@ export { EngineDatabaseError, EngineUnavailableError, quoteIdent } from './manag
|
|
|
58
58
|
export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatement, StatementResult as EngineStatementResult } from './managed-database.js';
|
|
59
59
|
export type { Mail, SmtpRoute } from './smtp.js';
|
|
60
60
|
export * as s3 from './s3/wire.js';
|
|
61
|
+
export * as xml from './xml.js';
|
|
61
62
|
export * as clickhouse from './clickhouse/index.js';
|
|
62
63
|
export { sampleFromSchema } from './schema-sample.js';
|
|
63
64
|
export * as openaiWire from './openai-wire.js';
|
|
@@ -70,6 +71,7 @@ export { presignSigV4, signSigV4, verifySigV4, type SigV4Verdict } from './sigv4
|
|
|
70
71
|
export * as redis from './redis/index.js';
|
|
71
72
|
export * as mysql from './mysql/index.js';
|
|
72
73
|
export * as live from './live/index.js';
|
|
74
|
+
export * as mongodb from './mongodb/index.js';
|
|
73
75
|
export * as git from './git/index.js';
|
|
74
76
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError } from './scenario.js';
|
|
75
77
|
export type { PackScenarioAdapter, ScenarioDecision, ScenarioDocument, ScenarioExtractorSpec, ScenarioFault, ScenarioFaultResult, ScenarioFeatures, ScenarioHandler, ScenarioMatcher, ScenarioMissRecord, ScenarioStatus } from './scenario.js';
|
|
@@ -77,3 +79,5 @@ export { verifyAttestationBundle } from './attestation.js';
|
|
|
77
79
|
export { browserSessionToken, startBrowserSession, endBrowserSession } from './browser-session.js';
|
|
78
80
|
export { readHostPort } from './host-port.js';
|
|
79
81
|
export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost, type BrowserCookie, type BrowserState, type BrowserStorageState, type BrowserContext, type BrowserIndexedDB } from './browser-state.js';
|
|
82
|
+
export * as managedOriginalInputs from './managed-original-inputs.js';
|
|
83
|
+
export * as managedGoPrograms from './managed-go-programs.js';
|
package/dist/src/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { GraphqlError } from "./graphql-wire.js";
|
|
|
29
29
|
export { VendorUnreachableError } from "./vendor-call.js";
|
|
30
30
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from "./request-scope.js";
|
|
31
31
|
// ── the serve factory ──────────────────────────────────────────────────────────────────────────
|
|
32
|
-
export { serveHttp, serveStream, StreamListenerUnavailable } from "./serve-http.js";
|
|
32
|
+
export { serveHttp, serveStream, nodeBuiltin, StreamListenerUnavailable } from "./serve-http.js";
|
|
33
33
|
export { privateTransportKey } from "./private-transport.js";
|
|
34
34
|
// where a page links a hostname its twin answers, inside the World that shows it
|
|
35
35
|
export { siteLabel, siteUrlOf, twinSiteUrl, twinVendorUrl } from "./twin-fetch.js";
|
|
@@ -49,6 +49,8 @@ export { passwordHash, personOf, personWith, recordPerson } from "./people.js";
|
|
|
49
49
|
export { EngineDatabaseError, EngineUnavailableError, quoteIdent } from "./managed-database.js";
|
|
50
50
|
// S3's wire (contract "S3's wire is a kernel library too") and the git plane (contract "The git plane is a kernel library")
|
|
51
51
|
export * as s3 from "./s3/wire.js";
|
|
52
|
+
// Generic strict XML grammar; vendor interpretation remains in its consumer.
|
|
53
|
+
export * as xml from "./xml.js";
|
|
52
54
|
export * as clickhouse from "./clickhouse/index.js";
|
|
53
55
|
export { sampleFromSchema } from "./schema-sample.js";
|
|
54
56
|
export * as openaiWire from "./openai-wire.js";
|
|
@@ -61,6 +63,7 @@ export { presignSigV4, signSigV4, verifySigV4 } from "./sigv4.js";
|
|
|
61
63
|
export * as redis from "./redis/index.js";
|
|
62
64
|
export * as mysql from "./mysql/index.js";
|
|
63
65
|
export * as live from "./live/index.js";
|
|
66
|
+
export * as mongodb from "./mongodb/index.js";
|
|
64
67
|
export * as git from "./git/index.js";
|
|
65
68
|
// ── the scenario grammar (architecture, "Behaviour") ───────────────────────────────────────────
|
|
66
69
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError } from "./scenario.js";
|
|
@@ -68,3 +71,5 @@ export { verifyAttestationBundle } from "./attestation.js";
|
|
|
68
71
|
export { browserSessionToken, startBrowserSession, endBrowserSession } from "./browser-session.js";
|
|
69
72
|
export { readHostPort } from "./host-port.js";
|
|
70
73
|
export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost } from "./browser-state.js";
|
|
74
|
+
export * as managedOriginalInputs from "./managed-original-inputs.js";
|
|
75
|
+
export * as managedGoPrograms from "./managed-go-programs.js";
|
package/dist/src/live/ingest.js
CHANGED
|
@@ -25,7 +25,6 @@ export function liveIngest(options) {
|
|
|
25
25
|
let priorInit;
|
|
26
26
|
let started = false;
|
|
27
27
|
let metadata;
|
|
28
|
-
let refusedCodec;
|
|
29
28
|
const recording = () => {
|
|
30
29
|
const row = store.rows(LIVE).find((r) => r.id === stream);
|
|
31
30
|
return recordingOf(row, segmentsOf(store, stream));
|
|
@@ -116,7 +115,12 @@ export function liveIngest(options) {
|
|
|
116
115
|
app: ask.app, key: ask.key, state: 'publishing', startedAt: typeof prior?.startedAt === 'string' ? prior.startedAt : now, endedAt: null,
|
|
117
116
|
publishes: Number(prior?.publishes ?? 0) + 1, window: ruling.window ?? 6, segmentSeconds: ruling.segmentSeconds ?? 2, ...(ruling.retain !== undefined ? { retain: ruling.retain } : {}),
|
|
118
117
|
}, 'live.publish'));
|
|
118
|
+
// Every admitted publish has its own encoder and hook state. Continuation comes from the recording above,
|
|
119
|
+
// including on a connection reused after FCUnpublish, never from the prior publish's in-memory state.
|
|
120
|
+
initIndex = -1;
|
|
119
121
|
priorInit = last?.init;
|
|
122
|
+
started = false;
|
|
123
|
+
metadata = undefined;
|
|
120
124
|
segmenter = new Segmenter({ targetMs: Math.max(0.5, ruling.segmentSeconds ?? 2) * 1000, offsetMs, firstSequence: last ? last.seq + 1 : 0 });
|
|
121
125
|
stream = named;
|
|
122
126
|
admitted = ruling;
|
|
@@ -137,12 +141,8 @@ export function liveIngest(options) {
|
|
|
137
141
|
if (!segmenter)
|
|
138
142
|
return;
|
|
139
143
|
const tag = readVideoTag(payload);
|
|
140
|
-
if (tag.kind === 'unsupported')
|
|
141
|
-
|
|
142
|
-
log(`${stream}: ${tag.codec} video is not recorded (H.264 is)`);
|
|
143
|
-
refusedCodec = tag.codec;
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
144
|
+
if (tag.kind === 'unsupported')
|
|
145
|
+
throw new RtmpRefusal('NetStream.Failed', `${tag.codec} video is not supported; publish H.264.`);
|
|
146
146
|
const s = segmenter;
|
|
147
147
|
if (tag.kind === 'config')
|
|
148
148
|
await keepAll(s.videoConfig(tag.config));
|
|
@@ -153,12 +153,8 @@ export function liveIngest(options) {
|
|
|
153
153
|
if (!segmenter)
|
|
154
154
|
return;
|
|
155
155
|
const tag = readAudioTag(payload);
|
|
156
|
-
if (tag.kind === 'unsupported')
|
|
157
|
-
|
|
158
|
-
log(`${stream}: ${tag.codec} audio is not recorded (AAC is)`);
|
|
159
|
-
refusedCodec = tag.codec;
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
156
|
+
if (tag.kind === 'unsupported')
|
|
157
|
+
throw new RtmpRefusal('NetStream.Failed', `${tag.codec} audio is not supported; publish AAC.`);
|
|
162
158
|
const s = segmenter;
|
|
163
159
|
if (tag.kind === 'config')
|
|
164
160
|
await keepAll(s.audioConfig(tag.config));
|
package/dist/src/live/rtmp.js
CHANGED
|
@@ -98,6 +98,9 @@ export function rtmpSession(sink, publisher, log = () => { }) {
|
|
|
98
98
|
chunks.clear();
|
|
99
99
|
current = undefined;
|
|
100
100
|
held = 0;
|
|
101
|
+
// A refused/ended transport owns the publish's cleanup too. An admission already awaiting the dialect settles
|
|
102
|
+
// first; messages queued behind the refusal cannot admit a new publish on the closed connection.
|
|
103
|
+
chain = chain.then(() => stop()).catch((error) => log(`the publish's end failed: ${String(error?.message ?? error)}`));
|
|
101
104
|
try {
|
|
102
105
|
sink.end();
|
|
103
106
|
}
|
|
@@ -165,6 +168,7 @@ export function rtmpSession(sink, publisher, log = () => { }) {
|
|
|
165
168
|
close();
|
|
166
169
|
return;
|
|
167
170
|
}
|
|
171
|
+
ended = false;
|
|
168
172
|
publishing = true;
|
|
169
173
|
publishStream = stream;
|
|
170
174
|
// user control StreamBegin (event 0) for the publish's stream, then its start
|
|
@@ -281,7 +285,7 @@ export function rtmpSession(sink, publisher, log = () => { }) {
|
|
|
281
285
|
overrun(`more than ${MAX_QUEUED} bytes of messages wait to be recorded`);
|
|
282
286
|
return;
|
|
283
287
|
}
|
|
284
|
-
chain = chain.then(() => (phase === 'closed'
|
|
288
|
+
chain = chain.then(() => (phase === 'closed' ? undefined : onMessage(message))).catch((error) => {
|
|
285
289
|
log(`a message failed: ${String(error?.message ?? error)}`);
|
|
286
290
|
close();
|
|
287
291
|
}).finally(() => { queued -= payload.length; });
|
|
@@ -447,10 +451,8 @@ export function rtmpSession(sink, publisher, log = () => { }) {
|
|
|
447
451
|
// message's bytes are not in it: only more bytes finish it, and what it may hold is bounded (MAX_HELD)
|
|
448
452
|
backlog: () => queued + pending.length,
|
|
449
453
|
close() {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
chain = chain.then(() => stop()).catch((error) => log(`the publish's end failed: ${String(error?.message ?? error)}`));
|
|
453
|
-
phase = 'closed';
|
|
454
|
+
// Peer close and server refusal share one end, after any admission or write already under way settles.
|
|
455
|
+
close();
|
|
454
456
|
},
|
|
455
457
|
};
|
|
456
458
|
}
|
|
@@ -39,14 +39,20 @@ export class Segmenter {
|
|
|
39
39
|
this.avc = config;
|
|
40
40
|
this.freshInit = true;
|
|
41
41
|
this.keyed = false;
|
|
42
|
+
this.unkeyedSince = undefined;
|
|
42
43
|
return out;
|
|
43
44
|
}
|
|
44
45
|
audioConfig(config) {
|
|
45
46
|
if (this.aac && same(this.aac.asc, config.asc))
|
|
46
47
|
return [];
|
|
47
|
-
const out = this.
|
|
48
|
+
const out = this.flush();
|
|
48
49
|
this.aac = config;
|
|
49
50
|
this.freshInit = true;
|
|
51
|
+
// A configuration boundary has no running video segment. Its first frame must be a new keyframe, and the
|
|
52
|
+
// audio sample clock belongs to the new configuration (which may have a different sample rate).
|
|
53
|
+
this.keyed = false;
|
|
54
|
+
this.unkeyedSince = undefined;
|
|
55
|
+
this.audioNext = undefined;
|
|
50
56
|
return out;
|
|
51
57
|
}
|
|
52
58
|
/** A video frame at its decode time: the segment it closes, if it closes one. */
|
package/dist/src/log.d.ts
CHANGED
|
@@ -102,7 +102,7 @@ export declare function originLogPath(service: string, root?: string): string;
|
|
|
102
102
|
/** A parent is at a URL when it is not a path on this machine. */
|
|
103
103
|
export declare function isUrlParent(at: string): boolean;
|
|
104
104
|
export declare function checkpointPath(service: string, root?: string): string;
|
|
105
|
-
export declare function readBranchMeta(service: string, root?: string): BranchMeta | null;
|
|
105
|
+
export declare function readBranchMeta(service: string, root?: string, read?: string | null): BranchMeta | null;
|
|
106
106
|
export declare function writeBranchMeta(service: string, meta: BranchMeta, root?: string): void;
|
|
107
107
|
/** A v1 observed row as an entry: a delta event folds its `changed.*.after`; anything else folds
|
|
108
108
|
* nothing and rides along for `listEvents`. An entry row is itself. */
|
|
@@ -130,13 +130,21 @@ export declare function landedIds(parent: Entry[]): Set<string>;
|
|
|
130
130
|
* before it had observed the vendor's own subject under that id (its tree was behind) must not fold
|
|
131
131
|
* the vendor's subject onto the landed one, nor send a caller who addresses the vendor's id there. */
|
|
132
132
|
export declare function aliasesFrom(parent: Entry[]): Map<string, string>;
|
|
133
|
-
type Subject = {
|
|
133
|
+
export type Subject = {
|
|
134
134
|
type: string;
|
|
135
135
|
id: string;
|
|
136
136
|
updatedAt: string;
|
|
137
137
|
fields: SubjectFields;
|
|
138
138
|
};
|
|
139
139
|
export type Tree = Map<string, Subject>;
|
|
140
|
+
/** One subject read by the same guarded key as a write's answer; restore the vendor's own metadata fields. */
|
|
141
|
+
export declare function heldSubject(service: string, type: string, id: string, root?: string): Subject | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* One local write folded onto its subject as the tree folds its entry (`overlay`): the subject as it was (undefined
|
|
144
|
+
* for one new to the tree) is copied, never changed. A command's writes staged in `ctx.batch` are read back by
|
|
145
|
+
* folding them this way, so a read inside the batch sees what the tree will hold once they land (derived-core.ts).
|
|
146
|
+
*/
|
|
147
|
+
export declare function foldLocalWrite(previous: Subject | undefined, type: string, id: string, fields: SubjectFields, at: string): Subject;
|
|
140
148
|
/** Fold entries onto a tree, in order: `set` overlays its fields and applies its projection; a
|
|
141
149
|
* reverted entry folds nothing; a `confirm` row (v1) folds nothing. `skip` names entries the
|
|
142
150
|
* parent already holds. */
|
|
@@ -152,6 +160,15 @@ export declare function ownFields(r: TwinResource): Record<string, unknown>;
|
|
|
152
160
|
export declare function dropCheckpoint(service: string, root?: string): void;
|
|
153
161
|
/** Preserve each ancestor's observed-then-local overlay, including landed IDs and aliases. */
|
|
154
162
|
export declare function foldHistory(entries: Entry[], layout: HistoryLayout, service?: string): Tree;
|
|
163
|
+
/**
|
|
164
|
+
* One subject of the current tree as a resource, by its type and id: the resource `projectResources(...).find` gives
|
|
165
|
+
* for the same type and id, read by the subject's key without cloning or projecting the rest of the tree. Every subject is
|
|
166
|
+
* held under `${type}:${id}` (overlay is the only setter, and an alias re-keys through it), so the key is the
|
|
167
|
+
* subject. A write's answer read back the whole tree this way (serve.ts, applyTwinWrite's `resource`), once per
|
|
168
|
+
* written subject: a derived pack writing each document, index and collection of a database's startup paid a
|
|
169
|
+
* projection of every subject the World holds for each (S2 R17).
|
|
170
|
+
*/
|
|
171
|
+
export declare function readSubject(service: string, type: string, id: string, root?: string): TwinResource | undefined;
|
|
155
172
|
export declare function readTree(service: string, root?: string, opts?: {
|
|
156
173
|
until?: string;
|
|
157
174
|
at?: number;
|
|
@@ -171,6 +188,10 @@ export declare function readTreeMap(service: string, root?: string, opts?: {
|
|
|
171
188
|
at?: number;
|
|
172
189
|
view?: string;
|
|
173
190
|
}): Tree;
|
|
191
|
+
/** Append `data` (whole JSONL lines of a service's branch log, `append` writing them) and, when the reads' memo held
|
|
192
|
+
* the tree as it stood, extend it by them. Called holding the directory's history lock. Answers the log's facts
|
|
193
|
+
* after the append (`size:mtimeMs`) when it read them, for a caller keyed on the same. */
|
|
194
|
+
export declare function appendToBranchLog(service: string, root: string | undefined, data: string, append: () => void): string | undefined;
|
|
174
195
|
/** What changed in a service's tree since `since` (a `treeStamp` an earlier read was made at): the subjects
|
|
175
196
|
* written since, as resources; the keys (`type:id`) of those no longer in the tree; and, of the changed, the keys
|
|
176
197
|
* now at the tree's end in the order they stand there (new to the tree, or deleted and made again), which a
|
|
@@ -257,4 +278,3 @@ export declare function subjectHistory(service: string, subject: {
|
|
|
257
278
|
type: string;
|
|
258
279
|
id?: string;
|
|
259
280
|
}, root?: string): Entry[];
|
|
260
|
-
export {};
|
package/dist/src/log.js
CHANGED
|
@@ -29,14 +29,14 @@ import { canonicalStatePath, checkParent, commitParentPin, pinParent, withAncest
|
|
|
29
29
|
import { resolveReferences } from "./references.js";
|
|
30
30
|
import { hashFieldValue } from "./hash.js";
|
|
31
31
|
import { worldPaths } from "./storage.js";
|
|
32
|
-
import { getActiveWorldStore } from "./world-store.js";
|
|
32
|
+
import { getActiveWorldStore, TREE_FILES } from "./world-store.js";
|
|
33
33
|
export const CHECKPOINT_EVERY = 100;
|
|
34
34
|
// Retained checkpoints and process memos are disposable projections, never history.
|
|
35
35
|
const TREE_PROJECTION_VERSION = 2;
|
|
36
36
|
export const DELTA_SUFFIX = '.delta';
|
|
37
37
|
export function parentLogPath(service, root) { return worldPaths(service, root).events; }
|
|
38
|
-
export function branchLogPath(service, root) { return join(dirname(worldPaths(service, root).events),
|
|
39
|
-
export function branchMetaPath(service, root) { return join(dirname(worldPaths(service, root).events),
|
|
38
|
+
export function branchLogPath(service, root) { return join(dirname(worldPaths(service, root).events), TREE_FILES.branch); }
|
|
39
|
+
export function branchMetaPath(service, root) { return join(dirname(worldPaths(service, root).events), TREE_FILES.pointer); }
|
|
40
40
|
/** The fetched cache of a URL parent's whole log — what `fetch` extends and a read folds up to the position. */
|
|
41
41
|
export function originLogPath(service, root) { return join(dirname(worldPaths(service, root).events), 'origin.jsonl'); }
|
|
42
42
|
/** A parent is at a URL when it is not a path on this machine. */
|
|
@@ -114,8 +114,9 @@ function readRows(path) {
|
|
|
114
114
|
cache.set(path, tail !== undefined ? { rows, lines: lines.length - 1, bytes: Buffer.byteLength(content), tail } : { content, rows, lines: lines.length - 1 });
|
|
115
115
|
return rows.slice();
|
|
116
116
|
}
|
|
117
|
-
export function readBranchMeta(service, root) {
|
|
118
|
-
|
|
117
|
+
export function readBranchMeta(service, root, read) {
|
|
118
|
+
// `read`: branch.json's text where the caller has just read it (a tree's stamp reads it once for both uses).
|
|
119
|
+
const raw = read !== undefined ? read : getActiveWorldStore().read(branchMetaPath(service, root));
|
|
119
120
|
if (raw === null)
|
|
120
121
|
return null;
|
|
121
122
|
const meta = parseExactJson(raw);
|
|
@@ -179,8 +180,8 @@ export function writeBranchMeta(service, meta, root) {
|
|
|
179
180
|
getActiveWorldStore().writeAtomic(path, `${JSON.stringify(meta, null, 2)}\n`);
|
|
180
181
|
if (meta.parent?.directory && !isUrlParent(meta.parent.at))
|
|
181
182
|
commitParentPin(meta.parent.directory, path);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
183
|
+
}, { operation: 'pointer publication' });
|
|
184
|
+
}, { operation: 'pointer publication' });
|
|
184
185
|
}
|
|
185
186
|
/** A v1 observed row as an entry: a delta event folds its `changed.*.after`; anything else folds
|
|
186
187
|
* nothing and rides along for `listEvents`. An entry row is itself. */
|
|
@@ -251,7 +252,7 @@ export function appendOriginEntry(entry, root) {
|
|
|
251
252
|
return { appended: false };
|
|
252
253
|
store.append(path, `${JSON.stringify(entry)}\n`);
|
|
253
254
|
return { appended: true };
|
|
254
|
-
}));
|
|
255
|
+
}, { operation: 'origin append' }), { operation: 'origin append' });
|
|
255
256
|
}
|
|
256
257
|
/** Where the parent stands now: the length of its whole log (live at a path, the cache for a URL). */
|
|
257
258
|
export function parentPosition(service, root) {
|
|
@@ -300,6 +301,22 @@ function overlay(tree, type, id, fields, at) {
|
|
|
300
301
|
delete merged.deleted;
|
|
301
302
|
tree.set(key, { ...existing, updatedAt: at, fields: merged });
|
|
302
303
|
}
|
|
304
|
+
/** One subject read by the same guarded key as a write's answer; restore the vendor's own metadata fields. */
|
|
305
|
+
export function heldSubject(service, type, id, root) {
|
|
306
|
+
const row = readSubject(service, type, id, root);
|
|
307
|
+
return row ? { type: row.type, id: row.id, updatedAt: row.updatedAt, fields: ownFields(row) } : undefined;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* One local write folded onto its subject as the tree folds its entry (`overlay`): the subject as it was (undefined
|
|
311
|
+
* for one new to the tree) is copied, never changed. A command's writes staged in `ctx.batch` are read back by
|
|
312
|
+
* folding them this way, so a read inside the batch sees what the tree will hold once they land (derived-core.ts).
|
|
313
|
+
*/
|
|
314
|
+
export function foldLocalWrite(previous, type, id, fields, at) {
|
|
315
|
+
const key = `${type}:${id}`;
|
|
316
|
+
const tree = previous ? new Map([[key, { ...previous, fields: { ...previous.fields } }]]) : new Map();
|
|
317
|
+
overlay(tree, type, id, fields, at);
|
|
318
|
+
return tree.get(key);
|
|
319
|
+
}
|
|
303
320
|
/** Fold entries onto a tree, in order: `set` overlays its fields and applies its projection; a
|
|
304
321
|
* reverted entry folds nothing; a `confirm` row (v1) folds nothing. `skip` names entries the
|
|
305
322
|
* parent already holds. */
|
|
@@ -402,6 +419,22 @@ export function foldHistory(entries, layout, service) {
|
|
|
402
419
|
foldAliases(tree, aliasesFrom(parent), service);
|
|
403
420
|
return tree;
|
|
404
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* One subject of the current tree as a resource, by its type and id: the resource `projectResources(...).find` gives
|
|
424
|
+
* for the same type and id, read by the subject's key without cloning or projecting the rest of the tree. Every subject is
|
|
425
|
+
* held under `${type}:${id}` (overlay is the only setter, and an alias re-keys through it), so the key is the
|
|
426
|
+
* subject. A write's answer read back the whole tree this way (serve.ts, applyTwinWrite's `resource`), once per
|
|
427
|
+
* written subject: a derived pack writing each document, index and collection of a database's startup paid a
|
|
428
|
+
* projection of every subject the World holds for each (S2 R17).
|
|
429
|
+
*/
|
|
430
|
+
export function readSubject(service, type, id, root) {
|
|
431
|
+
const key = `${type}:${id}`;
|
|
432
|
+
const held = currentTree(service, root).tree.get(key);
|
|
433
|
+
// a type or id holding ':' can share a key with another (type, id): only the subject itself is it
|
|
434
|
+
if (!held || held.type !== type || held.id !== id)
|
|
435
|
+
return undefined;
|
|
436
|
+
return treeResources(new Map([[key, { ...held, fields: { ...held.fields } }]]))[0];
|
|
437
|
+
}
|
|
405
438
|
export function readTree(service, root, opts = {}) {
|
|
406
439
|
return treeResources(readTreeMap(service, root, opts));
|
|
407
440
|
}
|
|
@@ -414,7 +447,7 @@ export function readParentTreeMap(service, root) {
|
|
|
414
447
|
return foldHistory([...(inherited ? historyEntries(inherited) : []), ...own], {
|
|
415
448
|
...(inherited ? { base: inherited.layout } : {}), parent: own.length, branch: 0,
|
|
416
449
|
}, service);
|
|
417
|
-
});
|
|
450
|
+
}, { operation: 'tree fold' });
|
|
418
451
|
}
|
|
419
452
|
const treeMemos = new WeakMap();
|
|
420
453
|
/** How many extensions a memo remembers the touched subjects of; a projection further behind refolds. */
|
|
@@ -422,11 +455,44 @@ const TREE_STEPS_KEPT = 64;
|
|
|
422
455
|
function statKey(path) { const s = getActiveWorldStore().stat(path); return s ? `${s.size}:${s.mtimeMs}` : '-'; }
|
|
423
456
|
/** What a service's PARENT side at `root` is folded from (its parent log, branch.json and inherited history), as one
|
|
424
457
|
* string: while it stands, its aliases and landed ids are the same, whatever the branch's own log does. */
|
|
425
|
-
export function parentStamp(service, root) { return
|
|
426
|
-
function parentKeyOf(service, root) { return
|
|
458
|
+
export function parentStamp(service, root) { return treeFacts(service, root).parentKey; }
|
|
459
|
+
function parentKeyOf(service, root) { return parentFacts(service, root).key; }
|
|
460
|
+
/** The parent side's key, and whether it names a file outside the tree's own directory (an inherited view). */
|
|
461
|
+
function parentFacts(service, root) {
|
|
462
|
+
// branch.json is small and every stamp needs it: kept while its own facts stand (world-store.ts readKept), so an
|
|
463
|
+
// unchanged pointer costs one check. Its text is still in the key, so a changed pointer still moves the stamp.
|
|
464
|
+
const store = getActiveWorldStore(), pointer = branchMetaPath(service, root);
|
|
465
|
+
const meta = store.readKept ? store.readKept(pointer) : store.read(pointer);
|
|
466
|
+
const inherited = inheritedHistoryKey(service, root, meta);
|
|
467
|
+
return { key: `${TREE_PROJECTION_VERSION}|${statKey(parentLogPath(service, root))}|${meta ?? ''}|${inherited}`, inherits: inherited !== '' };
|
|
468
|
+
}
|
|
469
|
+
const keptFacts = new WeakMap();
|
|
470
|
+
function treeFacts(service, root) {
|
|
471
|
+
const store = getActiveWorldStore();
|
|
472
|
+
const branchLog = branchLogPath(service, root);
|
|
473
|
+
if (!store.generation)
|
|
474
|
+
return { parentKey: parentKeyOf(service, root), branchKey: statKey(branchLog) };
|
|
475
|
+
const directory = dirname(branchLog);
|
|
476
|
+
const generation = store.generation(directory);
|
|
477
|
+
let kept = keptFacts.get(store);
|
|
478
|
+
if (!kept) {
|
|
479
|
+
kept = new Map();
|
|
480
|
+
keptFacts.set(store, kept);
|
|
481
|
+
}
|
|
482
|
+
const held = kept.get(directory);
|
|
483
|
+
if (generation !== null && held?.generation === generation)
|
|
484
|
+
return held;
|
|
485
|
+
const parent = parentFacts(service, root);
|
|
486
|
+
const facts = { parentKey: parent.key, branchKey: statKey(branchLog) };
|
|
487
|
+
if (generation !== null && !parent.inherits)
|
|
488
|
+
kept.set(directory, { ...facts, generation });
|
|
489
|
+
else
|
|
490
|
+
kept.delete(directory);
|
|
491
|
+
return facts;
|
|
492
|
+
}
|
|
427
493
|
/** What a service's tree (and every subject's history) at `root` is folded from, as one string: while it
|
|
428
494
|
* stands, nothing was written. A pack that projects the tree into its own state memoizes on it. */
|
|
429
|
-
export function treeStamp(service, root) {
|
|
495
|
+
export function treeStamp(service, root) { const facts = treeFacts(service, root); return `${facts.parentKey}#${facts.branchKey}`; }
|
|
430
496
|
const cloneTree = (tree) => new Map([...tree].map(([k, s]) => [k, { ...s, fields: { ...s.fields } }]));
|
|
431
497
|
const memoStamp = (m) => `${m.parentKey}#${m.branchKey}`;
|
|
432
498
|
/** The subject keys entries write (as foldEntries folds them), and the vendor rows an alias moves a local one onto. */
|
|
@@ -484,8 +550,11 @@ function currentTree(service, root) {
|
|
|
484
550
|
// A hit needs no lock: a memo is only ever stored from a fold made under the history lock, and while the
|
|
485
551
|
// facts still match that fold, nothing was written since. A writer mid-batch has moved them.
|
|
486
552
|
const unlocked = memos.get(slot);
|
|
487
|
-
if (unlocked
|
|
488
|
-
|
|
553
|
+
if (unlocked) {
|
|
554
|
+
const facts = treeFacts(service, root);
|
|
555
|
+
if (unlocked.parentKey === facts.parentKey && unlocked.branchKey === facts.branchKey)
|
|
556
|
+
return { tree: unlocked.tree, memo: unlocked };
|
|
557
|
+
}
|
|
489
558
|
return withHistoryLock(worldPaths(service, root).dir, () => {
|
|
490
559
|
const parentKey = parentKeyOf(service, root);
|
|
491
560
|
const branchKey = statKey(branchLogPath(service, root));
|
|
@@ -497,24 +566,7 @@ function currentTree(service, root) {
|
|
|
497
566
|
if (held && held.parentKey === parentKey && branch.length >= held.branchCount
|
|
498
567
|
&& (held.branchCount === 0 ? held.lastBranchId === null : branch[held.branchCount - 1]?.id === held.lastBranchId)
|
|
499
568
|
&& !branch.slice(held.branchCount).some((e) => e.op === 'revert')) {
|
|
500
|
-
|
|
501
|
-
const keys = touchedKeys(fresh, held.aliases);
|
|
502
|
-
const from = memoStamp(held);
|
|
503
|
-
let appended;
|
|
504
|
-
try {
|
|
505
|
-
appended = insertionsDuring(held.tree, () => {
|
|
506
|
-
foldEntries(held.tree, fresh, held.landed);
|
|
507
|
-
foldAliases(held.tree, held.aliases, service, keys);
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
catch (error) {
|
|
511
|
-
memos.delete(slot);
|
|
512
|
-
throw error;
|
|
513
|
-
} // a half-folded memo is never read again
|
|
514
|
-
Object.assign(held, { branchKey, branchCount: branch.length, lastBranchId: last(branch) });
|
|
515
|
-
held.steps.push({ from, to: memoStamp(held), keys, appended });
|
|
516
|
-
if (held.steps.length > TREE_STEPS_KEPT)
|
|
517
|
-
held.steps.splice(0, held.steps.length - TREE_STEPS_KEPT);
|
|
569
|
+
extendMemo(memos, slot, held, branch.slice(held.branchCount), branchKey, service);
|
|
518
570
|
return { tree: held.tree, memo: held };
|
|
519
571
|
}
|
|
520
572
|
const tree = readTreeMapFull(service, root);
|
|
@@ -526,7 +578,78 @@ function currentTree(service, root) {
|
|
|
526
578
|
return { tree, memo };
|
|
527
579
|
}
|
|
528
580
|
return { tree };
|
|
529
|
-
});
|
|
581
|
+
}, { operation: 'tree fold' });
|
|
582
|
+
}
|
|
583
|
+
/** The memo extended in place by `fresh`, the branch log's entries after its own, which leave the log at `branchKey`,
|
|
584
|
+
* with the step that records the subjects they touched. */
|
|
585
|
+
function extendMemo(memos, slot, held, fresh, branchKey, service) {
|
|
586
|
+
const keys = touchedKeys(fresh, held.aliases);
|
|
587
|
+
const from = memoStamp(held);
|
|
588
|
+
let appended;
|
|
589
|
+
try {
|
|
590
|
+
appended = insertionsDuring(held.tree, () => {
|
|
591
|
+
foldEntries(held.tree, fresh, held.landed);
|
|
592
|
+
foldAliases(held.tree, held.aliases, service, keys);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
memos.delete(slot);
|
|
597
|
+
throw error;
|
|
598
|
+
} // a half-folded memo is never read again
|
|
599
|
+
Object.assign(held, { branchKey, branchCount: held.branchCount + fresh.length, lastBranchId: fresh.length ? fresh[fresh.length - 1].id : held.lastBranchId });
|
|
600
|
+
held.steps.push({ from, to: memoStamp(held), keys, appended });
|
|
601
|
+
if (held.steps.length > TREE_STEPS_KEPT)
|
|
602
|
+
held.steps.splice(0, held.steps.length - TREE_STEPS_KEPT);
|
|
603
|
+
}
|
|
604
|
+
// AN OWN APPEND EXTENDS THE READS' MEMO (S2 R17c). A write appended its entries and the next read found the facts
|
|
605
|
+
// moved, so it took the history lock, read the facts again and the log's new bytes, and extended the memo with the
|
|
606
|
+
// entries this process had just written: in a tab, where each of those is a call into the kernel, about twenty calls
|
|
607
|
+
// per command that writes, taken again by the command after it. The writer, still holding the history lock, extends
|
|
608
|
+
// the memo with its own entries and keeps the tree's facts under the generation its own write left, so the next read
|
|
609
|
+
// asks one question. It does so only where the store can say its write was the one write since the generation it
|
|
610
|
+
// read just before (`generationAfterOwnWrite`), the facts were kept under that generation and the memo stood at them,
|
|
611
|
+
// the parent side's facts read after the append are still the kept ones, and the log grew by exactly its bytes;
|
|
612
|
+
// otherwise it extends nothing, and the next read checks as before.
|
|
613
|
+
/** Append `data` (whole JSONL lines of a service's branch log, `append` writing them) and, when the reads' memo held
|
|
614
|
+
* the tree as it stood, extend it by them. Called holding the directory's history lock. Answers the log's facts
|
|
615
|
+
* after the append (`size:mtimeMs`) when it read them, for a caller keyed on the same. */
|
|
616
|
+
export function appendToBranchLog(service, root, data, append) {
|
|
617
|
+
const store = getActiveWorldStore();
|
|
618
|
+
const memos = treeMemos.get(store);
|
|
619
|
+
const slot = `${service}\u0000${root ?? ''}`;
|
|
620
|
+
const held = memos?.get(slot);
|
|
621
|
+
const branchLog = branchLogPath(service, root);
|
|
622
|
+
const directory = dirname(branchLog);
|
|
623
|
+
const kept = keptFacts.get(store)?.get(directory);
|
|
624
|
+
const standing = held !== undefined && kept !== undefined && kept.parentKey === held.parentKey && kept.branchKey === held.branchKey;
|
|
625
|
+
const before = standing && store.generation && store.generationAfterOwnWrite ? store.generation(directory) : null;
|
|
626
|
+
append();
|
|
627
|
+
if (!memos || !held || !kept || before === null || kept.generation !== before)
|
|
628
|
+
return undefined;
|
|
629
|
+
const after = store.generationAfterOwnWrite(directory, before);
|
|
630
|
+
if (after === null)
|
|
631
|
+
return undefined;
|
|
632
|
+
// read after the generation, as treeFacts reads its facts. The parent side is read again too: a writer the generation
|
|
633
|
+
// does not see (an older version's, or one that died before its second byte) may have moved it under the kept
|
|
634
|
+
// generation, and carried forward its old key would outlive every append this process makes (S2 R17c review).
|
|
635
|
+
const branchKey = statKey(branchLog);
|
|
636
|
+
if (parentKeyOf(service, root) !== kept.parentKey)
|
|
637
|
+
return branchKey;
|
|
638
|
+
const grown = (key) => (key === '-' ? 0 : Number(key.slice(0, key.indexOf(':'))));
|
|
639
|
+
const fresh = [];
|
|
640
|
+
// line numbers in a refusal count from this append's first line
|
|
641
|
+
parseLines(`${branchLog} (appended)`, data.split('\n'), 0, fresh);
|
|
642
|
+
if (grown(branchKey) !== grown(held.branchKey) + Buffer.byteLength(data) || fresh.some((e) => e.op === 'revert'))
|
|
643
|
+
return branchKey;
|
|
644
|
+
// the write has landed: a fold that fails here drops the memo (extendMemo) and the next read folds in full, never failing the write
|
|
645
|
+
try {
|
|
646
|
+
extendMemo(memos, slot, held, fresh, branchKey, service);
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
return branchKey;
|
|
650
|
+
}
|
|
651
|
+
keptFacts.get(store).set(directory, { parentKey: kept.parentKey, branchKey, generation: after });
|
|
652
|
+
return branchKey;
|
|
530
653
|
}
|
|
531
654
|
/** What changed in a service's tree since `since` (a `treeStamp` an earlier read was made at): the subjects
|
|
532
655
|
* written since, as resources; the keys (`type:id`) of those no longer in the tree; and, of the changed, the keys
|
|
@@ -637,7 +760,7 @@ function readTreeMapFull(service, root, opts = {}) {
|
|
|
637
760
|
getActiveWorldStore().writeAtomic(checkpointPath(service, root), JSON.stringify({ parentDigest, branchDigest, branchCount: branch.length, subjects: [...tree.values()], ...(now === facts ? { facts } : {}) }));
|
|
638
761
|
}
|
|
639
762
|
return tree;
|
|
640
|
-
});
|
|
763
|
+
}, { operation: 'tree fold' });
|
|
641
764
|
}
|
|
642
765
|
export function cutCheckpoint(service, root) {
|
|
643
766
|
return withHistoryLock(worldPaths(service, root).dir, () => {
|
|
@@ -646,7 +769,7 @@ export function cutCheckpoint(service, root) {
|
|
|
646
769
|
const inherited = inheritedHistory(service, root);
|
|
647
770
|
const own = ownParentEntries(service, root);
|
|
648
771
|
getActiveWorldStore().writeAtomic(checkpointPath(service, root), JSON.stringify({ parentDigest: hashFieldValue({ projectionVersion: TREE_PROJECTION_VERSION, inherited, own }), branchDigest: hashFieldValue(branchEntries(service, root)), branchCount: branchEntries(service, root).length, subjects }));
|
|
649
|
-
});
|
|
772
|
+
}, { operation: 'checkpoint' });
|
|
650
773
|
}
|
|
651
774
|
/** The branch's entries the parent does not hold: `set` rows, not reverted, not landed. */
|
|
652
775
|
export function unpushedEntries(service, root) {
|
|
@@ -745,7 +868,7 @@ export function appendParentEntry(entry, root) {
|
|
|
745
868
|
index.ids.add(entry.id);
|
|
746
869
|
index.key = statKey(path);
|
|
747
870
|
return { appended: true };
|
|
748
|
-
}));
|
|
871
|
+
}, { operation: 'parent append' }), { operation: 'parent append' });
|
|
749
872
|
}
|
|
750
873
|
/** The whole log of a twin as a served world hands it out: the parent view, then the branch's own
|
|
751
874
|
* entries, one sequence with a position. A clone's parent is exactly this. */
|
|
@@ -823,7 +946,7 @@ export function rebaseBranch(service, root, opts = {}) {
|
|
|
823
946
|
dropCheckpoint(service, root);
|
|
824
947
|
}
|
|
825
948
|
return { moved, from, to, conflicts };
|
|
826
|
-
});
|
|
949
|
+
}, { operation: 'branch-pointer adjustment' });
|
|
827
950
|
}
|
|
828
951
|
/**
|
|
829
952
|
* HISTORY through the kernel: every entry that touched a subject (or every subject of a type), parent
|
package/dist/src/machines.js
CHANGED
|
@@ -14,6 +14,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
14
14
|
// is reached only when a compute pool is asked to act. The light pool delegates to the host's Substrate adapter.
|
|
15
15
|
import { refuseReadOnlyWrite } from "./request-scope.js";
|
|
16
16
|
import { nodeBuiltin } from "./serve-http.js";
|
|
17
|
+
import { refuseRequiredMachineTransport } from '../required-credential-transport.cjs';
|
|
17
18
|
/** A declared compute capability gap, distinct from a failed admitted guest. */
|
|
18
19
|
export function isMachineCapabilityGap(error) {
|
|
19
20
|
return typeof error === 'object' && error !== null && 'code' in error && error.code === 'WORLD_MACHINE_CAPABILITY_GAP';
|
|
@@ -60,6 +61,7 @@ function dockerPool(world) {
|
|
|
60
61
|
});
|
|
61
62
|
return {
|
|
62
63
|
kind: 'docker',
|
|
64
|
+
async admitRun() { refuseRequiredMachineTransport(process.env, 'Docker machine application transport'); },
|
|
63
65
|
async status(name) {
|
|
64
66
|
const state = JSON.parse(await docker(['inspect', '--format', '{{json .State}}', containerName(world, name)]));
|
|
65
67
|
return state.Running ? { state: 'started' } : { state: 'exited', exitCode: state.ExitCode, ...(state.Error ? { reason: state.Error } : {}) };
|
|
@@ -67,6 +69,7 @@ function dockerPool(world) {
|
|
|
67
69
|
async ports() { throw new Error('port discovery requires the light guest backing'); },
|
|
68
70
|
async files() { throw new Error('file access requires the light guest backing'); },
|
|
69
71
|
async run(spec) {
|
|
72
|
+
refuseRequiredMachineTransport(process.env, 'Docker machine application transport');
|
|
70
73
|
const name = containerName(world, spec.name);
|
|
71
74
|
await docker(['rm', '-f', name]).catch(() => undefined);
|
|
72
75
|
const env = Object.entries(spec.env ?? {}).flatMap(([k, v]) => ['-e', `${k}=${v}`]);
|
|
@@ -80,6 +83,7 @@ function dockerPool(world) {
|
|
|
80
83
|
return { ports: published };
|
|
81
84
|
},
|
|
82
85
|
async exec(name, spec) {
|
|
86
|
+
refuseRequiredMachineTransport(process.env, 'Docker machine exec transport');
|
|
83
87
|
const { spawn } = nodeBuiltin('node:child_process');
|
|
84
88
|
const env = Object.entries(spec.env ?? {}).flatMap(([k, v]) => ['-e', `${k}=${v}`]);
|
|
85
89
|
// the shell names its own pid first, then becomes the process: its pid is the process's
|
|
@@ -165,7 +169,7 @@ function dockerPool(world) {
|
|
|
165
169
|
return fetch(`http://127.0.0.1:${host}${source.pathname}${source.search}`, { method: request.method, headers, body, redirect: 'manual', signal: request.signal });
|
|
166
170
|
},
|
|
167
171
|
async pause(name) { await docker(['pause', containerName(world, name)]); },
|
|
168
|
-
async resume(name) { await docker(['unpause', containerName(world, name)]); },
|
|
172
|
+
async resume(name) { refuseRequiredMachineTransport(process.env, 'Docker machine resume transport'); await docker(['unpause', containerName(world, name)]); },
|
|
169
173
|
async stop(name) { await docker(['stop', containerName(world, name)]).catch(() => undefined); },
|
|
170
174
|
async remove(name) { await docker(['rm', '-f', containerName(world, name)]).catch(() => undefined); },
|
|
171
175
|
};
|