From 2414180c58bb1029686f4eac1ec3025a9c2f45e3 Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Thu, 4 Apr 2024 18:56:27 +0200 Subject: [PATCH] refactor!: Reduce complexity of paths --- packages/nest/README.md | 14 +- packages/nest/package.json | 2 +- packages/nest/src/class.ts | 152 +++---- packages/nest/src/common.ts | 26 +- packages/nest/src/data/sample.ts | 15 +- packages/nest/src/errors.ts | 18 +- packages/nest/src/mounts.ts | 36 +- packages/nest/src/path.ts | 570 ++++------------------- packages/nest/src/queries.ts | 76 +++- packages/nest/src/references.ts | 16 +- packages/nest/src/root-tree/basic.ts | 11 +- packages/nest/src/share.ts | 23 +- packages/nest/src/transaction.ts | 133 +++--- packages/nest/src/types.ts | 12 +- packages/nest/src/types/internal.ts | 2 +- packages/nest/src/unix.ts | 66 ++- packages/nest/test/class.test.ts | 645 ++++++++++----------------- packages/nest/test/helpers/index.ts | 57 ++- packages/nest/test/path.test.ts | 377 ---------------- 19 files changed, 706 insertions(+), 1545 deletions(-) delete mode 100644 packages/nest/test/path.test.ts diff --git a/packages/nest/README.md b/packages/nest/README.md index 1e67d97..01c57f5 100644 --- a/packages/nest/README.md +++ b/packages/nest/README.md @@ -25,17 +25,15 @@ pnpm install @wnfs-wg/nest ## Usage +Scenario 1:
+🚀 Create a new file system, create a new file and read it back. + ```ts import { FileSystem, Path } from '@wnfs-wg/nest' // Provide some block store of the `Blockstore` type from the `interface-blockstore` package import { IDBBlockstore } from 'blockstore-idb' -``` -Scenario 1:
-🚀 Create a new file system, create a new file and read it back. - -```ts const blockstore = new IDBBlockstore('path/to/store') await blockstore.open() @@ -49,9 +47,9 @@ const { capsuleKey } = await fs.createPrivateNode({ }) // Write & Read -await fs.write(Path.file('private', 'file'), 'utf8', '🪺') +await fs.write(['private', 'file'], 'utf8', '🪺') -const contents = await fs.read(Path.file('private', 'file'), 'utf8') +const contents = await fs.read(['private', 'file'], 'utf8') ``` Scenario 2:
@@ -68,7 +66,7 @@ of our root tree, the pointer to our file system. let fsPointer: CID = await fs.calculateDataRoot() // When we make a modification to the file system a verification is performed. -await fs.write(Path.file('private', 'file'), 'utf8', '🪺') +await fs.write(['private', 'file'], 'utf8', '🪺') // If the commit is approved, the changes are reflected in the file system and // the `commit` and `publish` events are emitted. diff --git a/packages/nest/package.json b/packages/nest/package.json index 09159d0..9d78239 100644 --- a/packages/nest/package.json +++ b/packages/nest/package.json @@ -106,7 +106,7 @@ "src" ], "scripts": { - "lint": "tsc --build && eslint . --quiet --ignore-pattern='README.md' && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore", + "lint": "tsc --build && eslint . --quiet && prettier --check '**/*.{js,ts,yml,json}' --ignore-path ../../.gitignore", "build": "tsc --build", "test": "pnpm run test:node && pnpm run test:browser", "test:node": "mocha 'test/**/!(*.browser).test.ts' --bail --timeout 30000", diff --git a/packages/nest/src/class.ts b/packages/nest/src/class.ts index b7476c1..eb20354 100644 --- a/packages/nest/src/class.ts +++ b/packages/nest/src/class.ts @@ -264,42 +264,45 @@ export class FileSystem { */ async createPrivateNode( node: { - path: Path.Distinctive + path: Path.Segments exchangeKeyPair: { publicKey: CryptoKey | Uint8Array privateKey: CryptoKey } + kind?: Path.Kind }, mutationOptions?: MutationOptions ): Promise<{ - path: Path.Distinctive> + path: Path.Partitioned capsuleKey: Uint8Array shareId: string }> async createPrivateNode( node: { - path: Path.Distinctive + path: Path.Segments + kind?: Path.Kind }, mutationOptions?: MutationOptions ): Promise<{ - path: Path.Distinctive> + path: Path.Partitioned capsuleKey: Uint8Array }> async createPrivateNode( node: { - path: Path.Distinctive + path: Path.Segments exchangeKeyPair?: { publicKey: CryptoKey | Uint8Array privateKey: CryptoKey } + kind?: Path.Kind }, mutationOptions?: MutationOptions ): Promise<{ - path: Path.Distinctive> + path: Path.Partitioned capsuleKey: Uint8Array shareId?: string }> { - const { path } = node + const { kind, path } = node const absolutePosixPath = Path.toPosix(path, { absolute: true }) if (this.#privateNodes[absolutePosixPath] !== undefined) { @@ -321,17 +324,18 @@ export class FileSystem { } // Create - const privateNode = Path.isFile(path) - ? new PrivateFile( - this.#rootTree.privateForest().emptyName(), - new Date(), - this.#rng - ).asNode() - : new PrivateDirectory( - this.#rootTree.privateForest().emptyName(), - new Date(), - this.#rng - ).asNode() + const privateNode = + kind === Path.Kind.File + ? new PrivateFile( + this.#rootTree.privateForest().emptyName(), + new Date(), + this.#rng + ).asNode() + : new PrivateDirectory( + this.#rootTree.privateForest().emptyName(), + new Date(), + this.#rng + ).asNode() // Store const storeResult = await privateNode.store( @@ -405,11 +409,11 @@ export class FileSystem { async mountPrivateNode( node: | { - path: Path.Distinctive + path: Path.Segments capsuleKey: Uint8Array } | { - path: Path.Distinctive + path: Path.Segments exchangeKeyPair: { publicKey: CryptoKey | Uint8Array privateKey: CryptoKey @@ -433,11 +437,11 @@ export class FileSystem { async mountPrivateNodes( nodes: Array< | { - path: Path.Distinctive + path: Path.Segments capsuleKey: Uint8Array } | { - path: Path.Distinctive + path: Path.Segments exchangeKeyPair: { publicKey: CryptoKey | Uint8Array privateKey: CryptoKey @@ -496,7 +500,7 @@ export class FileSystem { * @param path * @group Mounting */ - unmountPrivateNode(path: Path.Distinctive): void { + unmountPrivateNode(path: Path.Segments): void { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete this.#privateNodes[Path.toPosix(path, { absolute: true })] } @@ -508,9 +512,7 @@ export class FileSystem { * @param path * @group Querying */ - async contentCID( - path: Path.File> - ): Promise { + async contentCID(path: Partitioned): Promise { return await this.#transactionContext().contentCID(path) } @@ -518,9 +520,7 @@ export class FileSystem { * @param path * @group Querying */ - async capsuleCID( - path: Path.Distinctive> - ): Promise { + async capsuleCID(path: Partitioned): Promise { return await this.#transactionContext().capsuleCID(path) } @@ -529,7 +529,7 @@ export class FileSystem { * @group Querying */ async capsuleKey( - path: Path.Distinctive> + path: Partitioned ): Promise { return await this.#transactionContext().capsuleKey(path) } @@ -538,26 +538,26 @@ export class FileSystem { * @param path * @group Querying */ - async exists( - path: Path.Distinctive> - ): Promise { + async exists(path: Partitioned): Promise { return await this.#transactionContext().exists(path) } /** @group Querying */ async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions: { withItemKind: true } ): Promise async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions: { withItemKind: false } ): Promise + async listDirectory(path: Partitioned): Promise async listDirectory( - path: Path.Directory> - ): Promise + path: Partitioned, + listOptions?: { withItemKind: boolean } + ): Promise async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions?: { withItemKind: boolean } ): Promise { return await this.#transactionContext().listDirectory(path, listOptions) @@ -569,7 +569,7 @@ export class FileSystem { /** @group Querying */ async read( path: - | Path.File> + | PartitionedNonEmpty | { contentCID: CID } | { capsuleCID: CID } | { @@ -580,7 +580,7 @@ export class FileSystem { ): Promise> async read( path: - | Path.File> + | PartitionedNonEmpty | { contentCID: CID } | { capsuleCID: CID } | { @@ -600,7 +600,7 @@ export class FileSystem { * @param path * @group Querying */ - async size(path: Path.File>): Promise { + async size(path: PartitionedNonEmpty): Promise { return await this.#transactionContext().size(path) } @@ -609,15 +609,13 @@ export class FileSystem { /** @group Mutating */ async copy( - from: Path.Distinctive>, - to: Path.File> | Path.Directory>, + from: PartitionedNonEmpty, + to: Partitioned, mutationOptions?: MutationOptions ): Promise> async copy( - from: Path.Distinctive>, - to: - | Path.File> - | Path.Directory>, + from: PartitionedNonEmpty, + to: Partitioned, mutationOptions: MutationOptions = {} ): Promise> { return await this.#infusedTransaction( @@ -634,19 +632,14 @@ export class FileSystem { /** @group Mutating */ async createDirectory

