IO/B/Stream/Level1

From CommonJS Spec Wiki
< IO‎ | B‎ | Stream
Jump to: navigation, search

This proposal defines the Stream class portion of IO/B.

Please also see Binary/C and IO/B/Stream/Level0.

Preamble

This proposal defines a Stream class which is completely generic. New types of streams can be made JavaScript side either by using the raw IO interface defined by IO/B/Raw, or by subclassing stream and borrowing methods directly defined on another stream. (The former permits streams built on other data sources which only require a simple raw api, and the latter permits Stream subclasses which simply munge data or arguments for certain methods or add new ones)

The Stream class is intended to be extremely abstract makes no assumption to the capabilities of the underlying source of the data; Almost every operation works the same whether the stream is binary or text. Capabilities are duck-typed onto the Stream instead of defining separate types of streams. Access to a binary layer below a text layer is not assumed as streams may be implemented for any data source, and some of those data sources (Like a Stream that works on a StringBuffer) will already be text and thus will not support dual-layer text wrapped streaming.

Terminology

contentConstructor
See Binary/C.
sequence
See Binary/C.
# units
A length measured in the relevant unit (Byte or Characters) for the given sequence type of the stream.

Class api

new Stream(Object def);
(Only required by the base Stream class)
Creates a new generic stream instance using def as a object defining what the stream can do and how to do it using the Raw io api.
s.contentConstructor -> Function;
contentConstructor MUST return either Blob or String to indicate the underlying type of the stream content.
s.canRead -> boolean;
s.canWrite -> boolean;
(canRead MAY not be present if the stream is not readable)
(canWrite MAY not be present if the stream is not writeable)
Boolean getters indicating the readable state of the stream.
  • canRead/canWrite should be false if read/write is not able to read/write data right away.
  • canRead/canWrite should be true if read/write will return with data right away.
  • canRead MUST NOT be false if EOF has been reached and read will return an EOF indication right away.
  • canRead/canWrite MUST assume things are not blocked and return true if the underlying io api does not provide a mechanism to get this information.
s.seenEOF -> boolean;
Boolean property indicating if EOF has been reached.
  • .seenEOF must NOT be true before .read has returned an EOF indicator.
s.position -> uint;
The current position within the stream.
  • If position is so large that the number cannot be accurately represented (p == p+1 is true) then position MUST return Infinity.
  • position is measured in the same units as the type of sequence specified by .contentConstructor;
  • position MUST work even when Stream is constructed without a position: key.
    • This may be implemented by incrementing an internal position counter with lengths as .reads, .writes, .skips, etc... are performed, and modifying it along with a .seek where .tell should also include information that can reconstruct .the .position inside of an opaque cookie.
s.read() -> Sequence;
s.read(uint max) -> Sequence;
(MUST not be present if the stream is not readable)
Reads data from the stream.
  • .read MUST ONLY return sequences of the same type as .contentConstructor;
  • If max is 0, negative or not a integer a TypeError SHOULD be thrown.
    • 0 MAY be given a different error message as rather than being an invalid number the issue is you cannot read nothing.
  • max is measured in the same units as the type of sequence specified by .contentConstructor;
  • If max is omitted then .read buffers the entire stream then returns it's contents.
  • If max is specified then .read will do a partial read.
    • The resulting data MAY be of length max OR less than max but MUST have a length greater than 0.
  • If EOF has been reached then .read should return an empty sequence of length 0.
  • If max is null it should be set to 1 (?narwhal)
s.skip(uint max) -> uint;
(MUST not be present if the stream is not readable)
Skips over data as if reading from the stream.
  • If max is 0, negative or not a integer a TypeError SHOULD be thrown.
    • 0 MAY be given a different error message as rather than being an invalid number the issue is you cannot skip nothing.
  • max is measured in the same units as the type of sequence specified by .contentConstructor;
  • .skip does partial skips behaving similarly to .read;
  • .skip returns the number of units skipped in the skip. This number must be greater than 0.
  • If EOF has been reached then .skip should return 0.
  • .skip MUST be implemented on a readable stream even if not available in the underlying layer (it should use .read and discard the data if it needs to).
s.write(Sequence|Buffer|OpaqueRange data) -> ???;
(MUST not be present if the stream is not writable)
Writes data to the stream.
  • .write must fully write the data, it may not do partial writes.
  • .write SHOULD attempt try to memcopy data.
s.writePartial(Sequence|Buffer|OpaqueRange data, [offset=0]) -> uint;
Writes data to the stream without blocking to wait for all the chunks to go through.
  • .write MUST write at least 1 unit of data or block till it is permitted to write.
  • .write returns the length of the data it wrote into the stream. The return value may be combined together and used as the offset to further calls.
s.tell() -> OpaqueCookie|uint;
Returns the location within the stream in a format that can be used later by .seek
  • If this returns a Number it MUST be an integer indicating the location within the stream measured in the same units as the type of sequence specified by .contentConstructor;
    • Therefore, if this returns a number it should be the same as position.
  • An OpaqueCookie MUST contain enough information for .seek to later return to this location within the data and set .position back to the current state.
  • An OpaqueCookie returned by this function is only valid for this stream.
  • An OpaqueCookie returned by this function is invalid if a write has been made to a position in the stream before the location specified by this OpaqueCookie.
s.seek(OpaqueCookie|uint to);
(MUST not be present if the stream is not seekable)
Seeks to another location within the file.
  • This method MUST accept any valid value outputted by .tell on the same stream.
  • If an OpaqueCookie is used as input and a write was made to a location before the location specified by that OpaqueCookie in between the time .tell was called and .seek is called behaviour is not defined and will likely break.
s.rewind();
(MUST not be present if the stream is not seekable)
Seeks to the very beginning of the stream.
s.flush();
Flushes data which may be buffered in this or an underlying stream
  • If the underlying stream does not have a way to flush then this will be a no-op function.
s.close();
Closes the stream preventing any further reads or writes.
  • This must remove any .read, .write, or .seek present on the stream object.

Requirements

Stream's constructor MUST be implemented such that Stream.call(this, def); within a function that has a .prototype that inherits from Stream.prototype will modify the object specified by this instead of constructing a new object.

This requirement is defined so that subclasses of Stream may be made using the simple stub:

function StreamSubclass(o) {
	Stream.call(this, o);
}
StreamSubclass.prototype = new Stream();

Notes and comment bait

  • Should we specify that the OpaqueCookie returned by .tell should respond to > and < calls (this can fairly easily be made to work using .valueOf)