@legit-sdk/core 0.4.3 → 0.4.5

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/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { fileSave, FileWithHandle } from 'browser-fs-access';
2
2
  import * as nodeFs from 'node:fs';
3
3
  import { MakeDirectoryOptions, Mode } from 'node:fs';
4
4
  import * as memfs_lib_node_types_misc_js from 'memfs/lib/node/types/misc.js';
5
- import { IDir, TFileHandleReadResult, TData, TMode, IStats, TTime, TFileHandleWriteResult, TFileHandleWritevResult, TFileHandleReadvResult, IFileHandle, TDataOut, IDirent, PathLike } from 'memfs/lib/node/types/misc.js';
5
+ import { IDir, PathLike, TFileHandleReadResult, TData, TMode, IStats, TTime, TFileHandleWriteResult, TFileHandleWritevResult, TFileHandleReadvResult, IFileHandle, TDataOut, IDirent } from 'memfs/lib/node/types/misc.js';
6
6
  import { IAppendFileOptions, IStatOptions, IReadFileOptions, IWriteFileOptions, IReadableWebStreamOptions } from 'memfs/lib/node/types/options.js';
7
7
  import { PathLike as PathLike$1 } from 'fs';
8
8
  import { IFs } from 'memfs';
@@ -28,6 +28,117 @@ type LegitAuth = {
28
28
 
29
29
  type CompositeSubFsDir = Pick<IDir, "path" | "close" | "read" | typeof Symbol.asyncIterator>;
30
30
 
31
+ /**
32
+ * Context information for filesystem operations
33
+ * This context is provided by CompositeFs when routing to a specific SubFS
34
+ */
35
+ interface FsOperationContext {
36
+ /** The full original path that was requested */
37
+ fullPath: string;
38
+ /** Extracted route parameters (e.g., { branchName: 'main', filePath: 'path/to/file' }) */
39
+ params: Record<string, string>;
40
+ /** Static siblings from route matching */
41
+ staticSiblings: {
42
+ segment: string;
43
+ type: 'folder' | 'file';
44
+ }[];
45
+ }
46
+
47
+ declare abstract class BaseCompositeSubFs implements CompositeSubFs {
48
+ /** Reference to the parent CompositeFs instance - late init */
49
+ compositeFs: CompositeFs;
50
+ /** Context for the current filesystem operation */
51
+ context?: FsOperationContext;
52
+ newContext?: FsOperationContext;
53
+ fsType: FsType;
54
+ /**
55
+ * Unique instance ID that persists across contextual instances
56
+ * This allows us to check if two contextual instances wrap the same base SubFS
57
+ */
58
+ readonly rootInstanceId: string;
59
+ name: string;
60
+ rootPath: string;
61
+ constructor({ name, rootPath }: {
62
+ name: string;
63
+ rootPath: string;
64
+ });
65
+ attach(compositFs: CompositeFs): void;
66
+ /**
67
+ * Create a new instance with context bound to it
68
+ * This creates a shallow copy where all mutable state (like open file handles)
69
+ * is shared between the original and the contextual instance, but the context
70
+ * is unique to this instance.
71
+ *
72
+ * Each operation gets its own contextual instance, ensuring that concurrent
73
+ * operations don't interfere with each other's context.
74
+ */
75
+ withContext(context: FsOperationContext): this;
76
+ abstract responsible(filePath: string): Promise<boolean>;
77
+ abstract fileType(): number;
78
+ open(path: PathLike, flags: string, mode?: number): Promise<CompositFsFileHandle>;
79
+ access(path: PathLike, mode?: number): Promise<void>;
80
+ stat(path: PathLike, opts?: {
81
+ bigint?: false;
82
+ }): Promise<nodeFs.Stats>;
83
+ stat(path: PathLike, opts: {
84
+ bigint: true;
85
+ }): Promise<nodeFs.BigIntStats>;
86
+ stat(path: PathLike, opts?: {
87
+ bigint?: boolean;
88
+ }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
89
+ lstat(path: PathLike, opts?: {
90
+ bigint?: false;
91
+ }): Promise<nodeFs.Stats>;
92
+ lstat(path: PathLike, opts: {
93
+ bigint: true;
94
+ }): Promise<nodeFs.BigIntStats>;
95
+ lstat(path: PathLike, opts?: {
96
+ bigint?: boolean;
97
+ }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
98
+ opendir(path: PathLike, options?: nodeFs.OpenDirOptions): Promise<CompositeSubFsDir>;
99
+ link(existingPath: PathLike, newPath: PathLike): Promise<void>;
100
+ mkdir(path: PathLike, options?: nodeFs.MakeDirectoryOptions | nodeFs.Mode | null): Promise<void>;
101
+ readDirFiltering?(path: PathLike, entries: IDir[] | string[]): Promise<string[]>;
102
+ readdir(path: PathLike, options?: (nodeFs.ObjectEncodingOptions & {
103
+ withFileTypes?: false | undefined;
104
+ recursive?: boolean | undefined;
105
+ }) | BufferEncoding | null): Promise<string[]>;
106
+ readdir(path: PathLike, options?: {
107
+ encoding: 'buffer';
108
+ withFileTypes?: false | undefined;
109
+ recursive?: boolean | undefined;
110
+ } | 'buffer' | null): Promise<Buffer[]>;
111
+ readdir(path: PathLike, options?: (nodeFs.ObjectEncodingOptions & {
112
+ withFileTypes?: false | undefined;
113
+ recursive?: boolean | undefined;
114
+ }) | BufferEncoding | null): Promise<string[] | Buffer[]>;
115
+ readdir(path: PathLike, options: nodeFs.ObjectEncodingOptions & {
116
+ withFileTypes: true;
117
+ recursive?: boolean | undefined;
118
+ }): Promise<nodeFs.Dirent[]>;
119
+ readlink(path: PathLike, ...args: any[]): Promise<any>;
120
+ unlink(path: PathLike): Promise<void>;
121
+ rename(oldPath: PathLike, newPath: PathLike): Promise<void>;
122
+ rmdir(path: PathLike, ...args: any[]): Promise<void>;
123
+ symlink(target: PathLike, path: PathLike, type?: string | null): Promise<void>;
124
+ lookup(filePath: string): Promise<number>;
125
+ resolvePath(fd: number): string;
126
+ close(fh: CompositFsFileHandle): Promise<void>;
127
+ dataSync(fh: CompositFsFileHandle): Promise<void>;
128
+ read(fh: CompositFsFileHandle, buffer: Buffer | Uint8Array, offset: number, length: number, position: number): Promise<TFileHandleReadResult>;
129
+ appendFile(fh: CompositFsFileHandle, data: TData, options?: IAppendFileOptions | string): Promise<void>;
130
+ fchmod(fh: CompositFsFileHandle, mode: TMode): Promise<void>;
131
+ fchown(fh: CompositFsFileHandle, uid: number, gid: number): Promise<void>;
132
+ ftruncate(fh: CompositFsFileHandle, len?: number): Promise<void>;
133
+ fstat(fh: CompositFsFileHandle, options?: IStatOptions): Promise<IStats>;
134
+ futimes(fh: CompositFsFileHandle, atime: TTime, mtime: TTime): Promise<void>;
135
+ write(fh: CompositFsFileHandle, buffer: Buffer | ArrayBufferView | DataView, offset?: number, length?: number, position?: number): Promise<TFileHandleWriteResult>;
136
+ writev(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleWritevResult>;
137
+ readv(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleReadvResult>;
138
+ readFile(path: PathLike | IFileHandle, options?: IReadFileOptions | string): Promise<TDataOut>;
139
+ writeFile(path: string, data: TData, options: IWriteFileOptions | string): Promise<void>;
140
+ }
141
+
31
142
  type FileHandleDelegate = {
32
143
  name: string;
33
144
  fileType: () => number;
@@ -49,9 +160,24 @@ type FileHandleDelegate = {
49
160
  readv(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleReadvResult>;
50
161
  };
51
162
 
163
+ type FsType = 'folder' | 'file' | 'fs';
52
164
  type CompositeSubFs = Pick<typeof nodeFs.promises, 'access' | 'link' | 'readdir' | 'readlink' | 'unlink' | 'rename' | 'rmdir' | 'symlink'> & {
53
165
  name: string;
54
- readFile(path: nodeFs.PathLike | IFileHandle, options?: IReadFileOptions | string): Promise<TDataOut>;
166
+ /**
167
+ * Explicit declaration of the filesystem type.
168
+ * - 'folder': This SubFS only handles directories
169
+ * - 'file': This SubFS only handles files
170
+ * - 'fs': This SubFS handles both files and folders (default)
171
+ */
172
+ fsType: FsType;
173
+ context?: FsOperationContext;
174
+ newContext?: FsOperationContext;
175
+ readonly rootInstanceId: string;
176
+ compositeFs: CompositeFs;
177
+ withContext(context: FsOperationContext): BaseCompositeSubFs;
178
+ attach(compositFs: CompositeFs): void;
179
+ readDirFiltering?(path: nodeFs.PathLike, entries: string[]): Promise<string[]>;
180
+ readFile(path: nodeFs.PathLike, options?: IReadFileOptions | string): Promise<TDataOut>;
55
181
  stat(path: nodeFs.PathLike, opts?: IStatOptions & {
56
182
  bigint?: false;
57
183
  }): Promise<IStats<number>>;
@@ -162,17 +288,138 @@ declare const createFsOperationFileLogger: (fs: {
162
288
  operationArgs: any;
163
289
  }) => Promise<void>;
164
290
 
291
+ /**
292
+ * Routes filesystem paths to appropriate SubFS handlers using a pattern-based system.
293
+ *
294
+ * The PathRouter supports a flexible pattern syntax for defining dynamic routes:
295
+ *
296
+ * | Pattern | Description | Example | Matches |
297
+ * |----------------|-----------------------|-------------------|------------------------------------------------------|
298
+ * | `static` | Static segment | `branches` | `branches` only |
299
+ * | `[param]` | Dynamic segment | `[branchName]` | `main`, `dev`, etc. (single segment) |
300
+ * | `[[...param]]` | Optional catch-all | `[[...filePath]]` | matches zero or more segments |
301
+ * | `.` | Folder index handler | `{ '.': handler }`| handles the folder itself |
302
+ *
303
+ * Priority is given to more specific routes (static > dynamic > catch-all).
304
+ *
305
+ * ## Static Sibling Union
306
+ *
307
+ * When multiple route patterns match at the same parent path level, their static siblings
308
+ * are merged (union operation). For example, if both `.legit` and `[[...filePath]]/.legit`
309
+ * match at root, the static siblings from both routes are combined.
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const router = new PathRouter({
314
+ * '[[...filePath]]': {
315
+ * '.': catchAllHandler,
316
+ * '.legit': { changes: changesHandler }
317
+ * },
318
+ * '.legit': {
319
+ * '.': legitHandler,
320
+ * head: headHandler,
321
+ * branches: { ... }
322
+ * }
323
+ * }, '/root');
324
+ *
325
+ * // Match with highest priority handler and union of all static siblings
326
+ * const result = router.match('/root/.legit');
327
+ * // result.handler === legitHandler (higher priority)
328
+ * // result.staticSiblings === [
329
+ * // { segment: 'head', type: 'file' }, // from .legit route
330
+ * // { segment: 'branches', type: 'folder' }, // from .legit route
331
+ * // { segment: 'changes', type: 'file' } // from [[...filePath]]/.legit
332
+ * // ]
333
+ * ```
334
+ */
335
+
336
+ type MatchResult = {
337
+ handler: CompositeSubFs;
338
+ matchingPattern: string;
339
+ staticSiblings: {
340
+ segment: string;
341
+ type: 'folder' | 'file';
342
+ }[];
343
+ params: Record<string, string>;
344
+ };
345
+ interface LegitRouteFolder {
346
+ [key: string]: PathRouteDescription;
347
+ }
348
+ type PathRouteDescription = CompositeSubFs | LegitRouteFolder;
349
+ /**
350
+ * PathRouter matches filesystem paths to SubFS handlers using pattern-based routing.
351
+ *
352
+ * The router evaluates all compiled patterns twice:
353
+ * 1. To find the highest-priority matching handler for the requested path
354
+ * 2. To find all patterns that match the parent path, merging their static siblings (union)
355
+ *
356
+ * This allows dynamic routes like `[[...filePath]]/.legit` to contribute siblings
357
+ * that are visible when matching the static `.legit` route.
358
+ *
359
+ * @example
360
+ * ```typescript
361
+ * const router = new PathRouter({
362
+ * '[[...filePath]]': {
363
+ * '.': catchAllHandler,
364
+ * '.legit': { changes: changesHandler }
365
+ * },
366
+ * '.legit': {
367
+ * '.': legitHandler,
368
+ * head: headHandler,
369
+ * }
370
+ * }, '/root');
371
+ *
372
+ * const result = router.match('/root/.legit');
373
+ * // Returns:
374
+ * // {
375
+ * // handler: legitHandler, // highest priority match
376
+ * // params: {},
377
+ * // staticSiblings: [
378
+ * // { segment: 'head', type: 'file' },
379
+ * // { segment: 'changes', type: 'file' } // merged from catchall route!
380
+ * // ]
381
+ * // }
382
+ * ```
383
+ */
384
+ declare class PathRouter {
385
+ routes: LegitRouteFolder;
386
+ private rootPath;
387
+ private compiledRoutes;
388
+ /**
389
+ * Creates a new PathRouter with the given route configuration.
390
+ *
391
+ * @param routes - The route tree defining all possible paths
392
+ * @param rootPath - The root path to strip from incoming paths (e.g., '/root')
393
+ */
394
+ constructor(routes: LegitRouteFolder, rootPath: string);
395
+ /**
396
+ * Matches a path against all route patterns and returns the best match.
397
+ *
398
+ * The matching algorithm:
399
+ * 1. Normalizes the path by removing the root prefix
400
+ * 2. Finds all route patterns that match the path (by priority order)
401
+ * 3. Returns the highest-priority match
402
+ * 4. For static siblings, evaluates ALL patterns against the parent path
403
+ * and merges their siblings (union operation)
404
+ *
405
+ * @param path - The full filesystem path to match
406
+ * @returns Match result with handler, params, and union of static siblings, or undefined if no match
407
+ */
408
+ match(path: string): MatchResult | undefined;
409
+ }
410
+
165
411
  /**
166
412
  *
167
413
  * The CompositFs handles distribution of file operations to its sub filesystems and keeps track of open file handles.
168
- * open returns a CompositFsFileHandle that wraps the real filehandle from the responsible SubFs and allows
414
+ *
415
+ * open() returns a CompositFsFileHandle that wraps the real filehandle from the responsible SubFs and allows
169
416
  * to forward operations
170
417
  *
171
418
  * Each SubFs determines if it is responsible for a given path. The Composite fs probes each subsystem for responsibility.
172
419
  *
173
420
  * The responisbility is probed in the following order:
174
421
  *
175
- * hiddenFilesFileSystem -> ephemeralFilesFileSystem -> other subFs in order of addition -> passThroughFileSystem
422
+ * hiddenFilesFileSystem -> ephemeralFilesFileSystem -> other subFs in order of addition
176
423
  *
177
424
  * Composit fs consists of two special sub filesystems:
178
425
  *
@@ -223,52 +470,42 @@ declare class CompositeFs {
223
470
  writeFile: (file: nodeFs.PathOrFileDescriptor, data: string | Buffer | Uint8Array, options?: any) => Promise<void>;
224
471
  getFilehandle: (fd: number) => CompositFsFileHandle | undefined;
225
472
  };
226
- gitRoot: string;
227
- ephemeralFilesFileSystem: CompositeSubFs | undefined;
228
- hiddenFilesFileSystem: CompositeSubFs | undefined;
229
- passThroughFileSystem: CompositeSubFs;
230
- subFilesystems: CompositeSubFs[];
473
+ filterLayers: CompositeSubFs[];
474
+ router: PathRouter;
231
475
  parentFs: CompositeFs | undefined;
232
476
  name: string;
233
- defaultBranch: string;
234
- gitCache: any;
477
+ rootPath: string;
235
478
  pathToFileDescriptors: Map<
236
479
  /** path */
237
480
  string, number[]>;
238
481
  openFileHandles: Map<number, CompositFsFileHandle>;
239
482
  logOperation: FsOperationLogger | undefined;
240
483
  private getNextFileDescriptor;
241
- constructor({ name, storageFs, gitRoot, defaultBranch, }: {
484
+ constructor({ name, filterLayers, routes, rootPath, }: {
242
485
  name: string;
243
- storageFs: typeof nodeFs;
244
- gitRoot: string;
245
- defaultBranch?: string;
486
+ filterLayers: CompositeSubFs[];
487
+ routes: LegitRouteFolder;
488
+ rootPath: string;
246
489
  });
247
490
  setLoggger(logger: FsOperationLogger | undefined): void;
248
491
  logOperationOnFileDescsriptor(fd: CompositFsFileHandle, operation: string, args: any): Promise<void>;
249
492
  getFilehandle(fd: number): CompositFsFileHandle | undefined;
250
- setEphemeralFilesSubFs(subFs: CompositeSubFs): void;
251
- setHiddenFilesSubFs(subFs: CompositeSubFs): void;
252
- addSubFs(subFs: CompositeSubFs): void;
253
493
  /**
254
- * helper function that takes a filePath and returns the fs that is responsible Sub filesystem for it
494
+ * Helper function that takes a filePath and returns the sub fs that is responsible for it.
495
+ *
496
+ * Order is:
497
+ * hidden -> if hidden no more questions - hide it!
498
+ *
499
+ * ephemeral -> file is marked as ephemeral *.DS_STORE and lock files - ignore if versioned at some point - always handle it as ephemeral
500
+ * versioned ->
501
+ * nonVersioned -> files
502
+ *
503
+ * other subFs in order of addition -> passThrough
255
504
  * @param filePath
256
- * @returns
505
+ * @returns A contextual SubFS instance with route information bound to it
257
506
  */
258
507
  private getResponsibleFs;
259
508
  access(filePath: string, mode?: number): Promise<void>;
260
- opendir(dirPath: nodeFs.PathLike, options?: nodeFs.OpenDirOptions): Promise<CompositeFsDir>;
261
- mkdir(dirPath: string, options?: any): Promise<void>;
262
- /**
263
- * Read dir needs to check if one subfs takes control.
264
- *
265
- * @param dirPath
266
- * @param options
267
- * @returns
268
- */
269
- readdir(dirPath: nodeFs.PathLike, options?: any): Promise<string[]>;
270
- open(filePath: string, flags: string, mode?: number): Promise<CompositFsFileHandle>;
271
- close(fh: CompositFsFileHandle): Promise<void>;
272
509
  stat(path: nodeFs.PathLike, opts?: {
273
510
  bigint?: false;
274
511
  }): Promise<nodeFs.Stats>;
@@ -287,11 +524,23 @@ declare class CompositeFs {
287
524
  lstat(path: nodeFs.PathLike, opts?: {
288
525
  bigint?: boolean;
289
526
  }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
527
+ unlink(filePath: nodeFs.PathLike): Promise<void>;
528
+ mkdir(dirPath: string, options?: any): Promise<void>;
529
+ rmdir(dirPath: nodeFs.PathLike, options?: nodeFs.RmDirOptions): Promise<void>;
530
+ opendir(dirPath: nodeFs.PathLike, options?: nodeFs.OpenDirOptions): Promise<CompositeFsDir>;
531
+ /**
532
+ * Read dir needs to check if one subfs takes control.
533
+ *
534
+ * @param dirPath
535
+ * @param options
536
+ * @returns
537
+ */
538
+ readdir(dirPath: nodeFs.PathLike, options?: any): Promise<string[]>;
539
+ open(filePath: string, flags: string, mode?: number): Promise<CompositFsFileHandle>;
540
+ close(fh: CompositFsFileHandle): Promise<void>;
290
541
  link(existingPath: nodeFs.PathLike, newPath: nodeFs.PathLike): Promise<void>;
291
542
  readlink(path: nodeFs.PathLike, options?: nodeFs.ObjectEncodingOptions | BufferEncoding | null): Promise<void>;
292
- unlink(filePath: nodeFs.PathLike): Promise<void>;
293
543
  rename(oldPath: nodeFs.PathLike, newPath: nodeFs.PathLike): Promise<void>;
294
- rmdir(dirPath: nodeFs.PathLike, options?: nodeFs.RmDirOptions): Promise<void>;
295
544
  symlink(target: nodeFs.PathLike, path: nodeFs.PathLike, type?: string | null): Promise<void>;
296
545
  readFile(path: nodeFs.PathOrFileDescriptor, options?: {
297
546
  encoding?: null;
@@ -317,6 +566,7 @@ declare function openLegitFsWithMemoryFs(props?: Parameters<typeof openLegitFs>[
317
566
  loadBranch: (branch: string) => Promise<void>;
318
567
  sequentialPush: (branchesToPush: string[]) => Promise<void>;
319
568
  };
569
+ _storageFs: CompositeFs;
320
570
  setLogger(logger: FsOperationLogger | undefined): void;
321
571
  push: (branches: string[]) => Promise<void>;
322
572
  shareCurrentBranch: () => Promise<string>;
@@ -326,7 +576,7 @@ declare function openLegitFsWithMemoryFs(props?: Parameters<typeof openLegitFs>[
326
576
  /**
327
577
  * Creates and configures a LegitFs instance with CompositeFs, GitSubFs, HiddenFileSubFs, and EphemeralSubFs.
328
578
  */
329
- declare function openLegitFs({ storageFs, gitRoot, anonymousBranch, showKeepFiles, initialAuthor, serverUrl, publicKey, claudeHandler, }: {
579
+ declare function openLegitFs({ storageFs, gitRoot, anonymousBranch, showKeepFiles, initialAuthor, serverUrl, publicKey, ephemaralGitConfig, additionalFilterLayers, }: {
330
580
  storageFs: typeof nodeFs;
331
581
  gitRoot: string;
332
582
  anonymousBranch?: string;
@@ -334,7 +584,8 @@ declare function openLegitFs({ storageFs, gitRoot, anonymousBranch, showKeepFile
334
584
  initialAuthor?: LegitUser;
335
585
  serverUrl?: string;
336
586
  publicKey?: string;
337
- claudeHandler?: boolean;
587
+ ephemaralGitConfig?: boolean;
588
+ additionalFilterLayers?: CompositeSubFs[];
338
589
  }): Promise<CompositeFs & {
339
590
  auth: LegitAuth;
340
591
  sync: {
@@ -344,6 +595,7 @@ declare function openLegitFs({ storageFs, gitRoot, anonymousBranch, showKeepFile
344
595
  loadBranch: (branch: string) => Promise<void>;
345
596
  sequentialPush: (branchesToPush: string[]) => Promise<void>;
346
597
  };
598
+ _storageFs: CompositeFs;
347
599
  setLogger(logger: FsOperationLogger | undefined): void;
348
600
  push: (branches: string[]) => Promise<void>;
349
601
  shareCurrentBranch: () => Promise<string>;
@@ -358,81 +610,6 @@ declare function getLegitFsAccess(legitFs: Awaited<ReturnType<typeof openLegitFs
358
610
  openFile: (filePath: string) => Promise<FileWithHandle>;
359
611
  }>;
360
612
 
361
- declare abstract class BaseCompositeSubFs implements CompositeSubFs {
362
- protected toStr(p: any): string;
363
- protected compositFs: CompositeFs;
364
- protected gitRoot: string;
365
- name: string;
366
- constructor({ name, parentFs, gitRoot, }: {
367
- name: string;
368
- parentFs: CompositeFs;
369
- gitRoot: string;
370
- });
371
- abstract responsible(filePath: string): Promise<boolean>;
372
- abstract fileType(): number;
373
- open(path: PathLike, flags: string, mode?: number): Promise<CompositFsFileHandle>;
374
- access(path: PathLike, mode?: number): Promise<void>;
375
- stat(path: PathLike, opts?: {
376
- bigint?: false;
377
- }): Promise<nodeFs.Stats>;
378
- stat(path: PathLike, opts: {
379
- bigint: true;
380
- }): Promise<nodeFs.BigIntStats>;
381
- stat(path: PathLike, opts?: {
382
- bigint?: boolean;
383
- }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
384
- lstat(path: PathLike, opts?: {
385
- bigint?: false;
386
- }): Promise<nodeFs.Stats>;
387
- lstat(path: PathLike, opts: {
388
- bigint: true;
389
- }): Promise<nodeFs.BigIntStats>;
390
- lstat(path: PathLike, opts?: {
391
- bigint?: boolean;
392
- }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
393
- opendir(path: PathLike, options?: nodeFs.OpenDirOptions): Promise<CompositeSubFsDir>;
394
- link(existingPath: PathLike, newPath: PathLike): Promise<void>;
395
- mkdir(path: PathLike, options?: nodeFs.MakeDirectoryOptions | nodeFs.Mode | null): Promise<void>;
396
- readdir(path: PathLike, options?: (nodeFs.ObjectEncodingOptions & {
397
- withFileTypes?: false | undefined;
398
- recursive?: boolean | undefined;
399
- }) | BufferEncoding | null): Promise<string[]>;
400
- readdir(path: PathLike, options?: {
401
- encoding: 'buffer';
402
- withFileTypes?: false | undefined;
403
- recursive?: boolean | undefined;
404
- } | 'buffer' | null): Promise<Buffer[]>;
405
- readdir(path: PathLike, options?: (nodeFs.ObjectEncodingOptions & {
406
- withFileTypes?: false | undefined;
407
- recursive?: boolean | undefined;
408
- }) | BufferEncoding | null): Promise<string[] | Buffer[]>;
409
- readdir(path: PathLike, options: nodeFs.ObjectEncodingOptions & {
410
- withFileTypes: true;
411
- recursive?: boolean | undefined;
412
- }): Promise<nodeFs.Dirent[]>;
413
- readlink(path: PathLike, ...args: any[]): Promise<any>;
414
- unlink(path: PathLike): Promise<void>;
415
- rename(oldPath: PathLike, newPath: PathLike): Promise<void>;
416
- rmdir(path: PathLike, ...args: any[]): Promise<void>;
417
- symlink(target: PathLike, path: PathLike, type?: string | null): Promise<void>;
418
- lookup(filePath: string): Promise<number>;
419
- resolvePath(fd: number): string;
420
- close(fh: CompositFsFileHandle): Promise<void>;
421
- dataSync(fh: CompositFsFileHandle): Promise<void>;
422
- read(fh: CompositFsFileHandle, buffer: Buffer | Uint8Array, offset: number, length: number, position: number): Promise<TFileHandleReadResult>;
423
- appendFile(fh: CompositFsFileHandle, data: TData, options?: IAppendFileOptions | string): Promise<void>;
424
- fchmod(fh: CompositFsFileHandle, mode: TMode): Promise<void>;
425
- fchown(fh: CompositFsFileHandle, uid: number, gid: number): Promise<void>;
426
- ftruncate(fh: CompositFsFileHandle, len?: number): Promise<void>;
427
- fstat(fh: CompositFsFileHandle, options?: IStatOptions): Promise<IStats>;
428
- futimes(fh: CompositFsFileHandle, atime: TTime, mtime: TTime): Promise<void>;
429
- write(fh: CompositFsFileHandle, buffer: Buffer | ArrayBufferView | DataView, offset?: number, length?: number, position?: number): Promise<TFileHandleWriteResult>;
430
- writev(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleWritevResult>;
431
- readv(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleReadvResult>;
432
- readFile(path: PathLike | IFileHandle, options?: IReadFileOptions | string): Promise<TDataOut>;
433
- writeFile(path: string, data: TData, options: IWriteFileOptions | string): Promise<void>;
434
- }
435
-
436
613
  /**
437
614
  * FS utilized to hide files, it is responsible for files found in hiddenFiles
438
615
  *
@@ -440,11 +617,10 @@ declare abstract class BaseCompositeSubFs implements CompositeSubFs {
440
617
  */
441
618
  declare class HiddenFileSubFs extends BaseCompositeSubFs {
442
619
  private ig;
443
- constructor({ name, parentFs, gitRoot, hiddenFiles, }: {
620
+ constructor({ name, hiddenFiles, rootPath, }: {
444
621
  name: string;
445
- parentFs: CompositeFs;
446
- gitRoot: string;
447
622
  hiddenFiles: string[];
623
+ rootPath: string;
448
624
  });
449
625
  responsible(filePath: string): Promise<boolean>;
450
626
  fileType(): number;
@@ -456,6 +632,7 @@ declare class HiddenFileSubFs extends BaseCompositeSubFs {
456
632
  opendir(path: PathLike$1): Promise<any>;
457
633
  link(existingPath: PathLike$1): Promise<void>;
458
634
  mkdir(path: PathLike$1): Promise<any>;
635
+ readDirFiltering?(path: PathLike$1, entries: string[]): Promise<string[]>;
459
636
  readdir(path: PathLike$1): Promise<any>;
460
637
  readlink(path: PathLike$1): Promise<any>;
461
638
  unlink(path: PathLike$1): Promise<void>;
@@ -479,10 +656,9 @@ declare class EphemeralSubFs extends BaseCompositeSubFs {
479
656
  private ig;
480
657
  patterns: string[];
481
658
  private normalizePath;
482
- constructor({ name, parentFs, gitRoot, ephemeralPatterns, }: {
659
+ constructor({ name, rootPath, ephemeralPatterns, }: {
483
660
  name: string;
484
- parentFs: CompositeFs;
485
- gitRoot: string;
661
+ rootPath: string;
486
662
  ephemeralPatterns: string[];
487
663
  });
488
664
  responsible(filePath: string): Promise<boolean>;
@@ -533,6 +709,88 @@ declare class EphemeralSubFs extends BaseCompositeSubFs {
533
709
  writeFile(filePath: string, data: TData, options: IWriteFileOptions | string): Promise<void>;
534
710
  }
535
711
 
712
+ /**
713
+ * Copy-on-Write filesystem implementation
714
+ *
715
+ * This SubFs provides copy-on-write semantics:
716
+ * - Reads check copyToFs first, then fall back to sourceFs
717
+ * - Writes always go to copyToFs (creating a copy if needed)
718
+ * - Configured with patterns to determine which files to track
719
+ * - Original files in sourceFs are never modified
720
+ */
721
+ declare class CopyOnWriteSubFs extends BaseCompositeSubFs {
722
+ private openFh;
723
+ private sourceFs;
724
+ private copyToFs;
725
+ private copyPath;
726
+ private ig;
727
+ patterns: string[];
728
+ constructor({ name, sourceFs, copyToFs, copyToRootPath, rootPath, patterns, }: {
729
+ name: string;
730
+ sourceFs: any;
731
+ copyToFs: any;
732
+ copyToRootPath: string;
733
+ rootPath: string;
734
+ patterns: string[];
735
+ });
736
+ private normalizeCopyPath;
737
+ private normalizePath;
738
+ responsible(filePath: string): Promise<boolean>;
739
+ fileType(): number;
740
+ /**
741
+ * Check if a file has been copied (exists in copyToFs)
742
+ */
743
+ private isCopied;
744
+ /**
745
+ * Copy a file from sourceFs to copyToFs
746
+ */
747
+ private copyFromSource;
748
+ open(filePath: string, flags: string, mode?: number): Promise<CompositFsFileHandle>;
749
+ access(filePath: PathLike$1, mode?: number): Promise<void>;
750
+ stat(statPath: PathLike$1, opts?: {
751
+ bigint?: false;
752
+ }): Promise<nodeFs.Stats>;
753
+ stat(statPath: PathLike$1, opts: {
754
+ bigint: true;
755
+ }): Promise<nodeFs.BigIntStats>;
756
+ stat(statPath: PathLike$1, opts?: {
757
+ bigint?: boolean;
758
+ }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
759
+ lstat(lstatPath: PathLike$1, opts?: {
760
+ bigint?: false;
761
+ }): Promise<nodeFs.Stats>;
762
+ lstat(lstatPath: PathLike$1, opts: {
763
+ bigint: true;
764
+ }): Promise<nodeFs.BigIntStats>;
765
+ lstat(lstatPath: PathLike$1, opts?: {
766
+ bigint?: boolean;
767
+ }): Promise<nodeFs.Stats | nodeFs.BigIntStats>;
768
+ opendir(folderPath: nodeFs.PathLike, options?: nodeFs.OpenDirOptions): Promise<CompositeSubFsDir>;
769
+ link(existingPath: PathLike$1, newPath: PathLike$1): Promise<void>;
770
+ mkdir(dirPath: PathLike$1, options?: nodeFs.MakeDirectoryOptions | nodeFs.Mode | null): Promise<void>;
771
+ readdir(readdirPath: PathLike$1, ...args: any[]): Promise<any>;
772
+ readlink(readlinkPath: PathLike$1): Promise<any>;
773
+ unlink(unlinkPath: PathLike$1): Promise<void>;
774
+ rename(oldPath: PathLike$1, newPath: PathLike$1): Promise<void>;
775
+ rmdir(rmdirPath: PathLike$1, options?: nodeFs.RmDirOptions): Promise<void>;
776
+ symlink(target: PathLike$1, linkPath: PathLike$1, type?: string | null): Promise<void>;
777
+ lookup(filePath: string): Promise<number>;
778
+ resolvePath(fd: number): string;
779
+ close(fh: CompositFsFileHandle): Promise<void>;
780
+ dataSync(fh: CompositFsFileHandle): Promise<void>;
781
+ read(fh: CompositFsFileHandle, buffer: Buffer | Uint8Array, offset: number, length: number, position: number): Promise<TFileHandleReadResult>;
782
+ fchmod(fh: CompositFsFileHandle, mode: TMode): Promise<void>;
783
+ fchown(fh: CompositFsFileHandle, uid: number, gid: number): Promise<void>;
784
+ write(fh: CompositFsFileHandle, buffer: Buffer | ArrayBufferView | DataView, offset?: number, length?: number, position?: number): Promise<TFileHandleWriteResult>;
785
+ ftruncate(fh: CompositFsFileHandle, len?: number): Promise<void>;
786
+ fstat(fh: CompositFsFileHandle, options?: IStatOptions): Promise<IStats>;
787
+ futimes(fh: CompositFsFileHandle, atime: TTime, mtime: TTime): Promise<void>;
788
+ writev(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleWritevResult>;
789
+ readv(fh: CompositFsFileHandle, buffers: ArrayBufferView[], position?: number | null): Promise<TFileHandleReadvResult>;
790
+ readFile(filePath: PathLike$1 | IFileHandle, options?: IReadFileOptions | string): Promise<TDataOut>;
791
+ writeFile(filePath: string, data: TData, options: IWriteFileOptions | string): Promise<void>;
792
+ }
793
+
536
794
  /**
537
795
  * FS utilized to provide pass-through access to the underlying filesystem
538
796
  */
@@ -540,10 +798,10 @@ declare class PassThroughSubFs extends BaseCompositeSubFs {
540
798
  private openFh;
541
799
  private memFs;
542
800
  private targetFs;
543
- constructor({ name, parentFs, gitRoot, }: {
801
+ constructor({ name, parentFs, rootPath, }: {
544
802
  name: string;
545
803
  parentFs: CompositeFs;
546
- gitRoot: string;
804
+ rootPath: string;
547
805
  });
548
806
  responsible(filePath: string): Promise<boolean>;
549
807
  fileType(): number;
@@ -619,6 +877,14 @@ interface VirtualFileArgs {
619
877
  timezoneOffset: number;
620
878
  };
621
879
  }
880
+ /**
881
+ * Definition of a virtual file in the Git subsystem
882
+ *
883
+ * ->
884
+ *
885
+ * writeFile -> writes the whole file
886
+ * readFile -> writes the whole file
887
+ */
622
888
  type VirtualFileDefinition = {
623
889
  type: string;
624
890
  rootType: 'folder' | 'file';
@@ -639,73 +905,52 @@ type VirtualFileDefinition = {
639
905
  rmdir?: (args: VirtualFileArgs) => Promise<void>;
640
906
  };
641
907
 
642
- interface LegitRouteFolder {
643
- [key: string]: LegitRouteDescriptor;
644
- }
645
- type LegitRouteDescriptor = VirtualFileDefinition | LegitRouteFolder;
646
-
647
- /**
648
- * Topics to discuss:
649
- * # Ref space polution
650
- * - Local vs Remote (we dont push all refs all the time)
651
- * - required refs (what are the miniumum refs required to keep the repo healthy)
652
- * - ref branch concept (branch pointing to oids to keep refs alive withouth poluting refs)
653
- *
654
- * # Performance (we tackle whens appear)
655
- * - Git operations (especially history traversal) can be expensive
656
- * - Current memfs caching might not scale for large repos
657
- * - Consider lazy loading and bounded caches
658
- */
659
908
  /**
660
- * Git-backed CompositeSubFs implementation.
909
+ * CompositeSubFsAdapter - Adapts a virtual file handler to work as a SubFS
661
910
  *
911
+ * This class adapts a single VirtualFileDefinition to work as a CompositeSubFs.
912
+ * It receives routing context (extracted parameters) from CompositeFs, eliminating the need for internal routing.
662
913
  *
663
- * docx file
664
- * - we unpack the docx and store xml files as blobs in git
665
- * mpeg file
666
- * - we chunk the file and sstsore chunks as blobs in git
667
- **/
668
- declare class GitSubFs extends BaseCompositeSubFs implements CompositeSubFs {
669
- private static readonly LEGIT_DIR;
670
- private pathRouter;
914
+ * Each virtual file type (branch file, commit file, etc.) gets its own adapter instance.
915
+ */
916
+ declare class CompositeSubFsAdapter extends BaseCompositeSubFs implements CompositeSubFs {
671
917
  private memFs;
672
918
  private openFh;
673
919
  storageFs: any;
920
+ protected gitRoot: string;
921
+ /**
922
+ * The virtual file handler for this adapter
923
+ * This defines how to read/write/stat the virtual file
924
+ */
925
+ handler: VirtualFileDefinition;
674
926
  getAuthor(): Promise<{
675
927
  name: string;
676
928
  email: string;
677
929
  date: number;
678
930
  timezoneOffset: number;
679
931
  }>;
680
- constructor({ name, parentFs, gitStorageFs, gitRoot, routerConfig, }: {
932
+ constructor({ name, gitStorageFs, gitRoot, handler, rootPath, }: {
681
933
  name: string;
682
- parentFs: CompositeFs;
683
934
  gitStorageFs: any;
684
935
  gitRoot: string;
685
- routerConfig: LegitRouteFolder;
936
+ handler: VirtualFileDefinition;
937
+ rootPath: string;
686
938
  });
687
939
  responsible(filePath: string): Promise<boolean>;
688
- private getRouteHandler;
689
940
  /**
690
- * Opens a virtual file from the Git-based virtual file system.
691
- *
692
- * This method retrieves a virtual file descriptor for the given `filePath`, checks if the file is writable
693
- * based on its type and the provided `flags`, and ensures that write operations are only allowed for
694
- * certain file types (e.g., "branch-file", "branch-head", "branch-tip"). It then loads the file's content
695
- * into the in-memory file system (`memFs`), ensures parent directories exist, and finally opens the file,
696
- * returning a `CompositFsFileHandle` for further operations.
941
+ * Get route parameters from the operation context
942
+ * CompositeFs sets this context when routing to this adapter
943
+ */
944
+ private getRouteParams;
945
+ /**
946
+ * Get static siblings from the operation context
947
+ * These are static entries that should appear in directory listings
948
+ */
949
+ private getStaticSiblings;
950
+ /**
951
+ * Opens a virtual file using the configured handler.
697
952
  *
698
- * @param filePath - The path to the virtual file to open.
699
- * @param flags - The file system flags indicating the desired open mode (e.g., "r" for read, "w" for write, "a" for append, "x" for exclusive creation).
700
- * - "r": Open file for reading. An exception occurs if the file does not exist.
701
- * - "w": Open file for writing. The file is created (if it does not exist) or truncated (if it exists).
702
- * - "a": Open file for appending. The file is created if it does not exist.
703
- * - "x": Exclusive flag. Used with "w" or "a" to fail if the file exists.
704
- * - Combinations like "wx", "ax", etc., are also supported.
705
- * @param mode - Optional file mode (permission and sticky bits) to use if creating a file.
706
- * @returns A promise that resolves to a `CompositFsFileHandle` for the opened file.
707
- * @throws If the file is not a virtual legit file, if write operations are not allowed for the file type,
708
- * or if the file does not exist.
953
+ * The handler receives route parameters via context set by CompositeFs.
709
954
  */
710
955
  open(filePath: string, flags: string, mode?: number): Promise<CompositFsFileHandle>;
711
956
  mkdir(path: PathLike, options?: nodeFs.MakeDirectoryOptions | nodeFs.Mode | null): Promise<void>;
@@ -782,7 +1027,24 @@ type Operation = {
782
1027
  message: string;
783
1028
  originBranchOid?: string;
784
1029
  };
785
- declare const gitBranchOperationsVirtualFile: VirtualFileDefinition;
1030
+ /**
1031
+ * Creates a CompositeSubFsAdapter for branch operations history
1032
+ *
1033
+ * This adapter handles reading the history of branch operations.
1034
+ *
1035
+ * @example
1036
+ * ```ts
1037
+ * const adapter = createBranchOperationsAdapter({
1038
+ * gitStorageFs: memFs,
1039
+ * gitRoot: '/my-repo',
1040
+ * });
1041
+ * ```
1042
+ */
1043
+ declare function createBranchOperationsAdapter({ gitStorageFs, gitRoot, rootPath, }: {
1044
+ gitStorageFs: any;
1045
+ gitRoot: string;
1046
+ rootPath?: string;
1047
+ }): CompositeSubFsAdapter;
786
1048
 
787
1049
  declare const createLegitSyncService: ({ fs, gitRepoPath, serverUrl, auth, anonymousBranch, authHeaderPrefix, }: {
788
1050
  fs: FsClient;
@@ -812,5 +1074,5 @@ type HistoryItem = {
812
1074
  author: User;
813
1075
  };
814
1076
 
815
- export { CompositeFs, EphemeralSubFs, GitSubFs, HiddenFileSubFs, PassThroughSubFs, createFsOperationFileLogger, createLegitSyncService, getLegitFsAccess, gitBranchOperationsVirtualFile, openLegitFs, openLegitFsWithMemoryFs };
1077
+ export { CompositeFs, CompositeSubFsAdapter, CopyOnWriteSubFs, EphemeralSubFs, HiddenFileSubFs, PassThroughSubFs, createBranchOperationsAdapter, createFsOperationFileLogger, createLegitSyncService, getLegitFsAccess, openLegitFs, openLegitFsWithMemoryFs };
816
1078
  export type { FsOperationLogger, HistoryItem, Operation, User };