Solid · guide

Initial values and ending a stream

What a stream holds before any text, what to do when Solid cannot work that out, and how to tell a reply that stopped early from a whole one.

Install it
pnpm add @niscorp/solid zod
Step 1 of 5

The shape

The schema is the shape you asked the model for. Solid needs it before any text arrives.

The shape
import { z } from 'zod';
import { createStream } from '@niscorp/solid';

const Suggestion = z.object({
  show: z.string(),
  seats: z.array(z.string()),
  price_cents: z.number(),
  why: z.string(),
});
Step 2 of 5

Where it starts

With no initial, Solid builds the first object from the schema: a default where there is one, an empty string, 0, an empty list, and for an enum its first value.

Where it starts
console.log(JSON.stringify(createStream({ schema: Suggestion }).current()));
Step 3 of 5

A schema it cannot start from

The first object has to pass the schema. Where an empty value would not, createStream throws and says so, and you give it an initial that does.

A schema it cannot start from
const Seats = z.object({ count: z.number().min(1) });
try {
  createStream({ schema: Seats });
} catch (error) {
  console.log(error instanceof Error ? error.message.split('. ')[0] : error);
}
console.log(JSON.stringify(createStream({ schema: Seats, initial: { count: 1 } }).current()));
Step 4 of 5

A reply that stops early

A model can stop in the middle. Solid does not know that it has: final() waits for the closing brace.

close() says no more is coming, and final() resolves with what there is, defaults and half values included. That object can pass your schema. So note whether onFinal was called before you closed: if it was not, the reply was not whole.

A reply that stops early
const stream = createStream({ schema: Suggestion });
let whole = false;
stream.onFinal(() => (whole = true));

stream.write('{"show":"The Tempest","seats":["C4"');
console.log('whole:', whole);
stream.close();
console.log(JSON.stringify(await stream.final()));
Step 5 of 5

A stream you no longer want

destroy() drops every listener and rejects a final() somebody is waiting on. It is what to call when the reader has moved on.

A stream you no longer want
const dropped = createStream({ schema: Suggestion });
const waiting = dropped.final().catch((error: Error) => error.message);
dropped.destroy();
console.log(await waiting);
Then

Run it.

The first object, the reason a schema could not be started from and the object it was given, whole: false with what the stream held when it was closed, and what destroy() rejects with.

Run it
npx tsx ends.mts
It prints
{"show":"","seats":[],"price_cents":0,"why":""}
[solid] cannot derive valid base from schema defaults
{"count":1}
whole: false
{"show":"The Tempest","seats":["C4"],"price_cents":0,"why":""}
[solid] stream destroyed