chore: setup linting

This commit is contained in:
Max Richter
2026-02-02 16:22:14 +01:00
parent 137425b31b
commit 30e897468a
174 changed files with 6043 additions and 5107 deletions

View File

@@ -2,17 +2,17 @@
name = "nodarium_macros"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Procedural macros for the nodarium crate"
homepage = "https://nodes.max-richter.dev/docs"
license = "MIT"
repository = "https://github.com/jim-fx/nodes"
description = "Procedural macros for the nodarium crate"
[lib]
proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full"] }
nodarium_types = { version = "0.1.0", path = "../types" }
quote = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
quote = "1.0"
nodarium_types = { version = "0.1.0", path = "../types" }
syn = { version = "2.0", features = ["full"] }

View File

@@ -1,17 +0,0 @@
{
"name": "@nodarium/registry",
"version": "0.0.0",
"description": "",
"main": "src/index.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@nodarium/types": "workspace:",
"@nodarium/utils": "workspace:",
"idb": "^8.0.3"
}
}

View File

@@ -1,2 +0,0 @@
export * from "./node-registry-cache";
export * from "./node-registry-client";

View File

@@ -1,58 +0,0 @@
import type { AsyncCache } from '@nodarium/types';
import { openDB, type IDBPDatabase } from 'idb';
export class IndexDBCache implements AsyncCache<unknown> {
size: number = 100;
db: Promise<IDBPDatabase<unknown>>;
private _cache = new Map<string, unknown>();
constructor(id: string) {
this.db = openDB<unknown>('cache/' + id, 1, {
upgrade(db) {
db.createObjectStore('keyval');
},
});
}
async get<T>(key: string): Promise<T> {
let res = this._cache.get(key);
if (!res) {
res = await (await this.db).get('keyval', key);
}
if (res) {
this._cache.set(key, res);
}
return res as T;
}
async getArrayBuffer(key: string) {
const res = await this.get(key);
if (!res) return;
if (res instanceof ArrayBuffer) {
return res;
}
return
}
async getString(key: string) {
const res = await this.get(key);
if (!res) return;
if (typeof res === "string") {
return res;
}
return
}
async set(key: string, value: unknown) {
this._cache.set(key, value);
const db = await this.db;
await db.put('keyval', value, key);
}
clear() {
this.db.then(db => db.clear('keyval'));
}
}

View File

@@ -1,158 +0,0 @@
import {
type AsyncCache,
type NodeDefinition,
NodeDefinitionSchema,
type NodeRegistry
} from '@nodarium/types';
import { createLogger, createWasmWrapper } from '@nodarium/utils';
const log = createLogger('node-registry');
log.mute();
export class RemoteNodeRegistry implements NodeRegistry {
status: 'loading' | 'ready' | 'error' = 'loading';
private nodes: Map<string, NodeDefinition> = new Map();
constructor(
private url: string,
public cache?: AsyncCache<ArrayBuffer | string>
) { }
async fetchJson(url: string, skipCache = false) {
const finalUrl = `${this.url}/${url}`;
if (!skipCache && this.cache) {
const cachedValue = await this.cache?.get<string>(finalUrl);
if (cachedValue) {
// fetch again in the background, maybe implement that only refetch after a certain time
this.fetchJson(url, true);
return JSON.parse(cachedValue);
}
}
const response = await fetch(finalUrl);
if (!response.ok) {
log.error(`Failed to load ${url}`, { response, url, host: this.url });
throw new Error(`Failed to load ${url}`);
}
const result = await response.json();
this.cache?.set(finalUrl, JSON.stringify(result));
return result;
}
async fetchArrayBuffer(url: string, skipCache = false) {
const finalUrl = `${this.url}/${url}`;
if (!skipCache && this.cache) {
const cachedNode = await this.cache?.get<ArrayBuffer>(finalUrl);
if (cachedNode) {
// fetch again in the background, maybe implement that only refetch after a certain time
this.fetchArrayBuffer(url, true);
return cachedNode;
}
}
const response = await fetch(finalUrl);
if (!response.ok) {
log.error(`Failed to load ${url}`, { response, url, host: this.url });
throw new Error(`Failed to load ${url}`);
}
const buffer = await response.arrayBuffer();
this.cache?.set(finalUrl, buffer);
return buffer;
}
async fetchUsers() {
return this.fetchJson(`nodes/users.json`);
}
async fetchUser(userId: `${string}`) {
return this.fetchJson(`user/${userId}.json`);
}
async fetchCollection(userCollectionId: `${string}/${string}`) {
const col = await this.fetchJson(`nodes/${userCollectionId}.json`);
return col;
}
async fetchNodeDefinition(nodeId: `${string}/${string}/${string}`) {
return this.fetchJson(`nodes/${nodeId}.json`);
}
private async fetchNodeWasm(nodeId: `${string}/${string}/${string}`) {
const node = await this.fetchArrayBuffer(`nodes/${nodeId}.wasm`);
if (!node) {
throw new Error(`Failed to load node wasm ${nodeId}`);
}
return node;
}
async load(nodeIds: `${string}/${string}/${string}`[]) {
const a = performance.now();
const nodes = (await Promise.all(
[...new Set(nodeIds).values()].map(async (id) => {
if (this.nodes.has(id)) {
return this.nodes.get(id)!;
}
const wasmBuffer = await this.fetchNodeWasm(id);
try {
return await this.register(wasmBuffer);
} catch (e) {
console.log('Failed to register: ', id);
console.error(e);
return;
}
})
)).filter(Boolean) as NodeDefinition[];
const duration = performance.now() - a;
log.group('loaded nodes in', duration, 'ms');
log.info(nodeIds);
log.info(nodes);
log.groupEnd();
this.status = 'ready';
return nodes;
}
async register(wasmBuffer: ArrayBuffer) {
const wrapper = createWasmWrapper(wasmBuffer);
const definition = NodeDefinitionSchema.safeParse(wrapper.get_definition());
if (definition.error) {
throw definition.error;
}
if (this.cache) {
this.cache.set(definition.data.id, wasmBuffer);
}
let node = {
...definition.data,
execute: wrapper.execute
};
this.nodes.set(definition.data.id, node);
return node;
}
getNode(id: string) {
return this.nodes.get(id);
}
getAllNodes() {
return [...this.nodes.values()];
}
}

View File

@@ -1,16 +0,0 @@
{
"name": "store-client",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"generate": "openapi-ts -i ../../store/openapi.json -o src/client -c @hey-api/client-fetch"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@hey-api/openapi-ts": "^0.90.4"
}
}

View File

@@ -1,16 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import { type ClientOptions, type Config, createClient, createConfig } from './client';
import type { ClientOptions as ClientOptions2 } from './types.gen';
/**
* The `createClientConfig()` function will be called on client initialization
* and the returned object will become the client's initial configuration.
*
* You may want to initialize your client this way instead of calling
* `setConfig()`. This is useful for example if you're using Next.js
* to ensure your client always has the correct values.
*/
export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>;
export const client = createClient(createConfig<ClientOptions2>());

View File

@@ -1,311 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import { createSseClient } from '../core/serverSentEvents.gen';
import type { HttpMethod } from '../core/types.gen';
import { getValidRequestBody } from '../core/utils.gen';
import type {
Client,
Config,
RequestOptions,
ResolvedRequestOptions,
} from './types.gen';
import {
buildUrl,
createConfig,
createInterceptors,
getParseAs,
mergeConfigs,
mergeHeaders,
setAuthParams,
} from './utils.gen';
type ReqInit = Omit<RequestInit, 'body' | 'headers'> & {
body?: any;
headers: ReturnType<typeof mergeHeaders>;
};
export const createClient = (config: Config = {}): Client => {
let _config = mergeConfigs(createConfig(), config);
const getConfig = (): Config => ({ ..._config });
const setConfig = (config: Config): Config => {
_config = mergeConfigs(_config, config);
return getConfig();
};
const interceptors = createInterceptors<
Request,
Response,
unknown,
ResolvedRequestOptions
>();
const beforeRequest = async (options: RequestOptions) => {
const opts = {
..._config,
...options,
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
headers: mergeHeaders(_config.headers, options.headers),
serializedBody: undefined,
};
if (opts.security) {
await setAuthParams({
...opts,
security: opts.security,
});
}
if (opts.requestValidator) {
await opts.requestValidator(opts);
}
if (opts.body !== undefined && opts.bodySerializer) {
opts.serializedBody = opts.bodySerializer(opts.body);
}
// remove Content-Type header if body is empty to avoid sending invalid requests
if (opts.body === undefined || opts.serializedBody === '') {
opts.headers.delete('Content-Type');
}
const url = buildUrl(opts);
return { opts, url };
};
const request: Client['request'] = async (options) => {
// @ts-expect-error
const { opts, url } = await beforeRequest(options);
const requestInit: ReqInit = {
redirect: 'follow',
...opts,
body: getValidRequestBody(opts),
};
let request = new Request(url, requestInit);
for (const fn of interceptors.request.fns) {
if (fn) {
request = await fn(request, opts);
}
}
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = opts.fetch!;
let response: Response;
try {
response = await _fetch(request);
} catch (error) {
// Handle fetch exceptions (AbortError, network errors, etc.)
let finalError = error;
for (const fn of interceptors.error.fns) {
if (fn) {
finalError = (await fn(
error,
undefined as any,
request,
opts,
)) as unknown;
}
}
finalError = finalError || ({} as unknown);
if (opts.throwOnError) {
throw finalError;
}
// Return error response
return opts.responseStyle === 'data'
? undefined
: {
error: finalError,
request,
response: undefined as any,
};
}
for (const fn of interceptors.response.fns) {
if (fn) {
response = await fn(response, request, opts);
}
}
const result = {
request,
response,
};
if (response.ok) {
const parseAs =
(opts.parseAs === 'auto'
? getParseAs(response.headers.get('Content-Type'))
: opts.parseAs) ?? 'json';
if (
response.status === 204 ||
response.headers.get('Content-Length') === '0'
) {
let emptyData: any;
switch (parseAs) {
case 'arrayBuffer':
case 'blob':
case 'text':
emptyData = await response[parseAs]();
break;
case 'formData':
emptyData = new FormData();
break;
case 'stream':
emptyData = response.body;
break;
case 'json':
default:
emptyData = {};
break;
}
return opts.responseStyle === 'data'
? emptyData
: {
data: emptyData,
...result,
};
}
let data: any;
switch (parseAs) {
case 'arrayBuffer':
case 'blob':
case 'formData':
case 'text':
data = await response[parseAs]();
break;
case 'json': {
// Some servers return 200 with no Content-Length and empty body.
// response.json() would throw; read as text and parse if non-empty.
const text = await response.text();
data = text ? JSON.parse(text) : {};
break;
}
case 'stream':
return opts.responseStyle === 'data'
? response.body
: {
data: response.body,
...result,
};
}
if (parseAs === 'json') {
if (opts.responseValidator) {
await opts.responseValidator(data);
}
if (opts.responseTransformer) {
data = await opts.responseTransformer(data);
}
}
return opts.responseStyle === 'data'
? data
: {
data,
...result,
};
}
const textError = await response.text();
let jsonError: unknown;
try {
jsonError = JSON.parse(textError);
} catch {
// noop
}
const error = jsonError ?? textError;
let finalError = error;
for (const fn of interceptors.error.fns) {
if (fn) {
finalError = (await fn(error, response, request, opts)) as string;
}
}
finalError = finalError || ({} as string);
if (opts.throwOnError) {
throw finalError;
}
// TODO: we probably want to return error and improve types
return opts.responseStyle === 'data'
? undefined
: {
error: finalError,
...result,
};
};
const makeMethodFn =
(method: Uppercase<HttpMethod>) => (options: RequestOptions) =>
request({ ...options, method });
const makeSseFn =
(method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
const { opts, url } = await beforeRequest(options);
return createSseClient({
...opts,
body: opts.body as BodyInit | null | undefined,
headers: opts.headers as unknown as Record<string, string>,
method,
onRequest: async (url, init) => {
let request = new Request(url, init);
for (const fn of interceptors.request.fns) {
if (fn) {
request = await fn(request, opts);
}
}
return request;
},
serializedBody: getValidRequestBody(opts) as
| BodyInit
| null
| undefined,
url,
});
};
return {
buildUrl,
connect: makeMethodFn('CONNECT'),
delete: makeMethodFn('DELETE'),
get: makeMethodFn('GET'),
getConfig,
head: makeMethodFn('HEAD'),
interceptors,
options: makeMethodFn('OPTIONS'),
patch: makeMethodFn('PATCH'),
post: makeMethodFn('POST'),
put: makeMethodFn('PUT'),
request,
setConfig,
sse: {
connect: makeSseFn('CONNECT'),
delete: makeSseFn('DELETE'),
get: makeSseFn('GET'),
head: makeSseFn('HEAD'),
options: makeSseFn('OPTIONS'),
patch: makeSseFn('PATCH'),
post: makeSseFn('POST'),
put: makeSseFn('PUT'),
trace: makeSseFn('TRACE'),
},
trace: makeMethodFn('TRACE'),
} as Client;
};

View File

@@ -1,25 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export type { Auth } from '../core/auth.gen';
export type { QuerySerializerOptions } from '../core/bodySerializer.gen';
export {
formDataBodySerializer,
jsonBodySerializer,
urlSearchParamsBodySerializer,
} from '../core/bodySerializer.gen';
export { buildClientParams } from '../core/params.gen';
export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen';
export { createClient } from './client.gen';
export type {
Client,
ClientOptions,
Config,
CreateClientConfig,
Options,
RequestOptions,
RequestResult,
ResolvedRequestOptions,
ResponseStyle,
TDataShape,
} from './types.gen';
export { createConfig, mergeHeaders } from './utils.gen';

View File

