@rigxyz/tapd 0.6.7 → 0.6.9
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/dist/bin.js +37 -3
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-32K6ANXS.js → chunk-57Z2Y3S2.js} +0 -0
- package/dist/chunk-57Z2Y3S2.js.map +1 -0
- package/dist/index.d.ts +95 -10
- package/dist/index.js +5 -1
- package/package.json +3 -2
- package/dist/chunk-32K6ANXS.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -237,6 +237,7 @@ type StateDb = {
|
|
|
237
237
|
listPaths(): string[];
|
|
238
238
|
setMeta(key: string, value: string): void;
|
|
239
239
|
getMeta(key: string): string | null;
|
|
240
|
+
deleteMeta(key: string): void;
|
|
240
241
|
close(): void;
|
|
241
242
|
};
|
|
242
243
|
declare function openStateDb(dbPath: string): StateDb;
|
|
@@ -443,11 +444,18 @@ declare function revoke(opts: {
|
|
|
443
444
|
/**
|
|
444
445
|
* Apply remote change events to the local filesystem.
|
|
445
446
|
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
447
|
+
* Echo suppression is handled via the state DB: an event at or before the
|
|
448
|
+
* path's last_applied_change_id (our own push coming back, or a version
|
|
449
|
+
* already superseded here), or whose hash matches our last_seen_hash, is
|
|
450
|
+
* skipped without a download.
|
|
451
|
+
*
|
|
452
|
+
* A change made on both sides is first three-way merged (merge.ts) against
|
|
453
|
+
* the version this device last synced; a clean merge becomes the file and
|
|
454
|
+
* is uploaded on top of the remote change. Otherwise the remote stays the
|
|
455
|
+
* file and the local edit moves to a `.conflict-from.<this device>.<chg>`
|
|
456
|
+
* copy — a local copy that hasn't seen a newer remote version must never
|
|
457
|
+
* replace it (the 2026-09-30 clear-harbor incident). Identical bytes on
|
|
458
|
+
* both sides are not a conflict.
|
|
451
459
|
*
|
|
452
460
|
* Materialization is atomic: download to `<file>.tap-tmp.<rand>` then
|
|
453
461
|
* `rename` into place. Atomic on POSIX; on Windows, `fs.rename` falls
|
|
@@ -479,8 +487,8 @@ declare function conflictSidecarPath(path: string, ev: {
|
|
|
479
487
|
* Apply mode.
|
|
480
488
|
*
|
|
481
489
|
* - `primary`: the local copy has write authority. Local-divergence
|
|
482
|
-
* guards protect unpushed edits (
|
|
483
|
-
* clobber a dir)
|
|
490
|
+
* guards protect unpushed edits (conflict copies, keep-on-delete,
|
|
491
|
+
* refuse to clobber a dir).
|
|
484
492
|
* - `mirror`: a read-only import of another binding. The mount has no
|
|
485
493
|
* local authority, so **remote always wins** across every event type —
|
|
486
494
|
* every divergence guard flips to remote-authoritative. There is no
|
|
@@ -502,8 +510,8 @@ type ApplyResult = {
|
|
|
502
510
|
errors: ReadonlyArray<ApplyEventError>;
|
|
503
511
|
};
|
|
504
512
|
/**
|
|
505
|
-
* One iteration of the apply loop:
|
|
506
|
-
* materialize each event, update the cursor.
|
|
513
|
+
* One iteration of the apply loop: page through `GET /changes?after=<cursor>`
|
|
514
|
+
* until caught up, materialize each event, update the cursor.
|
|
507
515
|
*
|
|
508
516
|
* Per-event errors (hash mismatch, file/directory collision, disk full,
|
|
509
517
|
* etc.) are caught, logged, and recorded to the state DB — the cursor
|
|
@@ -521,6 +529,62 @@ declare function applyOnce(opts: {
|
|
|
521
529
|
mode?: ApplyMode;
|
|
522
530
|
}): Promise<ApplyResult>;
|
|
523
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Line-based three-way merge for a file changed on both sides (layer 0:
|
|
534
|
+
* deterministic, no AI). The applier tries it before falling back to a
|
|
535
|
+
* conflict copy: base = the version this device last synced, ours = the
|
|
536
|
+
* local file, theirs = the remote version being applied.
|
|
537
|
+
*
|
|
538
|
+
* The merge itself is node-diff3's `diff3Merge` (overlapping or touching
|
|
539
|
+
* hunks are a conflict; the same change on both sides is not). Two things
|
|
540
|
+
* are layered around it:
|
|
541
|
+
*
|
|
542
|
+
* - Only text it can't corrupt: valid UTF-8, no NUL, ≤ 1 MB per side, and
|
|
543
|
+
* not a format where a clean line merge can still be wrong (TOML, YAML,
|
|
544
|
+
* lockfiles, CSV/TSV). A merged `.json` must still parse.
|
|
545
|
+
* - Bounded work. node-diff3's LCS slows down sharply on many repeated
|
|
546
|
+
* lines (a 5,000-line doc that is one-third blank lines takes seconds),
|
|
547
|
+
* and this runs on the daemon's event loop. So the lines all three
|
|
548
|
+
* versions share at the start and end are set aside, the rest is cut at
|
|
549
|
+
* lines that occur exactly once in every version and in the same order
|
|
550
|
+
* (a merge can't move them), and each piece is merged on its own. Text
|
|
551
|
+
* still too repetitive to merge quickly gives up — a conflict copy,
|
|
552
|
+
* never a hang.
|
|
553
|
+
*
|
|
554
|
+
* Line endings: if every version uses one style, lines are compared
|
|
555
|
+
* without it and the result takes the style a side switched to (ours if
|
|
556
|
+
* both did), else the base's. A file mixing styles is merged as raw lines.
|
|
557
|
+
* The trailing newline follows the same rule.
|
|
558
|
+
*/
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* One auto-merge: this device's edit to `path` and another member's change
|
|
562
|
+
* `theirsChangeId`, both made on top of `baseChangeId`. Successive merges
|
|
563
|
+
* into an edit not yet uploaded update one record (`theirsChangeId` is
|
|
564
|
+
* then the newest remote change merged in). `changeId` is the upload that
|
|
565
|
+
* carried the merged file, once it went up.
|
|
566
|
+
*/
|
|
567
|
+
type MergeRecord = {
|
|
568
|
+
path: string;
|
|
569
|
+
at: string;
|
|
570
|
+
baseChangeId: string | null;
|
|
571
|
+
theirsChangeId: string;
|
|
572
|
+
theirsDeviceId: string | null;
|
|
573
|
+
/** The local bytes before the merge (not stored on the relay). */
|
|
574
|
+
oursHash: string;
|
|
575
|
+
resultHash: string;
|
|
576
|
+
changeId?: string;
|
|
577
|
+
};
|
|
578
|
+
declare function readMerges(stateDb: StateDb): MergeRecord[];
|
|
579
|
+
type MergeResult = {
|
|
580
|
+
ok: true;
|
|
581
|
+
bytes: Buffer;
|
|
582
|
+
} | {
|
|
583
|
+
ok: false;
|
|
584
|
+
reason: string;
|
|
585
|
+
};
|
|
586
|
+
declare function mergeText(path: string, base: Buffer, ours: Buffer, theirs: Buffer): MergeResult;
|
|
587
|
+
|
|
524
588
|
/**
|
|
525
589
|
* Claude Code context provider (Context Layer § 4, docs/context-layer-design-doc.md).
|
|
526
590
|
*
|
|
@@ -627,6 +691,8 @@ declare function scanAndPush(opts: {
|
|
|
627
691
|
* map and the batch simply carries no intent.
|
|
628
692
|
*/
|
|
629
693
|
resolveIntents?: (changedPaths: ReadonlyArray<string>) => Promise<Map<string, IntentAssignment>>;
|
|
694
|
+
/** Internal: this is the re-scan that uploads merges a stale_base refusal produced. */
|
|
695
|
+
afterMerge?: boolean;
|
|
630
696
|
}): Promise<ScanAndPushResult>;
|
|
631
697
|
|
|
632
698
|
/**
|
|
@@ -774,6 +840,19 @@ type SubscriptionHandle = {
|
|
|
774
840
|
stop(): Promise<void>;
|
|
775
841
|
/** Force one apply cycle now (through the mutex). */
|
|
776
842
|
applyNow(): Promise<ApplyResult>;
|
|
843
|
+
/**
|
|
844
|
+
* Apply every remote change, then run `fn` before any other apply
|
|
845
|
+
* starts — so an upload only ever goes out from a tree that has seen the
|
|
846
|
+
* relay's latest. `{ ok: false }` (fn not run) when the changes couldn't
|
|
847
|
+
* be fetched.
|
|
848
|
+
*/
|
|
849
|
+
catchUpThen<T>(fn: () => Promise<T>): Promise<{
|
|
850
|
+
ok: true;
|
|
851
|
+
value: T;
|
|
852
|
+
} | {
|
|
853
|
+
ok: false;
|
|
854
|
+
error: unknown;
|
|
855
|
+
}>;
|
|
777
856
|
/** Underlying state DB. Caller MUST NOT close it — stop() does. */
|
|
778
857
|
stateDb: StateDb;
|
|
779
858
|
};
|
|
@@ -946,6 +1025,12 @@ declare function subscribe(opts: SseSubscriberOptions): SseSubscriber;
|
|
|
946
1025
|
* subscription.ts; this file wires the pieces together and owns the
|
|
947
1026
|
* primary writer.
|
|
948
1027
|
*
|
|
1028
|
+
* Every upload goes through the primary subscription's catchUpThen: remote
|
|
1029
|
+
* changes are applied first, under the same lock. When they can't be
|
|
1030
|
+
* fetched (relay down, offline) nothing is uploaded — a copy that hasn't
|
|
1031
|
+
* seen the latest remote changes must not be pushed over them — and the
|
|
1032
|
+
* push is retried.
|
|
1033
|
+
*
|
|
949
1034
|
* Critical invariant: import mount points are HARD-excluded from the
|
|
950
1035
|
* primary writer (watcher `ignored` + walker `excludePaths`), so a
|
|
951
1036
|
* mirrored subtree is never re-uploaded into the primary's own binding
|
|
@@ -1014,4 +1099,4 @@ type DaemonHandle = {
|
|
|
1014
1099
|
};
|
|
1015
1100
|
declare function start(opts: StartOptions): Promise<DaemonHandle>;
|
|
1016
1101
|
|
|
1017
|
-
export { type AddImportOptions, type AddImportResult, AlreadyInitializedError, AlreadyJoinedError, type ApplyMode, type ApplyResult, BINDING_CONFIG_PATH, BUILTIN_IGNORE_LINES, type BindingConfig, type DaemonHandle, type ImportHandle, type ImportMountFile, type ImportSpec, ImportTokenScopeError, type ImportValidationError, ImportValidationFailed, type InitOptions, type InitResult, InvalidInviteUrlError, type JoinOptions, type JoinResult, type LocalPathState, type MintInviteOptions, NotInitializedError, type NotSyncedFile, RelayClient, type RelayClientOptions, RelayError, type RemoveImportResult, STATE_KEY_CONFLICT_COUNT, type ScanAndPushResult, type SseSubscriber, type SseSubscriberOptions, type StartOptions, type StartSubscriptionOptions, type StateDb, type SubscriptionConfig, type SubscriptionHandle, type WalkFile, type WalkResult, type WalkWarning, addImport, applyOnce, bindingConfigFile, conflictSidecarPath, createBinding, hashFile, importMountFile, importStateDbPath, importsDir, init, isAlwaysExcluded, join, list as listInvites, loadIgnore, loadImportMounts, mint as mintInvite, normalizeImportPath, openStateDb, parseInviteUrl, primaryStateDbPath, readBindingConfig, readImportMount, readNotSynced, removeImport, revoke as revokeInvite, scanAndPush, subscribe as sseSubscribe, start, startSubscription, validateImportName, validateImportPath, validateImports, walk, writeBindingConfig, writeImportMount };
|
|
1102
|
+
export { type AddImportOptions, type AddImportResult, AlreadyInitializedError, AlreadyJoinedError, type ApplyMode, type ApplyResult, BINDING_CONFIG_PATH, BUILTIN_IGNORE_LINES, type BindingConfig, type DaemonHandle, type ImportHandle, type ImportMountFile, type ImportSpec, ImportTokenScopeError, type ImportValidationError, ImportValidationFailed, type InitOptions, type InitResult, InvalidInviteUrlError, type JoinOptions, type JoinResult, type LocalPathState, type MergeRecord, type MintInviteOptions, NotInitializedError, type NotSyncedFile, RelayClient, type RelayClientOptions, RelayError, type RemoveImportResult, STATE_KEY_CONFLICT_COUNT, type ScanAndPushResult, type SseSubscriber, type SseSubscriberOptions, type StartOptions, type StartSubscriptionOptions, type StateDb, type SubscriptionConfig, type SubscriptionHandle, type WalkFile, type WalkResult, type WalkWarning, addImport, applyOnce, bindingConfigFile, conflictSidecarPath, createBinding, hashFile, importMountFile, importStateDbPath, importsDir, init, isAlwaysExcluded, join, list as listInvites, loadIgnore, loadImportMounts, mergeText, mint as mintInvite, normalizeImportPath, openStateDb, parseInviteUrl, primaryStateDbPath, readBindingConfig, readImportMount, readMerges, readNotSynced, removeImport, revoke as revokeInvite, scanAndPush, subscribe as sseSubscribe, start, startSubscription, validateImportName, validateImportPath, validateImports, walk, writeBindingConfig, writeImportMount };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
list,
|
|
27
27
|
loadIgnore,
|
|
28
28
|
loadImportMounts,
|
|
29
|
+
mergeText,
|
|
29
30
|
mint,
|
|
30
31
|
normalizeImportPath,
|
|
31
32
|
openStateDb,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
primaryStateDbPath,
|
|
34
35
|
readBindingConfig,
|
|
35
36
|
readImportMount,
|
|
37
|
+
readMerges,
|
|
36
38
|
readNotSynced,
|
|
37
39
|
removeImport,
|
|
38
40
|
revoke,
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
walk,
|
|
47
49
|
writeBindingConfig,
|
|
48
50
|
writeImportMount
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-57Z2Y3S2.js";
|
|
50
52
|
export {
|
|
51
53
|
AlreadyInitializedError,
|
|
52
54
|
AlreadyJoinedError,
|
|
@@ -74,6 +76,7 @@ export {
|
|
|
74
76
|
list as listInvites,
|
|
75
77
|
loadIgnore,
|
|
76
78
|
loadImportMounts,
|
|
79
|
+
mergeText,
|
|
77
80
|
mint as mintInvite,
|
|
78
81
|
normalizeImportPath,
|
|
79
82
|
openStateDb,
|
|
@@ -81,6 +84,7 @@ export {
|
|
|
81
84
|
primaryStateDbPath,
|
|
82
85
|
readBindingConfig,
|
|
83
86
|
readImportMount,
|
|
87
|
+
readMerges,
|
|
84
88
|
readNotSynced,
|
|
85
89
|
removeImport,
|
|
86
90
|
revoke as revokeInvite,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigxyz/tapd",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "Local daemon for the hosted Tap relay — watches files, hashes content, uploads/downloads objects, applies remote changes. Pairs with @rigxyz/cli for shared-rig sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Dylan Bourgeois <dtsbourg@gmail.com>",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"chokidar": "^4.0.1",
|
|
42
42
|
"ignore": "^5.3.0",
|
|
43
|
-
"kleur": "^4.1.5"
|
|
43
|
+
"kleur": "^4.1.5",
|
|
44
|
+
"node-diff3": "3.2.1"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@tap/core": "workspace:*",
|