mrplex 0.0.2 → 0.1.0
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 +98 -183
- package/dist/cli/main.js +45 -7
- package/dist/cli/main.js.map +1 -1
- package/dist/mcp/tools.js +2 -2
- package/dist/mcp/tools.js.map +1 -1
- package/dist/shell/guard.d.ts +2 -1
- package/dist/shell/guard.js +11 -4
- package/dist/shell/guard.js.map +1 -1
- package/dist/shell/policy.d.ts +25 -3
- package/dist/shell/policy.js +57 -2
- package/dist/shell/policy.js.map +1 -1
- package/dist/sync/converge.d.ts +27 -0
- package/dist/sync/converge.js +46 -0
- package/dist/sync/converge.js.map +1 -0
- package/dist/sync/daemon.d.ts +4 -0
- package/dist/sync/daemon.js +60 -7
- package/dist/sync/daemon.js.map +1 -1
- package/dist/sync/feed.d.ts +16 -0
- package/dist/sync/feed.js +81 -56
- package/dist/sync/feed.js.map +1 -1
- package/dist/sync/fs-store.js +10 -0
- package/dist/sync/fs-store.js.map +1 -1
- package/dist/sync/hot-path.d.ts +83 -0
- package/dist/sync/hot-path.js +232 -0
- package/dist/sync/hot-path.js.map +1 -0
- package/dist/sync/push.js +7 -31
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/reconcile.d.ts +7 -1
- package/dist/sync/reconcile.js +59 -55
- package/dist/sync/reconcile.js.map +1 -1
- package/package.json +1 -1
package/dist/shell/policy.js
CHANGED
|
@@ -72,6 +72,49 @@ export function loadPolicyFile(path) {
|
|
|
72
72
|
}
|
|
73
73
|
return parsePolicy(text);
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Render a minimal starter policy. Pure — no I/O. Always parses via
|
|
77
|
+
* `parsePolicy`. Two principals: `admin` (operator — create/delete repos) and
|
|
78
|
+
* the named maintainer (MCP day-to-day — no structural admin).
|
|
79
|
+
*/
|
|
80
|
+
export function scaffoldPolicyYaml(opts) {
|
|
81
|
+
const principal = opts.principal.trim();
|
|
82
|
+
const author = opts.author.trim();
|
|
83
|
+
if (!principal)
|
|
84
|
+
throw new PolicyError("scaffold principal id must be non-empty");
|
|
85
|
+
if (!author)
|
|
86
|
+
throw new PolicyError("scaffold author must be non-empty");
|
|
87
|
+
// Keep the template free of quoting footguns: principal ids that aren't
|
|
88
|
+
// plain YAML barewords would need escaping in the rendered file.
|
|
89
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(principal)) {
|
|
90
|
+
throw new PolicyError(`scaffold principal id "${principal}" must start with a letter and contain only letters, digits, _ and -`);
|
|
91
|
+
}
|
|
92
|
+
if (principal === "admin") {
|
|
93
|
+
throw new PolicyError('scaffold principal id cannot be "admin" (reserved for the operator principal)');
|
|
94
|
+
}
|
|
95
|
+
// JSON string literals are valid YAML double-quoted scalars — handles spaces
|
|
96
|
+
// and angle brackets in "Name <email>" authors without a hand-rolled escape.
|
|
97
|
+
const authorYaml = JSON.stringify(author);
|
|
98
|
+
return `roles:
|
|
99
|
+
maintainer:
|
|
100
|
+
grants: [{ repo: "*", read: "**", write: "**" }]
|
|
101
|
+
maintain: true
|
|
102
|
+
operator:
|
|
103
|
+
grants: [{ repo: "*", read: "**", write: "**" }]
|
|
104
|
+
destructive: true
|
|
105
|
+
|
|
106
|
+
# author: prefer "Full Name <email@domain>" (git-style) — stamped on writes
|
|
107
|
+
principals:
|
|
108
|
+
admin:
|
|
109
|
+
author: ${authorYaml}
|
|
110
|
+
roles: [operator]
|
|
111
|
+
keys: []
|
|
112
|
+
${principal}:
|
|
113
|
+
author: ${authorYaml}
|
|
114
|
+
roles: [maintainer]
|
|
115
|
+
keys: []
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
75
118
|
function validateGrant(g, where) {
|
|
76
119
|
if (!isObject(g))
|
|
77
120
|
throw new PolicyError(`${where} must be a mapping`);
|
|
@@ -106,6 +149,9 @@ function validateRoles(raw) {
|
|
|
106
149
|
}
|
|
107
150
|
const grantsRaw = (value.grants ?? []);
|
|
108
151
|
const grants = grantsRaw.map((g, i) => validateGrant(g, `${where}.grants[${i}]`));
|
|
152
|
+
if (value.maintain !== undefined && typeof value.maintain !== "boolean") {
|
|
153
|
+
throw new PolicyError(`${where}.maintain must be a boolean`);
|
|
154
|
+
}
|
|
109
155
|
if (value.destructive !== undefined && typeof value.destructive !== "boolean") {
|
|
110
156
|
throw new PolicyError(`${where}.destructive must be a boolean`);
|
|
111
157
|
}
|
|
@@ -113,6 +159,8 @@ function validateRoles(raw) {
|
|
|
113
159
|
throw new PolicyError(`${where}.impersonate must be a boolean`);
|
|
114
160
|
}
|
|
115
161
|
const role = { grants };
|
|
162
|
+
if (value.maintain !== undefined)
|
|
163
|
+
role.maintain = value.maintain;
|
|
116
164
|
if (value.destructive !== undefined)
|
|
117
165
|
role.destructive = value.destructive;
|
|
118
166
|
if (value.impersonate !== undefined)
|
|
@@ -190,7 +238,9 @@ function validatePrincipals(raw, roles) {
|
|
|
190
238
|
* Resolve a principal to its `Entitlement` — the union of its roles' grants and
|
|
191
239
|
* op booleans. Pure: no I/O, no clock, no globals. A principal's read/write
|
|
192
240
|
* claim lists are the concatenation of every role's grants (union semantics,
|
|
193
|
-
* §8.2); `destructive`/`impersonate` are OR'd across roles.
|
|
241
|
+
* §8.2); `maintain`/`destructive`/`impersonate` are OR'd across roles.
|
|
242
|
+
* `destructive` always implies `maintain` (structural admin may also press
|
|
243
|
+
* the maintenance buttons).
|
|
194
244
|
*
|
|
195
245
|
* `author` is `principal.author` when set. For an OIDC principal with no static
|
|
196
246
|
* author, the front derives `name <email>` from claims and passes it as
|
|
@@ -204,6 +254,7 @@ export function compile(policy, principalId, derivedAuthor) {
|
|
|
204
254
|
}
|
|
205
255
|
const read = [];
|
|
206
256
|
const write = [];
|
|
257
|
+
let maintain = false;
|
|
207
258
|
let destructive = false;
|
|
208
259
|
let impersonate = false;
|
|
209
260
|
for (const roleName of principal.roles) {
|
|
@@ -212,6 +263,8 @@ export function compile(policy, principalId, derivedAuthor) {
|
|
|
212
263
|
// hand-built Policy object can't slip an unknown role past compile.
|
|
213
264
|
if (!role)
|
|
214
265
|
throw new PolicyError(`principal "${principalId}" references unknown role "${roleName}"`);
|
|
266
|
+
if (role.maintain)
|
|
267
|
+
maintain = true;
|
|
215
268
|
if (role.destructive)
|
|
216
269
|
destructive = true;
|
|
217
270
|
if (role.impersonate)
|
|
@@ -223,10 +276,12 @@ export function compile(policy, principalId, derivedAuthor) {
|
|
|
223
276
|
write.push({ repo: grant.repo, paths: grant.write });
|
|
224
277
|
}
|
|
225
278
|
}
|
|
279
|
+
if (destructive)
|
|
280
|
+
maintain = true;
|
|
226
281
|
const author = principal.author ?? derivedAuthor;
|
|
227
282
|
if (author === undefined) {
|
|
228
283
|
throw new PolicyError(`principal "${principalId}" has no static author and none was derived (OIDC login supplies it)`);
|
|
229
284
|
}
|
|
230
|
-
return { author, read, write, destructive, impersonate };
|
|
285
|
+
return { author, read, write, maintain, destructive, impersonate };
|
|
231
286
|
}
|
|
232
287
|
//# sourceMappingURL=policy.js.map
|
package/dist/shell/policy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/shell/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/shell/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared disk↔kernel write helpers used by push, feed, and reconcile so
|
|
3
|
+
* inbound rebase and outbound ack stay byte-identical (better-sync.plan).
|
|
4
|
+
*/
|
|
5
|
+
import type { KernelClient } from "../client/kernel-client.js";
|
|
6
|
+
import type { Version } from "../kernel/wire.js";
|
|
7
|
+
import type { FileStore } from "./reconcile.js";
|
|
8
|
+
export type UserContent = {
|
|
9
|
+
frontmatter_raw: string;
|
|
10
|
+
body: string;
|
|
11
|
+
};
|
|
12
|
+
/** Split a file into stored-shape fields, dropping all `$*` intrinsic lines. */
|
|
13
|
+
export declare function stripToUserContent(text: string): UserContent;
|
|
14
|
+
/**
|
|
15
|
+
* After a successful kernel write, restamp the local file. If the editor saved
|
|
16
|
+
* again during the round-trip, keep those bytes and only update provenance —
|
|
17
|
+
* never write the snapshot we just pushed over newer typing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ackLocalWrite(store: FileStore, path: string, v: Version, pushed: UserContent): Promise<void>;
|
|
20
|
+
/** Optimistic put of local user bytes, then ack provenance on disk. */
|
|
21
|
+
export declare function putAndAck(client: KernelClient, store: FileStore, repo: string, path: string, prevVersionId: string, user: UserContent): Promise<Version>;
|
|
22
|
+
/** Park remote as `<name>-<version_id>.md` with `$sync: ignore` (§4.8). */
|
|
23
|
+
export declare function parkIgnoredSibling(store: FileStore, path: string, version: Version): Promise<void>;
|
|
24
|
+
/** Write remote version bytes at the canonical path (fast-forward / materialize). */
|
|
25
|
+
export declare function materializeAt(store: FileStore, path: string, version: Version, opts?: {
|
|
26
|
+
preserveMtime?: boolean;
|
|
27
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared disk↔kernel write helpers used by push, feed, and reconcile so
|
|
3
|
+
* inbound rebase and outbound ack stay byte-identical (better-sync.plan).
|
|
4
|
+
*/
|
|
5
|
+
import { withVersionSuffix } from "../kernel/deletion.js";
|
|
6
|
+
import { extractSystemProperties, split } from "../markdown/frontmatter.js";
|
|
7
|
+
import { renderIgnoredSibling, renderMaterialized, stampProvenance } from "./intrinsics.js";
|
|
8
|
+
/** Split a file into stored-shape fields, dropping all `$*` intrinsic lines. */
|
|
9
|
+
export function stripToUserContent(text) {
|
|
10
|
+
const lf = text.replace(/\r\n/g, "\n");
|
|
11
|
+
const { frontmatter_raw, body } = split(lf);
|
|
12
|
+
return { frontmatter_raw: extractSystemProperties(frontmatter_raw).raw, body };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* After a successful kernel write, restamp the local file. If the editor saved
|
|
16
|
+
* again during the round-trip, keep those bytes and only update provenance —
|
|
17
|
+
* never write the snapshot we just pushed over newer typing.
|
|
18
|
+
*/
|
|
19
|
+
export async function ackLocalWrite(store, path, v, pushed) {
|
|
20
|
+
const now = await store.read(path);
|
|
21
|
+
if (now === null)
|
|
22
|
+
return;
|
|
23
|
+
const current = stripToUserContent(now);
|
|
24
|
+
if (current.body === pushed.body && current.frontmatter_raw === pushed.frontmatter_raw) {
|
|
25
|
+
await store.write(path, renderMaterialized(v), { preserveMtime: true });
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
await store.write(path, stampProvenance(now, v.version_id, v.content_hash), {
|
|
29
|
+
preserveMtime: true,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Optimistic put of local user bytes, then ack provenance on disk. */
|
|
33
|
+
export async function putAndAck(client, store, repo, path, prevVersionId, user) {
|
|
34
|
+
const v = await client.docs.put(repo, prevVersionId, path, user);
|
|
35
|
+
await ackLocalWrite(store, path, v, user);
|
|
36
|
+
return v;
|
|
37
|
+
}
|
|
38
|
+
/** Park remote as `<name>-<version_id>.md` with `$sync: ignore` (§4.8). */
|
|
39
|
+
export async function parkIgnoredSibling(store, path, version) {
|
|
40
|
+
await store.write(withVersionSuffix(path, version.version_id), renderIgnoredSibling(version));
|
|
41
|
+
}
|
|
42
|
+
/** Write remote version bytes at the canonical path (fast-forward / materialize). */
|
|
43
|
+
export async function materializeAt(store, path, version, opts) {
|
|
44
|
+
await store.write(path, renderMaterialized(version), opts);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=converge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"converge.js","sourceRoot":"","sources":["../../src/sync/converge.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAK5F,gFAAgF;AAChF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACvC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,EAAE,eAAe,EAAE,uBAAuB,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;AACjF,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAgB,EAChB,IAAY,EACZ,CAAU,EACV,MAAmB;IAEnB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO;IACzB,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,eAAe,KAAK,MAAM,CAAC,eAAe,EAAE,CAAC;QACvF,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACxE,OAAO;IACT,CAAC;IACD,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,GAAG,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE;QAC1E,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;AACL,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAoB,EACpB,KAAgB,EAChB,IAAY,EACZ,IAAY,EACZ,aAAqB,EACrB,IAAiB;IAEjB,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACjE,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1C,OAAO,CAAC,CAAC;AACX,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAgB,EAChB,IAAY,EACZ,OAAgB;IAEhB,MAAM,KAAK,CAAC,KAAK,CAAC,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAC,CAAC;AAChG,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAgB,EAChB,IAAY,EACZ,OAAgB,EAChB,IAAkC;IAElC,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;AAC7D,CAAC"}
|
package/dist/sync/daemon.d.ts
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* first so unlink+add of a rename is a move, not a delete (§4.7); each
|
|
10
10
|
* path is still stated, not trusted by event type (§4.6).
|
|
11
11
|
*
|
|
12
|
+
* better-sync.plan: `--settle` is an mtime-age gate on both directions; inbound
|
|
13
|
+
* conflicts on hot files are held in an in-memory deferred map and retried on
|
|
14
|
+
* later polls (cursor still advances).
|
|
15
|
+
*
|
|
12
16
|
* chokidar is confined to this module (§4.4). Echo suppression falls out of
|
|
13
17
|
* self-description (§4.5): our own pushes come back on the feed but the local
|
|
14
18
|
* file already embeds that version+hash, and our own writes trip the watcher
|
package/dist/sync/daemon.js
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* first so unlink+add of a rename is a move, not a delete (§4.7); each
|
|
10
10
|
* path is still stated, not trusted by event type (§4.6).
|
|
11
11
|
*
|
|
12
|
+
* better-sync.plan: `--settle` is an mtime-age gate on both directions; inbound
|
|
13
|
+
* conflicts on hot files are held in an in-memory deferred map and retried on
|
|
14
|
+
* later polls (cursor still advances).
|
|
15
|
+
*
|
|
12
16
|
* chokidar is confined to this module (§4.4). Echo suppression falls out of
|
|
13
17
|
* self-description (§4.5): our own pushes come back on the feed but the local
|
|
14
18
|
* file already embeds that version+hash, and our own writes trip the watcher
|
|
@@ -18,6 +22,7 @@ import { watch } from "chokidar";
|
|
|
18
22
|
import { readCursor, sourceFields, writeCursor } from "./cursor.js";
|
|
19
23
|
import { applyFeed } from "./feed.js";
|
|
20
24
|
import { createFsStore } from "./fs-store.js";
|
|
25
|
+
import { pathIsHot, retryDeferredEntry, } from "./hot-path.js";
|
|
21
26
|
import { isIgnored, readFileIntrinsics } from "./intrinsics.js";
|
|
22
27
|
import { SYNC_DIR, makeScopeFilter, toDocPath } from "./paths.js";
|
|
23
28
|
import { pushBurst, pushPath } from "./push.js";
|
|
@@ -31,8 +36,10 @@ export function startDaemon(client, opts) {
|
|
|
31
36
|
const store = createFsStore(opts.root);
|
|
32
37
|
const scope = makeScopeFilter({ include: opts.include, exclude: opts.exclude });
|
|
33
38
|
const map = new Map();
|
|
39
|
+
const deferred = new Map();
|
|
34
40
|
const intervalMs = opts.intervalMs ?? 5000;
|
|
35
41
|
const debounceMs = opts.debounceMs ?? 5000;
|
|
42
|
+
const settleMs = opts.settleMs ?? 0;
|
|
36
43
|
let stopped = false;
|
|
37
44
|
let watcher;
|
|
38
45
|
const pending = new Set();
|
|
@@ -73,22 +80,45 @@ export function startDaemon(client, opts) {
|
|
|
73
80
|
}
|
|
74
81
|
}
|
|
75
82
|
}
|
|
83
|
+
/** Retry in-memory deferred inbound holds before draining new feed pages. */
|
|
84
|
+
async function retryDeferred() {
|
|
85
|
+
if (deferred.size === 0)
|
|
86
|
+
return;
|
|
87
|
+
for (const [path, entry] of [...deferred.entries()]) {
|
|
88
|
+
if (!scope.matches(path) && entry.ref.op !== "delete") {
|
|
89
|
+
deferred.delete(path);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const { done } = await retryDeferredEntry(client, store, opts.repo, path, entry, {
|
|
94
|
+
settleMs,
|
|
95
|
+
map,
|
|
96
|
+
deferred,
|
|
97
|
+
});
|
|
98
|
+
if (done)
|
|
99
|
+
deferred.delete(path);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
log(`defer retry error\t${path}\t${err.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
76
106
|
async function pollFeedOnce() {
|
|
107
|
+
await retryDeferred();
|
|
77
108
|
const { cursor: next } = await applyFeed(client, store, scope, {
|
|
78
109
|
repo: opts.repo,
|
|
79
110
|
since: cursor,
|
|
80
111
|
log,
|
|
81
112
|
map,
|
|
113
|
+
settleMs,
|
|
114
|
+
deferred,
|
|
82
115
|
});
|
|
83
116
|
if (next !== cursor) {
|
|
84
117
|
cursor = next;
|
|
85
118
|
await persistCursor();
|
|
86
119
|
}
|
|
87
120
|
}
|
|
88
|
-
function
|
|
89
|
-
if (!scope.matches(docPath))
|
|
90
|
-
return;
|
|
91
|
-
pending.add(docPath);
|
|
121
|
+
function armBurstTimer() {
|
|
92
122
|
if (burstTimer)
|
|
93
123
|
clearTimeout(burstTimer);
|
|
94
124
|
burstTimer = setTimeout(() => {
|
|
@@ -98,10 +128,28 @@ export function startDaemon(client, opts) {
|
|
|
98
128
|
void serialize(async () => {
|
|
99
129
|
if (stopped)
|
|
100
130
|
return;
|
|
101
|
-
|
|
131
|
+
const ready = [];
|
|
132
|
+
for (const path of batch) {
|
|
133
|
+
if (settleMs > 0 && (await pathIsHot(store, path, settleMs))) {
|
|
134
|
+
pending.add(path);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
ready.push(path);
|
|
138
|
+
}
|
|
139
|
+
if (pending.size > 0)
|
|
140
|
+
armBurstTimer();
|
|
141
|
+
if (ready.length > 0) {
|
|
142
|
+
await pushBurst(ready, { client, store, repo: opts.repo, map, log });
|
|
143
|
+
}
|
|
102
144
|
});
|
|
103
145
|
}, debounceMs);
|
|
104
146
|
}
|
|
147
|
+
function schedulePush(docPath) {
|
|
148
|
+
if (!scope.matches(docPath))
|
|
149
|
+
return;
|
|
150
|
+
pending.add(docPath);
|
|
151
|
+
armBurstTimer();
|
|
152
|
+
}
|
|
105
153
|
const ready = (async () => {
|
|
106
154
|
// 1. Startup is deterministic on the cursor marker (§4.9, §7).
|
|
107
155
|
const existing = await readCursor(opts.root);
|
|
@@ -134,8 +182,13 @@ export function startDaemon(client, opts) {
|
|
|
134
182
|
ignoreInitial: true,
|
|
135
183
|
// Prune the sync state dir; the scope filter still guards everything else.
|
|
136
184
|
ignored: (p) => p.includes(`/${SYNC_DIR}/`) || p.endsWith(`/${SYNC_DIR}`),
|
|
137
|
-
...(
|
|
138
|
-
? {
|
|
185
|
+
...(settleMs > 0
|
|
186
|
+
? {
|
|
187
|
+
awaitWriteFinish: {
|
|
188
|
+
stabilityThreshold: Math.min(settleMs, 2000),
|
|
189
|
+
pollInterval: 100,
|
|
190
|
+
},
|
|
191
|
+
}
|
|
139
192
|
: {}),
|
|
140
193
|
});
|
|
141
194
|
const onEvent = (abs) => {
|
package/dist/sync/daemon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/sync/daemon.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/sync/daemon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAGjC,OAAO,EAAmB,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAEL,SAAS,EACT,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAoB,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,EAAkB,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAkB,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAsB/D;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAoB,EAAE,IAAmB;IACnE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,eAAe,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAChF,MAAM,GAAG,GAAc,IAAI,GAAG,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAgB,IAAI,GAAG,EAAE,CAAC;IACxC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;IAEpC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,OAA8B,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,UAAsC,CAAC;IAC3C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,cAAc,GAAsB,IAAI,CAAC;IAC7C,6EAA6E;IAC7E,sCAAsC;IACtC,IAAI,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,CAAC,EAAuB,EAAiB,EAAE;QAC3D,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,KAAK,UAAU,aAAa;QAC1B,MAAM,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE;YAC3B,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC;YAC3D,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,sBAAsB,EAAE,MAAM;SAC/B,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,UAAU,cAAc;QAC3B,KAAK,MAAM,OAAO,IAAI,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;gBAAE,SAAS;YACtC,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;YACxE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,eAAe,OAAO,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,KAAK,UAAU,aAAa;QAC1B,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAChC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YACpD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACtD,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE;oBAC/E,QAAQ;oBACR,GAAG;oBACH,QAAQ;iBACT,CAAC,CAAC;gBACH,IAAI,IAAI;oBAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,sBAAsB,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,UAAU,YAAY;QACzB,MAAM,aAAa,EAAE,CAAC;QACtB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE;YAC7D,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,MAAM;YACb,GAAG;YACH,GAAG;YACH,QAAQ;YACR,QAAQ;SACT,CAAC,CAAC;QACH,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,MAAM,GAAG,IAAI,CAAC;YACd,MAAM,aAAa,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED,SAAS,aAAa;QACpB,IAAI,UAAU;YAAE,YAAY,CAAC,UAAU,CAAC,CAAC;QACzC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAC3B,UAAU,GAAG,SAAS,CAAC;YACvB,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;YAC3B,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,KAAK,SAAS,CAAC,KAAK,IAAI,EAAE;gBACxB,IAAI,OAAO;oBAAE,OAAO;gBACpB,MAAM,KAAK,GAAa,EAAE,CAAC;gBAC3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;wBAC7D,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAClB,SAAS;oBACX,CAAC;oBACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnB,CAAC;gBACD,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC;oBAAE,aAAa,EAAE,CAAC;gBACtC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACrB,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC;IAED,SAAS,YAAY,CAAC,OAAe;QACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,OAAO;QACpC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,aAAa,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE;QACxB,+DAA+D;QAC/D,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,cAAc,GAAG,QAAQ,CAAC;QAC1B,0EAA0E;QAC1E,6DAA6D;QAC7D,MAAM,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC1C,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,0EAA0E;YAC1E,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YACnF,uEAAuE;YACvE,0CAA0C;YAC1C,MAAM,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YAC1C,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC;QAClC,CAAC;aAAM,CAAC;YACN,sEAAsE;YACtE,yEAAyE;YACzE,0EAA0E;YAC1E,wEAAwE;YACxE,qEAAqE;YACrE,MAAM,cAAc,EAAE,CAAC;YACvB,MAAM,GAAG,QAAQ,CAAC,sBAAsB,CAAC;QAC3C,CAAC;QACD,MAAM,aAAa,EAAE,CAAC;QAEtB,0EAA0E;QAC1E,MAAM,YAAY,EAAE,CAAC;QAErB,yCAAyC;QACzC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE;YACzB,aAAa,EAAE,IAAI;YACnB,2EAA2E;YAC3E,OAAO,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,QAAQ,EAAE,CAAC;YACjF,GAAG,CAAC,QAAQ,GAAG,CAAC;gBACd,CAAC,CAAC;oBACE,gBAAgB,EAAE;wBAChB,kBAAkB,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC;wBAC5C,YAAY,EAAE,GAAG;qBAClB;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;SACR,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,CAAC,GAAW,EAAQ,EAAE;YACpC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC1C,IAAI,OAAO,KAAK,IAAI;gBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC,CAAC;QACF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACvE,0EAA0E;QAC1E,yEAAyE;QACzE,MAAM,CAAC,GAAG,OAAO,CAAC;QAClB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAEvE,yCAAyC;QACzC,KAAK,QAAQ,EAAE,CAAC;IAClB,CAAC,CAAC,EAAE,CAAC;IAEL,KAAK,UAAU,QAAQ;QACrB,OAAO,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;YACxB,IAAI,OAAO;gBAAE,MAAM;YACnB,MAAM,SAAS,CAAC,KAAK,IAAI,EAAE;gBACzB,IAAI,OAAO;oBAAE,OAAO;gBACpB,IAAI,CAAC;oBACH,MAAM,YAAY,EAAE,CAAC;gBACvB,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,GAAG,CAAC,eAAgB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC/C,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK;QACL,KAAK,CAAC,IAAI;YACR,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,UAAU;gBAAE,YAAY,CAAC,UAAU,CAAC,CAAC;YACzC,UAAU,GAAG,SAAS,CAAC;YACvB,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,OAAO;gBAAE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;YACnC,MAAM,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,KAAK,UAAU,gBAAgB,CAC7B,KAAgB,EAChB,KAAkB,EAClB,GAAc;IAEd,KAAK,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,IAAI,KAAK,IAAI;YAAE,SAAS;QAC5B,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,SAAS;QACrE,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
|
package/dist/sync/feed.d.ts
CHANGED
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
* absent file is a no-op; the dirty check prevents a replay from clobbering an
|
|
9
9
|
* edit made in a crash window. So a crash between apply and cursor-advance
|
|
10
10
|
* simply replays the batch harmlessly.
|
|
11
|
+
*
|
|
12
|
+
* better-sync.plan: hot files defer inbound effects into an in-memory map
|
|
13
|
+
* (cursor still advances); dirty+stale local files rebase onto remote current
|
|
14
|
+
* before parking a sibling.
|
|
11
15
|
*/
|
|
12
16
|
import type { KernelClient } from "../client/kernel-client.js";
|
|
17
|
+
import { type DeferredMap } from "./hot-path.js";
|
|
13
18
|
import type { ScopeFilter } from "./paths.js";
|
|
14
19
|
import type { RemoteMap } from "./push.js";
|
|
15
20
|
import type { FileStore } from "./reconcile.js";
|
|
@@ -23,12 +28,23 @@ export type ApplyFeedOptions = {
|
|
|
23
28
|
* witnessed unlink knows the file's prev_version_id.
|
|
24
29
|
*/
|
|
25
30
|
map?: RemoteMap;
|
|
31
|
+
/**
|
|
32
|
+
* Minimum mtime age before inbound touch (better-sync `--settle`). When a
|
|
33
|
+
* deferred map is also supplied, hot paths enqueue instead of writing.
|
|
34
|
+
*/
|
|
35
|
+
settleMs?: number;
|
|
36
|
+
/** In-memory deferred holds (daemon only). Absent → never defer (e.g. `--once`). */
|
|
37
|
+
deferred?: DeferredMap;
|
|
38
|
+
/** Injectable clock for tests. */
|
|
39
|
+
nowMs?: () => number;
|
|
26
40
|
};
|
|
27
41
|
export type ApplyFeedResult = {
|
|
28
42
|
/** Final resume cursor after draining the currently-safe feed. */
|
|
29
43
|
cursor: string;
|
|
30
44
|
/** Number of refs whose filesystem effect was applied. */
|
|
31
45
|
applied: number;
|
|
46
|
+
/** Number of refs held in the deferred map (hot path). */
|
|
47
|
+
deferred: number;
|
|
32
48
|
};
|
|
33
49
|
/**
|
|
34
50
|
* Drain the safe feed from `since` to the live tip, applying each in-scope ref.
|
package/dist/sync/feed.js
CHANGED
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
* absent file is a no-op; the dirty check prevents a replay from clobbering an
|
|
9
9
|
* edit made in a crash window. So a crash between apply and cursor-advance
|
|
10
10
|
* simply replays the batch harmlessly.
|
|
11
|
+
*
|
|
12
|
+
* better-sync.plan: hot files defer inbound effects into an in-memory map
|
|
13
|
+
* (cursor still advances); dirty+stale local files rebase onto remote current
|
|
14
|
+
* before parking a sibling.
|
|
11
15
|
*/
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { isDirty, isIgnored, readFileIntrinsics
|
|
16
|
+
import { materializeAt } from "./converge.js";
|
|
17
|
+
import { decideInbound, effectInbound, enqueueDeferred, pathIsHot, versionAtOrAhead, } from "./hot-path.js";
|
|
18
|
+
import { isDirty, isIgnored, readFileIntrinsics } from "./intrinsics.js";
|
|
15
19
|
/**
|
|
16
20
|
* Drain the safe feed from `since` to the live tip, applying each in-scope ref.
|
|
17
21
|
* Returns the final cursor to persist. Stops when a poll returns no new refs
|
|
@@ -21,11 +25,15 @@ export async function applyFeed(client, store, scope, opts) {
|
|
|
21
25
|
const log = opts.log ?? (() => { });
|
|
22
26
|
let cursor = opts.since;
|
|
23
27
|
let applied = 0;
|
|
28
|
+
let deferredCount = 0;
|
|
24
29
|
for (;;) {
|
|
25
30
|
const page = await client.history.since({ after_version: cursor, repo: opts.repo });
|
|
26
31
|
for (const ref of page.refs) {
|
|
27
|
-
|
|
32
|
+
const outcome = await applyRef(client, store, scope, opts, ref, log);
|
|
33
|
+
if (outcome === "applied")
|
|
28
34
|
applied++;
|
|
35
|
+
else if (outcome === "deferred")
|
|
36
|
+
deferredCount++;
|
|
29
37
|
}
|
|
30
38
|
// No forward progress → caught up (or a hot gap). Stop draining.
|
|
31
39
|
if (page.next_since === cursor || page.refs.length === 0) {
|
|
@@ -34,92 +42,109 @@ export async function applyFeed(client, store, scope, opts) {
|
|
|
34
42
|
}
|
|
35
43
|
cursor = page.next_since;
|
|
36
44
|
}
|
|
37
|
-
return { cursor, applied };
|
|
45
|
+
return { cursor, applied, deferred: deferredCount };
|
|
38
46
|
}
|
|
39
|
-
/** Apply one feed ref to the local store.
|
|
40
|
-
async function applyRef(client, store, scope,
|
|
47
|
+
/** Apply one feed ref to the local store. */
|
|
48
|
+
async function applyRef(client, store, scope, opts, ref, log) {
|
|
49
|
+
const settleMs = opts.settleMs ?? 0;
|
|
50
|
+
const nowMs = opts.nowMs?.() ?? Date.now();
|
|
51
|
+
const canDefer = opts.deferred !== undefined;
|
|
41
52
|
if (ref.op === "delete") {
|
|
42
|
-
// Remove the local file at prev_path IF clean; dirty ⇒ keep (resurrection
|
|
43
|
-
// happens on its next local-change cycle). §4.3 delete.
|
|
44
53
|
const target = ref.prev_path;
|
|
45
54
|
if (target === null || !scope.matches(target))
|
|
46
|
-
return
|
|
47
|
-
map?.delete(target);
|
|
55
|
+
return "noop";
|
|
56
|
+
opts.map?.delete(target);
|
|
48
57
|
const text = await store.read(target);
|
|
49
58
|
if (text === null)
|
|
50
|
-
return
|
|
59
|
+
return "noop";
|
|
51
60
|
const intr = readFileIntrinsics(text);
|
|
52
61
|
if (isIgnored(intr) || isDirty(intr))
|
|
53
|
-
return
|
|
62
|
+
return "noop"; // preserve local work
|
|
63
|
+
if (canDefer && (await pathIsHot(store, target, settleMs, nowMs))) {
|
|
64
|
+
enqueueDeferred(opts.deferred, target, ref, nowMs);
|
|
65
|
+
return "deferred";
|
|
66
|
+
}
|
|
54
67
|
await store.remove(target);
|
|
55
68
|
log(`feed delete\t${target}`);
|
|
56
|
-
return
|
|
69
|
+
return "applied";
|
|
57
70
|
}
|
|
58
71
|
if (ref.op === "move") {
|
|
59
72
|
const from = ref.prev_path;
|
|
60
73
|
if (from !== null && scope.matches(from)) {
|
|
61
|
-
map?.delete(from);
|
|
74
|
+
opts.map?.delete(from);
|
|
62
75
|
const text = await store.read(from);
|
|
63
76
|
if (text !== null) {
|
|
64
77
|
const intr = readFileIntrinsics(text);
|
|
65
|
-
if (!isIgnored(intr) && !isDirty(intr))
|
|
78
|
+
if (!isIgnored(intr) && !isDirty(intr)) {
|
|
79
|
+
if (canDefer && (await pathIsHot(store, from, settleMs, nowMs))) {
|
|
80
|
+
// Hold the whole move (including dest) until source is cold.
|
|
81
|
+
enqueueDeferred(opts.deferred, ref.path, ref, nowMs);
|
|
82
|
+
return "deferred";
|
|
83
|
+
}
|
|
66
84
|
await store.remove(from);
|
|
85
|
+
}
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
// Fall through to materialize at the destination path.
|
|
70
89
|
}
|
|
71
|
-
// create / update / move-destination: materialize at ref.path unless the
|
|
72
|
-
// local file already holds these bytes, is at-or-ahead of this ref, is
|
|
73
|
-
// dirty against a *newer* remote, or is $sync: ignore.
|
|
74
90
|
if (!scope.matches(ref.path))
|
|
75
|
-
return
|
|
91
|
+
return "noop";
|
|
76
92
|
const existing = await store.read(ref.path);
|
|
77
93
|
if (existing !== null) {
|
|
78
94
|
const intr = readFileIntrinsics(existing);
|
|
79
95
|
if (isIgnored(intr))
|
|
80
|
-
return
|
|
96
|
+
return "noop";
|
|
97
|
+
// Fast path: bytes already match — provenance repair or defer when hot.
|
|
81
98
|
if (intr.computed_hash === ref.content_hash) {
|
|
82
|
-
|
|
83
|
-
map?.set(ref.path, { version_id: ref.version_id, content_hash: ref.content_hash });
|
|
84
|
-
// Repair provenance only if the embedded version lags.
|
|
99
|
+
opts.map?.set(ref.path, { version_id: ref.version_id, content_hash: ref.content_hash });
|
|
85
100
|
if (intr.version === ref.version_id)
|
|
86
|
-
return
|
|
101
|
+
return "noop";
|
|
87
102
|
if (versionAtOrAhead(intr.version, ref.version_id))
|
|
88
|
-
return
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
return "noop";
|
|
104
|
+
const hot = await pathIsHot(store, ref.path, settleMs, nowMs);
|
|
105
|
+
if (hot && canDefer) {
|
|
106
|
+
enqueueDeferred(opts.deferred, ref.path, ref, nowMs);
|
|
107
|
+
return "deferred";
|
|
108
|
+
}
|
|
109
|
+
const v = await client.docs.get_version(opts.repo, ref.version_id);
|
|
110
|
+
await materializeAt(store, ref.path, v, { preserveMtime: true });
|
|
111
|
+
log(`feed adopt\t${ref.path}`);
|
|
112
|
+
return "applied";
|
|
92
113
|
}
|
|
93
|
-
// Local is at or ahead of this ref (echo of our push, or a replay of an
|
|
94
|
-
// older version). Never clobber and never park a sibling of something we
|
|
95
|
-
// already have — including when the user has typed more since (dirty).
|
|
96
114
|
if (versionAtOrAhead(intr.version, ref.version_id))
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
return "noop";
|
|
116
|
+
// Divergent local vs newer remote → decideInbound (rebase / defer / park).
|
|
117
|
+
const hot = await pathIsHot(store, ref.path, settleMs, nowMs);
|
|
118
|
+
const v = await client.docs.get_version(opts.repo, ref.version_id);
|
|
119
|
+
const decision = decideInbound({
|
|
120
|
+
localText: existing,
|
|
121
|
+
remote: v,
|
|
122
|
+
hot,
|
|
123
|
+
canDefer,
|
|
124
|
+
});
|
|
125
|
+
const result = await effectInbound(client, store, opts.repo, ref.path, decision, {
|
|
126
|
+
deferred: opts.deferred,
|
|
127
|
+
ref,
|
|
128
|
+
map: opts.map,
|
|
129
|
+
nowMs,
|
|
130
|
+
});
|
|
131
|
+
if (result === "deferred")
|
|
132
|
+
return "deferred";
|
|
133
|
+
if (result === "noop")
|
|
134
|
+
return "noop";
|
|
135
|
+
if (result === "parked")
|
|
103
136
|
log(`feed conflict\t${ref.path}`);
|
|
104
|
-
|
|
105
|
-
|
|
137
|
+
else if (decision.action === "rebase")
|
|
138
|
+
log(`feed rebase\t${ref.path}`);
|
|
139
|
+
else
|
|
140
|
+
log(`feed ${ref.op}\t${ref.path}`);
|
|
141
|
+
return "applied";
|
|
106
142
|
}
|
|
107
|
-
|
|
108
|
-
await
|
|
109
|
-
|
|
143
|
+
// Absent locally → materialize (unless somehow hot — can't be; no mtime).
|
|
144
|
+
const v = await client.docs.get_version(opts.repo, ref.version_id);
|
|
145
|
+
await materializeAt(store, ref.path, v);
|
|
146
|
+
opts.map?.set(ref.path, { version_id: ref.version_id, content_hash: ref.content_hash });
|
|
110
147
|
log(`feed ${ref.op}\t${ref.path}`);
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
/** True when the local embedded version is this ref or a later one. */
|
|
114
|
-
function versionAtOrAhead(localVersion, refVersion) {
|
|
115
|
-
if (!localVersion)
|
|
116
|
-
return false;
|
|
117
|
-
if (localVersion === refVersion)
|
|
118
|
-
return true;
|
|
119
|
-
const local = decodeVersionId(localVersion);
|
|
120
|
-
const ref = decodeVersionId(refVersion);
|
|
121
|
-
if (local === null || ref === null)
|
|
122
|
-
return false;
|
|
123
|
-
return local > ref;
|
|
148
|
+
return "applied";
|
|
124
149
|
}
|
|
125
150
|
//# sourceMappingURL=feed.js.map
|