@@ -1,241 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Auth } from '../core/auth.gen';
import type {
ServerSentEventsOptions,
ServerSentEventsResult,
} from '../core/serverSentEvents.gen';
import type {
Client as CoreClient,
Config as CoreConfig,
} from '../core/types.gen';
import type { Middleware } from './utils.gen';
export type ResponseStyle = 'data' | 'fields';
export interface Config<T extends ClientOptions = ClientOptions>
extends Omit<RequestInit, 'body' | 'headers' | 'method'>,
CoreConfig {
/**
* Base URL for all requests made by this client.
*/
baseUrl?: T['baseUrl'];
/**
* Fetch API implementation. You can use this option to provide a custom
* fetch instance.
*
* @default globalThis.fetch
*/
fetch?: typeof fetch;
/**
* Please don't use the Fetch client for Next.js applications. The `next`
* options won't have any effect.
*
* Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead.
*/
next?: never;
/**
* Return the response data parsed in a specified format. By default, `auto`
* will infer the appropriate method from the `Content-Type` response header.
* You can override this behavior with any of the {@link Body} methods.
* Select `stream` if you don't want to parse response data at all.
*
* @default 'auto'
*/
parseAs?:
| 'arrayBuffer'
| 'auto'
| 'blob'
| 'formData'
| 'json'
| 'stream'
| 'text';
/**
* Should we return only data or multiple fields (data, error, response, etc.)?
*
* @default 'fields'
*/
responseStyle?: ResponseStyle;
/**
* Throw an error instead of returning it in the response?
*
* @default false
*/
throwOnError?: T['throwOnError'];
}
export interface RequestOptions<
TData = unknown,
TResponseStyle extends ResponseStyle = 'fields',
ThrowOnError extends boolean = boolean,
Url extends string = string,
> extends Config<{
responseStyle: TResponseStyle;
throwOnError: ThrowOnError;
}>,
Pick<
ServerSentEventsOptions<TData>,
| 'onSseError'
| 'onSseEvent'
| 'sseDefaultRetryDelay'
| 'sseMaxRetryAttempts'
| 'sseMaxRetryDelay'
> {
/**
* Any body that you want to add to your request.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#body}
*/
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
/**
* Security mechanism(s) to use for the request.
*/
security?: ReadonlyArray<Auth>;
url: Url;
}
export interface ResolvedRequestOptions<
TResponseStyle extends ResponseStyle = 'fields',
ThrowOnError extends boolean = boolean,
Url extends string = string,
> extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> {
serializedBody?: string;
}
export type RequestResult<
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = boolean,
TResponseStyle extends ResponseStyle = 'fields',
> = ThrowOnError extends true
? Promise<
TResponseStyle extends 'data'
? TData extends Record<string, unknown>
? TData[keyof TData]
: TData
: {
data: TData extends Record<string, unknown>
? TData[keyof TData]
: TData;
request: Request;
response: Response;
}
>
: Promise<
TResponseStyle extends 'data'
?
| (TData extends Record<string, unknown>
? TData[keyof TData]
: TData)
| undefined
: (
| {
data: TData extends Record<string, unknown>
? TData[keyof TData]
: TData;
error: undefined;
}
| {
data: undefined;
error: TError extends Record<string, unknown>
? TError[keyof TError]
: TError;
}
) & {
request: Request;
response: Response;
}
>;
export interface ClientOptions {
baseUrl?: string;
responseStyle?: ResponseStyle;
throwOnError?: boolean;
}
type MethodFn = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
type SseFn = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>,
) => Promise<ServerSentEventsResult<TData, TError>>;
type RequestFn = <
TData = unknown,
TError = unknown,
ThrowOnError extends boolean = false,
TResponseStyle extends ResponseStyle = 'fields',
>(
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> &
Pick<
Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>,
'method'
>,
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>;
type BuildUrlFn = <
TData extends {
body?: unknown;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
url: string;
},
>(
options: TData & Options<TData>,
) => string;
export type Client = CoreClient<
RequestFn,
Config,
MethodFn,
BuildUrlFn,
SseFn
> & {
interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>;
};
/**
* The `createClientConfig()` function will be called on client initialization
* and the returned object will become the client's initial configuration.
*
* You may want to initialize your client this way instead of calling
* `setConfig()`. This is useful for example if you're using Next.js
* to ensure your client always has the correct values.
*/
export type CreateClientConfig<T extends ClientOptions = ClientOptions> = (
override?: Config<ClientOptions & T>,
) => Config<Required<ClientOptions> & T>;
export interface TDataShape {
body?: unknown;
headers?: unknown;
path?: unknown;
query?: unknown;
url: string;
}
type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;
export type Options<
TData extends TDataShape = TDataShape,
ThrowOnError extends boolean = boolean,
TResponse = unknown,
TResponseStyle extends ResponseStyle = 'fields',
> = OmitKeys<
RequestOptions<TResponse, TResponseStyle, ThrowOnError>,
'body' | 'path' | 'query' | 'url'
> &
([TData] extends [never] ? unknown : Omit<TData, 'url'>);

View File

@@ -1,332 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import { getAuthToken } from '../core/auth.gen';
import type { QuerySerializerOptions } from '../core/bodySerializer.gen';
import { jsonBodySerializer } from '../core/bodySerializer.gen';
import {
serializeArrayParam,
serializeObjectParam,
serializePrimitiveParam,
} from '../core/pathSerializer.gen';
import { getUrl } from '../core/utils.gen';
import type { Client, ClientOptions, Config, RequestOptions } from './types.gen';
export const createQuerySerializer = <T = unknown>({
parameters = {},
...args
}: QuerySerializerOptions = {}) => {
const querySerializer = (queryParams: T) => {
const search: string[] = [];
if (queryParams && typeof queryParams === 'object') {
for (const name in queryParams) {
const value = queryParams[name];
if (value === undefined || value === null) {
continue;
}
const options = parameters[name] || args;
if (Array.isArray(value)) {
const serializedArray = serializeArrayParam({
allowReserved: options.allowReserved,
explode: true,
name,
style: 'form',
value,
...options.array,
});
if (serializedArray) search.push(serializedArray);
} else if (typeof value === 'object') {
const serializedObject = serializeObjectParam({
allowReserved: options.allowReserved,
explode: true,
name,
style: 'deepObject',
value: value as Record<string, unknown>,
...options.object,
});
if (serializedObject) search.push(serializedObject);
} else {
const serializedPrimitive = serializePrimitiveParam({
allowReserved: options.allowReserved,
name,
value: value as string,
});
if (serializedPrimitive) search.push(serializedPrimitive);
}
}
}
return search.join('&');
};
return querySerializer;
};
/**
* Infers parseAs value from provided Content-Type header.
*/
export const getParseAs = (
contentType: string | null,
): Exclude<Config['parseAs'], 'auto'> => {
if (!contentType) {
// If no Content-Type header is provided, the best we can do is return the raw response body,
// which is effectively the same as the 'stream' option.
return 'stream';
}
const cleanContent = contentType.split(';')[0]?.trim();
if (!cleanContent) {
return;
}
if (
cleanContent.startsWith('application/json') ||
cleanContent.endsWith('+json')
) {
return 'json';
}
if (cleanContent === 'multipart/form-data') {
return 'formData';
}
if (
['application/', 'audio/', 'image/', 'video/'].some((type) =>
cleanContent.startsWith(type),
)
) {
return 'blob';
}
if (cleanContent.startsWith('text/')) {
return 'text';
}
return;
};
const checkForExistence = (
options: Pick<RequestOptions, 'auth' | 'query'> & {
headers: Headers;
},
name?: string,
): boolean => {
if (!name) {
return false;
}
if (
options.headers.has(name) ||
options.query?.[name] ||
options.headers.get('Cookie')?.includes(`${name}=`)
) {
return true;
}
return false;
};
export const setAuthParams = async ({
security,
...options
}: Pick<Required<RequestOptions>, 'security'> &
Pick<RequestOptions, 'auth' | 'query'> & {
headers: Headers;
}) => {
for (const auth of security) {
if (checkForExistence(options, auth.name)) {
continue;
}
const token = await getAuthToken(auth, options.auth);
if (!token) {
continue;
}
const name = auth.name ?? 'Authorization';
switch (auth.in) {
case 'query':
if (!options.query) {
options.query = {};
}
options.query[name] = token;
break;
case 'cookie':
options.headers.append('Cookie', `${name}=${token}`);
break;
case 'header':
default:
options.headers.set(name, token);
break;
}
}
};
export const buildUrl: Client['buildUrl'] = (options) =>
getUrl({
baseUrl: options.baseUrl as string,
path: options.path,
query: options.query,
querySerializer:
typeof options.querySerializer === 'function'
? options.querySerializer
: createQuerySerializer(options.querySerializer),
url: options.url,
});
export const mergeConfigs = (a: Config, b: Config): Config => {
const config = { ...a, ...b };
if (config.baseUrl?.endsWith('/')) {
config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
}
config.headers = mergeHeaders(a.headers, b.headers);
return config;
};
const headersEntries = (headers: Headers): Array<[string, string]> => {
const entries: Array<[string, string]> = [];
headers.forEach((value, key) => {
entries.push([key, value]);
});
return entries;
};
export const mergeHeaders = (
...headers: Array<Required<Config>['headers'] | undefined>
): Headers => {
const mergedHeaders = new Headers();
for (const header of headers) {
if (!header) {
continue;
}
const iterator =
header instanceof Headers
? headersEntries(header)
: Object.entries(header);
for (const [key, value] of iterator) {
if (value === null) {
mergedHeaders.delete(key);
} else if (Array.isArray(value)) {
for (const v of value) {
mergedHeaders.append(key, v as string);
}
} else if (value !== undefined) {
// assume object headers are meant to be JSON stringified, i.e. their
// content value in OpenAPI specification is 'application/json'
mergedHeaders.set(
key,
typeof value === 'object' ? JSON.stringify(value) : (value as string),
);
}
}
}
return mergedHeaders;
};
type ErrInterceptor<Err, Res, Req, Options> = (
error: Err,
response: Res,
request: Req,
options: Options,
) => Err | Promise<Err>;
type ReqInterceptor<Req, Options> = (
request: Req,
options: Options,
) => Req | Promise<Req>;
type ResInterceptor<Res, Req, Options> = (
response: Res,
request: Req,
options: Options,
) => Res | Promise<Res>;
class Interceptors<Interceptor> {
fns: Array<Interceptor | null> = [];
clear(): void {
this.fns = [];
}
eject(id: number | Interceptor): void {
const index = this.getInterceptorIndex(id);
if (this.fns[index]) {
this.fns[index] = null;
}
}
exists(id: number | Interceptor): boolean {
const index = this.getInterceptorIndex(id);
return Boolean(this.fns[index]);
}
getInterceptorIndex(id: number | Interceptor): number {
if (typeof id === 'number') {
return this.fns[id] ? id : -1;
}
return this.fns.indexOf(id);
}
update(
id: number | Interceptor,
fn: Interceptor,
): number | Interceptor | false {
const index = this.getInterceptorIndex(id);
if (this.fns[index]) {
this.fns[index] = fn;
return id;
}
return false;
}
use(fn: Interceptor): number {
this.fns.push(fn);
return this.fns.length - 1;
}
}
export interface Middleware<Req, Res, Err, Options> {
error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>;
request: Interceptors<ReqInterceptor<Req, Options>>;
response: Interceptors<ResInterceptor<Res, Req, Options>>;
}
export const createInterceptors = <Req, Res, Err, Options>(): Middleware<
Req,
Res,
Err,
Options
> => ({
error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),
request: new Interceptors<ReqInterceptor<Req, Options>>(),
response: new Interceptors<ResInterceptor<Res, Req, Options>>(),
});
const defaultQuerySerializer = createQuerySerializer({
allowReserved: false,
array: {
explode: true,
style: 'form',
},
object: {
explode: true,
style: 'deepObject',
},
});
const defaultHeaders = {
'Content-Type': 'application/json',
};
export const createConfig = <T extends ClientOptions = ClientOptions>(
override: Config<Omit<ClientOptions, keyof T> & T> = {},
): Config<Omit<ClientOptions, keyof T> & T> => ({
...jsonBodySerializer,
headers: defaultHeaders,
parseAs: 'auto',
querySerializer: defaultQuerySerializer,
...override,
});

View File

@@ -1,42 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export type AuthToken = string | undefined;
export interface Auth {
/**
* Which part of the request do we use to send the auth?
*
* @default 'header'
*/
in?: 'header' | 'query' | 'cookie';
/**
* Header or query parameter name.
*
* @default 'Authorization'
*/
name?: string;
scheme?: 'basic' | 'bearer';
type: 'apiKey' | 'http';
}
export const getAuthToken = async (
auth: Auth,
callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,
): Promise<string | undefined> => {
const token =
typeof callback === 'function' ? await callback(auth) : callback;
if (!token) {
return;
}
if (auth.scheme === 'bearer') {
return `Bearer ${token}`;
}
if (auth.scheme === 'basic') {
return `Basic ${btoa(token)}`;
}
return token;
};

View File

@@ -1,100 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type {
ArrayStyle,
ObjectStyle,
SerializerOptions,
} from './pathSerializer.gen';
export type QuerySerializer = (query: Record<string, unknown>) => string;
export type BodySerializer = (body: any) => any;
type QuerySerializerOptionsObject = {
allowReserved?: boolean;
array?: Partial<SerializerOptions<ArrayStyle>>;
object?: Partial<SerializerOptions<ObjectStyle>>;
};
export type QuerySerializerOptions = QuerySerializerOptionsObject & {
/**
* Per-parameter serialization overrides. When provided, these settings
* override the global array/object settings for specific parameter names.
*/
parameters?: Record<string, QuerySerializerOptionsObject>;
};
const serializeFormDataPair = (
data: FormData,
key: string,
value: unknown,
): void => {
if (typeof value === 'string' || value instanceof Blob) {
data.append(key, value);
} else if (value instanceof Date) {
data.append(key, value.toISOString());
} else {
data.append(key, JSON.stringify(value));
}
};
const serializeUrlSearchParamsPair = (
data: URLSearchParams,
key: string,
value: unknown,
): void => {
if (typeof value === 'string') {
data.append(key, value);
} else {
data.append(key, JSON.stringify(value));
}
};
export const formDataBodySerializer = {
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
body: T,
): FormData => {
const data = new FormData();
Object.entries(body).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeFormDataPair(data, key, v));
} else {
serializeFormDataPair(data, key, value);
}
});
return data;
},
};
export const jsonBodySerializer = {
bodySerializer: <T>(body: T): string =>
JSON.stringify(body, (_key, value) =>
typeof value === 'bigint' ? value.toString() : value,
),
};
export const urlSearchParamsBodySerializer = {
bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(
body: T,
): string => {
const data = new URLSearchParams();
Object.entries(body).forEach(([key, value]) => {
if (value === undefined || value === null) {
return;
}
if (Array.isArray(value)) {
value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));
} else {
serializeUrlSearchParamsPair(data, key, value);
}
});
return data.toString();
},
};

