Skip to content

Commit

Permalink
feat: 🎸 start WriteStream implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 20, 2023
1 parent fa1be20 commit 5971c39
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 20 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -84,7 +84,8 @@
}
},
"dependencies": {
"fs-monkey": "^1.0.4"
"fs-monkey": "^1.0.4",
"thingies": "^1.11.1"
},
"devDependencies": {
"@semantic-release/changelog": "^6.0.1",
Expand Down
38 changes: 24 additions & 14 deletions src/fsa-to-node/FsaNodeFs.ts
Expand Up @@ -40,6 +40,7 @@ import { constants } from '../constants';
import { FsaNodeStats } from './FsaNodeStats';
import process from '../process';
import { FsSynchronousApi } from '../node/types/FsSynchronousApi';
import {FsaNodeWriteStream} from './FsaNodeWriteStream';
import type { FsCallbackApi, FsPromisesApi } from '../node/types';
import type * as misc from '../node/types/misc';
import type * as opts from '../node/types/options';
Expand Down Expand Up @@ -732,36 +733,45 @@ export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjec
);
};

fchmod(fd: number, mode: misc.TMode, callback: misc.TCallback<void>): void {
public readonly fchmod: FsCallbackApi['fchmod'] = (fd: number, mode: misc.TMode, callback: misc.TCallback<void>): void => {
callback(null);
}
};

chmod(path: misc.PathLike, mode: misc.TMode, callback: misc.TCallback<void>): void {
public readonly chmod: FsCallbackApi['chmod'] = (path: misc.PathLike, mode: misc.TMode, callback: misc.TCallback<void>): void => {
callback(null);
}
};

lchmod(path: misc.PathLike, mode: misc.TMode, callback: misc.TCallback<void>): void {
public readonly lchmod: FsCallbackApi['lchmod'] = (path: misc.PathLike, mode: misc.TMode, callback: misc.TCallback<void>): void => {
callback(null);
}
};

fchown(fd: number, uid: number, gid: number, callback: misc.TCallback<void>): void {
public readonly fchown: FsCallbackApi['fchown'] = (fd: number, uid: number, gid: number, callback: misc.TCallback<void>): void => {
callback(null);
}
};

chown(path: misc.PathLike, uid: number, gid: number, callback: misc.TCallback<void>): void {
public readonly chown: FsCallbackApi['chown'] = (path: misc.PathLike, uid: number, gid: number, callback: misc.TCallback<void>): void => {
callback(null);
}
};

lchown(path: misc.PathLike, uid: number, gid: number, callback: misc.TCallback<void>): void {
public readonly lchown: FsCallbackApi['lchown'] = (path: misc.PathLike, uid: number, gid: number, callback: misc.TCallback<void>): void => {
callback(null);
}
};

public readonly createWriteStream: FsCallbackApi['createWriteStream'] = (path: misc.PathLike, options?: opts.IWriteStreamOptions | string): FsaNodeWriteStream => {
const optionsObj: opts.IWriteStreamOptions = !options ? {} : typeof options === 'object' ? options : { encoding: options } as opts.IWriteStreamOptions;
const filename = pathToFilename(path);
const location = pathToLocation(filename);
const flags = flagsToNumber(optionsObj.flags);
const createIfMissing = !!(flags & FLAG.O_CREAT);
const handle = this.getFile(location[0], location[1], 'createWriteStream', createIfMissing);
return new FsaNodeWriteStream(handle, filename, optionsObj);
};

public readonly symlink: FsCallbackApi['symlink'] = notSupported;
public readonly link: FsCallbackApi['link'] = notSupported;
public readonly watchFile: FsCallbackApi['watchFile'] = notSupported;
public readonly unwatchFile: FsCallbackApi['unwatchFile'] = notSupported;
public readonly createReadStream: FsCallbackApi['createReadStream'] = notSupported;
public readonly createWriteStream: FsCallbackApi['createWriteStream'] = notSupported;
public readonly watch: FsCallbackApi['watch'] = notSupported;