( - path: Path.Directory>, + path: PartitionedNonEmpty

, mutationOptions?: MutationOptions - ): Promise< - MutationResult> }> - > + ): Promise }>> async createDirectory( - path: Path.Directory>, + path: PartitionedNonEmpty, mutationOptions: MutationOptions = {} ): Promise< - MutationResult< - Partition, - { path: Path.Directory> } - > + MutationResult }> > { let finalPath = path @@ -667,23 +660,18 @@ export class FileSystem { /** @group Mutating */ async createFile

( - path: Path.File>, + path: PartitionedNonEmpty

, dataType: DataType, data: DataForType, mutationOptions?: MutationOptions - ): Promise< - MutationResult> }> - > + ): Promise }>> async createFile( - path: Path.File>, + path: PartitionedNonEmpty, dataType: DataType, data: DataForType, mutationOptions: MutationOptions = {} ): Promise< - MutationResult< - Partition, - { path: Path.File> } - > + MutationResult }> > { let finalPath = path @@ -704,11 +692,11 @@ export class FileSystem { /** @group Mutating */ async ensureDirectory

( - path: Path.Directory>, + path: PartitionedNonEmpty

, mutationOptions?: MutationOptions ): Promise> async ensureDirectory( - path: Path.Directory>, + path: PartitionedNonEmpty, mutationOptions: MutationOptions = {} ): Promise> { return await this.#infusedTransaction( @@ -725,15 +713,13 @@ export class FileSystem { /** @group Mutating */ async move( - from: Path.Distinctive>, - to: Path.File> | Path.Directory>, + from: PartitionedNonEmpty, + to: PartitionedNonEmpty | Partitioned, mutationOptions?: MutationOptions ): Promise> async move( - from: Path.Distinctive>, - to: - | Path.File> - | Path.Directory>, + from: PartitionedNonEmpty, + to: PartitionedNonEmpty | Partitioned, mutationOptions: MutationOptions = {} ): Promise> { return await this.#infusedTransaction( @@ -754,7 +740,7 @@ export class FileSystem { * @group Mutating */ async remove( - path: Path.Distinctive>, + path: PartitionedNonEmpty, mutationOptions: MutationOptions = {} ): Promise { const transactionResult = await this.transaction(async (t) => { @@ -775,12 +761,12 @@ export class FileSystem { /** @group Mutating */ async rename

( - path: Path.Distinctive>, + path: PartitionedNonEmpty

, newName: string, mutationOptions?: MutationOptions ): Promise> async rename( - path: Path.Distinctive>, + path: PartitionedNonEmpty, newName: string, mutationOptions: MutationOptions = {} ): Promise> { @@ -795,13 +781,13 @@ export class FileSystem { /** @group Mutating */ async write

( - path: Path.File>, + path: PartitionedNonEmpty

, dataType: DataType, data: DataForType, mutationOptions?: MutationOptions ): Promise> async write( - path: Path.File>, + path: PartitionedNonEmpty, dataType: DataType, data: DataForType, mutationOptions: MutationOptions = {} @@ -937,7 +923,7 @@ export class FileSystem { * @group Sharing */ async share( - path: Path.Distinctive>, + path: Partitioned, receiverDataRoot: CID, opts: { mutationOptions?: MutationOptions @@ -1033,22 +1019,22 @@ export class FileSystem { async #infusedTransaction( handler: (t: TransactionContext) => Promise, - path: Path.Distinctive>, + path: Partitioned, mutationOptions?: MutationOptions ): Promise async #infusedTransaction( handler: (t: TransactionContext) => Promise, - path: Path.Distinctive>, + path: Partitioned, mutationOptions?: MutationOptions ): Promise async #infusedTransaction( handler: (t: TransactionContext) => Promise, - path: Path.Distinctive>, + path: Partitioned, mutationOptions?: MutationOptions ): Promise> async #infusedTransaction( handler: (t: TransactionContext) => Promise, - path: Path.Distinctive>, + path: Partitioned, mutationOptions: MutationOptions = {} ): Promise> { const transactionResult = await this.transaction(handler, mutationOptions) diff --git a/packages/nest/src/common.ts b/packages/nest/src/common.ts index a2eb251..abeb4e0 100644 --- a/packages/nest/src/common.ts +++ b/packages/nest/src/common.ts @@ -1,16 +1,16 @@ import * as Path from './path.js' +/** + * + * @param path + */ export function addOrIncreaseNameNumber( - path: Path.Directory> -): Path.Directory> -export function addOrIncreaseNameNumber( - path: Path.File> -): Path.File> -export function addOrIncreaseNameNumber( - path: Path.Distinctive> -): Path.Distinctive> { - const regex = Path.isFile(path) ? /( \((\d+)\))?(\.[^$]+)?$/ : /( \((\d+)\))$/ + path: Path.PartitionedNonEmpty +): Path.PartitionedNonEmpty { const terminus = Path.terminus(path) + const regex = terminus.includes('.') + ? /( \((\d+)\))?(\.[^$]+)?$/ + : /( \((\d+)\))$/ const suffixMatches = terminus.match(regex) return Path.replaceTerminus( @@ -26,10 +26,14 @@ export function addOrIncreaseNameNumber( ) } +/** + * + * @param path + */ export function pathSegmentsWithoutPartition( - path: Path.Distinctive> + path: Path.Partitioned ): Path.Segments { - return Path.unwrap(Path.removePartition(path)) + return Path.removePartition(path) } /** diff --git a/packages/nest/src/data/sample.ts b/packages/nest/src/data/sample.ts index 8e56386..25dc105 100644 --- a/packages/nest/src/data/sample.ts +++ b/packages/nest/src/data/sample.ts @@ -1,19 +1,20 @@ -import * as Path from '../path.js' import type { FileSystem } from '../class.js' /** * Adds some sample to the file system. + * + * @param fs */ export async function addSampleData(fs: FileSystem): Promise { - await fs.mkdir(Path.directory('private', 'Apps')) - await fs.mkdir(Path.directory('private', 'Audio')) - await fs.mkdir(Path.directory('private', 'Documents')) - await fs.mkdir(Path.directory('private', 'Photos')) - await fs.mkdir(Path.directory('private', 'Video')) + await fs.mkdir(['private', 'Apps']) + await fs.mkdir(['private', 'Audio']) + await fs.mkdir(['private', 'Documents']) + await fs.mkdir(['private', 'Photos']) + await fs.mkdir(['private', 'Video']) // Files await fs.write( - Path.file('private', 'Welcome.txt'), + ['private', 'Welcome.txt'], 'utf8', 'Welcome to your personal transportable encrypted file system 👋' ) diff --git a/packages/nest/src/errors.ts b/packages/nest/src/errors.ts index f2281da..7da9929 100644 --- a/packages/nest/src/errors.ts +++ b/packages/nest/src/errors.ts @@ -1,9 +1,11 @@ import * as Path from './path.js' -export function throwNoAccess( - path: Path.DistinctivePath, - accessType?: string -): never { +/** + * + * @param path + * @param accessType + */ +export function throwNoAccess(path: Path.Segments, accessType?: string): never { throw new Error( `Expected to have ${ typeof accessType === 'string' ? accessType + ' ' : '' @@ -11,9 +13,11 @@ export function throwNoAccess( ) } -export function throwInvalidPartition( - path: Path.Distinctive -): never { +/** + * + * @param path + */ +export function throwInvalidPartition(path: Path.Segments): never { throw new Error( `Expected either a public or private path, got '${Path.toPosix(path)}'` ) diff --git a/packages/nest/src/mounts.ts b/packages/nest/src/mounts.ts index 4a7abe0..1606f10 100644 --- a/packages/nest/src/mounts.ts +++ b/packages/nest/src/mounts.ts @@ -19,21 +19,18 @@ import { throwInvalidPartition, throwNoAccess } from './errors.js' * * Starts from the path `/` and works up to given path, * which could be a file or directory path. + * + * @param path + * @param privateNodes */ export function findPrivateNode( - path: Path.Distinctive>, + path: Partitioned, privateNodes: MountedPrivateNodes ): PrivateNodeQueryResult { - const pathKind = Path.kind(path) - const pathWithoutPartition = Path.removePartition(path) - const pathSegments = Path.unwrap(pathWithoutPartition) + const pathSegments = Path.removePartition(path) for (let i = 0; i <= pathSegments.length; i++) { - const path = Path.fromKind( - i === pathSegments.length ? pathKind : Path.Kind.Directory, - ...pathSegments.slice(0, i) - ) - + const path = pathSegments.slice(0, i) const result: MountedPrivateNode | undefined = privateNodes[Path.toPosix(path, { absolute: true })] @@ -49,25 +46,28 @@ export function findPrivateNode( } export function partition

( - path: Path.Distinctive> + path: PartitionedNonEmpty

): PartitionDiscoveryNonEmpty

export function partition

( - path: Path.Distinctive> + path: Partitioned

): PartitionDiscovery

-export function partition(path: Path.Distinctive>): { +/** + * + * @param path + */ +export function partition(path: Partitioned): { name: 'public' | 'private' - path: Path.Distinctive> + path: Partitioned segments: Path.Segments } { - const unwrapped = Path.unwrap(path) - const rest = unwrapped.slice(1) + const rest = path.slice(1) - switch (unwrapped[0]) { + switch (path[0]) { case 'public': { - return { name: 'public', path: path, segments: rest } + return { name: 'public', path, segments: rest } } case 'private': { - return { name: 'private', path: path, segments: rest } + return { name: 'private', path, segments: rest } } default: { throwInvalidPartition(path) diff --git a/packages/nest/src/path.ts b/packages/nest/src/path.ts index 0666587..c4a72ba 100644 --- a/packages/nest/src/path.ts +++ b/packages/nest/src/path.ts @@ -1,7 +1,5 @@ // 🧩 -import type { AppInfo } from './app-info.js' - export enum RootBranch { DID = 'did', Exchange = 'exchange', @@ -38,205 +36,43 @@ export type Public = 'public' | RootBranch.Public */ export type Partition = Private | Public +export function priv(...args: SegmentsNonEmpty): PartitionedNonEmpty +export function priv(...args: Segments): Partitioned /** - * A directory path. - */ -export interface DirectoryPath

{ - directory: P -} - -/** - * A file path. - */ -export interface FilePath

{ - file: P -} - -/** - * A file or directory path. - */ -export type DistinctivePath

= DirectoryPath

| FilePath

- -/** - * Alias for `DirectoryPath` - */ -export type Directory

= DirectoryPath

- -/** - * Alias for `FilePath` - */ -export type File

= FilePath

- -/** - * Alias for `DistinctivePath` - */ -export type Distinctive

= DistinctivePath

- -// CREATION - -/** - * Utility function to create a `DirectoryPath` + * Utility function to create a private path. * * @param args * @group 🪺 :: START HERE */ -export function directory

( - ...args: PartitionedNonEmpty

-): DirectoryPath> -export function directory

( - ...args: Partitioned

-): DirectoryPath> -export function directory( - ...args: SegmentsNonEmpty -): DirectoryPath -export function directory(...args: Segments): DirectoryPath -/** - * - * @param args - */ -export function directory(...args: Segments): DirectoryPath { - if (args.some((p) => p.includes('/'))) { - throw new Error('Forward slashes `/` are not allowed') - } - return { directory: args } +export function priv(...args: Segments): Partitioned { + return ['private', ...args] } +export function pub(...args: SegmentsNonEmpty): PartitionedNonEmpty +export function pub(...args: Segments): Partitioned /** - * Utility function to create a `FilePath` + * Utility function to create a public path. * * @param args * @group 🪺 :: START HERE */ -export function file

( - ...args: PartitionedNonEmpty

-): FilePath> -export function file(...args: SegmentsNonEmpty): FilePath -export function file(...args: Segments): FilePath -/** - * - * @param args - */ -export function file(...args: Segments): FilePath { - if (args.some((p) => p.includes('/'))) { - throw new Error('Forward slashes `/` are not allowed') - } - return { file: args } +export function pub(...args: Segments): Partitioned { + return ['public', ...args] } /** - * Utility function to create a path based on the given `Kind` + * Utility function to create a root path (aka. an empty array) * - * @param kind - * @param args * @group 🪺 :: START HERE */ -export function fromKind

( - kind: Kind.Directory, - ...args: PartitionedNonEmpty

-): DirectoryPath> -export function fromKind

( - kind: Kind.Directory, - ...args: Partitioned

-): DirectoryPath> -export function fromKind( - kind: Kind.Directory, - ...args: SegmentsNonEmpty -): DirectoryPath -export function fromKind( - kind: Kind.Directory, - ...args: Segments -): DirectoryPath -export function fromKind

( - kind: Kind.File, - ...args: PartitionedNonEmpty

-): FilePath> -export function fromKind( - kind: Kind.File, - ...args: SegmentsNonEmpty -): FilePath -export function fromKind(kind: Kind.File, ...args: Segments): FilePath -export function fromKind

( - kind: Kind, - ...args: PartitionedNonEmpty

-): DistinctivePath> -export function fromKind

( - kind: Kind, - ...args: Partitioned

-): DistinctivePath> -export function fromKind( - kind: Kind, - ...args: SegmentsNonEmpty -): DistinctivePath -export function fromKind( - kind: Kind, - ...args: Segments -): DistinctivePath -/** - * - * @param kind - * @param args - */ -export function fromKind( - kind: Kind, - ...args: Segments -): DistinctivePath { - return kind === Kind.Directory ? directory(...args) : file(...args) -} - -/** - * Utility function to create a root `DirectoryPath` - * - * @group 🪺 :: START HERE - */ -export function root(): DirectoryPath { - return { directory: [] } -} - -/** - * Utility function create an app data path. - * - * @param partition - * @param app - * @group 🪺 :: START HERE - */ -export function appData

( - partition: P, - app: AppInfo -): DirectoryPath> -export function appData

( - partition: P, - app: AppInfo, - suffix: FilePath -): FilePath> -export function appData

( - partition: P, - app: AppInfo, - suffix: DirectoryPath -): DirectoryPath> -export function appData

( - partition: P, - app: AppInfo, - suffix: DistinctivePath -): DistinctivePath> -/** - * - * @param partition - * @param app - * @param suffix - */ -export function appData

( - partition: P, - app: AppInfo, - suffix?: DistinctivePath -): DistinctivePath> { - const appDir = directory(partition, 'Apps', app.creator, app.name) - return suffix === undefined ? appDir : combine(appDir, suffix) +export function root(): [] { + return [] } // POSIX /** - * Transform a string into a `DistinctivePath`. + * Transform a POSIX string into a path. * * Directories should have the format `path/to/dir/` and * files should have the format `path/to/file`. @@ -246,15 +82,15 @@ export function appData

( * @param path * @group POSIX */ -export function fromPosix(path: string): DistinctivePath { +export function fromPosix(path: string): Segments { const split = path.replace(/^\/+/, '').split('/') - if (path.endsWith('/')) return { directory: split.slice(0, -1) } + if (path.endsWith('/')) return split.slice(0, -1) else if (path === '') return root() - return { file: split } + return split } /** - * Transform a `DistinctivePath` into a string. + * Transform a path into a POSIX string. * * Directories will have the format `path/to/dir/` and * files will have the format `path/to/file`. @@ -262,326 +98,160 @@ export function fromPosix(path: string): DistinctivePath { * @param path * @param options * @param options.absolute + * @param options.directory * @group POSIX */ export function toPosix( - path: DistinctivePath, - options?: { absolute?: boolean } + path: Segments, + options?: { absolute?: boolean; directory?: boolean } ): string { const prefix = options?.absolute === true ? '/' : '' - const joinedPath = unwrap(path).join('/') - if (isDirectory(path)) + const joinedPath = path.join('/') + if (options?.directory === true) return prefix + joinedPath + (joinedPath.length > 0 ? '/' : '') return prefix + joinedPath } // 🛠️ -/** - * Combine two `DistinctivePath`s. - * - * @param a - * @param b - */ -export function combine

( - a: DirectoryPath>, - b: FilePath -): FilePath> -export function combine

( - a: DirectoryPath>, - b: FilePath -): FilePath> -export function combine

( - a: DirectoryPath>, - b: FilePath -): FilePath> -export function combine( - a: DirectoryPath, - b: FilePath -): FilePath -export function combine( - a: DirectoryPath, - b: FilePath -): FilePath -export function combine

( - a: DirectoryPath>, - b: DirectoryPath -): DirectoryPath> -export function combine

( - a: DirectoryPath>, - b: DirectoryPath -): DirectoryPath> export function combine

( - a: DirectoryPath>, - b: DirectoryPath -): DirectoryPath> -export function combine( - a: DirectoryPath, - b: DirectoryPath -): DirectoryPath -export function combine( - a: DirectoryPath, - b: DirectoryPath -): DirectoryPath + a: PartitionedNonEmpty

, + b: Segments +): PartitionedNonEmpty

export function combine

( - a: DirectoryPath>, - b: DistinctivePath -): DistinctivePath> + a: Partitioned

, + b: SegmentsNonEmpty +): PartitionedNonEmpty

export function combine

( - a: DirectoryPath>, - b: DistinctivePath -): DistinctivePath> -export function combine

( - a: DirectoryPath>, - b: DistinctivePath -): DistinctivePath> -export function combine( - a: DirectoryPath, - b: DistinctivePath -): DistinctivePath -export function combine( - a: DirectoryPath, - b: DistinctivePath -): DistinctivePath + a: Partitioned

, + b: Segments +): Partitioned

+export function combine(a: Segments, b: SegmentsNonEmpty): SegmentsNonEmpty +export function combine(a: Segments, b: Segments): Segments /** + * Combine two `DistinctivePath`s. * * @param a * @param b */ -export function combine( - a: DirectoryPath, - b: DistinctivePath -): DistinctivePath { - return map((p) => [...unwrap(a), ...p], b) -} - -/** - * Is this `DistinctivePath` a directory? - * - * @param path - */ -export function isDirectory

( - path: DistinctivePath

-): path is DirectoryPath

{ - return 'directory' in path -} - -/** - * Is this `DistinctivePath` a file? - * - * @param path - */ -export function isFile

(path: DistinctivePath

): path is FilePath

{ - return 'file' in path +export function combine(a: Segments, b: Segments): Segments { + return [...a, ...b] } /** - * Is this `DistinctivePath` on the given `RootBranch`? + * Is this path on the given `RootBranch`? * * @param rootBranch * @param path */ export function isOnRootBranch( rootBranch: RootBranch, - path: DistinctivePath + path: Segments ): boolean { - return unwrap(path)[0] === rootBranch + return path[0] === rootBranch } /** - * Is this `DistinctivePath` of the given `Partition`? + * Is this path of the given `Partition`? * * @param partition * @param path */ -export function isPartition( - partition: Partition, - path: DistinctivePath -): boolean { - return unwrap(path)[0] === partition +export function isPartition(partition: Partition, path: Segments): boolean { + return path[0] === partition } /** - * Is this a partitioned `DistinctivePath`? + * Is this a partitioned path? * * @param path */ export function isPartitioned

( - path: DistinctivePath -): path is DistinctivePath> { - const soCalledPartition = unwrap(path)[0] + path: Segments +): path is Partitioned

{ + const soCalledPartition = path[0] return [RootBranch.Private, RootBranch.Public, 'private', 'public'].includes( soCalledPartition ) } /** - * Is this partitioned `DistinctivePath` non-empty? + * Is this partitioned path non-empty? * * @param path */ export function isPartitionedNonEmpty

( - path: DistinctivePath -): path is DistinctivePath> { - return isPartitioned(path) && length(path) > 1 + path: Segments +): path is PartitionedNonEmpty

{ + return isPartitioned(path) && path.length > 1 } /** - * Is this `DirectoryPath` a root directory? + * Is this path a root path? * * @param path */ -export function isRootDirectory(path: DirectoryPath): boolean { - return path.directory.length === 0 -} - -/** - * Check if two `DistinctivePath` have the same `Partition`. - * - * @param a - * @param b - */ -export function isSamePartition( - a: DistinctivePath, - b: DistinctivePath -): boolean { - return unwrap(a)[0] === unwrap(b)[0] +export function isRoot(path: Segments): boolean { + return path.length === 0 } /** - * Check if two `DistinctivePath` are of the same kind. + * Check if two paths have the same `Partition`. * * @param a * @param b */ -export function isSameKind( - a: DistinctivePath, - b: DistinctivePath -): boolean { - if (isDirectory(a) && isDirectory(b)) return true - else if (isFile(a) && isFile(b)) return true - else return false -} - -/** - * What `Kind` of path are we dealing with? - * - * @param path - */ -export function kind

(path: DistinctivePath

): Kind { - if (isDirectory(path)) return Kind.Directory - return Kind.File -} - -/** - * What's the length of a path? - * - * @param path - */ -export function length(path: DistinctivePath): number { - return unwrap(path).length -} - -/** - * Map a `DistinctivePath`. - * - * @param fn - * @param path - */ -export function map( - fn: (p: A) => B, - path: DistinctivePath -): DistinctivePath { - if (isDirectory(path)) return { directory: fn(path.directory) } - else if (isFile(path)) return { file: fn(path.file) } - return path +export function isSamePartition(a: Segments, b: Segments): boolean { + return a[0] === b[0] } -/** - * Get the parent directory of a `DistinctivePath`. - * - * @param path - */ -export function parent( - path: DistinctivePath<[Partition, Segment, Segment, ...Segments]> -): DirectoryPath> -export function parent( - path: DistinctivePath<[Segment, Segment, Segment, ...Segments]> -): DirectoryPath -export function parent( - path: DistinctivePath> -): DirectoryPath> export function parent( - path: DistinctivePath<[Partition, Segment]> -): DirectoryPath> + path: [Partition, Segment, Segment, ...Segments] +): PartitionedNonEmpty export function parent( - path: DistinctivePath> -): DirectoryPath + path: [Segment, Segment, Segment, ...Segments] +): SegmentsNonEmpty export function parent( - path: DistinctivePath -): DirectoryPath -export function parent(path: DistinctivePath<[Segment]>): DirectoryPath<[]> -export function parent(path: DistinctivePath<[]>): undefined -export function parent( - path: DistinctivePath -): DirectoryPath | undefined + path: PartitionedNonEmpty +): Partitioned +export function parent(path: [Partition, Segment]): Partitioned +export function parent(path: Partitioned): Segments +export function parent(path: SegmentsNonEmpty): Segments +export function parent(path: [Segment]): [] +export function parent(path: []): undefined +export function parent(path: Segments): Segments | undefined /** + * Get the parent directory of a path. * * @param path */ -export function parent( - path: DistinctivePath -): DirectoryPath | undefined { - return isDirectory(path) && isRootDirectory(path) - ? undefined - : directory(...unwrap(path).slice(0, -1)) +export function parent(path: Segments): Segments | undefined { + return path.slice(0, -1) } /** - * Remove the `Partition` of a `DistinctivePath` (ie. the top-level directory) + * Remove the `Partition` of a path (ie. the top-level directory) * * @param path */ -export function removePartition( - path: DistinctivePath -): DistinctivePath { - return map((p) => (isDirectory(path) || p.length > 1 ? p.slice(1) : p), path) +export function removePartition(path: Segments): Segments { + return isPartitioned(path) ? path.slice(1) : path } export function replaceTerminus( - path: FilePath>, - terminus: string -): FilePath> -export function replaceTerminus( - path: DirectoryPath>, - terminus: string -): DirectoryPath> -export function replaceTerminus( - path: DistinctivePath>, - terminus: string -): DistinctivePath> -export function replaceTerminus( - path: FilePath, - terminus: string -): FilePath -export function replaceTerminus( - path: DirectoryPath, + path: PartitionedNonEmpty, terminus: string -): DirectoryPath -export function replaceTerminus( - path: DistinctivePath, - terminus: string -): DistinctivePath +): PartitionedNonEmpty /** * * @param path * @param terminus */ export function replaceTerminus( - path: DistinctivePath | DistinctivePath, + path: SegmentsNonEmpty, terminus: string -): DistinctivePath { - return combine(parent(path), fromKind(kind(path), terminus)) +): SegmentsNonEmpty { + return combine(parent(path), [terminus]) } /** @@ -589,11 +259,10 @@ export function replaceTerminus( * @param path */ export function rootBranch( - path: DistinctivePath + path: Segments ): { branch: RootBranch; rest: Segments } | undefined { - const unwrapped = unwrap(path) - const firstSegment = unwrapped[0] - const rest = unwrapped.slice(1) + const firstSegment = path[0] + const rest = path.slice(1) switch (firstSegment) { case RootBranch.Exchange: { @@ -622,82 +291,39 @@ export function rootBranch( } } +export function terminus(path: PartitionedNonEmpty): string +export function terminus(path: Partitioned): string +export function terminus(path: SegmentsNonEmpty): string +export function terminus(path: Segments): string | undefined /** * Get the last part of the path. * * @param path */ -export function terminus( - path: DistinctivePath> -): string -export function terminus(path: DistinctivePath>): string -export function terminus(path: DistinctivePath): string -export function terminus(path: DistinctivePath): string | undefined -/** - * - * @param path - */ -export function terminus(path: DistinctivePath): string | undefined { - const u = unwrap(path) - if (u.length === 0) return undefined - return u.at(-1) +export function terminus(path: Segments): string | undefined { + if (path.length === 0) return undefined + return path.at(-1) } -/** - * Unwrap a `DistinctivePath`. - * - * @param path - */ -export function unwrap

(path: DistinctivePath

): P { - if (isDirectory(path)) { - return path.directory - } else if (isFile(path)) { - return path.file - } - - throw new Error('Path is neither a directory or a file') -} - -/** - * Utility function to prefix a path with a `Partition`. - * - * @param partition - * @param path - */ export function withPartition

( partition: P, - path: DirectoryPath -): DirectoryPath> + path: SegmentsNonEmpty +): PartitionedNonEmpty

export function withPartition

( partition: P, - path: DirectoryPath -): DirectoryPath> -export function withPartition

( - partition: P, - path: FilePath -): FilePath> -export function withPartition

( - partition: P, - path: FilePath -): FilePath> -export function withPartition

( - partition: P, - path: DistinctivePath -): DistinctivePath> -export function withPartition

( - partition: P, - path: DistinctivePath -): DistinctivePath> + path: Segments +): Partitioned

/** + * Utility function to prefix a path with a `Partition`. * * @param partition * @param path */ export function withPartition

( partition: P, - path: DistinctivePath -): DistinctivePath> { - return combine(directory(partition), path) + path: Segments +): Partitioned

{ + return [partition, ...path] } // 🔬 diff --git a/packages/nest/src/queries.ts b/packages/nest/src/queries.ts index 4d1b069..2844819 100644 --- a/packages/nest/src/queries.ts +++ b/packages/nest/src/queries.ts @@ -37,14 +37,20 @@ export interface PublicParams { export type Public = (params: PublicParams) => Promise export type PublicContext = Omit +/** + * + * @param path + * @param qry + * @param context + */ export async function publicQuery( - path: Path.Distinctive>, + path: Partitioned, qry: Public, context: PublicContext ): Promise { return await qry({ blockstore: context.blockstore, - pathSegments: Path.unwrap(Path.removePartition(path)), + pathSegments: Path.removePartition(path), rootTree: context.rootTree, }) } @@ -59,6 +65,21 @@ export const publicExists = () => { } } +export const publicItemKind = () => { + return async (params: PublicParams): Promise => { + const result = await params.rootTree + .publicRoot() + .getNode(params.pathSegments, Store.wnfs(params.blockstore)) + + if (result !== null && result !== undefined) + return (result as PublicNode).isFile() + ? Path.Kind.File + : Path.Kind.Directory + + return undefined + } +} + export const publicListDirectory = () => { return async (params: PublicParams): Promise => { return await params.rootTree @@ -92,9 +113,9 @@ export const publicListDirectoryWithKind = () => { return { ...item, kind, - path: Path.combine( - Path.directory('public', ...params.pathSegments), - Path.fromKind(kind, item.name) + path: Path.withPartition( + 'public', + Path.combine(params.pathSegments, [item.name]) ), } }) @@ -166,8 +187,14 @@ export type PrivateParams = { export type Private = (params: PrivateParams) => Promise export type PrivateContext = Omit +/** + * + * @param path + * @param qry + * @param context + */ export async function privateQuery( - path: Path.Distinctive>, + path: Partitioned, qry: Private, context: PrivateContext ): Promise { @@ -200,6 +227,28 @@ export const privateExists = () => { } } +export const privateItemKind = () => { + return async (params: PrivateParams): Promise => { + if (params.node.isFile()) return Path.Kind.File + + const result = await params.node + .asDir() + .getNode( + params.remainder, + searchLatest(), + params.rootTree.privateForest(), + Store.wnfs(params.blockstore) + ) + + if (result !== null && result !== undefined) + return (result as PrivateNode).isFile() + ? Path.Kind.File + : Path.Kind.Directory + + return undefined + } +} + export const privateListDirectory = () => { return async (params: PrivateParams): Promise => { if (params.node.isFile()) throw new Error('Cannot list a file') @@ -240,14 +289,10 @@ export const privateListDirectoryWithKind = () => { ) .then((a) => a.result) - const parentPath = Path.combine( - Path.directory('private', ...Path.unwrap(params.path)), - Path.directory(...params.remainder) - ) - - if (!Path.isDirectory(parentPath)) { - throw new Error("Didn't expect a file path") - } + const parentPath = Path.withPartition('private', [ + ...params.path, + ...params.remainder, + ]) const promises = items.map( async (item: DirectoryItem): Promise => { @@ -263,7 +308,7 @@ export const privateListDirectoryWithKind = () => { return { ...item, kind, - path: Path.combine(parentPath, Path.fromKind(kind, item.name)), + path: Path.combine(parentPath, [item.name]), } } ) @@ -327,7 +372,6 @@ export const privateReadFromAccessKey = ( if (node.isFile() === true) { const file: PrivateFile = node.asFile() - // TODO: Respect the offset and length options when available in rs-wnfs return await file.readAt( options?.offset ?? 0, options?.length ?? undefined, diff --git a/packages/nest/src/references.ts b/packages/nest/src/references.ts index ed3c436..c497281 100644 --- a/packages/nest/src/references.ts +++ b/packages/nest/src/references.ts @@ -9,10 +9,16 @@ import type { RootTree } from './root-tree.js' import * as Store from './store.js' import { pathSegmentsWithoutPartition } from './common.js' +/** + * + * @param blockstore + * @param rootTree + * @param path + */ export async function contentCID( blockstore: Blockstore, rootTree: RootTree, - path: Path.File> + path: Path.Partitioned ): Promise { const wnfsBlockstore = Store.wnfs(blockstore) const result = await rootTree @@ -30,10 +36,16 @@ export async function contentCID( : undefined } +/** + * + * @param blockstore + * @param rootTree + * @param path + */ export async function capsuleCID( blockstore: Blockstore, rootTree: RootTree, - path: Path.Distinctive> + path: Path.Partitioned ): Promise { const wnfsBlockstore = Store.wnfs(blockstore) const result = await rootTree diff --git a/packages/nest/src/root-tree/basic.ts b/packages/nest/src/root-tree/basic.ts index 154e8e2..93eb2df 100644 --- a/packages/nest/src/root-tree/basic.ts +++ b/packages/nest/src/root-tree/basic.ts @@ -7,7 +7,7 @@ import * as Raw from 'multiformats/codecs/raw' import * as Uint8Arrays from 'uint8arrays' import { CID } from 'multiformats/cid' -import { PrivateForest, PublicDirectory } from 'wnfs' +import { PrivateForest, PublicDirectory, type PublicNode } from 'wnfs' import * as Path from '../path.js' import * as References from '../references.js' @@ -275,8 +275,14 @@ export class BasicRootTree implements RootTree { return await Unix.removeNodeFromTree(oldRoot, path, this.#blockstore) } + const node: PublicNode = await dir.getNode( + path, + Store.wnfs(this.#blockstore) + ) + const itemKind = node.isFile() ? Path.Kind.File : Path.Kind.Directory + const contentCID = - Path.isFile(mod.path) && + itemKind === Path.Kind.File && Path.isPartitionedNonEmpty(mod.path) ? await References.contentCID( this.#blockstore, @@ -286,6 +292,7 @@ export class BasicRootTree implements RootTree { : undefined return await Unix.insertNodeIntoTree( + itemKind, oldRoot, path, this.#blockstore, diff --git a/packages/nest/src/share.ts b/packages/nest/src/share.ts index 64e0274..8286e39 100644 --- a/packages/nest/src/share.ts +++ b/packages/nest/src/share.ts @@ -67,27 +67,27 @@ export class Share { * @param path * @group Querying */ - async exists(path?: Path.Distinctive): Promise { + async exists(path?: Segments): Promise { return await this.#query(path ?? Path.root(), Queries.privateExists()) } /** @group Querying */ async listDirectory( - path: Path.Directory, + path: Segments, listOptions: { withItemKind: true } ): Promise async listDirectory( - path: Path.Directory, + path: Segments, listOptions: { withItemKind: false } ): Promise async listDirectory(): Promise - async listDirectory(path: Path.Directory): Promise + async listDirectory(path: Segments): Promise async listDirectory( - path?: Path.Directory, + path?: Segments, listOptions?: { withItemKind: boolean } ): Promise async listDirectory( - path?: Path.Directory, + path?: Segments, listOptions?: { withItemKind: boolean } ): Promise { return await this.#query( @@ -104,12 +104,12 @@ export class Share { /** @group Querying */ async read( dataType: D, - path?: Path.File, + path?: Segments, options?: { offset?: number; length?: number } ): Promise> async read( dataType: DataType, - path?: Path.File, + path?: Segments, options?: { offset?: number; length?: number } ): Promise> { const bytes = await this.#query( @@ -123,16 +123,13 @@ export class Share { * @param path * @group Querying */ - async size(path?: Path.File): Promise { + async size(path?: Segments): Promise { return await this.#query(path ?? Path.root(), Queries.privateSize()) } // ㊙️ - async #query( - path: Path.Distinctive, - query: Queries.Private - ): Promise { + async #query(path: Segments, query: Queries.Private): Promise { return await Queries.privateQuery( Path.withPartition('private', path), query, diff --git a/packages/nest/src/transaction.ts b/packages/nest/src/transaction.ts index 4aa39c3..8213192 100644 --- a/packages/nest/src/transaction.ts +++ b/packages/nest/src/transaction.ts @@ -59,7 +59,7 @@ export class TransactionContext { readonly #modifications: Set<{ type: MutationType - path: Path.Distinctive> + path: Partitioned }> /** @@ -93,7 +93,7 @@ export class TransactionContext { static async commit(context: TransactionContext): Promise< | { modifications: Array<{ - path: Path.Distinctive> + path: Partitioned type: MutationType }> privateNodes: MountedPrivateNodes @@ -117,7 +117,7 @@ export class TransactionContext { } const maybeNode = findPrivateNode( - mod.path as Path.Distinctive>, + mod.path as Path.Partitioned, context.#privateNodes ) @@ -151,9 +151,7 @@ export class TransactionContext { * @param path * @group Querying */ - async contentCID( - path: Path.File> - ): Promise { + async contentCID(path: Partitioned): Promise { return await References.contentCID(this.#blockstore, this.#rootTree, path) } @@ -161,9 +159,7 @@ export class TransactionContext { * @param path * @group Querying */ - async capsuleCID( - path: Path.Distinctive> - ): Promise { + async capsuleCID(path: Partitioned): Promise { return await References.capsuleCID(this.#blockstore, this.#rootTree, path) } @@ -172,7 +168,7 @@ export class TransactionContext { * @group Querying */ async capsuleKey( - path: Path.Distinctive> + path: Partitioned ): Promise { let priv: PrivateNodeQueryResult @@ -219,9 +215,7 @@ export class TransactionContext { * @param path * @group Querying */ - async exists( - path: Path.Distinctive> - ): Promise { + async exists(path: Partitioned): Promise { return await this.#query(path, { public: Queries.publicExists(), private: Queries.privateExists(), @@ -230,22 +224,20 @@ export class TransactionContext { /** @group Querying */ async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions: { withItemKind: true } ): Promise async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions: { withItemKind: false } ): Promise + async listDirectory(path: Partitioned): Promise async listDirectory( - path: Path.Directory> - ): Promise - async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions?: { withItemKind: boolean } ): Promise async listDirectory( - path: Path.Directory>, + path: Partitioned, listOptions?: { withItemKind: boolean } ): Promise { if (listOptions?.withItemKind === true) { @@ -267,7 +259,7 @@ export class TransactionContext { /** @group Querying */ async read( arg: - | Path.File> + | PartitionedNonEmpty | { contentCID: CID } | { capsuleCID: CID } | { @@ -278,7 +270,7 @@ export class TransactionContext { ): Promise> async read( arg: - | Path.File> + | PartitionedNonEmpty | { contentCID: CID } | { capsuleCID: CID } | { @@ -321,7 +313,7 @@ export class TransactionContext { AccessKey.fromBytes(arg.capsuleKey), options )(this.#privateContext()) - } else if ('file' in arg || 'directory' in arg) { + } else if (Array.isArray(arg)) { // Public or private from path bytes = await this.#query(arg, { public: Queries.publicRead(options), @@ -329,7 +321,7 @@ export class TransactionContext { }) } else { // ⚠️ - throw new Error('Invalid argument') + throw new TypeError('Invalid argument') } return dataFromBytes(dataType, bytes) @@ -339,7 +331,7 @@ export class TransactionContext { * @param path * @group Querying */ - async size(path: Path.File>): Promise { + async size(path: PartitionedNonEmpty): Promise { return await this.#query(path, { public: Queries.publicSize(), private: Queries.privateSize(), @@ -354,23 +346,21 @@ export class TransactionContext { * @group Mutating */ async copy( - fromParam: Path.Distinctive>, - toParam: - | Path.File> - | Path.Directory> + fromParam: PartitionedNonEmpty, + toParam: PartitionedNonEmpty | Partitioned ): Promise { const from = fromParam - let to = toParam + const to = toParam - if (Path.isDirectory(fromParam) && Path.isFile(toParam)) - throw new Error('Cannot copy a directory to a file') - if (Path.isFile(fromParam) && Path.isDirectory(toParam)) - to = Path.combine(toParam, Path.file(Path.terminus(from))) + const kind = await this.#query(from, { + public: Queries.publicItemKind(), + private: Queries.privateItemKind(), + }) - if (Path.isFile(from) && Path.isFile(to)) { + if (kind === Path.Kind.File && Path.isPartitionedNonEmpty(to)) { await this.#manualCopyFile(from, to) return - } else if (Path.isDirectory(from) && Path.isDirectory(to)) { + } else if (kind === Path.Kind.Directory) { await this.#manualCopyDirectory(from, to) return } @@ -389,8 +379,8 @@ export class TransactionContext { * @group Mutating */ async createDirectory( - path: Path.Directory> - ): Promise<{ path: Path.Directory> }> { + path: PartitionedNonEmpty + ): Promise<{ path: PartitionedNonEmpty }> { if (await this.exists(path)) { const newPath = addOrIncreaseNameNumber(path) return await this.createDirectory(newPath) @@ -407,10 +397,10 @@ export class TransactionContext { * @group Mutating */ async createFile( - path: Path.File>, + path: PartitionedNonEmpty, dataType: DataType, data: DataForType - ): Promise<{ path: Path.File> }> { + ): Promise<{ path: PartitionedNonEmpty }> { if (await this.exists(path)) { const newPath = addOrIncreaseNameNumber(path) return await this.createFile(newPath, dataType, data) @@ -424,9 +414,7 @@ export class TransactionContext { * @param path * @group Mutating */ - async ensureDirectory( - path: Path.Directory> - ): Promise { + async ensureDirectory(path: PartitionedNonEmpty): Promise { const partition = determinePartition(path) switch (partition.name) { @@ -459,18 +447,11 @@ export class TransactionContext { * @group Mutating */ async move( - fromParam: Path.Distinctive>, - toParam: - | Path.File> - | Path.Directory> + fromParam: PartitionedNonEmpty, + toParam: PartitionedNonEmpty | Partitioned ): Promise { const from = fromParam - let to = toParam - - if (Path.isDirectory(fromParam) && Path.isFile(toParam)) - throw new Error('Cannot move a directory to a file') - if (Path.isFile(fromParam) && Path.isDirectory(toParam)) - to = Path.combine(toParam, Path.file(Path.terminus(from))) + const to = toParam await this.#manualMove(from, to) } @@ -482,9 +463,7 @@ export class TransactionContext { * @param path * @group Mutating */ - async remove( - path: Path.Distinctive> - ): Promise { + async remove(path: PartitionedNonEmpty): Promise { const partition = determinePartition(path) switch (partition.name) { @@ -517,7 +496,7 @@ export class TransactionContext { * @group Mutating */ async rename( - path: Path.Distinctive>, + path: PartitionedNonEmpty, newName: string ): Promise { const fromPath = path @@ -533,7 +512,7 @@ export class TransactionContext { * @group Mutating */ async write( - path: Path.File>, + path: PartitionedNonEmpty, dataType: DataType, data: DataForType ): Promise { @@ -651,14 +630,10 @@ export class TransactionContext { ) // Create share context - const path = Path.fromKind( - sharedNode.isFile() ? Path.Kind.File : Path.Kind.Directory - ) - return new Share( shareId, sharerBlockstore, - { [Path.toPosix(path, { absolute: true })]: { path, node: sharedNode } }, + { '/': { path: Path.root(), node: sharedNode } }, this.#rng, sharerRootTree ) @@ -712,7 +687,7 @@ export class TransactionContext { * @group Sharing */ async share( - path: Path.Distinctive>, + path: Partitioned, receiverDataRoot: CID, opts: { receiverBlockstore?: Blockstore @@ -798,7 +773,7 @@ export class TransactionContext { // ㊙️ ▒▒ QUERIES async #query( - path: Path.Distinctive>, + path: Partitioned, queryFunctions: { public: Queries.Public private: Queries.Private @@ -828,15 +803,15 @@ export class TransactionContext { // ㊙️ ▒▒ MUTATIONS async #manualCopyFile( - from: Path.File>, - to: Path.File> + from: PartitionedNonEmpty, + to: PartitionedNonEmpty ): Promise { await this.write(to, 'bytes', await this.read(from, 'bytes')) } async #manualCopyDirectory( - from: Path.Directory>, - to: Path.Directory> + from: PartitionedNonEmpty, + to: Partitioned ): Promise { if (Path.isPartitionedNonEmpty(to)) await this.ensureDirectory(to) @@ -854,12 +829,12 @@ export class TransactionContext { item.kind === 'directory' ? await this.#manualCopyDirectory( - Path.combine(from, Path.directory(item.name)), - Path.combine(to, Path.directory(item.name)) + Path.combine(from, [item.name]), + Path.combine(to, [item.name]) ) : await this.#manualCopyFile( - Path.combine(from, Path.file(item.name)), - Path.combine(to, Path.file(item.name)) + Path.combine(from, [item.name]), + Path.combine(to, [item.name]) ) }, Promise.resolve() @@ -867,17 +842,15 @@ export class TransactionContext { } async #manualMove( - from: Path.Distinctive>, - to: - | Path.File> - | Path.Directory> + from: PartitionedNonEmpty, + to: PartitionedNonEmpty | Partitioned ): Promise { await this.copy(from, to) await this.remove(from) } async #publicMutation( - path: Path.Distinctive>, + path: Partitioned, mut: Mutations.Public, mutType: MutationType ): Promise { @@ -888,7 +861,7 @@ export class TransactionContext { const result = await mut({ blockstore: this.#blockstore, - pathSegments: Path.unwrap(Path.removePartition(path)), + pathSegments: Path.removePartition(path), rootTree: this.#rootTree, }) @@ -902,7 +875,7 @@ export class TransactionContext { } async #privateMutation( - path: Path.Distinctive>, + path: Partitioned, mut: Mutations.Private, mutType: MutationType ): Promise { diff --git a/packages/nest/src/types.ts b/packages/nest/src/types.ts index 6e89963..4314e7b 100644 --- a/packages/nest/src/types.ts +++ b/packages/nest/src/types.ts @@ -27,11 +27,11 @@ export interface DirectoryItem { export type DirectoryItemWithKind = DirectoryItem & { kind: Path.Kind - path: Path.Distinctive> + path: Path.PartitionedNonEmpty } export interface Modification { - path: Path.Distinctive> + path: Path.Partitioned type: MutationType } @@ -53,13 +53,13 @@ export type MutationType = 'added-or-updated' | 'removed' | 'shared' export type PartitionDiscovery

= P extends Path.Public ? { name: 'public' - path: Path.File> + path: Path.Partitioned segments: Path.Segments } : P extends Path.Private ? { name: 'private' - path: Path.File> + path: Path.Partitioned segments: Path.Segments } : never @@ -68,13 +68,13 @@ export type PartitionDiscoveryNonEmpty

= P extends Path.Public ? { name: 'public' - path: Path.File> + path: Path.PartitionedNonEmpty segments: Path.Segments } : P extends Path.Private ? { name: 'private' - path: Path.File> + path: Path.PartitionedNonEmpty segments: Path.Segments } : never diff --git a/packages/nest/src/types/internal.ts b/packages/nest/src/types/internal.ts index 8d175b9..1748ee1 100644 --- a/packages/nest/src/types/internal.ts +++ b/packages/nest/src/types/internal.ts @@ -12,7 +12,7 @@ export type MountedPrivateNodes = Record export interface MountedPrivateNode { node: PrivateNode - path: Path.Distinctive + path: Path.Segments } export type PrivateNodeQueryResult = MountedPrivateNode & { diff --git a/packages/nest/src/unix.ts b/packages/nest/src/unix.ts index 8bf2dcb..adc5346 100644 --- a/packages/nest/src/unix.ts +++ b/packages/nest/src/unix.ts @@ -14,6 +14,9 @@ import * as Store from './store.js' /** * Create a UnixFS directory. + * + * @param currentTime + * @param links */ export function createDirectory( currentTime: Date, @@ -29,6 +32,12 @@ export function createDirectory( /** * Get the bytes of a UnixFS file. + * + * @param cid + * @param store + * @param options + * @param options.offset + * @param options.length */ export async function exportFile( cid: CID, @@ -51,6 +60,9 @@ export async function exportFile( /** * Get the CID for some file bytes. + * + * @param bytes + * @param store */ export async function importFile( bytes: Uint8Array, @@ -63,29 +75,35 @@ export async function importFile( /** * Insert a node into UnixFS tree, creating directories when needed * and overwriting content. + * + * @param itemKind + * @param node + * @param path + * @param store + * @param fileCID */ export async function insertNodeIntoTree( + itemKind: Path.Kind, node: PBNode, - path: Path.Distinctive, + path: Path.Segments, store: Blockstore, fileCID?: CID ): Promise { - const pathKind = Path.kind(path) - const pathParts = Path.unwrap(path) - const name = pathParts[0] + const name = path[0] const link = node.Links.find((l) => l.Name === name) // Directory // --------- - if (Path.length(path) > 1) { + if (path.length > 1) { const dirNode: PBNode = link?.Hash === undefined ? createDirectory(new Date()) : await load(link.Hash, store) const newDirNode = await insertNodeIntoTree( + itemKind, dirNode, - Path.fromKind(pathKind, ...pathParts.slice(1)), + path.slice(1), store, fileCID ) @@ -107,7 +125,7 @@ export async function insertNodeIntoTree( // Last part of path // ----------------- // Directory - if (pathKind === 'directory') { + if (itemKind === Path.Kind.Directory) { if (link !== undefined) return node const dirNode = createDirectory(new Date()) @@ -131,6 +149,9 @@ export async function insertNodeIntoTree( /** * Load a UnixFS node. + * + * @param cid + * @param store */ export async function load(cid: CID, store: Blockstore): Promise { return DagPB.decode(await store.get(cid)) @@ -138,20 +159,22 @@ export async function load(cid: CID, store: Blockstore): Promise { /** * Remove a node from a UnixFS tree. + * + * @param node + * @param path + * @param store */ export async function removeNodeFromTree( node: PBNode, - path: Path.Distinctive, + path: Path.Segments, store: Blockstore ): Promise { - const pathKind = Path.kind(path) - const pathParts = Path.unwrap(path) - const name = pathParts[0] + const name = path[0] const link = node.Links.find((l) => l.Name === name) // Directory // --------- - if (Path.length(path) > 1) { + if (path.length > 1) { let dirNode: PBNode if (link?.Hash === undefined) { @@ -162,7 +185,7 @@ export async function removeNodeFromTree( const newDirNode = await removeNodeFromTree( dirNode, - Path.fromKind(pathKind, ...pathParts.slice(1)), + Path.removePartition(path), store ) @@ -188,14 +211,31 @@ export async function removeNodeFromTree( // ㊙️ +/** + * + * @param links + * @param name + * @param hash + */ function addLink(links: PBLink[], name: string, hash: CID): PBLink[] { return [...links, DagPB.createLink(name, 0, hash)].sort(linkSorter) } +/** + * + * @param links + * @param name + * @param hash + */ function replaceLinkHash(links: PBLink[], name: string, hash: CID): PBLink[] { return links.map((l) => (l.Name === name ? { ...l, Hash: hash } : l)) } +/** + * + * @param a + * @param b + */ function linkSorter(a: PBLink, b: PBLink): number { if ((a.Name ?? '') > (b.Name ?? '')) return 1 if ((a.Name ?? '') < (b.Name ?? '')) return -1 diff --git a/packages/nest/test/class.test.ts b/packages/nest/test/class.test.ts index 13e5d35..3aa7aa7 100644 --- a/packages/nest/test/class.test.ts +++ b/packages/nest/test/class.test.ts @@ -23,7 +23,7 @@ describe('File System Class', () => { let blockstore: Blockstore let fs: FileSystem let _mounts: Array<{ - path: Path.Distinctive + path: Path.Segments capsuleKey: Uint8Array }> @@ -53,8 +53,8 @@ describe('File System Class', () => { // ------- it('loads a file system and capsule keys + content cids', async () => { - const publicPath = Path.file('public', 'nested-public', 'public.txt') - const privatePath = Path.file('private', 'nested-private', 'private.txt') + const publicPath = Path.pub('nested-public', 'public.txt') + const privatePath = Path.priv('nested-private', 'private.txt') await fs.write(publicPath, 'utf8', 'public') const { capsuleKey, dataRoot } = await fs.write( @@ -81,19 +81,19 @@ describe('File System Class', () => { }) it('loads a file system and capsule keys + content cids after multiple changes', async () => { - const publicPath = Path.file('public', 'nested-public', 'public.txt') - const privatePath = Path.file('private', 'nested-private', 'private.txt') + const publicPath = Path.pub('nested-public', 'public.txt') + const privatePath = Path.priv('nested-private', 'private.txt') await fs.write(publicPath, 'utf8', 'public') await fs.write(privatePath, 'utf8', 'private') - await fs.write(Path.file('public', 'part.two'), 'utf8', 'public-2') + await fs.write(Path.pub('part.two'), 'utf8', 'public-2') const { dataRoot } = await fs.write( - Path.file('private', 'part.two'), + Path.priv('part.two'), 'utf8', 'private-2' ) - const capsuleKey = await fs.capsuleKey(Path.directory('private')) + const capsuleKey = await fs.capsuleKey(Path.priv()) const loadedFs = await FileSystem.fromCID(dataRoot, { blockstore, @@ -111,8 +111,8 @@ describe('File System Class', () => { }) it('loads a private file system given an older capsule key', async () => { - const privatePath = Path.file('private', 'nested-private', 'private.txt') - const oldCapsuleKey = await fs.capsuleKey(Path.directory('private')) + const privatePath = Path.priv('nested-private', 'private.txt') + const oldCapsuleKey = await fs.capsuleKey(Path.priv()) const { dataRoot } = await fs.write(privatePath, 'utf8', 'private') @@ -140,7 +140,7 @@ describe('File System Class', () => { // ----------------- it('writes and reads public files', async () => { - const path = Path.file('public', 'a') + const path = Path.pub('a') const bytes = new TextEncoder().encode('🚀') await fs.write(path, 'bytes', bytes) @@ -150,7 +150,7 @@ describe('File System Class', () => { }) it('writes and reads private files', async () => { - const path = Path.file('private', 'a') + const path = Path.priv('a') await fs.write(path, 'json', { foo: 'bar', a: 1 }) @@ -158,8 +158,8 @@ describe('File System Class', () => { }) it('writes and reads deeply nested files', async () => { - const pathPublic = Path.file('public', 'a', 'b', 'c.txt') - const pathPrivate = Path.file('private', 'a', 'b', 'c.txt') + const pathPublic = Path.pub('a', 'b', 'c.txt') + const pathPrivate = Path.priv('a', 'b', 'c.txt') await fs.write(pathPublic, 'utf8', '🌍') await fs.write(pathPrivate, 'utf8', '🔐') @@ -176,41 +176,38 @@ describe('File System Class', () => { }) it('creates files', async () => { - await fs.write(Path.file('private', 'File'), 'utf8', '🧞') - await fs.createFile(Path.file('private', 'File'), 'utf8', '🧞') + await fs.write(Path.priv('File'), 'utf8', '🧞') + await fs.createFile(Path.priv('File'), 'utf8', '🧞') - assert.equal(await fs.exists(Path.file('private', 'File (1)')), true) + assert.equal(await fs.exists(Path.priv('File (1)')), true) - await fs.createFile(Path.file('private', 'File'), 'utf8', '🧞') + await fs.createFile(Path.priv('File'), 'utf8', '🧞') - assert.equal(await fs.exists(Path.file('private', 'File (2)')), true) + assert.equal(await fs.exists(Path.priv('File (2)')), true) - await fs.createFile(Path.file('private', 'File (1)'), 'utf8', '🧞') + await fs.createFile(Path.priv('File (1)'), 'utf8', '🧞') - assert.equal(await fs.read(Path.file('private', 'File (3)'), 'utf8'), '🧞') + assert.equal(await fs.read(Path.priv('File (3)'), 'utf8'), '🧞') }) it('creates files with extensions', async () => { - await fs.write(Path.file('private', 'File.7z'), 'utf8', '🧞') - await fs.createFile(Path.file('private', 'File.7z'), 'utf8', '🧞') + await fs.write(Path.priv('File.7z'), 'utf8', '🧞') + await fs.createFile(Path.priv('File.7z'), 'utf8', '🧞') - assert.equal(await fs.exists(Path.file('private', 'File (1).7z')), true) + assert.equal(await fs.exists(Path.priv('File (1).7z')), true) - await fs.createFile(Path.file('private', 'File.7z'), 'utf8', '🧞') + await fs.createFile(Path.priv('File.7z'), 'utf8', '🧞') - assert.equal(await fs.exists(Path.file('private', 'File (2).7z')), true) + assert.equal(await fs.exists(Path.priv('File (2).7z')), true) - await fs.createFile(Path.file('private', 'File (1).7z'), 'utf8', '🧞') + await fs.createFile(Path.priv('File (1).7z'), 'utf8', '🧞') - assert.equal( - await fs.read(Path.file('private', 'File (3).7z'), 'utf8'), - '🧞' - ) + assert.equal(await fs.read(Path.priv('File (3).7z'), 'utf8'), '🧞') }) it('retrieves public content using a CID', async () => { const { contentCID, capsuleCID } = await fs.write( - Path.file('public', 'file'), + Path.pub('file'), 'utf8', '🌍' ) @@ -221,18 +218,14 @@ describe('File System Class', () => { }) it('retrieves private content using a capsule key', async () => { - const { capsuleKey } = await fs.write( - Path.file('private', 'file'), - 'utf8', - '🔐' - ) + const { capsuleKey } = await fs.write(Path.priv('file'), 'utf8', '🔐') assert.equal(await fs.read({ capsuleKey }, 'utf8'), '🔐') }) it('can read partial public content bytes', async () => { const { contentCID, capsuleCID } = await fs.write( - Path.file('public', 'file'), + Path.pub('file'), 'bytes', new Uint8Array([16, 24, 32]) ) @@ -254,7 +247,7 @@ describe('File System Class', () => { it('can read partial utf8 public content', async () => { const { contentCID, capsuleCID } = await fs.write( - Path.file('public', 'file'), + Path.pub('file'), 'utf8', 'abc' ) @@ -272,7 +265,7 @@ describe('File System Class', () => { it('can read partial private content bytes', async () => { const { capsuleKey } = await fs.write( - Path.file('private', 'file'), + Path.priv('file'), 'bytes', new Uint8Array([16, 24, 32]) ) @@ -286,11 +279,7 @@ describe('File System Class', () => { }) it('can read partial utf8 private content', async () => { - const { capsuleKey } = await fs.write( - Path.file('private', 'file'), - 'utf8', - 'abc' - ) + const { capsuleKey } = await fs.write(Path.priv('file'), 'utf8', 'abc') assert.equal( await fs.read({ capsuleKey }, 'utf8', { offset: 1, length: 1 }), @@ -302,57 +291,46 @@ describe('File System Class', () => { // ----------- it('ensures directories and checks for existence', async () => { - await fs.ensureDirectory(Path.directory('public', 'a')) - await fs.ensureDirectory(Path.directory('public', 'a', 'b')) - await fs.ensureDirectory(Path.directory('public', 'a', 'b', 'c')) + await fs.ensureDirectory(Path.pub('a')) + await fs.ensureDirectory(Path.pub('a', 'b')) + await fs.ensureDirectory(Path.pub('a', 'b', 'c')) - await fs.ensureDirectory(Path.directory('private', 'a')) - await fs.ensureDirectory(Path.directory('private', 'a', 'b')) - await fs.ensureDirectory(Path.directory('private', 'a', 'b', 'c')) + await fs.ensureDirectory(Path.priv('a')) + await fs.ensureDirectory(Path.priv('a', 'b')) + await fs.ensureDirectory(Path.priv('a', 'b', 'c')) - assert.equal(await fs.exists(Path.directory('public', 'a')), true) - assert.equal(await fs.exists(Path.directory('public', 'a', 'b')), true) - assert.equal(await fs.exists(Path.directory('public', 'a', 'b', 'c')), true) + assert.equal(await fs.exists(Path.pub('a')), true) + assert.equal(await fs.exists(Path.pub('a', 'b')), true) + assert.equal(await fs.exists(Path.pub('a', 'b', 'c')), true) - assert.equal(await fs.exists(Path.directory('private', 'a')), true) - assert.equal(await fs.exists(Path.directory('private', 'a', 'b')), true) - assert.equal( - await fs.exists(Path.directory('private', 'a', 'b', 'c')), - true - ) + assert.equal(await fs.exists(Path.priv('a')), true) + assert.equal(await fs.exists(Path.priv('a', 'b')), true) + assert.equal(await fs.exists(Path.priv('a', 'b', 'c')), true) // Does not throw for existing dirs - await fs.ensureDirectory(Path.directory('public', 'a')) - await fs.ensureDirectory(Path.directory('public', 'a', 'b')) + await fs.ensureDirectory(Path.pub('a')) + await fs.ensureDirectory(Path.pub('a', 'b')) - await fs.ensureDirectory(Path.directory('private', 'a')) - await fs.ensureDirectory(Path.directory('private', 'a', 'b')) + await fs.ensureDirectory(Path.priv('a')) + await fs.ensureDirectory(Path.priv('a', 'b')) - await assertUnixFsDirectory( - { blockstore }, - fs, - Path.directory('public', 'a') - ) - await assertUnixFsDirectory( - { blockstore }, - fs, - Path.directory('public', 'a', 'b') - ) + await assertUnixFsDirectory({ blockstore }, fs, Path.pub('a')) + await assertUnixFsDirectory({ blockstore }, fs, Path.pub('a', 'b')) }) it('lists public directories', async () => { - await fs.ensureDirectory(Path.directory('public', 'a')) - await fs.write(Path.file('public', 'a-file'), 'utf8', '🧞') - await fs.ensureDirectory(Path.directory('public', 'a', 'b')) - await fs.write(Path.file('public', 'a', 'b-file'), 'utf8', '💃') + await fs.ensureDirectory(Path.pub('a')) + await fs.write(Path.pub('a-file'), 'utf8', '🧞') + await fs.ensureDirectory(Path.pub('a', 'b')) + await fs.write(Path.pub('a', 'b-file'), 'utf8', '💃') - const a = await fs.listDirectory(Path.directory('public')) + const a = await fs.listDirectory(Path.pub()) assert.deepEqual( a.map((i) => i.name), ['a', 'a-file'] ) - const b = await fs.listDirectory(Path.directory('public', 'a')) + const b = await fs.listDirectory(Path.pub('a')) assert.deepEqual( b.map((i) => i.name), ['b', 'b-file'] @@ -360,17 +338,17 @@ describe('File System Class', () => { }) it('lists public directories with item kind', async () => { - const pathDirA = Path.directory('public', 'a') - const pathFileA = Path.file('public', 'a-file') - const pathDirB = Path.directory('public', 'a', 'b') - const pathFileB = Path.file('public', 'a', 'b-file') + const pathDirA = Path.pub('a') + const pathFileA = Path.pub('a-file') + const pathDirB = Path.pub('a', 'b') + const pathFileB = Path.pub('a', 'b-file') await fs.ensureDirectory(pathDirA) await fs.write(pathFileA, 'utf8', '🧞') await fs.ensureDirectory(pathDirB) await fs.write(pathFileB, 'utf8', '💃') - const a = await fs.listDirectory(Path.directory('public'), { + const a = await fs.listDirectory(Path.pub(), { withItemKind: true, }) assert.deepEqual( @@ -382,7 +360,7 @@ describe('File System Class', () => { [pathDirA, pathFileA] ) - const b = await fs.listDirectory(Path.directory('public', 'a'), { + const b = await fs.listDirectory(Path.pub('a'), { withItemKind: true, }) assert.deepEqual( @@ -396,18 +374,18 @@ describe('File System Class', () => { }) it('lists private directories', async () => { - await fs.ensureDirectory(Path.directory('private', 'a')) - await fs.write(Path.file('private', 'a-file'), 'utf8', '🧞') - await fs.ensureDirectory(Path.directory('private', 'a', 'b')) - await fs.write(Path.file('private', 'a', 'b-file'), 'utf8', '💃') + await fs.ensureDirectory(Path.priv('a')) + await fs.write(Path.priv('a-file'), 'utf8', '🧞') + await fs.ensureDirectory(Path.priv('a', 'b')) + await fs.write(Path.priv('a', 'b-file'), 'utf8', '💃') - const a = await fs.listDirectory(Path.directory('private')) + const a = await fs.listDirectory(Path.priv()) assert.deepEqual( a.map((i) => i.name), ['a', 'a-file'] ) - const b = await fs.listDirectory(Path.directory('private', 'a')) + const b = await fs.listDirectory(Path.priv('a')) assert.deepEqual( b.map((i) => i.name), ['b', 'b-file'] @@ -415,17 +393,17 @@ describe('File System Class', () => { }) it('lists private directories with item kind', async () => { - const pathDirA = Path.directory('private', 'a') - const pathFileA = Path.file('private', 'a-file') - const pathDirB = Path.directory('private', 'a', 'b') - const pathFileB = Path.file('private', 'a', 'b-file') + const pathDirA = Path.priv('a') + const pathFileA = Path.priv('a-file') + const pathDirB = Path.priv('a', 'b') + const pathFileB = Path.priv('a', 'b-file') await fs.ensureDirectory(pathDirA) await fs.write(pathFileA, 'utf8', '🧞') await fs.ensureDirectory(pathDirB) await fs.write(pathFileB, 'utf8', '💃') - const a = await fs.listDirectory(Path.directory('private'), { + const a = await fs.listDirectory(Path.priv(), { withItemKind: true, }) assert.deepEqual( @@ -437,7 +415,7 @@ describe('File System Class', () => { [pathDirA, pathFileA] ) - const b = await fs.listDirectory(Path.directory('private', 'a'), { + const b = await fs.listDirectory(Path.priv('a'), { withItemKind: true, }) assert.deepEqual( @@ -451,58 +429,25 @@ describe('File System Class', () => { }) it('creates directories', async () => { - await fs.ensureDirectory(Path.directory('private', 'Directory')) - await fs.createDirectory(Path.directory('private', 'Directory')) - - assert.equal( - await fs.exists(Path.directory('private', 'Directory (1)')), - true - ) - - await fs.createDirectory(Path.directory('private', 'Directory')) - - assert.equal( - await fs.exists(Path.directory('private', 'Directory (2)')), - true - ) - - await fs.createDirectory(Path.directory('private', 'Directory (1)')) - - assert.equal( - await fs.exists(Path.directory('private', 'Directory (3)')), - true - ) - }) - - it('creates directories with extensions', async () => { - await fs.ensureDirectory(Path.directory('private', 'Directory.7z')) - await fs.createDirectory(Path.directory('private', 'Directory.7z')) + await fs.ensureDirectory(Path.priv('Directory')) + await fs.createDirectory(Path.priv('Directory')) - assert.equal( - await fs.exists(Path.directory('private', 'Directory.7z (1)')), - true - ) + assert.equal(await fs.exists(Path.priv('Directory (1)')), true) - await fs.createDirectory(Path.directory('private', 'Directory.7z')) + await fs.createDirectory(Path.priv('Directory')) - assert.equal( - await fs.exists(Path.directory('private', 'Directory.7z (2)')), - true - ) + assert.equal(await fs.exists(Path.priv('Directory (2)')), true) - await fs.createDirectory(Path.directory('private', 'Directory.7z (1)')) + await fs.createDirectory(Path.priv('Directory (1)')) - assert.equal( - await fs.exists(Path.directory('private', 'Directory.7z (3)')), - true - ) + assert.equal(await fs.exists(Path.priv('Directory (3)')), true) }) // CIDS & REFS // ----------- it('can get a content CID for an existing public file', async () => { - const path = Path.file('public', 'a', 'b', 'file') + const path = Path.pub('a', 'b', 'file') const { contentCID } = await fs.write(path, 'utf8', '💃') const cid = await fs.contentCID(path) @@ -511,7 +456,7 @@ describe('File System Class', () => { }) it('can get a capsule CID for an existing public file', async () => { - const path = Path.file('public', 'a', 'b', 'file') + const path = Path.pub('a', 'b', 'file') const { capsuleCID } = await fs.write(path, 'utf8', '💃') const cid = await fs.capsuleCID(path) @@ -520,7 +465,7 @@ describe('File System Class', () => { }) it('can get a capsule CID for an existing public directory', async () => { - const path = Path.directory('public', 'a', 'b', 'directory') + const path = Path.pub('a', 'b', 'directory') const { capsuleCID } = await fs.ensureDirectory(path) const cid = await fs.capsuleCID(path) @@ -529,7 +474,7 @@ describe('File System Class', () => { }) it('can get a capsule key for an existing private file', async () => { - const path = Path.file('private', 'a', 'b', 'file') + const path = Path.priv('a', 'b', 'file') const { capsuleKey } = await fs.write(path, 'utf8', '💃') const key = await fs.capsuleKey(path) @@ -541,7 +486,7 @@ describe('File System Class', () => { }) it('can get a capsule CID for an existing private directory', async () => { - const path = Path.directory('private', 'a', 'b', 'directory') + const path = Path.priv('a', 'b', 'directory') const { capsuleKey } = await fs.ensureDirectory(path) const key = await fs.capsuleKey(path) @@ -553,7 +498,7 @@ describe('File System Class', () => { }) it('can get a capsule CID for a mounted private directory', async () => { - const path = Path.directory('private') + const path = Path.priv() const key = await fs.capsuleKey(path) assert.notEqual( @@ -566,7 +511,7 @@ describe('File System Class', () => { // ---- it('returns the size of public files', async () => { - const path = Path.file('public', 'file') + const path = Path.pub('file') await fs.write(path, 'bytes', new Uint8Array([1, 2, 3])) const size = await fs.size(path) @@ -575,7 +520,7 @@ describe('File System Class', () => { }) it('returns the size of private files', async () => { - const path = Path.file('private', 'file') + const path = Path.priv('file') await fs.write(path, 'bytes', new Uint8Array([1, 2, 3, 4])) const size = await fs.size(path) @@ -587,7 +532,7 @@ describe('File System Class', () => { // ------ it('removes public files', async () => { - const path = Path.file('public', 'a', 'b', 'file') + const path = Path.pub('a', 'b', 'file') await fs.write(path, 'utf8', '💃') await fs.remove(path) @@ -598,7 +543,7 @@ describe('File System Class', () => { }) it('removes private files', async () => { - const path = Path.file('private', 'a', 'b', 'file') + const path = Path.priv('a', 'b', 'file') await fs.write(path, 'utf8', '💃') await fs.remove(path) @@ -607,7 +552,7 @@ describe('File System Class', () => { }) it('removes public directories', async () => { - const path = Path.directory('public', 'a', 'b', 'directory') + const path = Path.pub('a', 'b', 'directory') await fs.ensureDirectory(path) await fs.remove(path) @@ -618,7 +563,7 @@ describe('File System Class', () => { }) it('removes private directories', async () => { - const path = Path.directory('private', 'a', 'b', 'directory') + const path = Path.priv('a', 'b', 'directory') await fs.ensureDirectory(path) await fs.remove(path) @@ -630,8 +575,8 @@ describe('File System Class', () => { // ------- it('copies public files', async () => { - const fromPath = Path.file('public', 'a', 'b', 'file') - const toPath = Path.file('public', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.pub('a', 'b', 'file') + const toPath = Path.pub('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.copy(fromPath, toPath) @@ -640,10 +585,10 @@ describe('File System Class', () => { }) it('copies public files into a directory that already exists', async () => { - await fs.ensureDirectory(Path.directory('public', 'a', 'b', 'c', 'd')) + await fs.ensureDirectory(Path.pub('a', 'b', 'c', 'd')) - const fromPath = Path.file('public', 'a', 'b', 'file') - const toPath = Path.file('public', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.pub('a', 'b', 'file') + const toPath = Path.pub('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.copy(fromPath, toPath) @@ -652,8 +597,8 @@ describe('File System Class', () => { }) it('copies private files', async () => { - const fromPath = Path.file('private', 'a', 'b', 'file') - const toPath = Path.file('private', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.priv('a', 'b', 'file') + const toPath = Path.priv('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.copy(fromPath, toPath) @@ -662,10 +607,10 @@ describe('File System Class', () => { }) it('copies private files into a directory that already exists', async () => { - await fs.ensureDirectory(Path.directory('private', 'a', 'b', 'c', 'd')) + await fs.ensureDirectory(Path.priv('a', 'b', 'c', 'd')) - const fromPath = Path.file('private', 'a', 'b', 'file') - const toPath = Path.file('private', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.priv('a', 'b', 'file') + const toPath = Path.priv('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.copy(fromPath, toPath) @@ -674,103 +619,71 @@ describe('File System Class', () => { }) it('copies public directories', async () => { - const fromPath = Path.directory('public', 'b', 'c') - const toPath = Path.directory('public', 'a', 'b', 'c', 'd', 'e') + const fromPath = Path.pub('b', 'c') + const toPath = Path.pub('a', 'b', 'c', 'd', 'e') - await fs.write(Path.combine(fromPath, Path.file('file')), 'utf8', '💃') - await fs.write( - Path.combine(fromPath, Path.file('nested', 'file')), - 'utf8', - '🧞' - ) + await fs.write(Path.combine(fromPath, ['file']), 'utf8', '💃') + await fs.write(Path.combine(fromPath, ['nested', 'file']), 'utf8', '🧞') + await fs.ensureDirectory(Path.combine(fromPath, ['nested-empty'])) await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-empty')) - ) - await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-2', 'deeply-nested')) + Path.combine(fromPath, ['nested-2', 'deeply-nested']) ) await fs.copy(fromPath, toPath) - assert.equal( - await fs.read(Path.combine(toPath, Path.file('file')), 'utf8'), - '💃' - ) + assert.equal(await fs.read(Path.combine(toPath, ['file']), 'utf8'), '💃') assert.equal( - await fs.read(Path.combine(toPath, Path.file('nested', 'file')), 'utf8'), + await fs.read(Path.combine(toPath, ['nested', 'file']), 'utf8'), '🧞' ) - assert.equal( - await fs.exists(Path.combine(toPath, Path.directory('nested-empty'))), - true - ) + assert.equal(await fs.exists(Path.combine(toPath, ['nested-empty'])), true) assert.equal( - await fs.exists( - Path.combine(toPath, Path.directory('nested-2', 'deeply-nested')) - ), + await fs.exists(Path.combine(toPath, ['nested-2', 'deeply-nested'])), true ) - await fs.copy(Path.directory('public', 'a', 'b'), Path.directory('public')) + await fs.copy(Path.pub('a', 'b'), Path.pub()) assert.equal( - await fs.exists( - Path.directory('public', 'b', 'c', 'nested-2', 'deeply-nested') - ), + await fs.exists(Path.pub('b', 'c', 'nested-2', 'deeply-nested')), true ) }) it('copies private directories', async () => { - const fromPath = Path.directory('private', 'b', 'c') - const toPath = Path.directory('private', 'a', 'b', 'c', 'd', 'e') + const fromPath = Path.priv('b', 'c') + const toPath = Path.priv('a', 'b', 'c', 'd', 'e') - await fs.write(Path.combine(fromPath, Path.file('file')), 'utf8', '💃') - await fs.write( - Path.combine(fromPath, Path.file('nested', 'file')), - 'utf8', - '🧞' - ) - await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-empty')) - ) + await fs.write(Path.combine(fromPath, ['file']), 'utf8', '💃') + await fs.write(Path.combine(fromPath, ['nested', 'file']), 'utf8', '🧞') + await fs.ensureDirectory(Path.combine(fromPath, ['nested-empty'])) await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-2', 'deeply-nested')) + Path.combine(fromPath, ['nested-2', 'deeply-nested']) ) await fs.copy(fromPath, toPath) - assert.equal( - await fs.read(Path.combine(toPath, Path.file('file')), 'utf8'), - '💃' - ) + assert.equal(await fs.read(Path.combine(toPath, ['file']), 'utf8'), '💃') assert.equal( - await fs.read(Path.combine(toPath, Path.file('nested', 'file')), 'utf8'), + await fs.read(Path.combine(toPath, ['nested', 'file']), 'utf8'), '🧞' ) - assert.equal( - await fs.exists(Path.combine(toPath, Path.directory('nested-empty'))), - true - ) + assert.equal(await fs.exists(Path.combine(toPath, ['nested-empty'])), true) assert.equal( - await fs.exists( - Path.combine(toPath, Path.directory('nested-2', 'deeply-nested')) - ), + await fs.exists(Path.combine(toPath, ['nested-2', 'deeply-nested'])), true ) - await fs.copy(Path.directory('private', 'a'), Path.directory('private')) + await fs.copy(Path.priv('a'), Path.priv()) assert.equal( - await fs.exists( - Path.directory('private', 'b', 'c', 'nested-2', 'deeply-nested') - ), + await fs.exists(Path.priv('b', 'c', 'nested-2', 'deeply-nested')), true ) }) @@ -779,8 +692,8 @@ describe('File System Class', () => { // ------ it('moves public files', async () => { - const fromPath = Path.file('public', 'a', 'b', 'file') - const toPath = Path.file('public', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.pub('a', 'b', 'file') + const toPath = Path.pub('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.move(fromPath, toPath) @@ -790,8 +703,8 @@ describe('File System Class', () => { }) it('moves private files', async () => { - const fromPath = Path.file('private', 'a', 'b', 'file') - const toPath = Path.file('private', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.priv('a', 'b', 'file') + const toPath = Path.priv('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.move(fromPath, toPath) @@ -801,150 +714,100 @@ describe('File System Class', () => { }) it('moves public directories', async () => { - const fromPath = Path.directory('public', 'b', 'c') - const toPath = Path.directory('public', 'a', 'b', 'c', 'd', 'e') + const fromPath = Path.pub('b', 'c') + const toPath = Path.pub('a', 'b', 'c', 'd', 'e') - await fs.write(Path.combine(fromPath, Path.file('file')), 'utf8', '💃') - await fs.write( - Path.combine(fromPath, Path.file('nested', 'file')), - 'utf8', - '🧞' - ) - await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-empty')) - ) + await fs.write(Path.combine(fromPath, ['file']), 'utf8', '💃') + await fs.write(Path.combine(fromPath, ['nested', 'file']), 'utf8', '🧞') + await fs.ensureDirectory(Path.combine(fromPath, ['nested-empty'])) await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-2', 'deeply-nested')) + Path.combine(fromPath, ['nested-2', 'deeply-nested']) ) await fs.move(fromPath, toPath) - assert.equal( - await fs.read(Path.combine(toPath, Path.file('file')), 'utf8'), - '💃' - ) + assert.equal(await fs.read(Path.combine(toPath, ['file']), 'utf8'), '💃') assert.equal( - await fs.read(Path.combine(toPath, Path.file('nested', 'file')), 'utf8'), + await fs.read(Path.combine(toPath, ['nested', 'file']), 'utf8'), '🧞' ) - assert.equal( - await fs.exists(Path.combine(toPath, Path.directory('nested-empty'))), - true - ) + assert.equal(await fs.exists(Path.combine(toPath, ['nested-empty'])), true) assert.equal( - await fs.exists( - Path.combine(toPath, Path.directory('nested-2', 'deeply-nested')) - ), + await fs.exists(Path.combine(toPath, ['nested-2', 'deeply-nested'])), true ) assert.equal(await fs.exists(fromPath), false) - await fs.move(Path.directory('public', 'a'), Path.directory('public')) + await fs.move(Path.pub('a'), Path.pub()) assert.equal( - await fs.exists( - Path.directory('public', 'b', 'c', 'nested-2', 'deeply-nested') - ), + await fs.exists(Path.pub('b', 'c', 'nested-2', 'deeply-nested')), false ) - assert.equal(await fs.exists(Path.directory('public', 'a')), false) + assert.equal(await fs.exists(Path.pub('a')), false) assert.equal( await fs.exists( - Path.directory( - 'public', - 'a', - 'b', - 'c', - 'd', - 'e', - 'nested-2', - 'deeply-nested' - ) + Path.pub('a', 'b', 'c', 'd', 'e', 'nested-2', 'deeply-nested') ), false ) }) it('moves private directories', async () => { - const fromPath = Path.directory('private', 'b', 'c') - const toPath = Path.directory('private', 'a', 'b', 'c', 'd', 'e') + const fromPath = Path.priv('b', 'c') + const toPath = Path.priv('a', 'b', 'c', 'd', 'e') - await fs.write(Path.combine(fromPath, Path.file('file')), 'utf8', '💃') - await fs.write( - Path.combine(fromPath, Path.file('nested', 'file')), - 'utf8', - '🧞' - ) + await fs.write(Path.combine(fromPath, ['file']), 'utf8', '💃') + await fs.write(Path.combine(fromPath, ['nested', 'file']), 'utf8', '🧞') + await fs.ensureDirectory(Path.combine(fromPath, ['nested-empty'])) await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-empty')) - ) - await fs.ensureDirectory( - Path.combine(fromPath, Path.directory('nested-2', 'deeply-nested')) + Path.combine(fromPath, ['nested-2', 'deeply-nested']) ) await fs.move(fromPath, toPath) - assert.equal( - await fs.read(Path.combine(toPath, Path.file('file')), 'utf8'), - '💃' - ) + assert.equal(await fs.read(Path.combine(toPath, ['file']), 'utf8'), '💃') assert.equal( - await fs.read(Path.combine(toPath, Path.file('nested', 'file')), 'utf8'), + await fs.read(Path.combine(toPath, ['nested', 'file']), 'utf8'), '🧞' ) - assert.equal( - await fs.exists(Path.combine(toPath, Path.directory('nested-empty'))), - true - ) + assert.equal(await fs.exists(Path.combine(toPath, ['nested-empty'])), true) assert.equal( - await fs.exists( - Path.combine(toPath, Path.directory('nested-2', 'deeply-nested')) - ), + await fs.exists(Path.combine(toPath, ['nested-2', 'deeply-nested'])), true ) assert.equal(await fs.exists(fromPath), false) - await fs.move(Path.directory('private', 'a'), Path.directory('private')) + await fs.move(Path.priv('a'), Path.priv()) assert.equal( - await fs.exists( - Path.directory('public', 'b', 'c', 'nested-2', 'deeply-nested') - ), + await fs.exists(Path.pub('b', 'c', 'nested-2', 'deeply-nested')), false ) - assert.equal(await fs.exists(Path.directory('public', 'a')), false) + assert.equal(await fs.exists(Path.pub('a')), false) assert.equal( await fs.exists( - Path.directory( - 'public', - 'a', - 'b', - 'c', - 'd', - 'e', - 'nested-2', - 'deeply-nested' - ) + Path.pub('a', 'b', 'c', 'd', 'e', 'nested-2', 'deeply-nested') ), false ) }) it('moves a public file to the private partition', async () => { - const fromPath = Path.file('public', 'a', 'b', 'file') - const toPath = Path.file('private', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.pub('a', 'b', 'file') + const toPath = Path.priv('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.move(fromPath, toPath) @@ -954,8 +817,8 @@ describe('File System Class', () => { }) it('moves a private file to the public partition', async () => { - const fromPath = Path.file('private', 'a', 'b', 'file') - const toPath = Path.file('public', 'a', 'b', 'c', 'd', 'file') + const fromPath = Path.priv('a', 'b', 'file') + const toPath = Path.pub('a', 'b', 'c', 'd', 'file') await fs.write(fromPath, 'utf8', '💃') await fs.move(fromPath, toPath) @@ -968,39 +831,39 @@ describe('File System Class', () => { // -------- it('renames public files', async () => { - await fs.write(Path.file('public', 'a'), 'bytes', new Uint8Array()) - await fs.rename(Path.file('public', 'a'), 'b') + await fs.write(Path.pub('a'), 'bytes', new Uint8Array()) + await fs.rename(Path.pub('a'), 'b') - assert.equal(await fs.exists(Path.file('public', 'a')), false) + assert.equal(await fs.exists(Path.pub('a')), false) - assert.equal(await fs.exists(Path.file('public', 'b')), true) + assert.equal(await fs.exists(Path.pub('b')), true) }) it('renames private files', async () => { - await fs.write(Path.file('private', 'a'), 'bytes', new Uint8Array()) - await fs.rename(Path.file('private', 'a'), 'b') + await fs.write(Path.priv('a'), 'bytes', new Uint8Array()) + await fs.rename(Path.priv('a'), 'b') - assert.equal(await fs.exists(Path.file('private', 'a')), false) + assert.equal(await fs.exists(Path.priv('a')), false) - assert.equal(await fs.exists(Path.file('private', 'b')), true) + assert.equal(await fs.exists(Path.priv('b')), true) }) it('renames public directories', async () => { - await fs.ensureDirectory(Path.directory('public', 'a')) - await fs.rename(Path.directory('public', 'a'), 'b') + await fs.ensureDirectory(Path.pub('a')) + await fs.rename(Path.pub('a'), 'b') - assert.equal(await fs.exists(Path.directory('public', 'a')), false) + assert.equal(await fs.exists(Path.pub('a')), false) - assert.equal(await fs.exists(Path.directory('public', 'b')), true) + assert.equal(await fs.exists(Path.pub('b')), true) }) it('renames private directories', async () => { - await fs.ensureDirectory(Path.directory('private', 'a')) - await fs.rename(Path.directory('private', 'a'), 'b') + await fs.ensureDirectory(Path.priv('a')) + await fs.rename(Path.priv('a'), 'b') - assert.equal(await fs.exists(Path.directory('private', 'a')), false) + assert.equal(await fs.exists(Path.priv('a')), false) - assert.equal(await fs.exists(Path.directory('private', 'b')), true) + assert.equal(await fs.exists(Path.priv('b')), true) }) // PUBLISHING @@ -1018,15 +881,11 @@ describe('File System Class', () => { .then(resolve, reject) }) - await fs.write(Path.file('private', 'a'), 'bytes', new Uint8Array()) - await fs.write(Path.file('private', 'b'), 'bytes', new Uint8Array()) - await fs.write(Path.file('private', 'c'), 'bytes', new Uint8Array()) + await fs.write(Path.priv('a'), 'bytes', new Uint8Array()) + await fs.write(Path.priv('b'), 'bytes', new Uint8Array()) + await fs.write(Path.priv('c'), 'bytes', new Uint8Array()) - const d = await fs.write( - Path.file('private', 'd'), - 'bytes', - new Uint8Array() - ) + const d = await fs.write(Path.priv('d'), 'bytes', new Uint8Array()) const result = await promise assert.equal(result.toString(), d.dataRoot.toString()) @@ -1048,22 +907,20 @@ describe('File System Class', () => { published = true }) - await fs.mkdir(Path.directory('private', 'dir'), { skipPublish: true }) - await fs.write(Path.file('public', 'file'), 'bytes', new Uint8Array(), { + await fs.mkdir(Path.priv('dir'), { skipPublish: true }) + await fs.write(Path.pub('file'), 'bytes', new Uint8Array(), { skipPublish: true, }) - await fs.cp(Path.file('public', 'file'), Path.file('private', 'file'), { + await fs.cp(Path.pub('file'), Path.priv('file'), { skipPublish: true, }) - await fs.mv( - Path.file('private', 'file'), - Path.file('private', 'dir', 'file'), - { skipPublish: true } - ) - await fs.rename(Path.file('private', 'dir', 'file'), 'renamed', { + await fs.mv(Path.priv('file'), Path.priv('dir', 'file'), { + skipPublish: true, + }) + await fs.rename(Path.priv('dir', 'file'), 'renamed', { skipPublish: true, }) - await fs.rm(Path.file('private', 'dir', 'renamed'), { skipPublish: true }) + await fs.rm(Path.priv('dir', 'renamed'), { skipPublish: true }) await new Promise((resolve) => setTimeout(resolve, fsOpts.settleTimeBeforePublish * 1.5) @@ -1086,7 +943,7 @@ describe('File System Class', () => { }) const mutationResult = await fs.write( - Path.file('private', 'file'), + Path.priv('file'), 'bytes', new Uint8Array() ) @@ -1100,27 +957,27 @@ describe('File System Class', () => { it('commits a transaction', async () => { await fs.transaction(async (t) => { - await t.write(Path.file('private', 'file'), 'utf8', '💃') + await t.write(Path.priv('file'), 'utf8', '💃') await t.write( - Path.file('public', 'file'), + Path.pub('file'), 'bytes', - await t.read(Path.file('private', 'file'), 'bytes') + await t.read(Path.priv('file'), 'bytes') ) }) - assert.equal(await fs.read(Path.file('public', 'file'), 'utf8'), '💃') + assert.equal(await fs.read(Path.pub('file'), 'utf8'), '💃') }) it("doesn't commit a transaction when an error occurs inside of the transaction", async () => { await fs .transaction(async (t) => { - await t.write(Path.file('private', 'file'), 'utf8', '💃') + await t.write(Path.priv('file'), 'utf8', '💃') throw new Error('Whoops') }) .catch((_error) => {}) try { - await fs.read(Path.file('private', 'file'), 'utf8') + await fs.read(Path.priv('file'), 'utf8') } catch (error) { assert(error) } @@ -1136,7 +993,7 @@ describe('File System Class', () => { _mounts = [await fs.createPrivateNode({ path: Path.root() })] const result = await fs.transaction(async (t) => { - await t.write(Path.file('private', 'file'), 'utf8', '💃') + await t.write(Path.priv('file'), 'utf8', '💃') }) assert.equal(result, 'no-op') @@ -1159,7 +1016,7 @@ describe('File System Class', () => { const receiverDataRoot = await receiverFs.calculateDataRoot() await fs.transaction(async (t) => { - const path = Path.file('private', 'fileToShare') + const path = Path.priv('fileToShare') await t.assignIdentifier('did:test:1') await t.write(path, 'utf8', '🔒') await t.share(path, receiverDataRoot) @@ -1191,22 +1048,16 @@ describe('File System Class', () => { const receiverDataRoot = a.dataRoot // Assign sharer identifier & create share - const path = Path.file('private', 'nested', 'level 2', 'fileToShare') + const path = Path.priv('nested', 'level 2', 'fileToShare') await fs.assignIdentifier('did:test:1') await fs.write(path, 'utf8', '🔒') - const b = await fs.share( - Path.directory('private', 'nested'), - receiverDataRoot - ) + const b = await fs.share(Path.priv('nested'), receiverDataRoot) // Receive share const sharerDataRoot = b.dataRoot const share = await receiverFs.receive(sharerDataRoot, keypair) - const content = await share.read( - 'utf8', - Path.file('level 2', 'fileToShare') - ) + const content = await share.read('utf8', ['level 2', 'fileToShare']) // Assert assert.equal(content, '🔒') @@ -1219,13 +1070,10 @@ describe('File System Class', () => { await fs.registerExchangeKey('device', keypair.publicKey) // Assign sharer identifier & create share - const path = Path.file('private', 'nested', 'level 2', 'fileToShare') + const path = Path.priv('nested', 'level 2', 'fileToShare') await fs.assignIdentifier('did:test:2') await fs.write(path, 'utf8', '🔒') - await fs.share( - Path.directory('private', 'nested'), - await fs.calculateDataRoot() - ) + await fs.share(Path.priv('nested'), await fs.calculateDataRoot()) // Unmount root test node, otherwise we'd search from the root, // instead of the new mount. @@ -1233,12 +1081,12 @@ describe('File System Class', () => { // Mount node await fs.mountPrivateNode({ - path: Path.directory('shared', 'item'), + path: ['shared', 'item'], exchangeKeyPair: keypair, }) const content = await fs.read( - Path.file('private', 'shared', 'item', 'level 2', 'fileToShare'), + Path.priv('shared', 'item', 'level 2', 'fileToShare'), 'utf8' ) @@ -1253,10 +1101,10 @@ describe('File System Class', () => { await fs.registerExchangeKey('device', keypair.publicKey) // Assign sharer identifier & create share - const path = Path.file('private', 'nested', 'level 2', 'fileToShare') + const path = Path.priv('nested', 'level 2', 'fileToShare') await fs.assignIdentifier('did:test:3') await fs.write(path, 'utf8', '🔒') - await fs.share(Path.directory('private'), await fs.calculateDataRoot()) + await fs.share(Path.priv(), await fs.calculateDataRoot()) // Unmount existing root node fs.unmountPrivateNode(Path.root()) @@ -1269,29 +1117,25 @@ describe('File System Class', () => { // Check content const content = await fs.read( - Path.file('private', 'nested', 'level 2', 'fileToShare'), + Path.priv('nested', 'level 2', 'fileToShare'), 'utf8' ) assert.equal(content, '🔒') // Test mutations - await fs.write( - Path.file('private', 'nested', 'level 2', 'fileToShare'), - 'utf8', - '🚀' - ) + await fs.write(Path.priv('nested', 'level 2', 'fileToShare'), 'utf8', '🚀') const content2 = await fs.read( - Path.file('private', 'nested', 'level 2', 'fileToShare'), + Path.priv('nested', 'level 2', 'fileToShare'), 'utf8' ) assert.equal(content2, '🚀') - await fs.write(Path.file('private', 'dir', 'test'), 'utf8', '✌️') + await fs.write(Path.priv('dir', 'test'), 'utf8', '✌️') - const content3 = await fs.read(Path.file('private', 'dir', 'test'), 'utf8') + const content3 = await fs.read(Path.priv('dir', 'test'), 'utf8') assert.equal(content3, '✌️') }) @@ -1323,24 +1167,16 @@ describe('File System Class', () => { }), ] - await fs.write( - Path.file('private', 'nested', 'level 2', 'fileToShare'), - 'utf8', - '🚀' - ) + await fs.write(Path.priv('nested', 'level 2', 'fileToShare'), 'utf8', '🚀') const content2 = await fs.read( - Path.file('private', 'nested', 'level 2', 'fileToShare'), + Path.priv('nested', 'level 2', 'fileToShare'), 'utf8' ) assert.equal(content2, '🚀') - const { dataRoot } = await fs.write( - Path.file('private', 'dir', 'test'), - 'utf8', - '✌️' - ) + const { dataRoot } = await fs.write(Path.priv('dir', 'test'), 'utf8', '✌️') // Try to load a new instance const fsInstance = await FileSystem.fromCID(dataRoot, { @@ -1353,10 +1189,7 @@ describe('File System Class', () => { exchangeKeyPair: keypair, }) - const content = await fsInstance.read( - Path.file('private', 'dir', 'test'), - 'utf8' - ) + const content = await fsInstance.read(Path.priv('dir', 'test'), 'utf8') assert.equal(content, '✌️') }) @@ -1378,15 +1211,12 @@ describe('File System Class', () => { // Assign sharer identifier & create share await fs.assignIdentifier('did:test:5') - await fs.write(Path.file('private', 'file 1'), 'utf8', '🔐 1') - await fs.write(Path.file('private', 'file 2'), 'utf8', '🔒 2') + await fs.write(Path.priv('file 1'), 'utf8', '🔐 1') + await fs.write(Path.priv('file 2'), 'utf8', '🔒 2') - const { shareId } = await fs.share( - Path.file('private', 'file 1'), - receiverDataRoot - ) + const { shareId } = await fs.share(Path.priv('file 1'), receiverDataRoot) - await fs.share(Path.file('private', 'file 2'), receiverDataRoot) + await fs.share(Path.priv('file 2'), receiverDataRoot) // Receive shares const sharerDataRoot = await fs.calculateDataRoot() @@ -1416,49 +1246,42 @@ describe('File System Class', () => { await fs.registerExchangeKey('device', keypair.publicKey) const first = await fs.createPrivateNode({ - path: Path.directory('first'), + path: ['first'], exchangeKeyPair: keypair, }) const _second = await fs.createPrivateNode({ - path: Path.directory('second'), + path: ['second'], exchangeKeyPair: keypair, }) - await fs.write( - Path.file('private', 'first', 'nested', 'file'), - 'utf8', - '🔑' - ) + await fs.write(Path.priv('first', 'nested', 'file'), 'utf8', '🔑') - await fs.write(Path.file('private', 'second', 'file'), 'utf8', '🔒') + await fs.write(Path.priv('second', 'file'), 'utf8', '🔒') const fsInstance = await FileSystem.fromCID(await fs.calculateDataRoot(), { blockstore, }) await fsInstance.mountPrivateNode({ - path: Path.directory('first'), + path: ['first'], exchangeKeyPair: keypair, shareId: first.shareId, }) await fsInstance.mountPrivateNode({ - path: Path.directory('second'), + path: ['second'], exchangeKeyPair: keypair, }) const contents = await fsInstance.read( - Path.file('private', 'first', 'nested', 'file'), + Path.priv('first', 'nested', 'file'), 'utf8' ) assert.equal(contents, '🔑') - const contents2 = await fsInstance.read( - Path.file('private', 'second', 'file'), - 'utf8' - ) + const contents2 = await fsInstance.read(Path.priv('second', 'file'), 'utf8') assert.equal(contents2, '🔒') }) @@ -1480,7 +1303,7 @@ describe('File System Class', () => { }) const { capsuleKey, dataRoot } = await fs.write( - Path.file('private', 'file'), + Path.priv('file'), 'utf8', '👀' ) @@ -1494,7 +1317,7 @@ describe('File System Class', () => { capsuleKey, }) - const contents = await fs.read(Path.file('private', 'file'), 'utf8') + const contents = await fs.read(Path.priv('file'), 'utf8') assert.equal(contents, '👀') }) diff --git a/packages/nest/test/helpers/index.ts b/packages/nest/test/helpers/index.ts index ef590d5..6ef62ff 100644 --- a/packages/nest/test/helpers/index.ts +++ b/packages/nest/test/helpers/index.ts @@ -13,32 +13,33 @@ import * as Path from '../../src/path.js' // PATHS +/** + * + * @param partition + */ export function arbitraryDirectoryPath

( partition: P -): fc.Arbitrary>> { +): fc.Arbitrary> { return fc .array(arbitraryPathSegment(), { minLength: 1, maxLength: 8 }) - .map((array) => { - const path: Path.Directory> = { - directory: [partition, ...array] as any, - } - return path - }) + .map((array) => [partition, ...array] as Path.PartitionedNonEmpty

) } +/** + * + * @param partition + */ export function arbitraryFilePath

( partition: P -): fc.Arbitrary>> { +): fc.Arbitrary> { return fc .array(arbitraryPathSegment(), { minLength: 1, maxLength: 8 }) - .map((array) => { - const path: Path.File> = { - file: [partition, ...array] as any, - } - return path - }) + .map((array) => [partition, ...array] as Path.PartitionedNonEmpty

) } +/** + * + */ export function arbitraryPathSegment(): fc.Arbitrary { return fc.oneof( fc.webSegment().filter((segment) => segment.length > 0), @@ -48,10 +49,17 @@ export function arbitraryPathSegment(): fc.Arbitrary { // UNIX +/** + * + * @param opts + * @param opts.blockstore + * @param fs + * @param path + */ export async function assertUnixFsDirectory( opts: { blockstore: Blockstore }, fs: FileSystem, - path: Path.Directory> + path: Path.Partitioned ): Promise { const dataRoot = await fs.calculateDataRoot() @@ -69,10 +77,18 @@ export async function assertUnixFsDirectory( assert.equal(entry.type, 'directory') } +/** + * + * @param opts + * @param opts.blockstore + * @param fs + * @param path + * @param bytes + */ export async function assertUnixFsFile( opts: { blockstore: Blockstore }, fs: FileSystem, - path: Path.File>, + path: Path.Partitioned, bytes: Uint8Array ): Promise { const dataRoot = await fs.calculateDataRoot() @@ -94,10 +110,17 @@ export async function assertUnixFsFile( assert.equal(Uint8Arrays.equals(unixBytes, bytes), true) } +/** + * + * @param opts + * @param opts.blockstore + * @param fs + * @param path + */ export async function assertUnixNodeRemoval( opts: { blockstore: Blockstore }, fs: FileSystem, - path: Path.Distinctive> + path: Path.Partitioned ): Promise { const dataRoot = await fs.calculateDataRoot() diff --git a/packages/nest/test/path.test.ts b/packages/nest/test/path.test.ts deleted file mode 100644 index 7b33336..0000000 --- a/packages/nest/test/path.test.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { strict as assert } from 'assert' -import * as fc from 'fast-check' - -import type { DirectoryPath, FilePath } from '../src/path.js' -import * as Path from '../src/path.js' -import { RootBranch } from '../src/path.js' - -describe('Path functions', () => { - // CREATION - - it('creates directory paths', () => { - fc.assert( - fc.property(fc.array(fc.hexaString()), (data) => { - assert.deepEqual(Path.directory(...data), { directory: data }) - }) - ) - - assert.throws(() => Path.directory('/')) - - // Type testing - const _a: Path.Directory> = - Path.directory('private') - const _b: Path.Directory> = - Path.directory('public', 'a') - const _c: Path.Directory = Path.directory( - 'private', - 'a', - 'b' - ) - }) - - it('creates file paths', () => { - fc.assert( - fc.property(fc.array(fc.hexaString()), (data) => { - assert.deepEqual(Path.file(...data), { file: data }) - }) - ) - - assert.throws(() => Path.file('/')) - - // Type testing - const _a: Path.File> = Path.file( - 'private', - 'a' - ) - const _b: Path.File = Path.file('private', 'a', 'b') - }) - - it('creates directory paths with fromKind', () => { - fc.assert( - fc.property(fc.array(fc.hexaString()), (data) => { - assert.deepEqual(Path.fromKind(Path.Kind.Directory, ...data), { - directory: data, - }) - }) - ) - - // Type testing - const _a: Path.Directory> = Path.fromKind( - Path.Kind.Directory, - 'private' - ) - const _b: Path.Directory> = - Path.fromKind(Path.Kind.Directory, 'public', 'a') - const _c: Path.Directory = Path.fromKind( - Path.Kind.Directory, - 'private', - 'a', - 'b' - ) - }) - - it('creates file paths with fromKind', () => { - fc.assert( - fc.property(fc.array(fc.hexaString()), (data) => { - assert.deepEqual(Path.fromKind(Path.Kind.File, ...data), { file: data }) - }) - ) - - // Type testing - const _a: Path.File> = Path.fromKind( - Path.Kind.File, - 'private', - 'a' - ) - const _b: Path.File = Path.fromKind( - Path.Kind.File, - 'private', - 'a', - 'b' - ) - }) - - // POSIX - - it('creates a path from a POSIX formatted string', () => { - assert.deepEqual(Path.fromPosix('foo/bar/'), { directory: ['foo', 'bar'] }) - - assert.deepEqual(Path.fromPosix('/foo/bar/'), { directory: ['foo', 'bar'] }) - - assert.deepEqual(Path.fromPosix('/'), { directory: [] }) - - assert.deepEqual(Path.fromPosix('foo/bar'), { file: ['foo', 'bar'] }) - - assert.deepEqual(Path.fromPosix('/foo/bar'), { file: ['foo', 'bar'] }) - }) - - it('converts a path to the POSIX format', () => { - assert.equal(Path.toPosix({ directory: ['foo', 'bar'] }), 'foo/bar/') - - assert.equal(Path.toPosix({ directory: [] }), '') - - assert.equal(Path.toPosix({ file: ['foo', 'bar'] }), 'foo/bar') - }) - - // 🛠 - - it('can create app-data paths', () => { - const appInfo = { - name: 'Tests', - creator: 'WNFS WG', - } - - const root: DirectoryPath> = - Path.appData('private', appInfo) - - assert.deepEqual(root, { - directory: [RootBranch.Private, 'Apps', appInfo.creator, appInfo.name], - }) - - const dir: DirectoryPath> = - Path.appData('private', appInfo, Path.directory('a')) - - assert.deepEqual(dir, { - directory: [ - RootBranch.Private, - 'Apps', - appInfo.creator, - appInfo.name, - 'a', - ], - }) - - const file: FilePath> = Path.appData( - 'public', - appInfo, - Path.file('a') - ) - - assert.deepEqual(file, { - file: [RootBranch.Public, 'Apps', appInfo.creator, appInfo.name, 'a'], - }) - }) - - it('can be combined', () => { - const dir: DirectoryPath = Path.combine( - Path.directory('a'), - Path.directory('b') - ) - - assert.deepEqual(dir, { directory: ['a', 'b'] }) - - const file: FilePath = Path.combine( - Path.directory('a'), - Path.file('b') - ) - - assert.deepEqual(file, { file: ['a', 'b'] }) - - // Type testing - const _a: DirectoryPath> = - Path.combine(Path.directory('private'), Path.directory('a')) - - const _aa: FilePath> = Path.combine( - Path.directory('public'), - Path.file('a') - ) - - const _b: DirectoryPath> = Path.combine( - Path.directory('private'), - Path.directory() - ) - - const _bb: FilePath> = Path.combine( - Path.directory('public'), - Path.file() - ) - - const _c: DirectoryPath> = - Path.combine(Path.directory('private'), Path.directory('a')) - - const _cc: FilePath> = Path.combine( - Path.directory('public'), - Path.file('a') - ) - }) - - it('supports isOnRootBranch', () => { - assert.equal( - Path.isOnRootBranch( - RootBranch.Private, - Path.directory(RootBranch.Private, 'a') - ), - true - ) - - assert.equal( - Path.isOnRootBranch( - RootBranch.Public, - Path.directory(RootBranch.Private, 'a') - ), - false - ) - }) - - it('supports isDirectory', () => { - assert.equal(Path.isDirectory(Path.directory(RootBranch.Private)), true) - - assert.equal(Path.isDirectory(Path.file('foo')), false) - }) - - it('supports isFile', () => { - assert.equal(Path.isFile(Path.file('foo')), true) - - assert.equal(Path.isFile(Path.directory(RootBranch.Private)), false) - }) - - it('supports isRootDirectory', () => { - assert.equal(Path.isRootDirectory(Path.root()), true) - - assert.equal(Path.isRootDirectory(Path.directory()), true) - - assert.equal( - Path.isRootDirectory(Path.directory(RootBranch.Private)), - false - ) - }) - - it('supports isSamePartition', () => { - assert.equal( - Path.isSamePartition( - Path.directory(RootBranch.Private), - Path.directory(RootBranch.Private) - ), - true - ) - - assert.equal( - Path.isSamePartition( - Path.directory(RootBranch.Private), - Path.directory(RootBranch.Public) - ), - false - ) - }) - - it('supports isSameKind', () => { - assert.equal(Path.isSameKind(Path.directory(), Path.file()), false) - - assert.equal(Path.isSameKind(Path.file(), Path.directory()), false) - - assert.equal(Path.isSameKind(Path.directory(), Path.directory()), true) - - assert.equal(Path.isSameKind(Path.file(), Path.file()), true) - }) - - it('has kind', () => { - assert.equal(Path.kind(Path.directory()), Path.Kind.Directory) - - assert.equal(Path.kind(Path.file()), Path.Kind.File) - }) - - it('supports map', () => { - assert.deepEqual( - Path.map((p) => [...p, 'bar'], Path.directory('foo')), // eslint-disable-line unicorn/no-array-method-this-argument - { directory: ['foo', 'bar'] } - ) - - assert.deepEqual( - Path.map((p) => [...p, 'bar'], Path.file('foo')), // eslint-disable-line unicorn/no-array-method-this-argument - { file: ['foo', 'bar'] } - ) - }) - - it('supports parent', () => { - assert.deepEqual(Path.parent(Path.directory('foo')), Path.root()) - - assert.deepEqual(Path.parent(Path.file('foo')), Path.root()) - - assert.equal(Path.parent(Path.root()), undefined) - - // Type testing - const _a: DirectoryPath> = - Path.parent({ - directory: ['private', 'a', 'b'], - }) - - const _a_: DirectoryPath = Path.parent({ - directory: ['random', 'a', 'b'], - }) - - const _b: DirectoryPath> = Path.parent({ - directory: ['private', 'a'], - }) - - const _b_: DirectoryPath = Path.parent({ - directory: ['random', 'a'], - }) - - const _c: DirectoryPath = Path.parent({ - directory: ['private'], - }) - - const _c_: DirectoryPath = Path.parent({ - directory: ['random'], - }) - - // const _x: undefined = Path.parent({ - // directory: [], - // }) - }) - - it('supports removePartition', () => { - assert.deepEqual(Path.removePartition(Path.directory('foo')), { - directory: [], - }) - - assert.deepEqual( - Path.removePartition(Path.directory('foo', 'bar')), - Path.directory('bar') - ) - }) - - it('supports replaceTerminus', () => { - assert.deepEqual( - Path.replaceTerminus(Path.file('private', 'a', 'b'), 'c'), - Path.file('private', 'a', 'c') - ) - - // Type testing - const _a: DirectoryPath> = - Path.replaceTerminus( - { - directory: ['private', 'a'], - }, - 'b' - ) - - const _b: FilePath> = - Path.replaceTerminus( - { - file: ['private', 'a'], - }, - 'b' - ) - - const _c: DirectoryPath = Path.replaceTerminus( - { - directory: ['a'], - }, - 'b' - ) - - const _d: FilePath = Path.replaceTerminus( - { - file: ['a'], - }, - 'b' - ) - }) - - it('correctly unwraps', () => { - assert.deepEqual(Path.unwrap(Path.directory('foo')), ['foo']) - - assert.deepEqual(Path.unwrap(Path.file('foo')), ['foo']) - }) -})