View File

@@ -1,176 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
type Slot = 'body' | 'headers' | 'path' | 'query';
export type Field =
| {
in: Exclude<Slot, 'body'>;
/**
* Field name. This is the name we want the user to see and use.
*/
key: string;
/**
* Field mapped name. This is the name we want to use in the request.
* If omitted, we use the same value as `key`.
*/
map?: string;
}
| {
in: Extract<Slot, 'body'>;
/**
* Key isn't required for bodies.
*/
key?: string;
map?: string;
}
| {
/**
* Field name. This is the name we want the user to see and use.
*/
key: string;
/**
* Field mapped name. This is the name we want to use in the request.
* If `in` is omitted, `map` aliases `key` to the transport layer.
*/
map: Slot;
};
export interface Fields {
allowExtra?: Partial<Record<Slot, boolean>>;
args?: ReadonlyArray<Field>;
}
export type FieldsConfig = ReadonlyArray<Field | Fields>;
const extraPrefixesMap: Record<string, Slot> = {
$body_: 'body',
$headers_: 'headers',
$path_: 'path',
$query_: 'query',
};
const extraPrefixes = Object.entries(extraPrefixesMap);
type KeyMap = Map<
string,
| {
in: Slot;
map?: string;
}
| {
in?: never;
map: Slot;
}
>;
const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {
if (!map) {
map = new Map();
}
for (const config of fields) {
if ('in' in config) {
if (config.key) {
map.set(config.key, {
in: config.in,
map: config.map,
});
}
} else if ('key' in config) {
map.set(config.key, {
map: config.map,
});
} else if (config.args) {
buildKeyMap(config.args, map);
}
}
return map;
};
interface Params {
body: unknown;
headers: Record<string, unknown>;
path: Record<string, unknown>;
query: Record<string, unknown>;
}
const stripEmptySlots = (params: Params) => {
for (const [slot, value] of Object.entries(params)) {
if (value && typeof value === 'object' && !Object.keys(value).length) {
delete params[slot as Slot];
}
}
};
export const buildClientParams = (
args: ReadonlyArray<unknown>,
fields: FieldsConfig,
) => {
const params: Params = {
body: {},
headers: {},
path: {},
query: {},
};
const map = buildKeyMap(fields);
let config: FieldsConfig[number] | undefined;
for (const [index, arg] of args.entries()) {
if (fields[index]) {
config = fields[index];
}
if (!config) {
continue;
}
if ('in' in config) {
if (config.key) {
const field = map.get(config.key)!;
const name = field.map || config.key;
if (field.in) {
(params[field.in] as Record<string, unknown>)[name] = arg;
}
} else {
params.body = arg;
}
} else {
for (const [key, value] of Object.entries(arg ?? {})) {
const field = map.get(key);
if (field) {
if (field.in) {
const name = field.map || key;
(params[field.in] as Record<string, unknown>)[name] = value;
} else {
params[field.map] = value;
}
} else {
const extra = extraPrefixes.find(([prefix]) =>
key.startsWith(prefix),
);
if (extra) {
const [prefix, slot] = extra;
(params[slot] as Record<string, unknown>)[
key.slice(prefix.length)
] = value;
} else if ('allowExtra' in config && config.allowExtra) {
for (const [slot, allowed] of Object.entries(config.allowExtra)) {
if (allowed) {
(params[slot as Slot] as Record<string, unknown>)[key] = value;
break;
}
}
}
}
}
}
}
stripEmptySlots(params);
return params;
};

View File

@@ -1,181 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
interface SerializeOptions<T>
extends SerializePrimitiveOptions,
SerializerOptions<T> {}
interface SerializePrimitiveOptions {
allowReserved?: boolean;
name: string;
}
export interface SerializerOptions<T> {
/**
* @default true
*/
explode: boolean;
style: T;
}
export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited';
export type ArraySeparatorStyle = ArrayStyle | MatrixStyle;
type MatrixStyle = 'label' | 'matrix' | 'simple';
export type ObjectStyle = 'form' | 'deepObject';
type ObjectSeparatorStyle = ObjectStyle | MatrixStyle;
interface SerializePrimitiveParam extends SerializePrimitiveOptions {
value: string;
}
export const separatorArrayExplode = (style: ArraySeparatorStyle) => {
switch (style) {
case 'label':
return '.';
case 'matrix':
return ';';
case 'simple':
return ',';
default:
return '&';
}
};
export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {
switch (style) {
case 'form':
return ',';
case 'pipeDelimited':
return '|';
case 'spaceDelimited':
return '%20';
default:
return ',';
}
};
export const separatorObjectExplode = (style: ObjectSeparatorStyle) => {
switch (style) {
case 'label':
return '.';
case 'matrix':
return ';';
case 'simple':
return ',';
default:
return '&';
}
};
export const serializeArrayParam = ({
allowReserved,
explode,
name,
style,
value,
}: SerializeOptions<ArraySeparatorStyle> & {
value: unknown[];
}) => {
if (!explode) {
const joinedValues = (
allowReserved ? value : value.map((v) => encodeURIComponent(v as string))
).join(separatorArrayNoExplode(style));
switch (style) {
case 'label':
return `.${joinedValues}`;
case 'matrix':
return `;${name}=${joinedValues}`;
case 'simple':
return joinedValues;
default:
return `${name}=${joinedValues}`;
}
}
const separator = separatorArrayExplode(style);
const joinedValues = value
.map((v) => {
if (style === 'label' || style === 'simple') {
return allowReserved ? v : encodeURIComponent(v as string);
}
return serializePrimitiveParam({
allowReserved,
name,
value: v as string,
});
})
.join(separator);
return style === 'label' || style === 'matrix'
? separator + joinedValues
: joinedValues;
};
export const serializePrimitiveParam = ({
allowReserved,
name,
value,
}: SerializePrimitiveParam) => {
if (value === undefined || value === null) {
return '';
}
if (typeof value === 'object') {
throw new Error(
'Deeply-nested arrays/objects arent supported. Provide your own `querySerializer()` to handle these.',
);
}
return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;
};
export const serializeObjectParam = ({
allowReserved,
explode,
name,
style,
value,
valueOnly,
}: SerializeOptions<ObjectSeparatorStyle> & {
value: Record<string, unknown> | Date;
valueOnly?: boolean;
}) => {
if (value instanceof Date) {
return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;
}
if (style !== 'deepObject' && !explode) {
let values: string[] = [];
Object.entries(value).forEach(([key, v]) => {
values = [
...values,
key,
allowReserved ? (v as string) : encodeURIComponent(v as string),
];
});
const joinedValues = values.join(',');
switch (style) {
case 'form':
return `${name}=${joinedValues}`;
case 'label':
return `.${joinedValues}`;
case 'matrix':
return `;${name}=${joinedValues}`;
default:
return joinedValues;
}
}
const separator = separatorObjectExplode(style);
const joinedValues = Object.entries(value)
.map(([key, v]) =>
serializePrimitiveParam({
allowReserved,
name: style === 'deepObject' ? `${name}[${key}]` : key,
value: v as string,
}),
)
.join(separator);
return style === 'label' || style === 'matrix'
? separator + joinedValues
: joinedValues;
};

View File

@@ -1,136 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
/**
* JSON-friendly union that mirrors what Pinia Colada can hash.
*/
export type JsonValue =
| null
| string
| number
| boolean
| JsonValue[]
| { [key: string]: JsonValue };
/**
* Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes.
*/
export const queryKeyJsonReplacer = (_key: string, value: unknown) => {
if (
value === undefined ||
typeof value === 'function' ||
typeof value === 'symbol'
) {
return undefined;
}
if (typeof value === 'bigint') {
return value.toString();
}
if (value instanceof Date) {
return value.toISOString();
}
return value;
};
/**
* Safely stringifies a value and parses it back into a JsonValue.
*/
export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => {
try {
const json = JSON.stringify(input, queryKeyJsonReplacer);
if (json === undefined) {
return undefined;
}
return JSON.parse(json) as JsonValue;
} catch {
return undefined;
}
};
/**
* Detects plain objects (including objects with a null prototype).
*/
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
if (value === null || typeof value !== 'object') {
return false;
}
const prototype = Object.getPrototypeOf(value as object);
return prototype === Object.prototype || prototype === null;
};
/**
* Turns URLSearchParams into a sorted JSON object for deterministic keys.
*/
const serializeSearchParams = (params: URLSearchParams): JsonValue => {
const entries = Array.from(params.entries()).sort(([a], [b]) =>
a.localeCompare(b),
);
const result: Record<string, JsonValue> = {};
for (const [key, value] of entries) {
const existing = result[key];
if (existing === undefined) {
result[key] = value;
continue;
}
if (Array.isArray(existing)) {
(existing as string[]).push(value);
} else {
result[key] = [existing, value];
}
}
return result;
};
/**
* Normalizes any accepted value into a JSON-friendly shape for query keys.
*/
export const serializeQueryKeyValue = (
value: unknown,
): JsonValue | undefined => {
if (value === null) {
return null;
}
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
) {
return value;
}
if (
value === undefined ||
typeof value === 'function' ||
typeof value === 'symbol'
) {
return undefined;
}
if (typeof value === 'bigint') {
return value.toString();
}
if (value instanceof Date) {
return value.toISOString();
}
if (Array.isArray(value)) {
return stringifyToJsonValue(value);
}
if (
typeof URLSearchParams !== 'undefined' &&
value instanceof URLSearchParams
) {
return serializeSearchParams(value);
}
if (isPlainObject(value)) {
return stringifyToJsonValue(value);
}
return undefined;
};

View File

@@ -1,266 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Config } from './types.gen';
export type ServerSentEventsOptions<TData = unknown> = Omit<
RequestInit,
'method'
> &
Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & {
/**
* Fetch API implementation. You can use this option to provide a custom
* fetch instance.
*
* @default globalThis.fetch
*/
fetch?: typeof fetch;
/**
* Implementing clients can call request interceptors inside this hook.
*/
onRequest?: (url: string, init: RequestInit) => Promise<Request>;
/**
* Callback invoked when a network or parsing error occurs during streaming.
*
* This option applies only if the endpoint returns a stream of events.
*
* @param error The error that occurred.
*/
onSseError?: (error: unknown) => void;
/**
* Callback invoked when an event is streamed from the server.
*
* This option applies only if the endpoint returns a stream of events.
*
* @param event Event streamed from the server.
* @returns Nothing (void).
*/
onSseEvent?: (event: StreamEvent<TData>) => void;
serializedBody?: RequestInit['body'];
/**
* Default retry delay in milliseconds.
*
* This option applies only if the endpoint returns a stream of events.
*
* @default 3000
*/
sseDefaultRetryDelay?: number;
/**
* Maximum number of retry attempts before giving up.
*/
sseMaxRetryAttempts?: number;
/**
* Maximum retry delay in milliseconds.
*
* Applies only when exponential backoff is used.
*
* This option applies only if the endpoint returns a stream of events.
*
* @default 30000
*/
sseMaxRetryDelay?: number;
/**
* Optional sleep function for retry backoff.
*
* Defaults to using `setTimeout`.
*/
sseSleepFn?: (ms: number) => Promise<void>;
url: string;
};
export interface StreamEvent<TData = unknown> {
data: TData;
event?: string;
id?: string;
retry?: number;
}
export type ServerSentEventsResult<
TData = unknown,
TReturn = void,
TNext = unknown,
> = {
stream: AsyncGenerator<
TData extends Record<string, unknown> ? TData[keyof TData] : TData,
TReturn,
TNext
>;
};
export const createSseClient = <TData = unknown>({
onRequest,
onSseError,
onSseEvent,
responseTransformer,
responseValidator,
sseDefaultRetryDelay,
sseMaxRetryAttempts,
sseMaxRetryDelay,
sseSleepFn,
url,
...options
}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
let lastEventId: string | undefined;
const sleep =
sseSleepFn ??
((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
const createStream = async function* () {
let retryDelay: number = sseDefaultRetryDelay ?? 3000;
let attempt = 0;
const signal = options.signal ?? new AbortController().signal;
while (true) {
if (signal.aborted) break;
attempt++;
const headers =
options.headers instanceof Headers
? options.headers
: new Headers(options.headers as Record<string, string> | undefined);
if (lastEventId !== undefined) {
headers.set('Last-Event-ID', lastEventId);
}
try {
const requestInit: RequestInit = {
redirect: 'follow',
...options,
body: options.serializedBody,
headers,
signal,
};
let request = new Request(url, requestInit);
if (onRequest) {
request = await onRequest(url, requestInit);
}
// fetch must be assigned here, otherwise it would throw the error:
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
const _fetch = options.fetch ?? globalThis.fetch;
const response = await _fetch(request);
if (!response.ok)
throw new Error(
`SSE failed: ${response.status} ${response.statusText}`,
);
if (!response.body) throw new Error('No body in SSE response');
const reader = response.body
.pipeThrough(new TextDecoderStream())
.getReader();
let buffer = '';
const abortHandler = () => {
try {
reader.cancel();
} catch {
// noop
}
};
signal.addEventListener('abort', abortHandler);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += value;
// Normalize line endings: CRLF -> LF, then CR -> LF
buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
const chunks = buffer.split('\n\n');
buffer = chunks.pop() ?? '';
for (const chunk of chunks) {
const lines = chunk.split('\n');
const dataLines: Array<string> = [];
let eventName: string | undefined;
for (const line of lines) {
if (line.startsWith('data:')) {
dataLines.push(line.replace(/^data:\s*/, ''));
} else if (line.startsWith('event:')) {
eventName = line.replace(/^event:\s*/, '');
} else if (line.startsWith('id:')) {
lastEventId = line.replace(/^id:\s*/, '');
} else if (line.startsWith('retry:')) {
const parsed = Number.parseInt(
line.replace(/^retry:\s*/, ''),
10,
);
if (!Number.isNaN(parsed)) {
retryDelay = parsed;
}
}
}
let data: unknown;
let parsedJson = false;
if (dataLines.length) {
const rawData = dataLines.join('\n');
try {
data = JSON.parse(rawData);
parsedJson = true;
} catch {
data = rawData;
}
}
if (parsedJson) {
if (responseValidator) {
await responseValidator(data);
}
if (responseTransformer) {
data = await responseTransformer(data);
}
}
onSseEvent?.({
data,
event: eventName,
id: lastEventId,
retry: retryDelay,
});
if (dataLines.length) {
yield data as any;
}
}
}
} finally {
signal.removeEventListener('abort', abortHandler);
reader.releaseLock();
}
break; // exit loop on normal completion
} catch (error) {
// connection failed or aborted; retry after delay
onSseError?.(error);
if (
sseMaxRetryAttempts !== undefined &&
attempt >= sseMaxRetryAttempts
) {
break; // stop after firing error
}
// exponential backoff: double retry each attempt, cap at 30s
const backoff = Math.min(
retryDelay * 2 ** (attempt - 1),
sseMaxRetryDelay ?? 30000,
);
await sleep(backoff);
}
}
};
const stream = createStream();
return { stream };
};

