@hashbrownai/core
Version:
Runtime helpers for Hashbrown AI
1,576 lines (1,553 loc) • 238 kB
JavaScript
'use strict';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __await(v) {
return this instanceof __await ? (this.v = v, this) : new __await(v);
}
function __asyncGenerator(thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
}
function __asyncValues(o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* Decode a ReadableStream\<Uint8Array\> of length-prefixed JSON frames into a stream of T.
*
* Frame format: [4-byte BE length][UTF-8 JSON payload]
*
* @public
* @param stream - ReadableStream producing Uint8Array chunks
* @param options - decoding options
* @typeParam T - The type of the parsed JSON objects
* @returns - A stream of parsed JSON objects of type T
*/
function decodeFrames(stream, options) {
return __asyncGenerator(this, arguments, function* decodeFrames_1() {
const {
signal
} = options;
const reader = stream.getReader();
const textDecoder = new TextDecoder();
let buffer = new Uint8Array(0);
signal.addEventListener('abort', () => {
reader.cancel().catch(() => {
// ignore
});
}, {
once: true
});
if (signal.aborted) {
yield __await(reader.cancel());
return yield __await(void 0);
}
try {
while (true) {
if (signal.aborted) {
throw new Error('Decoding aborted');
}
const {
value: chunk,
done
} = yield __await(reader.read());
if (done) break;
const newBuffer = new Uint8Array(buffer.length + chunk.length);
newBuffer.set(buffer);
newBuffer.set(chunk, buffer.length);
buffer = newBuffer;
let offset = 0;
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
while (buffer.length - offset >= 4) {
const length = view.getUint32(offset, /* Big Endian */false);
if (buffer.length - offset < 4 + length) {
break;
}
const start = offset + 4;
const end = start + length;
try {
const payloadBytes = buffer.subarray(start, end);
const json = textDecoder.decode(payloadBytes);
const frame = JSON.parse(json);
yield yield __await(frame);
} catch (err) {
throw new Error(`Invalid JSON payload: ${err.message}`);
}
offset = end;
}
if (offset > 0) {
buffer = buffer.subarray(offset);
}
}
if (buffer.length > 0) {
throw new Error(`Stream ended with ${buffer.length} leftover bytes`);
}
} finally {
reader.releaseLock();
}
});
}
/**
* Encodes a frame into a binary format.
*
* @public
* @param frame - The frame to encode.
* @returns The encoded frame.
*/
function encodeFrame(frame) {
const encoder = new TextEncoder();
const jsonBytes = encoder.encode(JSON.stringify(frame));
const len = jsonBytes.length;
const out = new Uint8Array(4 + len);
const view = new DataView(out.buffer, out.byteOffset, out.byteLength);
view.setUint32(0, len, /* Big Endian */false);
out.set(jsonBytes, 4);
return out;
}
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Synchronous “trampoline” scheduler.
*
* All work executes in the same macrotask, but stack-safe:
* tasks scheduled from inside other tasks are queued
* and processed after the current one finishes.
*/
class TrampolineScheduler {
constructor() {
this.nextId = 0;
this.queue = new Map();
this.active = false;
}
flush() {
if (this.active) return;
this.active = true;
while (this.queue.size) {
const entry = this.queue.entries().next().value;
if (!entry) break;
const [id, task] = entry;
this.queue.delete(id);
try {
task();
} catch (err) {
// Surface errors asynchronously so one failure
// doesn’t prevent later tasks from running
setTimeout(() => {
throw err;
}, 0);
}
}
this.active = false;
}
scheduleTask(fn) {
const id = ++this.nextId;
this.queue.set(id, fn);
this.flush();
return id;
}
cancelTask(id) {
this.queue.delete(id);
}
}
/**
* Creates a payload projector function that returns its argument.
*
* @typeParam T - The payload type.
* @returns Function that returns the provided payload.
*/
function props() {
return payload => payload;
}
/**
* Creates an action creator with no payload.
*
* @returns Function that produces an action with only a type.
*/
function emptyProps() {
return () => {};
}
/**
* Generates a group of action creator functions with a common type prefix.
*
* @typeParam GroupName - The modifier for action types (e.g., feature name).
* @typeParam T - An object whose values are payload creator functions.
* @param name - The group prefix name.
* @param group - An object mapping action names to payload functions.
* @returns A set of action creators.
*/
function createActionGroup(name, group) {
return Object.fromEntries(Object.entries(group).map(([key, value]) => [key, Object.assign(typeof value === 'function' ? payload => ({
type: `[${name}] ${key}`,
payload
}) : () => ({
type: `[${name}] ${key}`
}), {
type: `[${name}] ${key}`
})]));
}
/**
* ================================
* === Reducers ===
* ================================
*/
/**
* Creates a reducer function that responds to specified action types.
*
* @typeParam State - The type of the slice of state.
* @typeParam Actions - An array of ActionCreator types to handle.
* @param params - One or more action creators followed by a reducer handler.
* @returns A reducer function.
*/
function on(...params) {
const actionFns = params.slice(0, -1);
const reducerFn = params[params.length - 1];
return (state, action) => {
const shouldReduceState = actionFns.some(param => param.type === action.type);
if (!shouldReduceState) {
return state;
}
return reducerFn(state, action);
};
}
/**
* Combines multiple reducer functions into a single root reducer.
*
* @typeParam State - The combined state shape.
* @param initialState - The initial state when undefined is passed.
* @param reducers - One or more reducer functions.
* @returns The root reducer.
*/
function createReducer(initialState, ...reducers) {
return (state, action) => {
return reducers.reduce((acc, reducer) => reducer(acc, action), state === undefined ? initialState : state);
};
}
/**
* Creates an effect function that can subscribe to store actions and return a cleanup function.
*
* @param effectFn - Function that receives the store and returns a teardown callback.
* @returns The provided effect function.
*/
function createEffect(effectFn) {
return effectFn;
}
function select(...params) {
let inputs = params.slice(0, -1);
let selectFn = params[params.length - 1];
let config;
if (typeof selectFn !== 'function') {
config = selectFn;
selectFn = params[params.length - 2];
inputs = params.slice(0, -2);
}
let lastInputValues = [];
let lastOutput;
return state => {
const inputValues = inputs.map(input => input(state));
if (inputValues.some((value, index) => {
const isMismatched = value !== lastInputValues[index];
if (isMismatched && config && config.debugName) {
console.log('Select Argument Mismatch:', config.debugName, `input[${index}]`, 'last:', lastInputValues[index], 'now:', value);
}
return isMismatched;
})) {
lastInputValues = inputValues;
lastOutput = selectFn(...inputValues);
}
return lastOutput;
};
}
/**
* ================================
* === Store ===
* ================================
*/
/**
* Creates a store with reducers and effects.
* @typeParam Reducers - An object mapping keys to reducer functions.
* @typeParam State - The resulting state shape inferred from Reducers.
* @param config - Configuration object.
* @returns The initialized store instance.
*/
function createStore(config) {
const scheduler = new TrampolineScheduler();
const devtools = config.debugName ? connectToChromeExtension({
name: config.debugName
}) : undefined;
const reducerFnEntries = Object.entries(config.reducers);
const reducerFn = function (state, action) {
return reducerFnEntries.reduce((acc, [key, value]) => {
return Object.assign(Object.assign({}, acc), {
[key]: value(acc === null || acc === void 0 ? void 0 : acc[key], action)
});
}, state);
};
const whenCallbackFnMap = new Map();
const selectCallbackFns = [];
let state = reducerFn(undefined, {
type: '@@init'
});
function dispatch(action) {
scheduler.scheduleTask(() => {
var _a, _b, _c;
state = reducerFn(state, action);
const whenCallbackFns = (_a = whenCallbackFnMap.get(action.type)) !== null && _a !== void 0 ? _a : [];
whenCallbackFns.forEach(callback => callback(action));
selectCallbackFns.forEach(callback => callback());
devtools === null || devtools === void 0 ? void 0 : devtools.send(action, (_c = (_b = config.projectStateForDevtools) === null || _b === void 0 ? void 0 : _b.call(config, state)) !== null && _c !== void 0 ? _c : state);
});
}
function when(...params) {
const actionFns = params.slice(0, -1);
const callbackFn = params[params.length - 1];
actionFns.forEach(actionFn => {
var _a;
if (!whenCallbackFnMap.has(actionFn.type)) {
whenCallbackFnMap.set(actionFn.type, []);
}
(_a = whenCallbackFnMap.get(actionFn.type)) === null || _a === void 0 ? void 0 : _a.push(callbackFn);
});
return () => {
actionFns.forEach(actionFn => {
var _a;
const callbacks = (_a = whenCallbackFnMap.get(actionFn.type)) !== null && _a !== void 0 ? _a : [];
whenCallbackFnMap.set(actionFn.type, callbacks.filter(cb => cb !== callbackFn));
});
};
}
function whenOnce(...params) {
const actionFns = params.slice(0, -1);
const callbackFn = params[params.length - 1];
const cleanupFn = when(...actionFns, action => {
callbackFn(action);
cleanupFn();
});
return cleanupFn;
}
function read(selector) {
return selector(state);
}
function select(selector, onChange) {
let currentValue = read(selector);
onChange(currentValue);
const callback = () => {
const newValue = read(selector);
if (newValue !== currentValue) {
currentValue = newValue;
onChange(newValue);
}
};
selectCallbackFns.push(callback);
return () => {
selectCallbackFns.splice(selectCallbackFns.indexOf(callback), 1);
};
}
function createSignal(selector) {
return Object.assign(() => read(selector), {
subscribe: onChange => select(selector, onChange)
});
}
function runEffects() {
var _a, _b;
devtools === null || devtools === void 0 ? void 0 : devtools.init((_b = (_a = config.projectStateForDevtools) === null || _a === void 0 ? void 0 : _a.call(config, state)) !== null && _b !== void 0 ? _b : state);
const cleanupFns = config.effects.map(effect => effect(store));
return () => {
cleanupFns.forEach(fn => fn());
devtools === null || devtools === void 0 ? void 0 : devtools.unsubscribe();
};
}
const store = {
dispatch,
read,
select,
when: when,
whenOnce: whenOnce,
createSignal,
runEffects
};
return store;
}
/**
* Creates an EntityAdapter for performing immutable updates on entity collections.
* @typeParam Entity - The entity type.
* @param config - Configuration with a selectId function.
* @returns Adapter with CRUD methods for entity state.
*/
function createEntityAdapter(config) {
const {
selectId
} = config;
function updateOne(state, changes) {
return Object.assign(Object.assign({}, state), {
entities: Object.assign(Object.assign({}, state.entities), {
[changes.id]: Object.assign(Object.assign({}, state.entities[changes.id]), changes.updates)
})
});
}
function updateMany(state, changes) {
return changes.reduce((acc, change) => updateOne(acc, change), state);
}
function addOne(state, entity) {
return Object.assign(Object.assign({}, state), {
ids: [...state.ids, selectId(entity)],
entities: Object.assign(Object.assign({}, state.entities), {
[selectId(entity)]: entity
})
});
}
function addMany(state, entities) {
return entities.reduce((acc, entity) => addOne(acc, entity), state);
}
function removeOne(state, idToRemove) {
const updatedEntities = Object.assign({}, state.entities);
delete updatedEntities[idToRemove];
return Object.assign(Object.assign({}, state), {
ids: state.ids.filter(id => id !== idToRemove),
entities: updatedEntities
});
}
function removeMany(state, ids) {
return Object.assign(Object.assign({}, state), {
ids: state.ids.filter(id => !ids.includes(id)),
entities: Object.fromEntries(Object.entries(state.entities).filter(([id]) => !ids.includes(id)))
});
}
return {
updateOne,
updateMany,
addOne,
addMany,
removeOne,
removeMany
};
}
function connectToChromeExtension(options) {
if (typeof window === 'undefined') {
return;
}
const extension = window.__REDUX_DEVTOOLS_EXTENSION__;
if (!extension) {
return;
}
return extension.connect({
name: options.name
});
}
var devActions = createActionGroup('dev', {
init: props(),
setMessages: props(),
sendMessage: props(),
resendMessages: props,
updateOptions: props(),
stopMessageGeneration: props()
});
var apiActions = createActionGroup('api', {
generateMessageStart: emptyProps(),
generateMessageChunk: props(),
generateMessageSuccess: props(),
generateMessageError: props(),
generateMessageExhaustedRetries: props(),
threadLoadStart: emptyProps(),
threadLoadSuccess: props(),
threadLoadFailure: props(),
threadSaveStart: emptyProps(),
threadSaveSuccess: props(),
threadSaveFailure: props(),
assistantTurnFinalized: emptyProps()
});
var internalActions = createActionGroup('internal', {
sizzle: emptyProps(),
runToolCallsSuccess: props(),
runToolCallsError: props(),
skippedToolCalls: emptyProps()
});
/**
* Skillet is an LLM-optimized streaming JSON Parser - perfectly suited for streaming hot and fresh JSON.
*
* Portions of this code are derived from Zod (MIT License) (https://github.com/colinhacks/zod).
* See the LICENSE file in the project root for full license text.
*
* @license MIT
* @author LiveLoveApp, LLC
* @see https://github.com/liveloveapp/hashbrown
* @see https://github.com/colinhacks/zod
*/
/**
* @internal
*/
const internal = '~schema';
const PRIMITIVE_WRAPPER_FIELD_NAME = '__wrappedPrimitive';
/**
* @internal
*/
const HashbrownTypeCtor = ({
name,
initializer,
toJsonSchemaImpl,
parseJsonSchemaImpl,
toTypeScriptImpl,
validateImpl,
toStreamingImpl
}) => {
class Class {
constructor(definition) {
Class.init(this, definition);
this.toJsonSchemaImpl = toJsonSchemaImpl;
this.parseJsonSchemaImpl = parseJsonSchemaImpl;
this.toTypeScriptImpl = toTypeScriptImpl;
this.validateImpl = validateImpl;
this.toStreamingImpl = toStreamingImpl;
}
static init(instance, definition) {
var _a;
(_a = instance[internal]) !== null && _a !== void 0 ? _a : instance[internal] = {
definition: {
description: '',
streaming: false
}
};
initializer(instance, definition);
instance[internal].definition = definition;
}
toJsonSchema() {
return this.toJsonSchemaImpl(this);
}
parseJsonSchema(object, path = []) {
return this.parseJsonSchemaImpl(this, object, path);
}
toTypeScript(pathSeen = new Set()) {
return this.toTypeScriptImpl(this, pathSeen);
}
validate(object, path = []) {
return this.validateImpl(this, this[internal].definition, object, path);
}
toStreaming(object, path = []) {
return this.toStreamingImpl(this, this[internal].definition, object, path);
}
}
Object.defineProperty(Class, 'name', {
value: name
});
return Class;
};
/**
* @public
*/
const HashbrownType = HashbrownTypeCtor({
name: 'HashbrownType',
initializer: (inst, def) => {
inst !== null && inst !== void 0 ? inst : inst = {};
inst[internal].definition = def;
},
toJsonSchemaImpl: () => {
return;
},
parseJsonSchemaImpl: () => {
return;
},
toTypeScriptImpl: () => {
return '';
},
validateImpl: () => {
return;
},
toStreamingImpl: () => {
return;
}
});
/**
* @public
*/
const StringType = HashbrownTypeCtor({
name: 'String',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'string',
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (typeof object !== 'string') throw new Error(`Expected a string at: ${path.join('.')}, got ${object}`);
return object;
},
toTypeScriptImpl: schema => {
return `/* ${schema[internal].definition.description} */ string`;
},
validateImpl: (schema, definition, object, path) => {
if (typeof object !== 'string') {
throw new Error(`Expected a string at: ${path.join('.')}, got ${typeof object}`);
}
return;
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isStringType(type) {
return type[internal].definition.type === 'string';
}
/**
* @public
*/
function string$1(description) {
return new StringType({
type: 'string',
description,
streaming: false
});
}
/**
* @public
*/
const LiteralType = HashbrownTypeCtor({
name: 'Literal',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
const isString = typeof schema[internal].definition.value === 'string';
const isNumber = typeof schema[internal].definition.value === 'number';
typeof schema[internal].definition.value === 'boolean';
return {
type: isString ? 'string' : isNumber ? 'number' : 'boolean',
const: schema[internal].definition.value,
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
const isString = typeof object === 'string';
const isNumber = typeof object === 'number';
const isBoolean = typeof object === 'boolean';
if (!isString && !isNumber && !isBoolean) throw new Error(`Expected a string, number, or boolean at: ${path.join('.')}, got ${object}, received ${schema[internal].definition.value}`);
return object;
},
toTypeScriptImpl: schema => {
return JSON.stringify(schema[internal].definition.value);
},
validateImpl: (schema, definition, object, path) => {
if (definition.value !== object) {
throw new Error(`Expected the literal value ${JSON.stringify(definition.value)} at: ${path.join('.')}, but got ${JSON.stringify(object)}`);
}
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isLiteralType(type) {
return type[internal].definition.type === 'literal';
}
/**
* @public
*/
function literal(value) {
return new LiteralType({
type: 'literal',
description: `${value}`,
value,
streaming: false
});
}
/**
* @public
*/
const NumberType = HashbrownTypeCtor({
name: 'Number',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'number',
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (typeof object !== 'number') throw new Error(`Expected a number at: ${path.join('.')}`);
return object;
},
toTypeScriptImpl: schema => {
return `/* ${schema[internal].definition.description} */ number`;
},
validateImpl: (schema, definition, object, path) => {
if (typeof object !== 'number') {
throw new Error(`Expected a number at: ${path.join('.')}`);
}
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isNumberType(type) {
return type[internal].definition.type === 'number';
}
/**
* @public
*/
function number(description) {
return new NumberType({
type: 'number',
description,
streaming: false
});
}
/**
* @public
*/
const BooleanType = HashbrownTypeCtor({
name: 'Boolean',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'boolean',
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (typeof object !== 'boolean') throw new Error(`Expected a boolean at: ${path.join('.')}`);
return object;
},
toTypeScriptImpl: schema => {
return `/* ${schema[internal].definition.description} */ boolean`;
},
validateImpl: (schema, definition, object, path) => {
if (typeof object !== 'boolean') throw new Error(`Expected a boolean at: ${path.join('.')}`);
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isBooleanType(type) {
return type[internal].definition.type === 'boolean';
}
/**
* @public
*/
function boolean(description) {
return new BooleanType({
type: 'boolean',
description,
streaming: false
});
}
/**
* @public
*/
const IntegerType = HashbrownTypeCtor({
name: 'Integer',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'integer',
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (typeof object !== 'number') throw new Error(`Expected a number at: ${path.join('.')}`);
if (!Number.isInteger(object)) throw new Error(`Expected an integer at: ${path.join('.')}`);
return object;
},
toTypeScriptImpl: schema => {
return `/* ${schema[internal].definition.description} */ integer`;
},
validateImpl: (schema, definition, object, path) => {
if (typeof object !== 'number') throw new Error(`Expected a number at: ${path.join('.')}`);
if (!Number.isInteger(object)) throw new Error(`Expected an integer at: ${path.join('.')}`);
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isIntegerType(type) {
return type[internal].definition.type === 'integer';
}
/**
* @public
*/
function integer(description) {
return new IntegerType({
type: 'integer',
description,
streaming: false
});
}
/**
* @public
*/
const ObjectType = HashbrownTypeCtor({
name: 'Object',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'object',
// Properties is populated externally because we need to find loops
properties: {},
required: Object.keys(schema[internal].definition.shape),
additionalProperties: false,
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
if (typeof object !== 'object' || object === null) throw new Error(`Expected an object at: ${path.join('.')}`);
const {
shape
} = schema[internal].definition;
Object.entries(shape).forEach(([key, child]) => {
// AnyOf unwrapping can change the desired form of the result object, so
// update the object as we parse
object[key] = child.parseJsonSchema(object[key], [...path, key]);
});
return object;
},
toTypeScriptImpl: (schema, pathSeen) => {
if (pathSeen.has(schema)) {
const desc = schema[internal].definition.description || '<anonymous>';
throw new Error(`Cycle detected in schema at "${desc}"`);
}
pathSeen.add(schema);
const depth = pathSeen.size - 1;
const entries = Object.entries(schema[internal].definition.shape);
const lines = entries.map(([key, child]) => {
// clone pathSeen for each branch
return `${' '.repeat(depth + 2)}${key}: ${child.toTypeScript(new Set(pathSeen))};`;
});
return `/* ${schema[internal].definition.description} */ {
${lines.join('\n')}
${' '.repeat(depth)}}`;
},
validateImpl: (schema, definition, object, path) => {
if (typeof object !== 'object' || object === null) throw new Error(`Expected an object at: ${path.join('.')}`);
const {
shape
} = definition;
Object.entries(shape).forEach(([key, child]) => {
child.validate(object[key], [...path, key]);
});
return object;
},
toStreamingImpl: (schema, definition, object, path) => {
const {
shape
} = definition;
const entries = Object.entries(shape);
return Object.fromEntries(entries.map(([key, value]) => {
return [key, value.toStreaming(object[key], [...path, key])];
}));
}
});
/**
* @public
*/
function isObjectType(type) {
return type[internal].definition.type === 'object';
}
/**
* @public
*/
function object$1(description, shape) {
return new ObjectType({
type: 'object',
description,
streaming: false,
shape
});
}
/**
* @public
*/
const ArrayType = HashbrownTypeCtor({
name: 'Array',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'array',
// items is populated externally since we find loops and duplicated sections
// through the whole schema
items: [],
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (!Array.isArray(object)) throw new Error(`Expected an array at: ${path.join('.')}`);
// AnyOf unwrapping can change the desired form of the result object, so
// update the object as we parse
object.forEach(item => {
item = schema[internal].definition.element.parseJsonSchema(item, path);
});
return object;
},
toTypeScriptImpl: (schema, pathSeen) => {
if (pathSeen.has(schema)) {
const desc = schema[internal].definition.description || '<anonymous>';
throw new Error(`Cycle detected in schema at "${desc}"`);
}
pathSeen.add(schema);
return `/* ${schema[internal].definition.description} */ Array<${schema[internal].definition.element.toTypeScript(new Set(pathSeen))}>`;
},
validateImpl: (schema, definition, object, path) => {
if (!Array.isArray(object)) throw new Error(`Expected an array at: ${path.join('.')}`);
object.forEach(item => {
definition.element.validate(item, path);
});
},
toStreamingImpl: (schema, definition, object, path) => {
return object.map(item => {
return definition.element.toStreaming(item, path);
});
}
});
/**
* @public
*/
function isArrayType(type) {
return type[internal].definition.type === 'array';
}
/**
* @public
*/
function array$1(description, item) {
return new ArrayType({
type: 'array',
description,
streaming: false,
element: item
});
}
/**
* @public
*/
const AnyOfType = HashbrownTypeCtor({
name: 'AnyOfType',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
anyOf: []
};
},
parseJsonSchemaImpl: (schema, object, path) => {
const options = schema[internal].definition.options;
let parsedObject = undefined;
const buildDiscriminatorMap = options => {
const map = {};
for (const opt of options) {
if (!isObjectType(opt)) {
return null;
}
const shape = opt[internal].definition.shape;
const literalEntries = Object.entries(shape).filter(([, v]) => isLiteralType(v));
// Require exactly one literal for clear discrimination
if (literalEntries.length !== 1) {
return null;
}
const [literalKey, litSchema] = literalEntries[0];
const literalValue = litSchema[internal].definition.value;
if (typeof literalValue !== 'string') {
// Only support string-based discriminators for wrapper keys
return null;
}
if (Object.prototype.hasOwnProperty.call(map, literalValue)) {
// Ambiguous (duplicate) discriminator value
return null;
}
map[literalValue] = {
schema: opt,
literalKey,
literalValue
};
}
return Object.keys(map).length === options.length ? map : null;
};
const discriminatorMap = buildDiscriminatorMap(options);
for (let i = 0; i < options.length; i++) {
try {
if (needsDiscriminatorWrapperInAnyOf(options[i]) && discriminatorMap) {
const discriminatorEntry = Object.entries(discriminatorMap).find(([, v]) => v.schema === options[i]);
if (!discriminatorEntry) {
throw new Error(`No discriminator key found for option ${options[i]}`);
}
const {
literalKey,
literalValue,
schema
} = discriminatorEntry[1];
const extractedObject = object[literalValue];
extractedObject[literalKey] = literalValue;
parsedObject = schema.parseJsonSchema(extractedObject);
} else if (needsDiscriminatorWrapperInAnyOf(options[i])) {
if (typeof object !== 'object' || object === null) {
throw new Error(`Expected an object at: ${path.join('.')}`);
}
const anyOfKeys = Object.keys(object);
if (anyOfKeys.length !== 1) {
throw new Error(`Malformed anyOf wrapper at ${path.join('.')}`);
}
const anyOfIndex = anyOfKeys[0];
if (anyOfIndex !== i.toString()) {
throw new Error(`Unexpected discriminator value ${anyOfIndex} for option ${i}`);
}
parsedObject = options[i].parseJsonSchema(object[anyOfIndex]);
} else {
parsedObject = options[i].parseJsonSchema(object);
}
break;
} catch (e) {
// console.log(e);
// Parsing failed, but that is not unexpected due to the looping.
// Just try the next option.
continue;
}
}
if (parsedObject == null) {
throw new Error(`All options in anyOf failed parsing at: ${path.join('.')}`);
}
return parsedObject;
},
toTypeScriptImpl: (schema, pathSeen) => {
if (pathSeen.has(schema)) {
const desc = schema[internal].definition.description || '<anonymous>';
throw new Error(`Cycle detected in schema at "${desc}"`);
}
pathSeen.add(schema);
return `/* ${schema[internal].definition.description} */ (${schema[internal].definition.options.map(opt => opt.toTypeScript(new Set(pathSeen))).join(' | ')})`;
},
validateImpl: (schema, definition, object, path) => {
const {
options
} = definition;
let foundMatch = false;
for (let i = 0; i < options.length; i++) {
try {
options[i].validate(object);
foundMatch = true;
break;
} catch (e) {
// console.log(e);
// Parsing failed, but that is not unexpected due to the looping.
// Just try the next option.
continue;
}
}
if (!foundMatch) {
throw new Error(`All options in anyOf failed parsing at: ${path.join('.')}`);
}
},
toStreamingImpl: (schema, definition, object, path) => {
const matchingOption = definition.options.find(opt => {
try {
opt.validate(object);
return true;
} catch (e) {
return false;
}
});
const buildDiscriminatorMap = options => {
const map = {};
for (const opt of options) {
if (!isObjectType(opt)) {
return null;
}
const shape = opt[internal].definition.shape;
const literalEntries = Object.entries(shape).filter(([, v]) => isLiteralType(v));
// Require exactly one literal for clear discrimination
if (literalEntries.length !== 1) {
return null;
}
const [literalKey, litSchema] = literalEntries[0];
const literalValue = litSchema[internal].definition.value;
if (typeof literalValue !== 'string') {
// Only support string-based discriminators for wrapper keys
return null;
}
if (Object.prototype.hasOwnProperty.call(map, literalValue)) {
// Ambiguous (duplicate) discriminator value
return null;
}
map[literalValue] = {
schema: opt,
literalKey,
literalValue
};
}
return Object.keys(map).length === options.length ? map : null;
};
const discriminatorMap = buildDiscriminatorMap(definition.options);
if (!matchingOption) {
throw new Error(`No matching option found in anyOf at: ${path.join('.')}`);
}
if (needsDiscriminatorWrapperInAnyOf(matchingOption) && discriminatorMap) {
const discriminatorEntry = Object.entries(discriminatorMap).find(([, v]) => v.schema === matchingOption);
if (!discriminatorEntry) {
throw new Error(`No discriminator key found for option ${matchingOption}`);
}
const {
literalKey,
literalValue,
schema
} = discriminatorEntry[1];
const streamingObject = schema.toStreaming(object, path);
delete streamingObject[literalKey];
return {
[literalValue]: streamingObject
};
} else if (needsDiscriminatorWrapperInAnyOf(matchingOption) && !discriminatorMap) {
const indexOfMatchingOption = definition.options.indexOf(matchingOption);
return {
[indexOfMatchingOption.toString()]: matchingOption.toStreaming(object, path)
};
}
return matchingOption.toStreaming(object, path);
}
});
/**
* @public
*/
function isAnyOfType(type) {
return type[internal].definition.type === 'any-of';
}
/**
* @public
*/
function anyOf(options) {
return new AnyOfType({
type: 'any-of',
description: 'any-of',
options,
streaming: false
});
}
/**
* @public
*/
const EnumType = HashbrownTypeCtor({
name: 'Enum',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'string',
enum: schema[internal].definition.entries,
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (typeof object !== 'string') throw new Error(`Expected a string at: ${path.join('.')}`);
if (!schema[internal].definition.entries.includes(object)) throw new Error(`Expected an enum value at: ${path.join('.')}`);
return object;
},
toTypeScriptImpl: schema => {
return schema[internal].definition.entries.map(e => `"${e}"`).join(' | ');
},
validateImpl: (schema, definition, object, path) => {
if (typeof object !== 'string') throw new Error(`Expected a string at: ${path.join('.')}`);
if (!definition.entries.includes(object)) throw new Error(`Expected an enum value at: ${path.join('.')}`);
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isEnumType(type) {
return type[internal].definition.type === 'enum';
}
/**
* @public
*/
function enumeration(description, entries) {
return new EnumType({
type: 'enum',
description,
entries,
streaming: false
});
}
/**
* @public
*/
const NullType = HashbrownTypeCtor({
name: 'Null',
initializer: (inst, def) => {
HashbrownType.init(inst, def);
},
toJsonSchemaImpl: schema => {
return {
type: 'null',
const: schema[internal].definition.value,
description: schema[internal].definition.description
};
},
parseJsonSchemaImpl: (schema, object, path) => {
// Is this a wrapped primitive?
if (object != null && typeof object === 'object' && Object.keys(object).includes(PRIMITIVE_WRAPPER_FIELD_NAME)) {
object = object[PRIMITIVE_WRAPPER_FIELD_NAME];
}
if (object !== null) throw new Error(`Expected a null at: ${path.join('.')}`);
return object;
},
toTypeScriptImpl: schema => {
return `/* ${schema[internal].definition.description} */ null`;
},
validateImpl: (schema, definition, object, path) => {
if (object !== null) throw new Error(`Expected a null at: ${path.join('.')}`);
},
toStreamingImpl: (schema, definition, object, path) => {
return object;
}
});
/**
* @public
*/
function isNullType(type) {
return type[internal].definition.type === 'null';
}
/**
* @public
*/
function nullish() {
return new NullType({
type: 'null',
description: '',
streaming: false
});
}
/**
* --------------------------------------
* --------------------------------------
* Streaming Helpers
* --------------------------------------
* --------------------------------------
*/
function needsDiscriminatorWrapperInAnyOf(schema) {
if (isAnyOfType(schema) || isArrayType(schema) || isObjectType(schema) || isStringType(schema) && isStreaming(schema)) {
return true;
}
return false;
}
function isStreaming(schema) {
return schema[internal].definition.streaming;
}
/**
* @public
*/
function isHashbrownType(type) {
return type[internal] !== undefined;
}
/**
* @public
*/
function getDescription(schema) {
return schema[internal].definition.description;
}
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* @public
*/
function string(description) {
return new StringType({
type: 'string',
description,
streaming: true
});
}
/**
* @public
*/
function object(description, shape) {
return new ObjectType({
type: 'object',
description,
streaming: true,
shape
});
}
/**
* @public
*/
function array(description, item) {
return new ArrayType({
type: 'array',
description,
streaming: true,
element: item
});
}
var streaming = /*#__PURE__*/Object.freeze({
__proto__: null,
array: array,
object: object,
string: string
});
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Convert an arbitrary description into a camelCase identifier.
*
* - Strips out any non-alphanumeric characters
* - Splits on whitespace
* - Lowercases all words, then uppercases the first letter of each subsequent word
* - Prefixes with `_` if the result would start with a digit
*/
function descriptionToCamelCase(description) {
const cleaned = description.replace(/[^a-zA-Z0-9]+/g, ' ').trim();
const words = cleaned.split(/\s+/).map(w => w.toLowerCase());
if (words.length === 0) return '';
const [first, ...rest] = words;
const core = first + rest.map(w => w[0].toUpperCase() + w.slice(1)).join('');
return /^\d/.test(core) ? `_${core}` : core;
}
/**
* Walks the HashbrownType graph, finds any sub-schemas seen more than once
* (excluding the root), assigns each a unique name, and emits a draft-07 JSON Schema
* with a $defs section. Cycles always become $refs.
*
* @public
*/
function toJsonSchema(schema) {
const rootNode = schema;
// 1) Collect repeated nodes
const seen = new Set();
const repeats = new Set();
(function visit(n, path) {
if (seen.has(n)) {
repeats.add(n);
return;
}
seen.add(n);
if (isObjectType(n)) {
Object.values(n[internal].definition.shape).forEach(child => visit(child, [...path, n]));
} else if (isArrayType(n)) {
visit(n[internal].definition.element, [...path, n]);
} else if (isAnyOfType(n)) {
n[internal].definition.options.forEach(opt => visit(opt, [...path, n]));
}
})(rootNode, []);
// never put the root itself into $defs
repeats.delete(rootNode);
// 2) Assign each repeated node a unique camelCase name
const defNameMap = new Map();
const usedNames = new Set();
let anon = 1;
for (const node of repeats) {
const desc = node[internal].definition.description || `def${anon++}`;
let name = descriptionToCamelCase(desc) || `def${anon++}`;
if (usedNames.has(name)) {
let i = 1;
while (usedNames.has(`${name}${i}`)) i++;
name = `${name}${i}`;
}
usedNames.add(name);
defNameMap.set(node, name);
}
/**
* Recursive printer.
*
* @param n - current node
* @param isRoot - true only for the very top-level schema
* @param inDef - if non-null, we're printing $defs[inDef] — any other def becomes $ref
* @param pathSeen - tracks the chain of inlined nodes to catch cycles
*/
function printNode(n, isRoot = false, inDef = null, pathSeen = new Set(),
// If provided, omit this property when printing an object. Used for
// anyOf literal-based envelopes where the literal is redundant.
omitObjectProp) {
// a) cycle back to the root
if (!isRoot && n === rootNode) {
return {
$ref: '#'
};
}
// b) any other shared def becomes a $ref
if (defNameMap.has(n) && n !== inDef) {
const nm = defNameMap.get(n);
return {
$ref: `#/$defs/${nm}`
};
}
// c) catch self-cycles or mutual cycles in inline portions
if (pathSeen.has(n)) {
// if it’s named, ref it; otherwise point at root
if (defNameMap.has(n)) {
const nm = defNameMap.get(n);
return {
$ref: `#/$defs/${nm}`
};
} else {
return {
$ref: '#'
};
}
}
// d) inline this node
pathSeen.add(n);
let result;
if (isObjectType(n)) {
// Sort props so that streaming ones are at the end
const shapeWithStreamingAtEnd = Object.entries(n[internal].definition.shape)
// If we're omitting a prop due to envelope discrimination, remove it
.filter(([key]) => omitObjectProp ? key !== omitObjectProp : true).sort((a, b) => {
if (!isStreaming(a[1]) && isStreaming(b[1])) {
return -1;
}
if (isStreaming(a[1]) && !isStreaming(b[1])) {
return 1;
}
return 0;
});
const props = {};
for (const [k, child] of shapeWithStreamingAtEnd) {
props[k] = printNode(child, false, inDef, pathSeen);
}
result = n.toJsonSchema();
result.properties = props;
// Ensure required keys do not include any omitted discriminator prop
result.required = Object.keys(props);
} else if (isArrayType(n)) {
result = n.toJsonSchema();
result.items = printNode(n[internal].definition.element, false, inDef, pathSeen);
if (isRoot) {
result = {
type: 'object',
additionalProperties: false,
required: [PRIMITIVE_WRAPPER_FIELD_NAME],
properties: {
[PRIMITIVE_WRAPPER_FIELD_NAME]: result
}
};
}
} else if (isAnyOfType(n)) {
result = n.toJsonSchema();
const buildDiscriminatorMap = options => {
const map = {};
for (const opt of options) {
if (!isObjectType(opt)) {
return null;
}
const shape = opt[internal].definition.shape;
const literalEntries = Object.entries(shape).filter(([, v]) => isLiteralType(v));
// Require exactly one literal for clear discrimination
if (literalEntries.length !== 1) {
return null;
}
const [literalKey, litSchema] = literalEntries[0];
const literalValue = litSchema[internal].definition.value;
if (typeof literalValue !== 'string') {
// Only support string-based discriminators for wrapper keys
return null;
}
if (Object.prototype.hasOwnProperty.call(map, literalValue)) {
// Ambiguous (duplicate) discriminator value
return null;