@avalanche-io/c4-node 1.0.10
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/packages/core/src/base58.d.ts +5 -0
- package/dist/packages/core/src/base58.d.ts.map +1 -0
- package/dist/packages/core/src/base58.js +42 -0
- package/dist/packages/core/src/base58.js.map +1 -0
- package/dist/packages/core/src/browser/filesystem.d.ts +34 -0
- package/dist/packages/core/src/browser/filesystem.d.ts.map +1 -0
- package/dist/packages/core/src/browser/filesystem.js +169 -0
- package/dist/packages/core/src/browser/filesystem.js.map +1 -0
- package/dist/packages/core/src/browser/index.d.ts +4 -0
- package/dist/packages/core/src/browser/index.d.ts.map +1 -0
- package/dist/packages/core/src/browser/index.js +6 -0
- package/dist/packages/core/src/browser/index.js.map +1 -0
- package/dist/packages/core/src/browser/store.d.ts +28 -0
- package/dist/packages/core/src/browser/store.d.ts.map +1 -0
- package/dist/packages/core/src/browser/store.js +142 -0
- package/dist/packages/core/src/browser/store.js.map +1 -0
- package/dist/packages/core/src/browser/worker.d.ts +34 -0
- package/dist/packages/core/src/browser/worker.d.ts.map +1 -0
- package/dist/packages/core/src/browser/worker.js +96 -0
- package/dist/packages/core/src/browser/worker.js.map +1 -0
- package/dist/packages/core/src/decoder.d.ts +23 -0
- package/dist/packages/core/src/decoder.d.ts.map +1 -0
- package/dist/packages/core/src/decoder.js +431 -0
- package/dist/packages/core/src/decoder.js.map +1 -0
- package/dist/packages/core/src/diff.d.ts +48 -0
- package/dist/packages/core/src/diff.d.ts.map +1 -0
- package/dist/packages/core/src/diff.js +169 -0
- package/dist/packages/core/src/diff.js.map +1 -0
- package/dist/packages/core/src/encoder.d.ts +13 -0
- package/dist/packages/core/src/encoder.d.ts.map +1 -0
- package/dist/packages/core/src/encoder.js +125 -0
- package/dist/packages/core/src/encoder.js.map +1 -0
- package/dist/packages/core/src/entry.d.ts +59 -0
- package/dist/packages/core/src/entry.d.ts.map +1 -0
- package/dist/packages/core/src/entry.js +266 -0
- package/dist/packages/core/src/entry.js.map +1 -0
- package/dist/packages/core/src/errors.d.ts +29 -0
- package/dist/packages/core/src/errors.d.ts.map +1 -0
- package/dist/packages/core/src/errors.js +56 -0
- package/dist/packages/core/src/errors.js.map +1 -0
- package/dist/packages/core/src/filesystem.d.ts +68 -0
- package/dist/packages/core/src/filesystem.d.ts.map +1 -0
- package/dist/packages/core/src/filesystem.js +62 -0
- package/dist/packages/core/src/filesystem.js.map +1 -0
- package/dist/packages/core/src/id.d.ts +33 -0
- package/dist/packages/core/src/id.d.ts.map +1 -0
- package/dist/packages/core/src/id.js +126 -0
- package/dist/packages/core/src/id.js.map +1 -0
- package/dist/packages/core/src/identify-content.d.ts +17 -0
- package/dist/packages/core/src/identify-content.d.ts.map +1 -0
- package/dist/packages/core/src/identify-content.js +70 -0
- package/dist/packages/core/src/identify-content.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +23 -0
- package/dist/packages/core/src/index.d.ts.map +1 -0
- package/dist/packages/core/src/index.js +41 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/manifest.d.ts +68 -0
- package/dist/packages/core/src/manifest.d.ts.map +1 -0
- package/dist/packages/core/src/manifest.js +463 -0
- package/dist/packages/core/src/manifest.js.map +1 -0
- package/dist/packages/core/src/memory-fs.d.ts +33 -0
- package/dist/packages/core/src/memory-fs.d.ts.map +1 -0
- package/dist/packages/core/src/memory-fs.js +187 -0
- package/dist/packages/core/src/memory-fs.js.map +1 -0
- package/dist/packages/core/src/memory-store.d.ts +21 -0
- package/dist/packages/core/src/memory-store.d.ts.map +1 -0
- package/dist/packages/core/src/memory-store.js +57 -0
- package/dist/packages/core/src/memory-store.js.map +1 -0
- package/dist/packages/core/src/naturalsort.d.ts +2 -0
- package/dist/packages/core/src/naturalsort.d.ts.map +1 -0
- package/dist/packages/core/src/naturalsort.js +88 -0
- package/dist/packages/core/src/naturalsort.js.map +1 -0
- package/dist/packages/core/src/observable.d.ts +54 -0
- package/dist/packages/core/src/observable.d.ts.map +1 -0
- package/dist/packages/core/src/observable.js +150 -0
- package/dist/packages/core/src/observable.js.map +1 -0
- package/dist/packages/core/src/pool.d.ts +38 -0
- package/dist/packages/core/src/pool.d.ts.map +1 -0
- package/dist/packages/core/src/pool.js +113 -0
- package/dist/packages/core/src/pool.js.map +1 -0
- package/dist/packages/core/src/reconcile.d.ts +43 -0
- package/dist/packages/core/src/reconcile.d.ts.map +1 -0
- package/dist/packages/core/src/reconcile.js +172 -0
- package/dist/packages/core/src/reconcile.js.map +1 -0
- package/dist/packages/core/src/resolver.d.ts +67 -0
- package/dist/packages/core/src/resolver.d.ts.map +1 -0
- package/dist/packages/core/src/resolver.js +110 -0
- package/dist/packages/core/src/resolver.js.map +1 -0
- package/dist/packages/core/src/safename.d.ts +7 -0
- package/dist/packages/core/src/safename.d.ts.map +1 -0
- package/dist/packages/core/src/safename.js +354 -0
- package/dist/packages/core/src/safename.js.map +1 -0
- package/dist/packages/core/src/scanner.d.ts +25 -0
- package/dist/packages/core/src/scanner.d.ts.map +1 -0
- package/dist/packages/core/src/scanner.js +97 -0
- package/dist/packages/core/src/scanner.js.map +1 -0
- package/dist/packages/core/src/store.d.ts +39 -0
- package/dist/packages/core/src/store.d.ts.map +1 -0
- package/dist/packages/core/src/store.js +53 -0
- package/dist/packages/core/src/store.js.map +1 -0
- package/dist/packages/core/src/tree.d.ts +16 -0
- package/dist/packages/core/src/tree.d.ts.map +1 -0
- package/dist/packages/core/src/tree.js +45 -0
- package/dist/packages/core/src/tree.js.map +1 -0
- package/dist/packages/core/src/verify.d.ts +29 -0
- package/dist/packages/core/src/verify.d.ts.map +1 -0
- package/dist/packages/core/src/verify.js +85 -0
- package/dist/packages/core/src/verify.js.map +1 -0
- package/dist/packages/core/src/workspace.d.ts +72 -0
- package/dist/packages/core/src/workspace.d.ts.map +1 -0
- package/dist/packages/core/src/workspace.js +135 -0
- package/dist/packages/core/src/workspace.js.map +1 -0
- package/dist/packages/node/src/index.d.ts +4 -0
- package/dist/packages/node/src/index.d.ts.map +1 -0
- package/dist/packages/node/src/index.js +6 -0
- package/dist/packages/node/src/index.js.map +1 -0
- package/dist/packages/node/src/node-fs.d.ts +24 -0
- package/dist/packages/node/src/node-fs.d.ts.map +1 -0
- package/dist/packages/node/src/node-fs.js +84 -0
- package/dist/packages/node/src/node-fs.js.map +1 -0
- package/dist/packages/node/src/tree-store.d.ts +22 -0
- package/dist/packages/node/src/tree-store.d.ts.map +1 -0
- package/dist/packages/node/src/tree-store.js +77 -0
- package/dist/packages/node/src/tree-store.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { parse as parseC4ID } from './id.js';
|
|
2
|
+
import { joinPath } from './filesystem.js';
|
|
3
|
+
import { isDir } from './entry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Bundle a manifest with its referenced content for portable transfer.
|
|
6
|
+
*
|
|
7
|
+
* Creates:
|
|
8
|
+
* outputDir/
|
|
9
|
+
* <name>.c4m — the manifest
|
|
10
|
+
* objects/ — content store (flat, keyed by C4 ID string)
|
|
11
|
+
*/
|
|
12
|
+
export async function pool(manifest, outputDir, fs, store, options) {
|
|
13
|
+
const name = options?.manifestName ?? 'manifest.c4m';
|
|
14
|
+
const objectsDir = joinPath(outputDir, 'objects');
|
|
15
|
+
let copied = 0;
|
|
16
|
+
let skipped = 0;
|
|
17
|
+
let missing = 0;
|
|
18
|
+
// Create output structure
|
|
19
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
20
|
+
await fs.mkdir(objectsDir, { recursive: true });
|
|
21
|
+
// Write manifest
|
|
22
|
+
const c4mText = manifest.encode();
|
|
23
|
+
const c4mBytes = new TextEncoder().encode(c4mText);
|
|
24
|
+
const manifestPath = joinPath(outputDir, name);
|
|
25
|
+
await fs.writeFile(manifestPath, c4mBytes);
|
|
26
|
+
// Collect unique C4 IDs from manifest
|
|
27
|
+
const ids = new Set();
|
|
28
|
+
for (const [, entry] of manifest) {
|
|
29
|
+
if (!isDir(entry) && entry.c4id && !entry.c4id.isNil()) {
|
|
30
|
+
ids.add(entry.c4id.toString());
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Copy objects
|
|
34
|
+
const idList = [...ids];
|
|
35
|
+
for (let i = 0; i < idList.length; i++) {
|
|
36
|
+
const idStr = idList[i];
|
|
37
|
+
options?.progress?.(idStr, i, idList.length);
|
|
38
|
+
const objectPath = joinPath(objectsDir, idStr);
|
|
39
|
+
// Check if already in bundle
|
|
40
|
+
try {
|
|
41
|
+
await fs.stat(objectPath);
|
|
42
|
+
skipped++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
catch { /* doesn't exist, proceed */ }
|
|
46
|
+
// Copy from store
|
|
47
|
+
try {
|
|
48
|
+
const c4id = parseC4ID(idStr);
|
|
49
|
+
if (await store.has(c4id)) {
|
|
50
|
+
const stream = await store.get(c4id);
|
|
51
|
+
await fs.writeFile(objectPath, stream);
|
|
52
|
+
copied++;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
missing++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
missing++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { copied, skipped, missing, manifestPath };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Absorb a pool bundle into a local store.
|
|
66
|
+
* Reads objects from bundleDir/objects/ and puts them in the store.
|
|
67
|
+
* Returns paths to any .c4m files found in the bundle.
|
|
68
|
+
*/
|
|
69
|
+
export async function ingest(bundleDir, fs, store, options) {
|
|
70
|
+
let copied = 0;
|
|
71
|
+
let skipped = 0;
|
|
72
|
+
const manifests = [];
|
|
73
|
+
// Find .c4m files in bundle root
|
|
74
|
+
for await (const entry of fs.readDir(bundleDir)) {
|
|
75
|
+
if (entry.name.endsWith('.c4m')) {
|
|
76
|
+
manifests.push(entry.name);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Ingest objects
|
|
80
|
+
const objectsDir = joinPath(bundleDir, 'objects');
|
|
81
|
+
const objects = [];
|
|
82
|
+
try {
|
|
83
|
+
for await (const entry of fs.readDir(objectsDir)) {
|
|
84
|
+
if (!entry.isDirectory && entry.name.startsWith('c4')) {
|
|
85
|
+
objects.push(entry.name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// No objects directory — just manifests
|
|
91
|
+
}
|
|
92
|
+
for (let i = 0; i < objects.length; i++) {
|
|
93
|
+
const name = objects[i];
|
|
94
|
+
options?.progress?.(name, i, objects.length);
|
|
95
|
+
try {
|
|
96
|
+
const c4id = parseC4ID(name);
|
|
97
|
+
if (await store.has(c4id)) {
|
|
98
|
+
skipped++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Invalid C4 ID filename — skip
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const objectPath = joinPath(objectsDir, name);
|
|
107
|
+
const stream = await fs.readFile(objectPath);
|
|
108
|
+
await store.put(stream);
|
|
109
|
+
copied++;
|
|
110
|
+
}
|
|
111
|
+
return { copied, skipped, manifests };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../../../core/src/pool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FileSystem } from './filesystem.js';
|
|
2
|
+
import type { Store } from './store.js';
|
|
3
|
+
import type { Manifest } from './manifest.js';
|
|
4
|
+
import { type Entry } from './entry.js';
|
|
5
|
+
/** Types of operations the reconciler can perform. */
|
|
6
|
+
export type ReconcileOpType = 'mkdir' | 'create' | 'update' | 'remove' | 'rmdir';
|
|
7
|
+
/** A single reconciliation operation. */
|
|
8
|
+
export interface ReconcileOp {
|
|
9
|
+
type: ReconcileOpType;
|
|
10
|
+
path: string;
|
|
11
|
+
entry?: Entry;
|
|
12
|
+
}
|
|
13
|
+
/** Plan computed by the reconciler before execution. */
|
|
14
|
+
export interface ReconcilePlan {
|
|
15
|
+
operations: ReconcileOp[];
|
|
16
|
+
missing: string[];
|
|
17
|
+
skipped: string[];
|
|
18
|
+
}
|
|
19
|
+
/** Result of executing a reconciliation plan. */
|
|
20
|
+
export interface ReconcileResult {
|
|
21
|
+
created: number;
|
|
22
|
+
updated: number;
|
|
23
|
+
removed: number;
|
|
24
|
+
skipped: number;
|
|
25
|
+
errors: Array<{
|
|
26
|
+
path: string;
|
|
27
|
+
error: Error;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
export interface ReconcileOptions {
|
|
31
|
+
progress?: (op: ReconcileOpType, path: string, index: number, total: number) => void;
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Plan reconciliation: determine what operations are needed to make
|
|
36
|
+
* the filesystem at rootPath match the manifest.
|
|
37
|
+
*/
|
|
38
|
+
export declare function plan(manifest: Manifest, fs: FileSystem, rootPath: string, store?: Store): Promise<ReconcilePlan>;
|
|
39
|
+
/**
|
|
40
|
+
* Execute a reconciliation plan.
|
|
41
|
+
*/
|
|
42
|
+
export declare function apply(reconcilePlan: ReconcilePlan, fs: FileSystem, rootPath: string, store?: Store, options?: ReconcileOptions): Promise<ReconcileResult>;
|
|
43
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../../../core/src/reconcile.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEjD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,EAAE,KAAK,KAAK,EAAS,MAAM,YAAY,CAAA;AAE9C,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEhF,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,KAAK,CAAA;CACd;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,WAAW,EAAE,CAAA;IACzB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,CAAC,CAAA;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACpF,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;GAGG;AACH,wBAAsB,IAAI,CACxB,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,KAAK,GACZ,OAAO,CAAC,aAAa,CAAC,CAyFxB;AAED;;GAEG;AACH,wBAAsB,KAAK,CACzB,aAAa,EAAE,aAAa,EAC5B,EAAE,EAAE,UAAU,EACd,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,KAAK,EACb,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,eAAe,CAAC,CAwD1B"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { identifyContent } from './identify-content.js';
|
|
2
|
+
import { streamToBytes, joinPath } from './filesystem.js';
|
|
3
|
+
import { isDir } from './entry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Plan reconciliation: determine what operations are needed to make
|
|
6
|
+
* the filesystem at rootPath match the manifest.
|
|
7
|
+
*/
|
|
8
|
+
export async function plan(manifest, fs, rootPath, store) {
|
|
9
|
+
const operations = [];
|
|
10
|
+
const missing = [];
|
|
11
|
+
const skipped = [];
|
|
12
|
+
// Build desired state from manifest
|
|
13
|
+
const desired = new Map();
|
|
14
|
+
const desiredDirs = new Set();
|
|
15
|
+
for (const [path, entry] of manifest) {
|
|
16
|
+
if (isDir(entry)) {
|
|
17
|
+
desiredDirs.add(path);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
desired.set(path, entry);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Collect what actually exists on disk
|
|
24
|
+
const actual = new Map(); // path -> isDir
|
|
25
|
+
await collectExisting(fs, rootPath, '', actual);
|
|
26
|
+
// Phase 1: directories to create (sorted by depth for correct ordering)
|
|
27
|
+
const sortedDirs = [...desiredDirs].sort();
|
|
28
|
+
for (const dir of sortedDirs) {
|
|
29
|
+
const fullPath = joinPath(rootPath, dir);
|
|
30
|
+
try {
|
|
31
|
+
const stat = await fs.stat(fullPath);
|
|
32
|
+
if (!stat.isDirectory) {
|
|
33
|
+
operations.push({ type: 'remove', path: dir });
|
|
34
|
+
operations.push({ type: 'mkdir', path: dir });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
operations.push({ type: 'mkdir', path: dir });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Phase 2: files to create or update
|
|
42
|
+
for (const [path, entry] of desired) {
|
|
43
|
+
const fullPath = joinPath(rootPath, path);
|
|
44
|
+
try {
|
|
45
|
+
const stat = await fs.stat(fullPath);
|
|
46
|
+
if (stat.isDirectory) {
|
|
47
|
+
// Directory exists where file should be — remove first
|
|
48
|
+
operations.push({ type: 'remove', path });
|
|
49
|
+
operations.push({ type: 'create', path, entry });
|
|
50
|
+
}
|
|
51
|
+
else if (entry.c4id) {
|
|
52
|
+
// File exists — check if content matches
|
|
53
|
+
const stream = await fs.readFile(fullPath);
|
|
54
|
+
const bytes = await streamToBytes(stream);
|
|
55
|
+
const actualId = await identifyContent(bytes);
|
|
56
|
+
if (actualId.equals(entry.c4id)) {
|
|
57
|
+
skipped.push(path);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Check store has the content
|
|
61
|
+
if (store && !(await store.has(entry.c4id))) {
|
|
62
|
+
missing.push(entry.c4id.toString());
|
|
63
|
+
}
|
|
64
|
+
operations.push({ type: 'update', path, entry });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
skipped.push(path); // no C4 ID to compare against
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// File doesn't exist — create it
|
|
73
|
+
if (entry.c4id && store && !(await store.has(entry.c4id))) {
|
|
74
|
+
missing.push(entry.c4id.toString());
|
|
75
|
+
}
|
|
76
|
+
operations.push({ type: 'create', path, entry });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Phase 3: files/dirs to remove (on disk but not in manifest)
|
|
80
|
+
const desiredPaths = new Set([...desired.keys(), ...desiredDirs]);
|
|
81
|
+
const toRemove = [];
|
|
82
|
+
for (const [path, pathIsDir] of actual) {
|
|
83
|
+
// Normalize: directory paths in manifest end with /
|
|
84
|
+
const manifestPath = pathIsDir ? (path.endsWith('/') ? path : path + '/') : path;
|
|
85
|
+
if (!desiredPaths.has(manifestPath) && !desiredPaths.has(path)) {
|
|
86
|
+
toRemove.push(path);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Sort removals deepest-first so children are removed before parents
|
|
90
|
+
toRemove.sort((a, b) => b.split('/').length - a.split('/').length);
|
|
91
|
+
for (const path of toRemove) {
|
|
92
|
+
const isDirectory = actual.get(path);
|
|
93
|
+
operations.push({ type: isDirectory ? 'rmdir' : 'remove', path });
|
|
94
|
+
}
|
|
95
|
+
return { operations, missing, skipped };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Execute a reconciliation plan.
|
|
99
|
+
*/
|
|
100
|
+
export async function apply(reconcilePlan, fs, rootPath, store, options) {
|
|
101
|
+
const result = {
|
|
102
|
+
created: 0,
|
|
103
|
+
updated: 0,
|
|
104
|
+
removed: 0,
|
|
105
|
+
skipped: reconcilePlan.skipped.length,
|
|
106
|
+
errors: [],
|
|
107
|
+
};
|
|
108
|
+
const ops = reconcilePlan.operations;
|
|
109
|
+
for (let i = 0; i < ops.length; i++) {
|
|
110
|
+
const op = ops[i];
|
|
111
|
+
const fullPath = joinPath(rootPath, op.path);
|
|
112
|
+
options?.progress?.(op.type, op.path, i, ops.length);
|
|
113
|
+
try {
|
|
114
|
+
switch (op.type) {
|
|
115
|
+
case 'mkdir':
|
|
116
|
+
await fs.mkdir(fullPath, { recursive: true });
|
|
117
|
+
break;
|
|
118
|
+
case 'create':
|
|
119
|
+
case 'update': {
|
|
120
|
+
if (op.entry?.c4id && store) {
|
|
121
|
+
const stream = await store.get(op.entry.c4id);
|
|
122
|
+
await fs.writeFile(fullPath, stream);
|
|
123
|
+
}
|
|
124
|
+
// Apply metadata if supported
|
|
125
|
+
if (op.entry && fs.setMeta) {
|
|
126
|
+
await fs.setMeta(fullPath, {
|
|
127
|
+
mode: op.entry.mode,
|
|
128
|
+
mtime: op.entry.timestamp,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (op.type === 'create')
|
|
132
|
+
result.created++;
|
|
133
|
+
else
|
|
134
|
+
result.updated++;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case 'remove':
|
|
138
|
+
await fs.remove(fullPath);
|
|
139
|
+
result.removed++;
|
|
140
|
+
break;
|
|
141
|
+
case 'rmdir':
|
|
142
|
+
await fs.remove(fullPath, { recursive: true });
|
|
143
|
+
result.removed++;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
result.errors.push({ path: op.path, error: err });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
/** Recursively collect all paths that exist on disk. */
|
|
154
|
+
async function collectExisting(fs, rootPath, relativePath, result) {
|
|
155
|
+
const fullPath = relativePath ? joinPath(rootPath, relativePath) : rootPath;
|
|
156
|
+
try {
|
|
157
|
+
for await (const entry of fs.readDir(fullPath)) {
|
|
158
|
+
// Skip hidden files/dirs
|
|
159
|
+
if (entry.name.startsWith('.'))
|
|
160
|
+
continue;
|
|
161
|
+
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
162
|
+
result.set(childRelative, entry.isDirectory);
|
|
163
|
+
if (entry.isDirectory) {
|
|
164
|
+
await collectExisting(fs, rootPath, childRelative, result);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Directory doesn't exist or isn't readable — skip
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../../../core/src/reconcile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { C4ID } from './id.js';
|
|
2
|
+
import type { Store } from './store.js';
|
|
3
|
+
/**
|
|
4
|
+
* A source of content addressable by C4 ID.
|
|
5
|
+
* Every Store is a ContentSource, but sources can also be
|
|
6
|
+
* HTTP endpoints, peer connections, or anything that serves bytes by ID.
|
|
7
|
+
*/
|
|
8
|
+
export interface ContentSource {
|
|
9
|
+
/** Human-readable name for this source. */
|
|
10
|
+
readonly name: string;
|
|
11
|
+
/** Priority (lower = tried first in sequential mode). */
|
|
12
|
+
readonly priority: number;
|
|
13
|
+
/** Check if content is available. */
|
|
14
|
+
has(id: C4ID): Promise<boolean>;
|
|
15
|
+
/** Retrieve content. Throws ContentNotFoundError if unavailable. */
|
|
16
|
+
get(id: C4ID): Promise<ReadableStream<Uint8Array>>;
|
|
17
|
+
}
|
|
18
|
+
/** Result of a resolved content fetch. */
|
|
19
|
+
export interface ResolveResult {
|
|
20
|
+
stream: ReadableStream<Uint8Array>;
|
|
21
|
+
source: string;
|
|
22
|
+
}
|
|
23
|
+
/** Wrap any Store as a ContentSource. */
|
|
24
|
+
export declare function storeAsSource(store: Store, name: string, priority?: number): ContentSource;
|
|
25
|
+
/**
|
|
26
|
+
* Multi-source content resolver. Tries sources in parallel (race mode)
|
|
27
|
+
* or sequential (priority mode) to find content by C4 ID.
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* const resolver = new ContentResolver()
|
|
31
|
+
* resolver.addSource(storeAsSource(localStore, 'local', 0))
|
|
32
|
+
* resolver.addSource(storeAsSource(remoteStore, 'remote', 10))
|
|
33
|
+
* const stream = await resolver.get(id)
|
|
34
|
+
*/
|
|
35
|
+
export declare class ContentResolver {
|
|
36
|
+
private sources;
|
|
37
|
+
/** Add a content source. */
|
|
38
|
+
addSource(source: ContentSource): void;
|
|
39
|
+
/** Remove a source by name. */
|
|
40
|
+
removeSource(name: string): void;
|
|
41
|
+
/** List registered sources. */
|
|
42
|
+
listSources(): ReadonlyArray<{
|
|
43
|
+
name: string;
|
|
44
|
+
priority: number;
|
|
45
|
+
}>;
|
|
46
|
+
/** Check if any source has the content. Tries all in parallel. */
|
|
47
|
+
has(id: C4ID): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Retrieve content from the fastest available source (race mode).
|
|
50
|
+
* All sources that claim to have the content are raced;
|
|
51
|
+
* the first to deliver wins.
|
|
52
|
+
*/
|
|
53
|
+
get(id: C4ID): Promise<ReadableStream<Uint8Array>>;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve content with metadata about which source served it.
|
|
56
|
+
* Uses a two-phase approach:
|
|
57
|
+
* 1. Check which sources have the content (parallel).
|
|
58
|
+
* 2. Race the available sources for the actual data.
|
|
59
|
+
*/
|
|
60
|
+
resolve(id: C4ID): Promise<ResolveResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Sequential resolution — try sources in priority order, return first success.
|
|
63
|
+
* More predictable than race mode; useful when source cost varies.
|
|
64
|
+
*/
|
|
65
|
+
getSequential(id: C4ID): Promise<ResolveResult>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../../../../core/src/resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AACnC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAGvC;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IAEzB,qCAAqC;IACrC,GAAG,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE/B,oEAAoE;IACpE,GAAG,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAA;CACnD;AAED,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAA;IAClC,MAAM,EAAE,MAAM,CAAA;CACf;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAU,GAAG,aAAa,CAO7F;AAED;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAsB;IAErC,4BAA4B;IAC5B,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAKtC,+BAA+B;IAC/B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIhC,+BAA+B;IAC/B,WAAW,IAAI,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAIhE,kEAAkE;IAC5D,GAAG,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAQrC;;;;OAIG;IACG,GAAG,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;IAKxD;;;;;OAKG;IACG,OAAO,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;IAqC/C;;;OAGG;IACG,aAAa,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC;CAatD"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ContentNotFoundError } from './store.js';
|
|
2
|
+
/** Wrap any Store as a ContentSource. */
|
|
3
|
+
export function storeAsSource(store, name, priority = 0) {
|
|
4
|
+
return {
|
|
5
|
+
name,
|
|
6
|
+
priority,
|
|
7
|
+
has: (id) => store.has(id),
|
|
8
|
+
get: (id) => store.get(id),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Multi-source content resolver. Tries sources in parallel (race mode)
|
|
13
|
+
* or sequential (priority mode) to find content by C4 ID.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* const resolver = new ContentResolver()
|
|
17
|
+
* resolver.addSource(storeAsSource(localStore, 'local', 0))
|
|
18
|
+
* resolver.addSource(storeAsSource(remoteStore, 'remote', 10))
|
|
19
|
+
* const stream = await resolver.get(id)
|
|
20
|
+
*/
|
|
21
|
+
export class ContentResolver {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.sources = [];
|
|
24
|
+
}
|
|
25
|
+
/** Add a content source. */
|
|
26
|
+
addSource(source) {
|
|
27
|
+
this.sources.push(source);
|
|
28
|
+
this.sources.sort((a, b) => a.priority - b.priority);
|
|
29
|
+
}
|
|
30
|
+
/** Remove a source by name. */
|
|
31
|
+
removeSource(name) {
|
|
32
|
+
this.sources = this.sources.filter(s => s.name !== name);
|
|
33
|
+
}
|
|
34
|
+
/** List registered sources. */
|
|
35
|
+
listSources() {
|
|
36
|
+
return this.sources.map(s => ({ name: s.name, priority: s.priority }));
|
|
37
|
+
}
|
|
38
|
+
/** Check if any source has the content. Tries all in parallel. */
|
|
39
|
+
async has(id) {
|
|
40
|
+
if (this.sources.length === 0)
|
|
41
|
+
return false;
|
|
42
|
+
const results = await Promise.all(this.sources.map(s => s.has(id).catch(() => false)));
|
|
43
|
+
return results.some(r => r);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Retrieve content from the fastest available source (race mode).
|
|
47
|
+
* All sources that claim to have the content are raced;
|
|
48
|
+
* the first to deliver wins.
|
|
49
|
+
*/
|
|
50
|
+
async get(id) {
|
|
51
|
+
const result = await this.resolve(id);
|
|
52
|
+
return result.stream;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve content with metadata about which source served it.
|
|
56
|
+
* Uses a two-phase approach:
|
|
57
|
+
* 1. Check which sources have the content (parallel).
|
|
58
|
+
* 2. Race the available sources for the actual data.
|
|
59
|
+
*/
|
|
60
|
+
async resolve(id) {
|
|
61
|
+
if (this.sources.length === 0) {
|
|
62
|
+
throw new ContentNotFoundError(id);
|
|
63
|
+
}
|
|
64
|
+
// Phase 1: find which sources have it
|
|
65
|
+
const availability = await Promise.all(this.sources.map(async (s) => {
|
|
66
|
+
try {
|
|
67
|
+
return { source: s, available: await s.has(id) };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { source: s, available: false };
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
const available = availability
|
|
74
|
+
.filter(a => a.available)
|
|
75
|
+
.map(a => a.source);
|
|
76
|
+
if (available.length === 0) {
|
|
77
|
+
throw new ContentNotFoundError(id);
|
|
78
|
+
}
|
|
79
|
+
// Phase 2: race available sources
|
|
80
|
+
if (available.length === 1) {
|
|
81
|
+
const s = available[0];
|
|
82
|
+
return { stream: await s.get(id), source: s.name };
|
|
83
|
+
}
|
|
84
|
+
// Race multiple sources
|
|
85
|
+
const result = await Promise.any(available.map(async (s) => {
|
|
86
|
+
const stream = await s.get(id);
|
|
87
|
+
return { stream, source: s.name };
|
|
88
|
+
}));
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Sequential resolution — try sources in priority order, return first success.
|
|
93
|
+
* More predictable than race mode; useful when source cost varies.
|
|
94
|
+
*/
|
|
95
|
+
async getSequential(id) {
|
|
96
|
+
for (const source of this.sources) {
|
|
97
|
+
try {
|
|
98
|
+
if (await source.has(id)) {
|
|
99
|
+
const stream = await source.get(id);
|
|
100
|
+
return { stream, source: source.name };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
throw new ContentNotFoundError(id);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../../../../core/src/resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function safeName(raw: string): string;
|
|
2
|
+
export declare function unsafeName(encoded: string): string;
|
|
3
|
+
export declare function escapeC4MName(s: string, isSequence: boolean): string;
|
|
4
|
+
export declare function unescapeC4MName(s: string): string;
|
|
5
|
+
export declare function formatName(name: string, isSequence: boolean): string;
|
|
6
|
+
export declare function formatTarget(target: string): string;
|
|
7
|
+
//# sourceMappingURL=safename.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safename.d.ts","sourceRoot":"","sources":["../../../../../core/src/safename.ts"],"names":[],"mappings":"AAqDA,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAyE5C;AAID,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA4ElD;AAwDD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CA6CpE;AAID,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBjD;AAKD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,GAAG,MAAM,CASpE;AAID,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAqBnD"}
|