View File

@@ -1,118 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Auth, AuthToken } from './auth.gen';
import type {
BodySerializer,
QuerySerializer,
QuerySerializerOptions,
} from './bodySerializer.gen';
export type HttpMethod =
| 'connect'
| 'delete'
| 'get'
| 'head'
| 'options'
| 'patch'
| 'post'
| 'put'
| 'trace';
export type Client<
RequestFn = never,
Config = unknown,
MethodFn = never,
BuildUrlFn = never,
SseFn = never,
> = {
/**
* Returns the final request URL.
*/
buildUrl: BuildUrlFn;
getConfig: () => Config;
request: RequestFn;
setConfig: (config: Config) => Config;
} & {
[K in HttpMethod]: MethodFn;
} & ([SseFn] extends [never]
? { sse?: never }
: { sse: { [K in HttpMethod]: SseFn } });
export interface Config {
/**
* Auth token or a function returning auth token. The resolved value will be
* added to the request payload as defined by its `security` array.
*/
auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;
/**
* A function for serializing request body parameter. By default,
* {@link JSON.stringify()} will be used.
*/
bodySerializer?: BodySerializer | null;
/**
* An object containing any HTTP headers that you want to pre-populate your
* `Headers` object with.
*
* {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}
*/
headers?:
| RequestInit['headers']
| Record<
string,
| string
| number
| boolean
| (string | number | boolean)[]
| null
| undefined
| unknown
>;
/**
* The request method.
*
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
*/
method?: Uppercase<HttpMethod>;
/**
* A function for serializing request query parameters. By default, arrays
* will be exploded in form style, objects will be exploded in deepObject
* style, and reserved characters are percent-encoded.
*
* This method will have no effect if the native `paramsSerializer()` Axios
* API function is used.
*
* {@link https://swagger.io/docs/specification/serialization/#query View examples}
*/
querySerializer?: QuerySerializer | QuerySerializerOptions;
/**
* A function validating request data. This is useful if you want to ensure
* the request conforms to the desired shape, so it can be safely sent to
* the server.
*/
requestValidator?: (data: unknown) => Promise<unknown>;
/**
* A function transforming response data before it's returned. This is useful
* for post-processing data, e.g. converting ISO strings into Date objects.
*/
responseTransformer?: (data: unknown) => Promise<unknown>;
/**
* A function validating response data. This is useful if you want to ensure
* the response conforms to the desired shape, so it can be safely passed to
* the transformers and returned to the user.
*/
responseValidator?: (data: unknown) => Promise<unknown>;
}
type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]
? true
: [T] extends [never | undefined]
? [undefined] extends [T]
? false
: true
: false;
export type OmitNever<T extends Record<string, unknown>> = {
[K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true
? never
: K]: T[K];
};

View File

@@ -1,143 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { BodySerializer, QuerySerializer } from './bodySerializer.gen';
import {
type ArraySeparatorStyle,
serializeArrayParam,
serializeObjectParam,
serializePrimitiveParam,
} from './pathSerializer.gen';
export interface PathSerializer {
path: Record<string, unknown>;
url: string;
}
export const PATH_PARAM_RE = /\{[^{}]+\}/g;
export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {
let url = _url;
const matches = _url.match(PATH_PARAM_RE);
if (matches) {
for (const match of matches) {
let explode = false;
let name = match.substring(1, match.length - 1);
let style: ArraySeparatorStyle = 'simple';
if (name.endsWith('*')) {
explode = true;
name = name.substring(0, name.length - 1);
}
if (name.startsWith('.')) {
name = name.substring(1);
style = 'label';
} else if (name.startsWith(';')) {
name = name.substring(1);
style = 'matrix';
}
const value = path[name];
if (value === undefined || value === null) {
continue;
}
if (Array.isArray(value)) {
url = url.replace(
match,
serializeArrayParam({ explode, name, style, value }),
);
continue;
}
if (typeof value === 'object') {
url = url.replace(
match,
serializeObjectParam({
explode,
name,
style,
value: value as Record<string, unknown>,
valueOnly: true,
}),
);
continue;
}
if (style === 'matrix') {
url = url.replace(
match,
`;${serializePrimitiveParam({
name,
value: value as string,
})}`,
);
continue;
}
const replaceValue = encodeURIComponent(
style === 'label' ? `.${value as string}` : (value as string),
);
url = url.replace(match, replaceValue);
}
}
return url;
};
export const getUrl = ({
baseUrl,
path,
query,
querySerializer,
url: _url,
}: {
baseUrl?: string;
path?: Record<string, unknown>;
query?: Record<string, unknown>;
querySerializer: QuerySerializer;
url: string;
}) => {
const pathUrl = _url.startsWith('/') ? _url : `/${_url}`;
let url = (baseUrl ?? '') + pathUrl;
if (path) {
url = defaultPathSerializer({ path, url });
}
let search = query ? querySerializer(query) : '';
if (search.startsWith('?')) {
search = search.substring(1);
}
if (search) {
url += `?${search}`;
}
return url;
};
export function getValidRequestBody(options: {
body?: unknown;
bodySerializer?: BodySerializer | null;
serializedBody?: unknown;
}) {
const hasBody = options.body !== undefined;
const isSerializedBody = hasBody && options.bodySerializer;
if (isSerializedBody) {
if ('serializedBody' in options) {
const hasSerializedBody =
options.serializedBody !== undefined && options.serializedBody !== '';
return hasSerializedBody ? options.serializedBody : null;
}
// not all clients implement a serializedBody property (i.e. client-axios)
return options.body !== '' ? options.body : null;
}
// plain/text body
if (hasBody) {
return options.body;
}
// no body was provided
return undefined;
}

View File

@@ -1,4 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export { getNodesByUserBySystemByNodeId_byVersionJson, getNodesByUserBySystemByNodeId_byVersionWasm, getNodesByUserBySystemByNodeIdJson, getNodesByUserBySystemByNodeIdVersionsJson, getNodesByUserBySystemByNodeIdWasm, getNodesByUserBySystemJson, getNodesByUserJson, getUsersByUserIdJson, getUsersUsersJson, type Options, postNodes } from './sdk.gen';
export type { ClientOptions, GetNodesByUserBySystemByNodeId_byVersionJsonData, GetNodesByUserBySystemByNodeId_byVersionJsonResponse, GetNodesByUserBySystemByNodeId_byVersionJsonResponses, GetNodesByUserBySystemByNodeId_byVersionWasmData, GetNodesByUserBySystemByNodeId_byVersionWasmResponses, GetNodesByUserBySystemByNodeIdJsonData, GetNodesByUserBySystemByNodeIdJsonResponse, GetNodesByUserBySystemByNodeIdJsonResponses, GetNodesByUserBySystemByNodeIdVersionsJsonData, GetNodesByUserBySystemByNodeIdVersionsJsonResponse, GetNodesByUserBySystemByNodeIdVersionsJsonResponses, GetNodesByUserBySystemByNodeIdWasmData, GetNodesByUserBySystemByNodeIdWasmResponses, GetNodesByUserBySystemJsonData, GetNodesByUserBySystemJsonResponse, GetNodesByUserBySystemJsonResponses, GetNodesByUserJsonData, GetNodesByUserJsonResponse, GetNodesByUserJsonResponses, GetUsersByUserIdJsonData, GetUsersByUserIdJsonResponse, GetUsersByUserIdJsonResponses, GetUsersUsersJsonData, GetUsersUsersJsonResponse, GetUsersUsersJsonResponses, NodeDefinition, NodeInput, PostNodesData, PostNodesResponse, PostNodesResponses, User } from './types.gen';

View File

@@ -1,39 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
import type { Client, Options as Options2, TDataShape } from './client';
import { client } from './client.gen';
import type { GetNodesByUserBySystemByNodeId_byVersionJsonData, GetNodesByUserBySystemByNodeId_byVersionJsonResponses, GetNodesByUserBySystemByNodeId_byVersionWasmData, GetNodesByUserBySystemByNodeId_byVersionWasmResponses, GetNodesByUserBySystemByNodeIdJsonData, GetNodesByUserBySystemByNodeIdJsonResponses, GetNodesByUserBySystemByNodeIdVersionsJsonData, GetNodesByUserBySystemByNodeIdVersionsJsonResponses, GetNodesByUserBySystemByNodeIdWasmData, GetNodesByUserBySystemByNodeIdWasmResponses, GetNodesByUserBySystemJsonData, GetNodesByUserBySystemJsonResponses, GetNodesByUserJsonData, GetNodesByUserJsonResponses, GetUsersByUserIdJsonData, GetUsersByUserIdJsonResponses, GetUsersUsersJsonData, GetUsersUsersJsonResponses, PostNodesData, PostNodesResponses } from './types.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/**
* You can provide a client instance returned by `createClient()` instead of
* individual options. This might be also useful if you want to implement a
* custom client.
*/
client?: Client;
/**
* You can pass arbitrary values through the `meta` object. This can be
* used to access values that aren't defined as part of the SDK function.
*/
meta?: Record<string, unknown>;
};
export const postNodes = <ThrowOnError extends boolean = false>(options?: Options<PostNodesData, ThrowOnError>) => (options?.client ?? client).post<PostNodesResponses, unknown, ThrowOnError>({ url: '/nodes', ...options });
export const getNodesByUserJson = <ThrowOnError extends boolean = false>(options?: Options<GetNodesByUserJsonData, ThrowOnError>) => (options?.client ?? client).get<GetNodesByUserJsonResponses, unknown, ThrowOnError>({ url: '/nodes/{user}.json', ...options });
export const getNodesByUserBySystemJson = <ThrowOnError extends boolean = false>(options: Options<GetNodesByUserBySystemJsonData, ThrowOnError>) => (options.client ?? client).get<GetNodesByUserBySystemJsonResponses, unknown, ThrowOnError>({ url: '/nodes/{user}/{system}.json', ...options });
export const getNodesByUserBySystemByNodeIdJson = <ThrowOnError extends boolean = false>(options: Options<GetNodesByUserBySystemByNodeIdJsonData, ThrowOnError>) => (options.client ?? client).get<GetNodesByUserBySystemByNodeIdJsonResponses, unknown, ThrowOnError>({ url: '/nodes/{user}/{system}/{nodeId}.json', ...options });
export const getNodesByUserBySystemByNodeId_byVersionJson = <ThrowOnError extends boolean = false>(options: Options<GetNodesByUserBySystemByNodeId_byVersionJsonData, ThrowOnError>) => (options.client ?? client).get<GetNodesByUserBySystemByNodeId_byVersionJsonResponses, unknown, ThrowOnError>({ url: '/nodes/{user}/{system}/{nodeId}@{version}.json', ...options });
export const getNodesByUserBySystemByNodeIdVersionsJson = <ThrowOnError extends boolean = false>(options: Options<GetNodesByUserBySystemByNodeIdVersionsJsonData, ThrowOnError>) => (options.client ?? client).get<GetNodesByUserBySystemByNodeIdVersionsJsonResponses, unknown, ThrowOnError>({ url: '/nodes/{user}/{system}/{nodeId}/versions.json', ...options });
export const getNodesByUserBySystemByNodeIdWasm = <ThrowOnError extends boolean = false>(options: Options<GetNodesByUserBySystemByNodeIdWasmData, ThrowOnError>) => (options.client ?? client).get<GetNodesByUserBySystemByNodeIdWasmResponses, unknown, ThrowOnError>({ url: '/nodes/{user}/{system}/{nodeId}.wasm', ...options });
export const getNodesByUserBySystemByNodeId_byVersionWasm = <ThrowOnError extends boolean = false>(options: Options<GetNodesByUserBySystemByNodeId_byVersionWasmData, ThrowOnError>) => (options.client ?? client).get<GetNodesByUserBySystemByNodeId_byVersionWasmResponses, unknown, ThrowOnError>({ url: '/nodes/{user}/{system}/{nodeId}@{version}.wasm', ...options });
export const getUsersUsersJson = <ThrowOnError extends boolean = false>(options?: Options<GetUsersUsersJsonData, ThrowOnError>) => (options?.client ?? client).get<GetUsersUsersJsonResponses, unknown, ThrowOnError>({ url: '/users/users.json', ...options });
export const getUsersByUserIdJson = <ThrowOnError extends boolean = false>(options?: Options<GetUsersByUserIdJsonData, ThrowOnError>) => (options?.client ?? client).get<GetUsersByUserIdJsonResponses, unknown, ThrowOnError>({ url: '/users/{userId}.json', ...options });