// --------------------------------------------------------- FsSynchronousApi
Expand Down Expand Up @@ -817,10 +827,10 @@ export class FsaNodeFs implements FsCallbackApi, FsSynchronousApi, FsCommonObjec
public readonly constants = constants;
public readonly Dirent = FsaNodeDirent;
public readonly Stats = FsaNodeStats<any>;
public readonly WriteStream = FsaNodeWriteStream;
public readonly StatFs = 0 as any;
public readonly Dir = 0 as any;
public readonly StatsWatcher = 0 as any;
public readonly FSWatcher = 0 as any;
public readonly ReadStream = 0 as any;
public readonly WriteStream = 0 as any;
}
67 changes: 67 additions & 0 deletions src/fsa-to-node/FsaNodeWriteStream.ts
@@ -0,0 +1,67 @@
import {Writable} from 'stream';
import {Defer} from 'thingies/es6/Defer';
import type {IFileSystemFileHandle} from '../fsa/types';
import type {IWriteStream} from '../node/types/misc';
import type {IWriteStreamOptions} from '../node/types/options';

/**
* This WriteStream implementation does not build on top of the `fs` module,
* but instead uses the lower-level `FileSystemFileHandle` interface. The reason
* is the different semantics in `fs` and FSA (File System Access API) write streams.
*
* When data is written to an FSA file, a new FSA stream is created, it copies
* the file to a temporary swap file. After each written chunk, that swap file
* is closed and the original file is replaced with the swap file. This means,
* if WriteStream was built on top of `fs`, each chunk write would result in
* a file copy, write, close, rename operations, which is not what we want.
*
* Instead this implementation hooks into the lower-level and closes the swap
* file only once the stream is closed. The downside is that the written data
* is not immediately visible to other processes (because it is written to the
* swap file), but that is the trade-off we have to make.
*/
export class FsaNodeWriteStream extends Writable implements IWriteStream {
protected __pending: boolean = true;
protected ready = new Defer<void>();
protected closed: boolean = false;

public constructor(protected readonly handle: Promise<IFileSystemFileHandle>, public readonly path: string, protected readonly options?: IWriteStreamOptions) {
super();
handle
.then(() => {
this.__pending = false;
this.ready.resolve();
})
.catch((error) => {
this.ready.reject(error);
});
}

// ------------------------------------------------------------- IWriteStream

public get bytesWritten(): number {
return 0;
}

public get pending(): boolean {
return this.__pending;
}

public close(): void {

}

// ----------------------------------------------------------------- Writable

_write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void {

}

_writev(chunks: Array<{ chunk: any, encoding: string }>, callback: (error?: Error | null) => void): void {

}

_final(callback: (error?: Error | null) => void): void {

}
}
3 changes: 1 addition & 2 deletions src/node/types/misc.ts
Expand Up @@ -94,8 +94,7 @@ export interface IReadStream extends Readable {
export interface IWriteStream extends Writable {
bytesWritten: number;
path: string;
new (path: PathLike, options: IWriteStreamOptions);
open();
pending: boolean;
close();
}

Expand Down
7 changes: 4 additions & 3 deletions src/node/types/options.ts
@@ -1,4 +1,4 @@
import type { TEncodingExtended, TFlags, TMode } from './misc';
import type { IFileHandle, TEncodingExtended, TFlags, TMode } from './misc';

export interface IOptions {
encoding?: BufferEncoding | TEncodingExtended;
Expand Down Expand Up @@ -72,10 +72,11 @@ export interface IReadStreamOptions {

export interface IWriteStreamOptions {
flags?: TFlags;
defaultEncoding?: BufferEncoding;
fd?: number;
encoding?: BufferEncoding;
fd?: number | IFileHandle;
mode?: TMode;
autoClose?: boolean;
emitClose?: boolean;
start?: number;
}

Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Expand Up @@ -6522,6 +6522,11 @@ text-table@~0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==

thingies@^1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.11.1.tgz#47a648e2b42c3bb011e493e0c99df20a2758c25d"
integrity sha512-SXWTuQA6TFjpfV4xiwPs4FkHashZShR/Hugosrm9sRMm63fAdgZaWKixt5YvuP6VwDaAROcDwf9cFeuw1DeekA==

through2@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764"
Expand Down

0 comments on commit 5971c39

Please sign in to comment.