View File

@@ -1,305 +0,0 @@
// This file is auto-generated by @hey-api/openapi-ts
export type ClientOptions = {
baseUrl: `${string}://${string}` | (string & {});
};
export type NodeInput = {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'seed';
value?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'boolean';
value?: boolean;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'float';
element?: 'slider';
value?: number;
min?: number;
max?: number;
step?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'integer';
element?: 'slider';
value?: number;
min?: number;
max?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'select';
options?: Array<string>;
value?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'seed';
value?: number;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'vec3';
value?: Array<number>;
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'geometry';
} | {
internal?: boolean;
external?: boolean;
setting?: string;
label?: string;
description?: string;
accepts?: Array<string>;
hidden?: boolean;
type: 'path';
};
export type NodeDefinition = {
id: string;
inputs?: {
[key: string]: NodeInput;
};
outputs?: Array<string>;
meta?: {
description?: string;
title?: string;
};
};
export type User = {
id: string;
name: string;
};
export type PostNodesData = {
body?: never;
path?: never;
query?: never;
url: '/nodes';
};
export type PostNodesResponses = {
/**
* Create a single node
*/
200: NodeDefinition;
};
export type PostNodesResponse = PostNodesResponses[keyof PostNodesResponses];
export type GetNodesByUserJsonData = {
body?: never;
path?: {
user?: string;
};
query?: never;
url: '/nodes/{user}.json';
};
export type GetNodesByUserJsonResponses = {
/**
* Retrieve nodes for a user
*/
200: Array<NodeDefinition>;
};
export type GetNodesByUserJsonResponse = GetNodesByUserJsonResponses[keyof GetNodesByUserJsonResponses];
export type GetNodesByUserBySystemJsonData = {
body?: never;
path: {
user: string;
system?: string;
};
query?: never;
url: '/nodes/{user}/{system}.json';
};
export type GetNodesByUserBySystemJsonResponses = {
/**
* Retrieve nodes for a system
*/
200: Array<NodeDefinition>;
};
export type GetNodesByUserBySystemJsonResponse = GetNodesByUserBySystemJsonResponses[keyof GetNodesByUserBySystemJsonResponses];
export type GetNodesByUserBySystemByNodeIdJsonData = {
body?: never;
path: {
user: string;
system: string;
nodeId?: string;
};
query?: never;
url: '/nodes/{user}/{system}/{nodeId}.json';
};
export type GetNodesByUserBySystemByNodeIdJsonResponses = {
/**
* Retrieve a single node definition
*/
200: NodeDefinition;
};
export type GetNodesByUserBySystemByNodeIdJsonResponse = GetNodesByUserBySystemByNodeIdJsonResponses[keyof GetNodesByUserBySystemByNodeIdJsonResponses];
export type GetNodesByUserBySystemByNodeId_byVersionJsonData = {
body?: never;
path: {
user: string;
system: string;
nodeId: string;
version?: string;
};
query?: never;
url: '/nodes/{user}/{system}/{nodeId}@{version}.json';
};
export type GetNodesByUserBySystemByNodeId_byVersionJsonResponses = {
/**
* Retrieve a single node definition
*/
200: NodeDefinition;
};
export type GetNodesByUserBySystemByNodeId_byVersionJsonResponse = GetNodesByUserBySystemByNodeId_byVersionJsonResponses[keyof GetNodesByUserBySystemByNodeId_byVersionJsonResponses];
export type GetNodesByUserBySystemByNodeIdVersionsJsonData = {
body?: never;
path: {
user: string;
system: string;
nodeId: string;
};
query?: never;
url: '/nodes/{user}/{system}/{nodeId}/versions.json';
};
export type GetNodesByUserBySystemByNodeIdVersionsJsonResponses = {
/**
* Retrieve a single node definition
*/
200: Array<NodeDefinition>;
};
export type GetNodesByUserBySystemByNodeIdVersionsJsonResponse = GetNodesByUserBySystemByNodeIdVersionsJsonResponses[keyof GetNodesByUserBySystemByNodeIdVersionsJsonResponses];
export type GetNodesByUserBySystemByNodeIdWasmData = {
body?: never;
path: {
user: string;
system: string;
nodeId?: string;
};
query?: never;
url: '/nodes/{user}/{system}/{nodeId}.wasm';
};
export type GetNodesByUserBySystemByNodeIdWasmResponses = {
/**
* Retrieve a node's WASM file
*/
200: unknown;
};
export type GetNodesByUserBySystemByNodeId_byVersionWasmData = {
body?: never;
path: {
user: string;
system: string;
nodeId: string;
version?: string;
};
query?: never;
url: '/nodes/{user}/{system}/{nodeId}@{version}.wasm';
};
export type GetNodesByUserBySystemByNodeId_byVersionWasmResponses = {
/**
* Retrieve a node's WASM file
*/
200: unknown;
};
export type GetUsersUsersJsonData = {
body?: never;
path?: never;
query?: never;
url: '/users/users.json';
};
export type GetUsersUsersJsonResponses = {
/**
* Retrieve a single node definition
*/
200: Array<User>;
};
export type GetUsersUsersJsonResponse = GetUsersUsersJsonResponses[keyof GetUsersUsersJsonResponses];
export type GetUsersByUserIdJsonData = {
body?: never;
path?: {
userId?: string;
};
query?: never;
url: '/users/{userId}.json';
};
export type GetUsersByUserIdJsonResponses = {
/**
* Retrieve a single node definition
*/
200: User;
};
export type GetUsersByUserIdJsonResponse = GetUsersByUserIdJsonResponses[keyof GetUsersByUserIdJsonResponses];

View File

@@ -3,8 +3,8 @@ name = "nodarium_types"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = "Types for Nodarium"
repository = "https://github.com/jim-fx/nodes"
description = "Types for Nodarium"
[dependencies]
serde = { version = "1.0", features = ["derive"] }

View File

@@ -3,6 +3,10 @@
"version": "0.0.0",
"description": "",
"main": "src/index.ts",
"scripts": {
"format": "dprint fmt -c '../../.dprint.jsonc' .",
"format:check": "dprint check -c '../../.dprint.jsonc' ."
},
"exports": {
".": {
"import": "./src/index.ts",
@@ -14,5 +18,8 @@
"license": "ISC",
"dependencies": {
"zod": "^4.3.5"
},
"devDependencies": {
"dprint": "^0.51.1"
}
}

View File

@@ -1,11 +1,11 @@
import type { Graph, NodeDefinition, NodeId } from "./types";
import type { Graph, NodeDefinition, NodeId } from './types';
export interface NodeRegistry {
/**
* The status of the node registry
* @remarks The status should be "loading" when the registry is loading, "ready" when the registry is ready, and "error" if an error occurred while loading the registry
*/
status: "loading" | "ready" | "error";
status: 'loading' | 'ready' | 'error';
/**
* Load the nodes with the given ids
* @param nodeIds - The ids of the nodes to load
@@ -31,7 +31,7 @@ export interface NodeRegistry {
* @param wasmBuffer - The WebAssembly buffer for the node
* @returns The node definition
*/
register: (wasmBuffer: ArrayBuffer) => Promise<NodeDefinition>;
register: (id: string, wasmBuffer: ArrayBuffer) => Promise<NodeDefinition>;
}
export interface RuntimeExecutor {
@@ -42,7 +42,7 @@ export interface RuntimeExecutor {
*/
execute: (
graph: Graph,
settings: Record<string, unknown>,
settings: Record<string, unknown>
) => Promise<Int32Array>;
}

View File

@@ -1,5 +1,14 @@
export type { AsyncCache, NodeRegistry, RuntimeExecutor, SyncCache } from './components';
export type { NodeInput } from './inputs';
export type { Box, Edge, Graph, NodeDefinition, NodeId, NodeInstance, SerializedNode, Socket } from './types';
export type {
Box,
Edge,
Graph,
NodeDefinition,
NodeId,
NodeInstance,
SerializedNode,
Socket
} from './types';
export { GraphSchema, NodeSchema } from './types';
export { NodeDefinitionSchema } from './types';

View File

@@ -1,4 +1,4 @@
import { z } from "zod";
import { z } from 'zod';
const DefaultOptionsSchema = z.object({
internal: z.boolean().optional(),
@@ -9,72 +9,74 @@ const DefaultOptionsSchema = z.object({
accepts: z
.array(
z.union([
z.literal("float"),
z.literal("integer"),
z.literal("boolean"),
z.literal("select"),
z.literal("seed"),
z.literal("vec3"),
z.literal("geometry"),
z.literal("path"),
]),
z.literal('float'),
z.literal('integer'),
z.literal('boolean'),
z.literal('select'),
z.literal('seed'),
z.literal('vec3'),
z.literal('geometry'),
z.literal('path')
])
)
.optional(),
hidden: z.boolean().optional(),
hidden: z.boolean().optional()
});
export const NodeInputFloatSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("float"),
element: z.literal("slider").optional(),
type: z.literal('float'),
element: z.literal('slider').optional(),
value: z.number().optional(),
min: z.number().optional(),
max: z.number().optional(),
step: z.number().optional(),
step: z.number().optional()
});
export const NodeInputIntegerSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("integer"),
element: z.literal("slider").optional(),
type: z.literal('integer'),
element: z.literal('slider').optional(),
value: z.number().optional(),
min: z.number().optional(),
max: z.number().optional(),
max: z.number().optional()
});
export const NodeInputBooleanSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("boolean"),
value: z.boolean().optional(),
type: z.literal('boolean'),
value: z.boolean().optional()
});
export const NodeInputSelectSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("select"),
type: z.literal('select'),
options: z.array(z.string()).optional(),
value: z.string().optional(),
value: z.string().optional()
});
export const NodeInputSeedSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("seed"),
value: z.number().optional(),
type: z.literal('seed'),
value: z.number().optional()
});
export const NodeInputVec3Schema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("vec3"),
value: z.array(z.number()).optional(),
type: z.literal('vec3'),
value: z.array(z.number()).optional()
});
export const NodeInputGeometrySchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("geometry"),
type: z.literal('geometry'),
value: z.array(z.number()).optional()
});
export const NodeInputPathSchema = z.object({
...DefaultOptionsSchema.shape,
type: z.literal("path"),
type: z.literal('path'),
value: z.array(z.number()).optional()
});
export const NodeInputSchema = z.union([
@@ -86,7 +88,7 @@ export const NodeInputSchema = z.union([
NodeInputSeedSchema,
NodeInputVec3Schema,
NodeInputGeometrySchema,
NodeInputPathSchema,
NodeInputPathSchema
]);
export type NodeInput = z.infer<typeof NodeInputSchema>;

View File

@@ -1,7 +1,5 @@
type ExtractValues<T> = {
[K in keyof T]: T[K] extends { value: infer V }
? V
: T[K] extends object
? ExtractValues<T[K]>
[K in keyof T]: T[K] extends { value: infer V } ? V
: T[K] extends object ? ExtractValues<T[K]>
: never;
};

View File

@@ -1,13 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -1,32 +0,0 @@
/** @type { import("eslint").Linter.Config } */
module.exports = {
root: true,
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:svelte/recommended",
"prettier",
"plugin:storybook/recommended"
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};

View File

@@ -1,4 +0,0 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -1,8 +0,0 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -0,0 +1,37 @@
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import path from 'node:path';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
export default defineConfig(
includeIgnoreFile(gitignorePath),
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs.recommended,
{
languageOptions: { globals: { ...globals.browser, ...globals.node } },
rules: {
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
'no-undef': 'off'
}
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
languageOptions: {
parserOptions: {
projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
tsconfigRootDir: import.meta.dirname,
svelteConfig
}
}
}
);

View File

@@ -8,11 +8,12 @@
"preview": "vite preview",
"package": "svelte-kit sync && svelte-package && publint",
"prepublishOnly": "npm run package",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"test": "vitest",
"lint-dprint": "dprint check -c '../../.dprint.jsonc' .",
"lint": "eslint ."
"format": "dprint fmt -c '../../.dprint.jsonc' .",
"format:check": "dprint check -c '../../.dprint.jsonc' .",
"lint": "eslint .",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json"
},
"exports": {
".": {
@@ -30,6 +31,9 @@
"svelte": "^4.0.0"
},
"devDependencies": {
"@eslint/compat": "^2.0.2",
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"@nodarium/types": "workspace:",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.50.0",
@@ -40,13 +44,17 @@
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"chokidar-cli": "^3.0.0",
"dprint": "^0.51.1",
"eslint": "^9.39.2",
"eslint-plugin-svelte": "^3.14.0",
"globals": "^17.3.0",
"publint": "^0.3.16",
"svelte": "^5.46.4",
"svelte-check": "^4.3.5",
"svelte-eslint-parser": "^1.4.1",
"tslib": "^2.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0",
"vite": "^7.3.1",
"vitest": "^4.0.17"
},

View File

@@ -1,13 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@@ -1,12 +1,12 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div>%sveltekit.body%</div>
</body>
</html>

View File

@@ -6,7 +6,8 @@
open?: boolean;
}
let { title = "Details", transparent = false, children, open = $bindable(false) }: Props = $props();
let { title = 'Details', transparent = false, children, open = $bindable(false) }: Props =
$props();
</script>
<details class:transparent bind:open>
@@ -33,5 +34,4 @@
padding: 0;
outline: none;
}
</style>

View File

@@ -1,25 +1,30 @@
<script lang="ts">
import type { NodeInput } from '@nodarium/types';
import type { NodeInput } from '@nodarium/types';
import { Checkbox, Number, Select, Vec3 } from './index.js';
import { Checkbox, Float, Select, Vec3 } from './index.js';
interface Props {
input: NodeInput;
value: any;
id?: string;
}
interface Props {
input: NodeInput;
value: unknown;
id?: string;
}
let { input, value = $bindable(), id }: Props = $props();
let { input, value = $bindable(), id }: Props = $props();
</script>
{#if input.type === 'float'}
<Number bind:value min={input?.min} max={input?.max} step={input?.step} />
<Float
bind:value={value as number}
min={input?.min}
max={input?.max}
step={input?.step}
/>
{:else if input.type === 'integer'}
<Number bind:value min={input?.min} max={input?.max} />
<Float bind:value={value as number} min={input?.min} max={input?.max} />
{:else if input.type === 'boolean'}
<Checkbox bind:value {id} />
<Checkbox bind:value={value as boolean} {id} />
{:else if input.type === 'select'}
<Select bind:value options={input.options} {id} />
<Select bind:value={value as number} options={input.options} {id} />
{:else if input.type === 'vec3'}
<Vec3 bind:value {id} />
<Vec3 bind:value={value as [number, number, number]} {id} />
{/if}

View File

@@ -1,39 +1,39 @@
<script lang="ts">
interface Props {
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
key: string | string[];
}
interface Props {
ctrl?: boolean;
shift?: boolean;
alt?: boolean;
key: string | string[];
}
let { ctrl = false, shift = false, alt = false, key }: Props = $props();
let { ctrl = false, shift = false, alt = false, key }: Props = $props();
</script>
<div class="command">
{#if ctrl}
<span>Ctrl</span>
{/if}
{#if shift}
<span>Shift</span>
{/if}
{#if alt}
<span>Alt</span>
{/if}
{key}
{#if ctrl}
<span>Ctrl</span>
{/if}
{#if shift}
<span>Shift</span>
{/if}
{#if alt}
<span>Alt</span>
{/if}
{key}
</div>
<style>
.command {
background: var(--layer-2);
padding: 0.4em;
font-size: 0.8em;
border-radius: 0.3em;
white-space: nowrap;
width: fit-content;
}
.command {
background: var(--layer-2);
padding: 0.4em;
font-size: 0.8em;
border-radius: 0.3em;
white-space: nowrap;
width: fit-content;
}
span::after {
content: ' +';
opacity: 0.5;
}
span::after {
content: " +";
opacity: 0.5;
}
</style>

View File

@@ -4,10 +4,10 @@
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Fira Code';
font-family: "Fira Code";
font-style: normal;
font-weight: 300;
src: url('/fonts/fira-code-v22-latin-300.woff2') format('woff2');
src: url("/fonts/fira-code-v22-latin-300.woff2") format("woff2");
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
@@ -15,15 +15,15 @@
@font-face {
font-display: swap;
/* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Fira Code';
font-family: "Fira Code";
font-style: normal;
font-weight: 600;
src: url('/fonts/fira-code-v22-latin-600.woff2') format('woff2');
src: url("/fonts/fira-code-v22-latin-600.woff2") format("woff2");
/* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
:root {
--font-family: 'Fira Code', monospace;
--font-family: "Fira Code", monospace;
font-family: var(--font-family);
/* Spacing */
@@ -37,14 +37,13 @@
/* Large spacing */
--spacing-xl: 32px;
/* Extra large spacing */
}
@theme {
--color-neutral-100: #E7E7E7;
--color-neutral-200: #CECECE;
--color-neutral-300: #7C7C7C;
--color-neutral-400: #2D2D2D;
--color-neutral-100: #e7e7e7;
--color-neutral-200: #cecece;
--color-neutral-300: #7c7c7c;
--color-neutral-400: #2d2d2d;
--color-neutral-500: #171717;
--color-neutral-800: #111111;
--color-neutral-900: #060606;
@@ -65,10 +64,10 @@
}
html {
--neutral-100: #E7E7E7;
--neutral-200: #CECECE;
--neutral-300: #7C7C7C;
--neutral-400: #2D2D2D;
--neutral-100: #e7e7e7;
--neutral-200: #cecece;
--neutral-300: #7c7c7c;
--neutral-400: #2d2d2d;
--neutral-500: #171717;
--neutral-800: #111111;
--neutral-900: #060606;
@@ -123,45 +122,45 @@ html.theme-solarized {
}
html.theme-catppuccin {
--text-color: #CDD6F4;
--text-color: #cdd6f4;
--outline: #3e3e4f;
--layer-3: #a8aac8;
--layer-2: #313244;
--layer-1: #1E1E2E;
--layer-1: #1e1e2e;
--layer-0: #11111b;
--connection: #444459;
}
html.theme-high-contrast {
--text-color: #FFFFFF;
--text-color: #ffffff;
--outline: white;
--layer-0: #000000;
--layer-1: black;
--layer-2: #222222;
--layer-3: #FFFFFF;
--connection: #FFF;
--layer-3: #ffffff;
--connection: #fff;
}
html.theme-nord {
--text-color: #D8DEE9;
--outline: #4C566A;
--layer-0: #2E3440;
--layer-1: #3B4252;
--layer-2: #434C5E;
--layer-3: #5E81AC;
--text-color: #d8dee9;
--outline: #4c566a;
--layer-0: #2e3440;
--layer-1: #3b4252;
--layer-2: #434c5e;
--layer-3: #5e81ac;
--active: #8999bd;
--selected: #b76c3f;
--connection: #4C566A;
--connection: #4c566a;
}
html.theme-dracula {
--text-color: #F8F8F2;
--outline: #6272A4;
--layer-0: #282A36;
--layer-1: #44475A;
--layer-2: #32374D;
--layer-3: #BD93F9;
--connection: #6272A4;
--text-color: #f8f8f2;
--outline: #6272a4;
--layer-0: #282a36;
--layer-1: #44475a;
--layer-2: #32374d;
--layer-3: #bd93f9;
--connection: #6272a4;
}
button {

View File

@@ -1,7 +1,6 @@
export { default as Input } from './Input.svelte';
export { default as Checkbox } from './inputs/Checkbox.svelte';
export { default as Float, default as Number } from './inputs/Float.svelte';
export { default as Integer } from './inputs/Integer.svelte';
export { default as Float } from './inputs/Float.svelte';
export { default as Select } from './inputs/Select.svelte';
export { default as Vec3 } from './inputs/Vec3.svelte';

View File

@@ -1,47 +1,47 @@
<script lang="ts">
interface Props {
value: boolean;
id?: string;
}
interface Props {
value: boolean;
id?: string;
}
let { value = $bindable(false), id }: Props = $props();
let { value = $bindable(false), id }: Props = $props();
$effect(() => {
if (typeof value === 'string') {
value = value === 'true';
} else if (typeof value === 'number') {
value = value === 1;
} else if (!(typeof value === 'boolean')) {
value = !!value;
}
});
$effect(() => {
if (typeof value === 'string') {
value = value === 'true';
} else if (typeof value === 'number') {
value = value === 1;
} else if (!(typeof value === 'boolean')) {
value = !!value;
}
});
</script>
<label
class="relative inline-flex h-[22px] w-[22px] cursor-pointer items-center justify-center bg-[var(--layer-2)] rounded-[5px]"
class="relative inline-flex h-[22px] w-[22px] cursor-pointer items-center justify-center bg-[var(--layer-2)] rounded-[5px]"
>
<input
type="checkbox"
bind:checked={value}
class="peer absolute h-px w-px overflow-hidden whitespace-nowrap border-0 p-0 [clip:rect(0,0,0,0)]"
{id}
/>
<span
class="absolute opacity-0 peer-checked:opacity-100 transition-opacity duration-100 flex w-full h-full items-center justify-center"
>
<svg
viewBox="0 0 19 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="h-[10px] w-[12px] text-[var(--text-color)]"
>
<path
d="M2 7L7 12L17 2"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<input
type="checkbox"
bind:checked={value}
class="peer absolute h-px w-px overflow-hidden whitespace-nowrap border-0 p-0 [clip:rect(0,0,0,0)]"
{id}
/>
<span
class="absolute opacity-0 peer-checked:opacity-100 transition-opacity duration-100 flex w-full h-full items-center justify-center"
>
<svg
viewBox="0 0 19 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="h-[10px] w-[12px] text-[var(--text-color)]"
>
<path
d="M2 7L7 12L17 2"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
</label>

View File

@@ -1,194 +1,199 @@
<script lang="ts">
interface Props {
value?: number;
step?: number;
min?: number;
max?: number;
id?: string;
}
interface Props {
value?: number;
step?: number;
min?: number;
max?: number;
id?: string;
}
let {
value = $bindable(0.5),
step,
min = $bindable(0),
max = $bindable(1),
id
}: Props = $props();
let {
value = $bindable(0.5),
step,
min = $bindable(0),
max = $bindable(1),
id
}: Props = $props();
if (min > max) {
[min, max] = [max, min];
}
if (value > max) {
max = value;
}
if (min > max) {
[min, max] = [max, min];
}
if (value > max) {
max = value;
}
// svelte-ignore state_referenced_locally only use initial values
const precision = ((step || value).toString().split('.')[1] || '').length;
const precision = $derived(
((step || value).toString().split('.')[1] || '').length
);
function strip(input: number) {
return +parseFloat(input + '').toFixed(precision);
}
function strip(input: number) {
return +parseFloat(input + '').toFixed(precision);
}
let inputEl: HTMLInputElement | undefined = $state();
let inputEl: HTMLInputElement | undefined = $state();
let oldValue: number;
function handleChange() {
if (value === oldValue) return;
oldValue = value;
}
let oldValue: number;
function handleChange() {
if (value === oldValue) return;
oldValue = value;
}
let isMouseDown = $state(false);
let downV = 0;
let vx = 0;
let rect: DOMRect;
let isMouseDown = $state(false);
let downV = 0;
let vx = 0;
let rect: DOMRect;
function handleMouseDown(ev: MouseEvent) {
ev.preventDefault();
function handleMouseDown(ev: MouseEvent) {
ev.preventDefault();
if (!inputEl) return;
if (!inputEl) return;
inputEl.focus();
inputEl.focus();
isMouseDown = true;
isMouseDown = true;
downV = value;
rect = inputEl.getBoundingClientRect();
downV = value;
rect = inputEl.getBoundingClientRect();
window.removeEventListener('mousemove', handleMouseMove);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
document.body.style.cursor = 'ew-resize';
(ev.target as HTMLElement)?.blur();
}
window.removeEventListener('mousemove', handleMouseMove);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
document.body.style.cursor = 'ew-resize';
(ev.target as HTMLElement)?.blur();
}
function handleMouseUp() {
isMouseDown = false;
function handleMouseUp() {
isMouseDown = false;
if (downV === value) {
inputEl?.focus();
}
if (downV === value) {
inputEl?.focus();
}
if (value > max) {
max = value;
}
if (value > max) {
max = value;
}
if (value < min) {
min = value;
}
if (value < min) {
min = value;
}
document.body.style.cursor = 'unset';
window.removeEventListener('mouseup', handleMouseUp);
window.removeEventListener('mousemove', handleMouseMove);
}
document.body.style.cursor = 'unset';
window.removeEventListener('mouseup', handleMouseUp);
window.removeEventListener('mousemove', handleMouseMove);
}
function handleKeyDown(ev: KeyboardEvent) {
if (ev.key === 'Escape' || ev.key === 'Enter') {
handleMouseUp();
inputEl?.blur();
}
}
function handleKeyDown(ev: KeyboardEvent) {
if (ev.key === 'Escape' || ev.key === 'Enter') {
handleMouseUp();
inputEl?.blur();
}
}
function handleMouseMove(ev: MouseEvent) {
vx = (ev.clientX - rect.left) / rect.width;
function handleMouseMove(ev: MouseEvent) {
vx = (ev.clientX - rect.left) / rect.width;
if (ev.ctrlKey) {
let v = min + (max - min) * vx;
value = v;
} else {
value = Math.max(Math.min(min + (max - min) * vx, max), min);
}
if (ev.ctrlKey) {
let v = min + (max - min) * vx;
value = v;
} else {
value = Math.max(Math.min(min + (max - min) * vx, max), min);
}
value = strip(value);
value = strip(value);
// With ctrl + outside of input ev.target becomes HTMLDocument
if (ev.target instanceof HTMLElement) {
ev.target?.blur();
}
}
// With ctrl + outside of input ev.target becomes HTMLDocument
if (ev.target instanceof HTMLElement) {
ev.target?.blur();
}
}
$effect(() => {
if (value.toString().length > 5) {
value = strip(value);
}
value !== undefined && handleChange();
});
$effect(() => {
if (value.toString().length > 5) {
value = strip(value);
}
if (value !== undefined) {
handleChange();
}
});
let width = $derived(
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px' : '20px'
);
let width = $derived(
Number.isFinite(value)
? Math.max((value?.toString().length ?? 1) * 8, 50) + 'px'
: '20px'
);
</script>
<div class="component-wrapper" class:is-down={isMouseDown}>
<span class="overlay" style={`width: ${((value - min) / (max - min)) * 100}%`}></span>
<input
bind:value
bind:this={inputEl}
{id}
{step}
{max}
{min}
onkeydown={handleKeyDown}
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
type="number"
style={`width:${width};`}
/>
<span class="overlay" style={`width: ${((value - min) / (max - min)) * 100}%`}></span>
<input
bind:value
bind:this={inputEl}
{id}
{step}
{max}
{min}
onkeydown={handleKeyDown}
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
type="number"
style={`width:${width};`}
/>
</div>
<style>
.component-wrapper {
position: relative;
background-color: var(--layer-2, #4b4b4b);
border-radius: 4px;
user-select: none;
transition: box-shadow 0.3s ease;
border: solid 1px var(--outline);
box-sizing: border-box;
overflow: hidden;
border-radius: var(--border-radius, 2px);
}
.component-wrapper {
position: relative;
background-color: var(--layer-2, #4b4b4b);
border-radius: 4px;
user-select: none;
transition: box-shadow 0.3s ease;
border: solid 1px var(--outline);
box-sizing: border-box;
overflow: hidden;
border-radius: var(--border-radius, 2px);
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
input[type="number"] {
box-sizing: border-box;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
font-family: var(--font-family);
font-variant-numeric: tabular-nums;
cursor: pointer;
color: var(--text-color);
background-color: transparent;
padding: var(--padding, 6px);
font-size: 1em;
padding-inline: 10px;
text-align: center;
border: none;
border-style: none;
min-width: 100%;
}
input[type="number"] {
box-sizing: border-box;
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
font-family: var(--font-family);
font-variant-numeric: tabular-nums;
cursor: pointer;
color: var(--text-color);
background-color: transparent;
padding: var(--padding, 6px);
font-size: 1em;
padding-inline: 10px;
text-align: center;
border: none;
border-style: none;
min-width: 100%;
}
.is-down > input {
cursor: ew-resize !important;
}
.is-down > input {
cursor: ew-resize !important;
}
.overlay {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
max-width: 100%;
background-color: var(--text-color);
opacity: 0.3;
pointer-events: none;
transition: width 0.3s ease;
}
.overlay {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
max-width: 100%;
background-color: var(--text-color);
opacity: 0.3;
pointer-events: none;
transition: width 0.3s ease;
}
.is-down > .overlay {
transition: none !important;
}
.is-down > .overlay {
transition: none !important;
}
</style>

View File

@@ -1,215 +0,0 @@
<!--
@component
@deprecated use Float.svelte
-->
<script lang="ts">
interface Props {
value?: number;
step?: number;
min?: number | undefined;
max?: number | undefined;
id?: string;
change?: (arg: number) => void;
}
let {
value = $bindable(0),
step = 1,
min = $bindable(0),
max = $bindable(1),
id,
change
}: Props = $props();
if (min > max) {
[min, max] = [max, min];
}
if (value > max) {
max = value;
}
function strip(input: number) {
return +parseFloat(input + '').toPrecision(2);
}
let inputEl = $state() as HTMLInputElement;
let wrapper = $state() as HTMLDivElement;
let prev = -1;
function update() {
if (prev === value) return;
prev = value;
change?.(value);
}
function handleChange(change: number) {
value = Math.max(min ?? -Infinity, Math.min(+value + change, max ?? Infinity));
}
let isMouseDown = $state(false);
let downX = 0;
let downV = 0;
let rect: DOMRect;
function handleMouseDown(ev: MouseEvent) {
ev.preventDefault();
downV = value;
downX = ev.clientX;
rect = wrapper.getBoundingClientRect();
window.removeEventListener('mousemove', handleMouseMove);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
document.body.style.cursor = 'ew-resize';
}
function handleMouseUp() {
if (downV === value) {
inputEl.focus();
} else {
inputEl.blur();
}
document.body.style.cursor = 'unset';
window.removeEventListener('mouseup', handleMouseUp);
window.removeEventListener('mousemove', handleMouseMove);
}
function handleKeyDown(ev: KeyboardEvent) {
if (ev.key === 'Escape' || ev.key === 'Enter') {
handleMouseUp();
inputEl?.blur();
}
}
function handleMouseMove(ev: MouseEvent) {
if (!ev.ctrlKey && typeof min === 'number' && typeof max === 'number') {
const vx = (ev.clientX - rect.left) / rect.width;
value = Math.max(Math.min(Math.round(min + (max - min) * vx), max), min);
} else {
const vx = ev.clientX - downX;
value = downV + Math.round(vx / 10);
}
}
$effect(() => {
if ((value || 0).toString().length > 5) {
value = strip(value || 0);
}
value !== undefined && update();
});
let width = $derived(
Number.isFinite(value) ? Math.max((value?.toString().length ?? 1) * 8, 30) + 'px' : '20px'
);
</script>
<div
class="component-wrapper"
bind:this={wrapper}
class:is-down={isMouseDown}
role="slider"
tabindex="0"
aria-valuenow={value}
onkeydown={handleKeyDown}
onmousedown={handleMouseDown}
onmouseup={handleMouseUp}
>
<button onclick={() => handleChange(-step)}>-</button>
<input
bind:value
bind:this={inputEl}
{id}
{step}
{max}
{min}
type="number"
style={`width:${width};`}
/>
<button onclick={() => handleChange(+step)}>+</button>
{#if typeof min !== 'undefined' && typeof max !== 'undefined'}
<span
class="overlay"
style={`width: ${Math.min((value - min) / (max - min), 1) * 100}%`}
></span>
{/if}
</div>
<style>
.component-wrapper {
position: relative;
display: flex;
background-color: var(--layer-2, #4b4b4b);
border-radius: 2px;
user-select: none;
transition: box-shadow 0.3s ease;
outline: solid 1px var(--outline);
overflow: hidden;
border-radius: var(--border-radius, 2px);
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
input[type="number"] {
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
cursor: pointer;
font-family: var(--font-family);
font-variant-numeric: tabular-nums;
color: var(--text-color);
background-color: transparent;
padding: var(--padding, 6px);
font-size: 1em;
padding-inline: 10px;
text-align: center;
border: none;
border-style: none;
flex: 1;
width: 72%;
}
.is-down > input {
cursor: ew-resize !important;
}
.overlay {
position: absolute;
top: 0px;
left: 0px;
height: 100%;
background-color: var(--layer-3);
opacity: 0.3;
pointer-events: none;
}
.is-down > .overlay {
transition: none !important;
}
button {
background-color: transparent;
border: none;
cursor: pointer;
line-height: 0px;
margin: 0;
color: var(--text-color);
margin-inline: 6px;
}
div input[type="number"] {
color: var(--text-color);
background-color: transparent;
padding: var(--padding, 6px);
padding-inline: 0px;
text-align: center;
border: none;
border-style: none;
}
</style>

View File

@@ -0,0 +1 @@

View File

@@ -1,27 +1,27 @@
<script lang="ts">
interface Props {
options?: string[];
value?: number;
id?: string;
}
interface Props {
options?: string[];
value?: number;
id?: string;
}
let { options = [], value = $bindable(0), id = '' }: Props = $props();
let { options = [], value = $bindable(0), id = '' }: Props = $props();
</script>
<select {id} bind:value>
{#each options as label, i}
<option value={i}>{label}</option>
{/each}
{#each options as label, i (label)}
<option value={i}>{label}</option>
{/each}
</select>
<style>
select {
background: var(--layer-2);
color: var(--text-color);
font-family: var(--font-family);
outline: solid 1px var(--outline);
padding: 0.8em 1em;
border-radius: 5px;
border: none;
}
select {
background: var(--layer-2);
color: var(--text-color);
font-family: var(--font-family);
outline: solid 1px var(--outline);
padding: 0.8em 1em;
border-radius: 5px;
border: none;
}
</style>

View File

@@ -1,34 +1,34 @@
<script lang="ts">
import { Number } from '$lib/index.js';
import Float from './Float.svelte';
interface Props {
value?: any;
id?: string;
}
interface Props {
value?: number[];
id?: string;
}
let { value = $bindable([0, 0, 0]), id = '' }: Props = $props();
let { value = $bindable([0, 0, 0]), id = '' }: Props = $props();
</script>
<div>
<Number id={`${id}-x`} bind:value={value[0]} step={0.01} />
<Number id={`${id}-y`} bind:value={value[1]} step={0.01} />
<Number id={`${id}-z`} bind:value={value[2]} step={0.01} />
<Float id={`${id}-x`} bind:value={value[0]} step={0.01} />
<Float id={`${id}-y`} bind:value={value[1]} step={0.01} />
<Float id={`${id}-z`} bind:value={value[2]} step={0.01} />
</div>
<style>
div > :global(.component-wrapper:nth-child(1)) {
border-radius: 4px 4px 0px 0px !important;
border-bottom: none !important;
}
div > :global(.component-wrapper:nth-child(2)) {
border-radius: 0px !important;
outline: none;
border: solid thin var(--outline);
border-top: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
border-bottom: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
}
div > :global(.component-wrapper:nth-child(3)) {
border-top: none !important;
border-radius: 0px 0px 4px 4px !important;
}
div > :global(.component-wrapper:nth-child(1)) {
border-radius: 4px 4px 0px 0px !important;
border-bottom: none !important;
}
div > :global(.component-wrapper:nth-child(2)) {
border-radius: 0px !important;
outline: none;
border: solid thin var(--outline);
border-top: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
border-bottom: solid thin color-mix(in srgb, var(--outline) 50%, transparent);
}
div > :global(.component-wrapper:nth-child(3)) {
border-top: none !important;
border-radius: 0px 0px 4px 4px !important;
}
</style>

View File

@@ -1,79 +1,86 @@
<script lang="ts">
import '$lib/app.css';
import { Checkbox, Details, Number, Select, ShortCut, Vec3 } from '$lib/index.js';
import Section from './Section.svelte';
import '$lib/app.css';
import { Checkbox, Details, Float, Select, ShortCut, Vec3 } from '$lib/index.js';
import Section from './Section.svelte';
let intValue = $state(0);
let floatValue = $state(0.2);
let float2Value = $state(0.02);
let float3Value = $state(1);
let vecValue = $state([0.2, 0.3, 0.4]);
const options = ['strawberry', 'raspberry', 'chickpeas'];
let selectValue = $state(0);
const d = $derived(options[selectValue]);
let intValue = $state(0);
let floatValue = $state(0.2);
let float2Value = $state(0.02);
let float3Value = $state(1);
let vecValue = $state([0.2, 0.3, 0.4]);
const options = ['strawberry', 'raspberry', 'chickpeas'];
let selectValue = $state(0);
const d = $derived(options[selectValue]);
let checked = $state(false);
let detailsOpen = $state(false);
let checked = $state(false);
let detailsOpen = $state(false);
const themes = ['light', 'solarized', 'catppuccin', 'high-contrast', 'nord', 'dracula'];
let themeIndex = $state(0);
$effect(() => {
const classList = document.documentElement.classList;
for (const c of classList) {
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
}
document.documentElement.classList.add(`theme-${themes[themeIndex]}`);
});
const themes = [
'light',
'solarized',
'catppuccin',
'high-contrast',
'nord',
'dracula'
];
let themeIndex = $state(0);
$effect(() => {
const classList = document.documentElement.classList;
for (const c of classList) {
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
}
document.documentElement.classList.add(`theme-${themes[themeIndex]}`);
});
</script>
<main class="flex flex-col gap-8 py-8">
<div class="flex gap-4">
<h1 class="text-4xl">@nodarium/ui</h1>
<Select bind:value={themeIndex} options={themes}></Select>
</div>
<div class="flex gap-4">
<h1 class="text-4xl">@nodarium/ui</h1>
<Select bind:value={themeIndex} options={themes}></Select>
</div>
<Section title="Integer (step inherit)" value={intValue}>
<Number bind:value={intValue} max={2} />
</Section>
<Section title="Integer (step inherit)" value={intValue}>
<Float bind:value={intValue} max={2} />
</Section>
<Section title="Float (step inherit)" value={floatValue}>
<Number bind:value={floatValue} />
</Section>
<Section title="Float (step inherit)" value={floatValue}>
<Float bind:value={floatValue} />
</Section>
<Section title="Float 2 (step inherit)" value={intValue}>
<Number bind:value={float2Value} />
</Section>
<Section title="Float 2 (step inherit)" value={intValue}>
<Float bind:value={float2Value} />
</Section>
<Section title="Float (0.01 step)" value={floatValue}>
<Number bind:value={float3Value} step={0.01} max={3} />
</Section>
<Section title="Float (0.01 step)" value={floatValue}>
<Float bind:value={float3Value} step={0.01} max={3} />
</Section>
<Section title="Vec3" value={JSON.stringify(vecValue)}>
<Vec3 bind:value={vecValue} />
</Section>
<Section title="Vec3" value={JSON.stringify(vecValue)}>
<Vec3 bind:value={vecValue} />
</Section>
<Section title="Select" value={d}>
<Select bind:value={selectValue} {options} />
</Section>
<Section title="Select" value={d}>
<Select bind:value={selectValue} {options} />
</Section>
<Section title="Checkbox" value={checked}>
<Checkbox bind:value={checked} />
</Section>
<Section title="Checkbox" value={checked}>
<Checkbox bind:value={checked} />
</Section>
<Section title="Details" value={detailsOpen}>
<Details title="More Information" bind:open={detailsOpen}>
<p>Here is some more information that was previously hidden.</p>
</Details>
</Section>
<Section title="Details" value={detailsOpen}>
<Details title="More Information" bind:open={detailsOpen}>
<p>Here is some more information that was previously hidden.</p>
</Details>
</Section>
<Section title="Shortcut">
<ShortCut ctrl key="S" />
</Section>
<Section title="Shortcut">
<ShortCut ctrl key="S" />
</Section>
</main>
<style>
main {
max-width: 800px;
margin: 0 auto;
}
main {
max-width: 800px;
margin: 0 auto;
}
</style>

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import { type Snippet } from 'svelte';
let { title, value, children } = $props<{
title?: string;
value?: unknown;
children?: Snippet;
}>();
import { type Snippet } from 'svelte';
let { title, value, children } = $props<{
title?: string;
value?: unknown;
children?: Snippet;
}>();
</script>
<section class="border border-1/2 mb-4 p-4 flex flex-col gap-4">
<h3 class="flex gap-2 font-bold">
{title}
<p class="font-normal! opacity-50!">{value}</p>
</h3>
<div>
{@render children()}
</div>
<h3 class="flex gap-2 font-bold">
{title}
<p class="font-normal! opacity-50!">{value}</p>
</h3>
<div>
{@render children()}
</div>
</section>

View File

@@ -1,7 +1,7 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
const { BASE_URL = "" } = process.env;
const { BASE_URL = '' } = process.env;
/** @type {import('@sveltejs/kit').Config} */
const config = {
@@ -11,7 +11,7 @@ const config = {
kit: {
paths: {
base: BASE_URL,
base: BASE_URL
},
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.

View File

@@ -1,15 +1,15 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}

View File

@@ -1,6 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vitest/config';
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],

View File

@@ -4,7 +4,9 @@
"description": "",
"main": "src/index.ts",
"scripts": {
"test": "vitest"
"test": "vitest",
"format": "dprint fmt -c '../../.dprint.jsonc' .",
"format:check": "dprint check -c '../../.dprint.jsonc' ."
},
"keywords": [],
"author": "",
@@ -13,6 +15,7 @@
"@nodarium/types": "workspace:"
},
"devDependencies": {
"dprint": "^0.51.1",
"vite": "^7.3.1",
"vitest": "^4.0.17"
}

View File

@@ -1,21 +1,21 @@
import { test, expect } from "vitest"
import { encodeFloat, decodeFloat } from "./encoding"
import { expect, test } from 'vitest';
import { decodeFloat, encodeFloat } from './encoding';
test("encode_float", () => {
test('encode_float', () => {
const input = 1.23;
const encoded = encodeFloat(input)
const output = decodeFloat(encoded)
console.log(input, output)
const encoded = encodeFloat(input);
const output = decodeFloat(encoded);
console.log(input, output);
expect(output).toBeCloseTo(input);
});
test("encode 2.0", () => {
test('encode 2.0', () => {
const input = 2.0;
const encoded = encodeFloat(input)
expect(encoded).toEqual(1073741824)
const encoded = encodeFloat(input);
expect(encoded).toEqual(1073741824);
});
test("floating point imprecision", () => {
test('floating point imprecision', () => {
let maxError = 0;
new Array(10_000).fill(null).forEach((_, i) => {
const input = i < 5_000 ? i : Math.random() * 100;
@@ -32,7 +32,7 @@ test("floating point imprecision", () => {
});
// Test with negative numbers
test("negative numbers", () => {
test('negative numbers', () => {
const inputs = [-1, -0.5, -123.456, -0.0001];
inputs.forEach(input => {
const encoded = encodeFloat(input);
@@ -42,31 +42,31 @@ test("negative numbers", () => {
});
// Test with very small numbers
test("very small numbers", () => {
test('very small numbers', () => {
const input = 1.2345e-38;
const encoded = encodeFloat(input)
const output = decodeFloat(encoded)
const encoded = encodeFloat(input);
const output = decodeFloat(encoded);
expect(output).toBeCloseTo(input);
});
// Test with zero
test("zero", () => {
test('zero', () => {
const input = 0;
const encoded = encodeFloat(input)
const output = decodeFloat(encoded)
const encoded = encodeFloat(input);
const output = decodeFloat(encoded);
expect(output).toBe(0);
});
// Test with infinity
test("infinity", () => {
test('infinity', () => {
const input = Infinity;
const encoded = encodeFloat(input)
const output = decodeFloat(encoded)
const encoded = encodeFloat(input);
const output = decodeFloat(encoded);
expect(output).toBe(Infinity);
});
// Test with large numbers
test("large numbers", () => {
test('large numbers', () => {
const inputs = [1e+5, 1e+10];
inputs.forEach(input => {
const encoded = encodeFloat(input);

View File

@@ -4,7 +4,7 @@ const view = new DataView(buffer);
export function encodeFloat(value: number): number {
// Write the number as a float to the buffer
view.setFloat32(0, value, true); // 'true' for little-endian
view.setFloat32(0, value, true); // 'true' for little-endian
// Read the buffer as an integer
return view.getInt32(0, true);

View File

@@ -1,4 +1,4 @@
import { test, expect } from 'vitest';
import { expect, test } from 'vitest';
import { fastHashArrayBuffer, fastHashString } from './fastHash';
test('fastHashString doesnt produce clashes', () => {
@@ -10,8 +10,7 @@ test('fastHashString doesnt produce clashes', () => {
expect(hashB).toEqual(hashC);
});
test("fastHashArray doesnt product collisions", () => {
test('fastHashArray doesnt product collisions', () => {
const a = new Int32Array(1000);
const hash_a = fastHashArrayBuffer(a);
@@ -20,11 +19,9 @@ test("fastHashArray doesnt product collisions", () => {
const hash_b = fastHashArrayBuffer(a);
expect(hash_a).not.toEqual(hash_b);
});
test('fastHashArray is fast(ish) < 20ms', () => {
const a = new Int32Array(10_000);
const t0 = performance.now();

View File

@@ -1,43 +1,43 @@
export function fastHashArrayBuffer(input: string | Int32Array): string {
const mask = (1n << 64n) - 1n
const mask = (1n << 64n) - 1n;
// FNV-1a 64-bit constants
let h = 0xcbf29ce484222325n // offset basis
const FNV_PRIME = 0x100000001b3n
let h = 0xcbf29ce484222325n; // offset basis
const FNV_PRIME = 0x100000001b3n;
// get bytes for string or Int32Array
let bytes: Uint8Array
if (typeof input === "string") {
let bytes: Uint8Array;
if (typeof input === 'string') {
// utf-8 encoding
bytes = new TextEncoder().encode(input)
bytes = new TextEncoder().encode(input);
} else {
// Int32Array -> bytes (little-endian)
bytes = new Uint8Array(input.length * 4)
bytes = new Uint8Array(input.length * 4);
for (let i = 0; i < input.length; i++) {
const v = input[i] >>> 0 // ensure unsigned 32-bit
const base = i * 4
bytes[base] = v & 0xff
bytes[base + 1] = (v >>> 8) & 0xff
bytes[base + 2] = (v >>> 16) & 0xff
bytes[base + 3] = (v >>> 24) & 0xff
const v = input[i] >>> 0; // ensure unsigned 32-bit
const base = i * 4;
bytes[base] = v & 0xff;
bytes[base + 1] = (v >>> 8) & 0xff;
bytes[base + 2] = (v >>> 16) & 0xff;
bytes[base + 3] = (v >>> 24) & 0xff;
}
}
// FNV-1a byte-wise
for (let i = 0; i < bytes.length; i++) {
h = (h ^ BigInt(bytes[i])) & mask
h = (h * FNV_PRIME) & mask
h = (h ^ BigInt(bytes[i])) & mask;
h = (h * FNV_PRIME) & mask;
}
// MurmurHash3's fmix64 finalizer (good avalanche)
h ^= h >> 33n
h = (h * 0xff51afd7ed558ccdn) & mask
h ^= h >> 33n
h = (h * 0xc4ceb9fe1a85ec53n) & mask
h ^= h >> 33n
h ^= h >> 33n;
h = (h * 0xff51afd7ed558ccdn) & mask;
h ^= h >> 33n;
h = (h * 0xc4ceb9fe1a85ec53n) & mask;
h ^= h >> 33n;
// to 16-char hex
return h.toString(16).padStart(16, "0").slice(-16)
return h.toString(16).padStart(16, '0').slice(-16);
}
export function fastHashString(input: string) {

View File

@@ -1,25 +1,23 @@
import { expect, test } from 'vitest'
import { decodeNestedArray, encodeNestedArray, concatEncodedArrays } from './flatTree'
test("it correctly concats nested arrays", () => {
import { expect, test } from 'vitest';
import { concatEncodedArrays, decodeNestedArray, encodeNestedArray } from './flatTree';
test('it correctly concats nested arrays', () => {
const input_a = encodeNestedArray([1, 2, 3]);
const input_b = 2;
const input_c = encodeNestedArray([4, 5, 6]);
const output = concatEncodedArrays([input_a, input_b, input_c]);
console.log("Output", output);
console.log('Output', output);
const decoded = decodeNestedArray(output);
expect(decoded[0]).toEqual([1, 2, 3]);
expect(decoded[1]).toEqual(2);
expect(decoded[2]).toEqual([4, 5, 6]);
});
test("it correctly concats nested arrays with nested arrays", () => {
test('it correctly concats nested arrays with nested arrays', () => {
const input_c = encodeNestedArray([1, 2, 3]);
const output = concatEncodedArrays([42, 12, input_c]);
const decoded = decodeNestedArray(output);
@@ -78,11 +76,11 @@ test('it correctly handles sequential nesting', () => {
});
// Test with mixed data types (if supported)
// Note: This test assumes your implementation supports mixed types.
// Note: This test assumes your implementation supports mixed types.
// If not, you can ignore or remove this test.
test('it correctly handles arrays with mixed data types', () => {
const input = [1, 'text', [true, [null, ['another text']]]];
//@ts-ignore
// @ts-ignore
const decoded = decodeNestedArray(encodeNestedArray(input));
expect(decoded).toEqual(input);
});

View File

@@ -1,7 +1,7 @@
type SparseArray<T = number> = (T | T[] | SparseArray<T>)[];
export function concatEncodedArrays(
input: (number | number[] | Int32Array)[],
input: (number | number[] | Int32Array)[]
): Int32Array {
let totalLength = 4;
for (let i = 0; i < input.length; i++) {
@@ -86,7 +86,7 @@ function decode_recursive(dense: number[] | Int32Array, index = 0) {
// Opening bracket detected
const [p, nextIndex, _nextBracketIndex] = decode_recursive(
dense,
index,
index
);
decoded.push(...p);
index = nextIndex + 1;
@@ -109,12 +109,10 @@ export function decodeNestedArray(dense: number[] | Int32Array) {
}
export function splitNestedArray(input: Int32Array) {
let index = 0;
const length = input.length;
let res: Int32Array[] = [];
let nextBracketIndex = 0;
let argStartIndex = 0;
let depth = -1;

View File

@@ -1,6 +1,6 @@
export * from "./wasm-wrapper";
export * from "./flatTree"
export * from "./encoding"
export * from "./fastHash"
export * from "./logger"
export * from "./performance"
export * from './encoding';
export * from './fastHash';
export * from './flatTree';
export * from './logger';
export * from './performance';
export * from './wasm-wrapper';

View File

@@ -7,23 +7,34 @@ export const createLogger = (() => {
let isGrouped = false;
function s(color: string, ...args: any) {
return isGrouped ? [...args] : [`[%c${scope.padEnd(maxLength, " ")}]:`, `color: ${color}`, ...args];
return isGrouped
? [...args]
: [`[%c${scope.padEnd(maxLength, ' ')}]:`, `color: ${color}`, ...args];
}
return {
log: (...args: any[]) => !muted && console.log(...s("#888", ...args)),
info: (...args: any[]) => !muted && console.info(...s("#888", ...args)),
warn: (...args: any[]) => !muted && console.warn(...s("#888", ...args)),
error: (...args: any[]) => console.error(...s("#f88", ...args)),
group: (...args: any[]) => { if (!muted) { console.groupCollapsed(...s("#888", ...args)); isGrouped = true; } },
groupEnd: () => { if (!muted) { console.groupEnd(); isGrouped = false } },
log: (...args: any[]) => !muted && console.log(...s('#888', ...args)),
info: (...args: any[]) => !muted && console.info(...s('#888', ...args)),
warn: (...args: any[]) => !muted && console.warn(...s('#888', ...args)),
error: (...args: any[]) => console.error(...s('#f88', ...args)),
group: (...args: any[]) => {
if (!muted) {
console.groupCollapsed(...s('#888', ...args));
isGrouped = true;
}
},
groupEnd: () => {
if (!muted) {
console.groupEnd();
isGrouped = false;
}
},
mute() {
muted = true;
},
unmute() {
muted = false;
}
}
}
};
};
})();

View File

@@ -11,7 +11,6 @@ export interface PerformanceStore {
}
export function createPerformanceStore(): PerformanceStore {
let data: PerformanceData = [];
let currentRun: Record<string, number[]> | undefined;
@@ -24,7 +23,7 @@ export function createPerformanceStore(): PerformanceStore {
return () => {
const i = listeners.indexOf(cb);
if (i > -1) listeners.splice(i, 1);
}
};
}
function set(v: PerformanceData) {
@@ -37,12 +36,12 @@ export function createPerformanceStore(): PerformanceStore {
lastPoint = undefined;
temp = {
start: performance.now()
}
};
}
function stopRun() {
if (currentRun && temp) {
currentRun["total"] = [performance.now() - temp.start];
currentRun['total'] = [performance.now() - temp.start];
data.push(currentRun);
data = data.slice(-100);
currentRun = undefined;
@@ -69,7 +68,6 @@ export function createPerformanceStore(): PerformanceStore {
}
function mergeData(newData: PerformanceData[number]) {
let r = currentRun;
if (!r) return;
@@ -99,5 +97,5 @@ export function createPerformanceStore(): PerformanceStore {
endPoint,
mergeData,
get
}
};
}

View File

@@ -13,12 +13,12 @@ export function createWasmWrapper(buffer: ArrayBuffer) {
host_log_panic: (ptr: number, len: number) => {
if (!exports) return;
const view = new Uint8Array(exports.memory.buffer, ptr, len);
console.error("RUST PANIC:", new TextDecoder().decode(view));
console.error('RUST PANIC:', new TextDecoder().decode(view));
},
host_log: (ptr: number, len: number) => {
if (!exports) return;
const view = new Uint8Array(exports.memory.buffer, ptr, len);
console.log("RUST:", new TextDecoder().decode(view));
console.log('RUST:', new TextDecoder().decode(view));
}
}
};
@@ -43,7 +43,7 @@ export function createWasmWrapper(buffer: ArrayBuffer) {
}
function get_definition() {
const sections = WebAssembly.Module.customSections(module, "nodarium_definition");
const sections = WebAssembly.Module.customSections(module, 'nodarium_definition');
if (sections.length > 0) {
const decoder = new TextDecoder();
const jsonString = decoder.decode(sections[0]);

View File

@@ -1,4 +1,3 @@
import { defineConfig } from 'vite'
import { defineConfig } from 'vite';
export default defineConfig({
})
export default defineConfig({});