mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 07:26:43 -04:00
Reorganize TS sdk (#3915)
# Description of Changes This moves a bunch of stuff from `lib` back into `server` and `sdk`, and removes all but one global variable from the server sdk in preparation for export-based reducer definition. # Expected complexity level and risk 2 - a pretty big refactor, but it's mostly just code movement. # Testing - [x] Refactor, so automated tests are sufficient.
This commit is contained in:
@@ -93,7 +93,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
@@ -177,7 +177,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
@@ -459,7 +459,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 22
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"brotli-size-cli": "^1.0.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-jsdoc": "^61.5.0",
|
||||
"globals": "^15.14.0",
|
||||
"size-limit": "^11.2.0",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UntypedTableDef } from './table';
|
||||
import type { table, UntypedTableDef } from './table';
|
||||
import type { ColumnMetadata } from './type_builders';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RowType, UntypedTableDef } from './table';
|
||||
import type { RowType, table, UntypedTableDef } from './table';
|
||||
import type { ColumnMetadata, IndexTypes } from './type_builders';
|
||||
import type { CollapseTuple, Prettify } from './type_util';
|
||||
import { Range } from '../server/range';
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import {
|
||||
AlgebraicType,
|
||||
ProductType,
|
||||
type Deserializer,
|
||||
type Serializer,
|
||||
} from '../lib/algebraic_type';
|
||||
import type { ConnectionId } from '../lib/connection_id';
|
||||
import type { Identity } from '../lib/identity';
|
||||
import type { Timestamp } from '../lib/timestamp';
|
||||
import type { HttpClient } from '../server/http_internal';
|
||||
import type { ParamsObj, ReducerCtx } from './reducers';
|
||||
import {
|
||||
MODULE_DEF,
|
||||
registerTypesRecursively,
|
||||
type UntypedSchemaDef,
|
||||
} from './schema';
|
||||
import {
|
||||
type Infer,
|
||||
type InferTypeOfRow,
|
||||
type TypeBuilder,
|
||||
} from './type_builders';
|
||||
import type { CamelCase } from './type_util';
|
||||
import {
|
||||
bsatnBaseSize,
|
||||
coerceParams,
|
||||
toCamelCase,
|
||||
type CoerceParams,
|
||||
} from './util';
|
||||
import type { Uuid } from './uuid';
|
||||
|
||||
export type ProcedureFn<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
Ret extends TypeBuilder<any, any>,
|
||||
> = (ctx: ProcedureCtx<S>, args: InferTypeOfRow<Params>) => Infer<Ret>;
|
||||
|
||||
export interface ProcedureCtx<S extends UntypedSchemaDef> {
|
||||
readonly sender: Identity;
|
||||
readonly identity: Identity;
|
||||
readonly timestamp: Timestamp;
|
||||
readonly connectionId: ConnectionId | null;
|
||||
readonly http: HttpClient;
|
||||
readonly counter_uuid: { value: number };
|
||||
withTx<T>(body: (ctx: TransactionCtx<S>) => T): T;
|
||||
newUuidV4(): Uuid;
|
||||
newUuidV7(): Uuid;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface TransactionCtx<S extends UntypedSchemaDef>
|
||||
extends ReducerCtx<S> {}
|
||||
|
||||
export function procedure<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
Ret extends TypeBuilder<any, any>,
|
||||
>(name: string, params: Params, ret: Ret, fn: ProcedureFn<S, Params, Ret>) {
|
||||
const paramsType: ProductType = {
|
||||
elements: Object.entries(params).map(([n, c]) => ({
|
||||
name: n,
|
||||
algebraicType: registerTypesRecursively(
|
||||
'typeBuilder' in c ? c.typeBuilder : c
|
||||
).algebraicType,
|
||||
})),
|
||||
};
|
||||
const returnType = registerTypesRecursively(ret).algebraicType;
|
||||
|
||||
MODULE_DEF.miscExports.push({
|
||||
tag: 'Procedure',
|
||||
value: {
|
||||
name,
|
||||
params: paramsType,
|
||||
returnType,
|
||||
},
|
||||
});
|
||||
|
||||
const { typespace } = MODULE_DEF;
|
||||
|
||||
PROCEDURES.push({
|
||||
fn,
|
||||
deserializeArgs: ProductType.makeDeserializer(paramsType, typespace),
|
||||
serializeReturn: AlgebraicType.makeSerializer(returnType, typespace),
|
||||
returnTypeBaseSize: bsatnBaseSize(typespace, returnType),
|
||||
});
|
||||
}
|
||||
|
||||
export const PROCEDURES: Array<{
|
||||
fn: ProcedureFn<any, any, any>;
|
||||
deserializeArgs: Deserializer<any>;
|
||||
serializeReturn: Serializer<any>;
|
||||
returnTypeBaseSize: number;
|
||||
}> = [];
|
||||
|
||||
export type UntypedProcedureDef = {
|
||||
name: string;
|
||||
accessorName: string;
|
||||
params: CoerceParams<ParamsObj>;
|
||||
returnType: TypeBuilder<any, any>;
|
||||
};
|
||||
|
||||
export type UntypedProceduresDef = {
|
||||
procedures: readonly UntypedProcedureDef[];
|
||||
};
|
||||
|
||||
export function procedures<const H extends readonly UntypedProcedureDef[]>(
|
||||
...handles: H
|
||||
): { procedures: H };
|
||||
|
||||
export function procedures<const H extends readonly UntypedProcedureDef[]>(
|
||||
handles: H
|
||||
): { procedures: H };
|
||||
|
||||
export function procedures<const H extends readonly UntypedProcedureDef[]>(
|
||||
...args: [H] | H
|
||||
): { procedures: H } {
|
||||
const procedures = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
return { procedures };
|
||||
}
|
||||
|
||||
type ProcedureDef<
|
||||
Name extends string,
|
||||
Params extends ParamsObj,
|
||||
ReturnType extends TypeBuilder<any, any>,
|
||||
> = {
|
||||
name: Name;
|
||||
accessorName: CamelCase<Name>;
|
||||
params: CoerceParams<Params>;
|
||||
returnType: ReturnType;
|
||||
};
|
||||
|
||||
export function procedureSchema<
|
||||
ProcedureName extends string,
|
||||
Params extends ParamsObj,
|
||||
ReturnType extends TypeBuilder<any, any>,
|
||||
>(
|
||||
name: ProcedureName,
|
||||
params: Params,
|
||||
returnType: ReturnType
|
||||
): ProcedureDef<ProcedureName, Params, ReturnType> {
|
||||
return {
|
||||
name,
|
||||
accessorName: toCamelCase(name),
|
||||
params: coerceParams(params),
|
||||
returnType,
|
||||
};
|
||||
}
|
||||
@@ -1,30 +1,15 @@
|
||||
import { ProductType } from './algebraic_type';
|
||||
import Lifecycle from './autogen/lifecycle_type';
|
||||
import type RawReducerDefV9 from './autogen/raw_reducer_def_v_9_type';
|
||||
import type { DbView } from '../server/db_view';
|
||||
import type { Random } from '../server/rng';
|
||||
import type { ConnectionId } from './connection_id';
|
||||
import type { Identity } from './identity';
|
||||
import { type UntypedSchemaDef } from './schema';
|
||||
import { type Timestamp } from './timestamp';
|
||||
import type { UntypedReducersDef } from '../sdk/reducers';
|
||||
import type { DbView } from '../server/db_view';
|
||||
import {
|
||||
MODULE_DEF,
|
||||
registerTypesRecursively,
|
||||
resolveType,
|
||||
type UntypedSchemaDef,
|
||||
} from './schema';
|
||||
import {
|
||||
ColumnBuilder,
|
||||
RowBuilder,
|
||||
type Infer,
|
||||
type InferTypeOfRow,
|
||||
type RowObj,
|
||||
type TypeBuilder,
|
||||
} from './type_builders';
|
||||
import type { ReducerSchema } from './reducer_schema';
|
||||
import { toCamelCase, toPascalCase } from './util';
|
||||
import type { CamelCase } from './type_util';
|
||||
import { Uuid } from './uuid.ts';
|
||||
import type { Random } from '../server/rng';
|
||||
|
||||
/**
|
||||
* Helper to extract the parameter types from an object type
|
||||
@@ -37,7 +22,8 @@ export type ParamsObj = Record<
|
||||
/**
|
||||
* Helper to convert a ParamsObj or RowObj into an object type
|
||||
*/
|
||||
type ParamsAsObject<ParamDef extends ParamsObj> = InferTypeOfRow<ParamDef>;
|
||||
export type ParamsAsObject<ParamDef extends ParamsObj> =
|
||||
InferTypeOfRow<ParamDef>;
|
||||
|
||||
/**
|
||||
* Defines a SpacetimeDB reducer function.
|
||||
@@ -68,7 +54,7 @@ type ParamsAsObject<ParamDef extends ParamsObj> = InferTypeOfRow<ParamDef>;
|
||||
export type Reducer<S extends UntypedSchemaDef, Params extends ParamsObj> = (
|
||||
ctx: ReducerCtx<S>,
|
||||
payload: ParamsAsObject<Params>
|
||||
) => void | { tag: 'ok' } | { tag: 'err'; value: string };
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Authentication information for the caller of a reducer.
|
||||
@@ -126,263 +112,3 @@ export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
|
||||
newUuidV7(): Uuid;
|
||||
random: Random;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* internal: pushReducer() helper used by reducer() and lifecycle wrappers
|
||||
*
|
||||
* @param name - The name of the reducer.
|
||||
* @param params - The parameters for the reducer.
|
||||
* @param fn - The reducer function.
|
||||
* @param lifecycle - Optional lifecycle hooks for the reducer.
|
||||
*/
|
||||
export function pushReducer(
|
||||
name: string,
|
||||
params: RowObj | RowBuilder<RowObj>,
|
||||
fn: Reducer<any, any>,
|
||||
lifecycle?: Infer<typeof RawReducerDefV9>['lifecycle']
|
||||
): void {
|
||||
if (existingReducers.has(name)) {
|
||||
throw new TypeError(`There is already a reducer with the name '${name}'`);
|
||||
}
|
||||
existingReducers.add(name);
|
||||
|
||||
if (!(params instanceof RowBuilder)) {
|
||||
params = new RowBuilder(params);
|
||||
}
|
||||
|
||||
if (params.typeName === undefined) {
|
||||
params.typeName = toPascalCase(name);
|
||||
}
|
||||
|
||||
const ref = registerTypesRecursively(params);
|
||||
const paramsType = resolveType(MODULE_DEF.typespace, ref).value;
|
||||
|
||||
MODULE_DEF.reducers.push({
|
||||
name,
|
||||
params: paramsType,
|
||||
lifecycle, // <- lifecycle flag lands here
|
||||
});
|
||||
|
||||
// If the function isn't named (e.g. `function foobar() {}`), give it the same
|
||||
// name as the reducer so that it's clear what it is in in backtraces.
|
||||
if (!fn.name) {
|
||||
Object.defineProperty(fn, 'name', { value: name, writable: false });
|
||||
}
|
||||
|
||||
REDUCERS.push(fn);
|
||||
}
|
||||
|
||||
const existingReducers = new Set<string>();
|
||||
export const REDUCERS: Reducer<any, any>[] = [];
|
||||
|
||||
/**
|
||||
* Defines a SpacetimeDB reducer function.
|
||||
*
|
||||
* Reducers are the primary way to modify the state of your SpacetimeDB application.
|
||||
* They are atomic, meaning that either all operations within a reducer succeed,
|
||||
* or none of them do.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the reducer.
|
||||
*
|
||||
* @param {string} name - The name of the reducer. This name will be used to call the reducer from clients.
|
||||
* @param {Params} params - An object defining the parameters that the reducer accepts.
|
||||
* Each key-value pair represents a parameter name and its corresponding
|
||||
* {@link TypeBuilder} or {@link ColumnBuilder}.
|
||||
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
|
||||
* reducer(
|
||||
* 'create_user',
|
||||
* {
|
||||
* username: t.string(),
|
||||
* email: t.string(),
|
||||
* },
|
||||
* (ctx, { username, email }) => {
|
||||
* // Access the 'user' table from the database view in the context
|
||||
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
||||
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function reducer<S extends UntypedSchemaDef, Params extends ParamsObj>(
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: (ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void
|
||||
): void {
|
||||
pushReducer(name, params, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an initialization reducer that runs when the SpacetimeDB module is published
|
||||
* for the first time.
|
||||
* This function is useful to set up any initial state of your database that is guaranteed
|
||||
* to run only once, and before any other reducers or client connections.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the initialization reducer.
|
||||
*
|
||||
* @param params - The parameters object defining the expected input for the initialization reducer.
|
||||
* @param fn - The initialization reducer function.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
*/
|
||||
export function init<S extends UntypedSchemaDef, Params extends ParamsObj>(
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): void {
|
||||
pushReducer(name, params, fn, Lifecycle.Init);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a new client establishes a connection.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the connection reducer.
|
||||
* @param params - The parameters object defining the expected input for the connection reducer.
|
||||
* @param fn - The connection reducer function itself.
|
||||
*/
|
||||
export function clientConnected<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
>(name: string, params: Params, fn: Reducer<S, Params>): void {
|
||||
pushReducer(name, params, fn, Lifecycle.OnConnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a client disconnects.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the disconnection reducer.
|
||||
* @param params - The parameters object defining the expected input for the disconnection reducer.
|
||||
* @param fn - The disconnection reducer function itself.
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.clientDisconnected(
|
||||
* { reason: t.string() },
|
||||
* (ctx, { reason }) => {
|
||||
* console.log(`Client ${ctx.connection_id} disconnected: ${reason}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function clientDisconnected<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
>(name: string, params: Params, fn: Reducer<S, Params>): void {
|
||||
pushReducer(name, params, fn, Lifecycle.OnDisconnect);
|
||||
}
|
||||
|
||||
class Reducers<ReducersDef extends UntypedReducersDef> {
|
||||
reducersType: ReducersDef;
|
||||
|
||||
constructor(handles: readonly ReducerSchema<any, any>[]) {
|
||||
this.reducersType = reducersToSchema(handles) as ReducersDef;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper type to convert an array of TableSchema into a schema definition
|
||||
*/
|
||||
type ReducersToSchema<T extends readonly ReducerSchema<any, any>[]> = {
|
||||
reducers: {
|
||||
/** @type {UntypedReducerDef} */
|
||||
readonly [i in keyof T]: {
|
||||
name: T[i]['reducerName'];
|
||||
accessorName: CamelCase<T[i]['accessorName']>;
|
||||
params: T[i]['params']['row'];
|
||||
paramsType: T[i]['paramsSpacetimeType'];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function reducersToSchema<
|
||||
const T extends readonly ReducerSchema<any, any>[],
|
||||
>(reducers: T): ReducersToSchema<T> {
|
||||
const mapped = reducers.map(r => {
|
||||
const paramsRow = r.params.row;
|
||||
|
||||
return {
|
||||
name: r.reducerName,
|
||||
// Prefer the schema's own accessorName if present at runtime; otherwise derive it.
|
||||
accessorName: r.accessorName,
|
||||
params: paramsRow,
|
||||
paramsType: r.paramsSpacetimeType,
|
||||
} as const;
|
||||
}) as {
|
||||
readonly [I in keyof T]: {
|
||||
name: T[I]['reducerName'];
|
||||
accessorName: T[I]['accessorName'];
|
||||
params: T[I]['params']['row'];
|
||||
paramsType: T[I]['paramsSpacetimeType'];
|
||||
};
|
||||
};
|
||||
|
||||
const result = { reducers: mapped } satisfies ReducersToSchema<T>;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function reducers<const H extends readonly ReducerSchema<any, any>[]>(
|
||||
...handles: H
|
||||
): Reducers<ReducersToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions (array overload)
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
*/
|
||||
export function reducers<const H extends readonly ReducerSchema<any, any>[]>(
|
||||
handles: H
|
||||
): Reducers<ReducersToSchema<H>>;
|
||||
|
||||
export function reducers<const H extends readonly ReducerSchema<any, any>[]>(
|
||||
...args: [H] | H
|
||||
): Reducers<ReducersToSchema<H>> {
|
||||
const handles = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
return new Reducers(handles);
|
||||
}
|
||||
|
||||
export function reducerSchema<
|
||||
ReducerName extends string,
|
||||
Params extends ParamsObj,
|
||||
>(name: ReducerName, params: Params): ReducerSchema<ReducerName, Params> {
|
||||
const paramType: ProductType = {
|
||||
elements: Object.entries(params).map(([n, c]) => ({
|
||||
name: n,
|
||||
algebraicType:
|
||||
'typeBuilder' in c ? c.typeBuilder.algebraicType : c.algebraicType,
|
||||
})),
|
||||
};
|
||||
return {
|
||||
reducerName: name,
|
||||
accessorName: toCamelCase(name),
|
||||
params: new RowBuilder<Params>(params),
|
||||
paramsSpacetimeType: paramType,
|
||||
reducerDef: {
|
||||
name,
|
||||
params: paramType,
|
||||
lifecycle: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import type RawTableDefV9 from './autogen/raw_table_def_v_9_type';
|
||||
import type Typespace from './autogen/typespace_type';
|
||||
import {
|
||||
AlgebraicType,
|
||||
ProductType,
|
||||
SumType,
|
||||
type AlgebraicTypeType,
|
||||
type AlgebraicTypeVariants,
|
||||
} from './algebraic_type';
|
||||
import type RawModuleDefV9 from './autogen/raw_module_def_v_9_type';
|
||||
import type RawScopedTypeNameV9 from './autogen/raw_scoped_type_name_v_9_type';
|
||||
import type { UntypedIndex } from './indexes';
|
||||
import type { UntypedTableDef } from './table';
|
||||
import type { UntypedTableSchema } from './table_schema';
|
||||
import {
|
||||
ArrayBuilder,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
ColumnBuilder,
|
||||
OptionBuilder,
|
||||
ProductBuilder,
|
||||
RefBuilder,
|
||||
ResultBuilder,
|
||||
RowBuilder,
|
||||
SumBuilder,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
@@ -16,38 +25,9 @@ import {
|
||||
type InferSpacetimeTypeOfTypeBuilder,
|
||||
type RowObj,
|
||||
type VariantsObj,
|
||||
ResultBuilder,
|
||||
} from './type_builders';
|
||||
import type { UntypedTableDef } from './table';
|
||||
import {
|
||||
clientConnected,
|
||||
clientDisconnected,
|
||||
init,
|
||||
reducer,
|
||||
type ParamsObj,
|
||||
type Reducer,
|
||||
} from './reducers';
|
||||
import type RawModuleDefV9 from './autogen/raw_module_def_v_9_type';
|
||||
import {
|
||||
AlgebraicType,
|
||||
ProductType,
|
||||
SumType,
|
||||
type AlgebraicTypeType,
|
||||
type AlgebraicTypeVariants,
|
||||
} from './algebraic_type';
|
||||
import type RawScopedTypeNameV9 from './autogen/raw_scoped_type_name_v_9_type';
|
||||
import type { CamelCase } from './type_util';
|
||||
import type { UntypedTableSchema } from './table_schema';
|
||||
import { toCamelCase } from './util';
|
||||
import {
|
||||
defineView,
|
||||
type AnonymousViewFn,
|
||||
type ViewFn,
|
||||
type ViewOpts,
|
||||
type ViewReturnTypeBuilder,
|
||||
} from './views';
|
||||
import type { UntypedIndex } from './indexes';
|
||||
import { procedure, type ProcedureFn } from './procedures';
|
||||
|
||||
export type TableNamesOf<S extends UntypedSchemaDef> =
|
||||
S['tables'][number]['name'];
|
||||
@@ -59,23 +39,15 @@ export type UntypedSchemaDef = {
|
||||
tables: readonly UntypedTableDef[];
|
||||
};
|
||||
|
||||
let REGISTERED_SCHEMA: UntypedSchemaDef | null = null;
|
||||
|
||||
export function getRegisteredSchema(): UntypedSchemaDef {
|
||||
if (REGISTERED_SCHEMA == null) {
|
||||
throw new Error('Schema has not been registered yet. Call schema() first.');
|
||||
}
|
||||
return REGISTERED_SCHEMA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper type to convert an array of TableSchema into a schema definition
|
||||
*/
|
||||
type TablesToSchema<T extends readonly UntypedTableSchema[]> = {
|
||||
export interface TablesToSchema<T extends readonly UntypedTableSchema[]>
|
||||
extends UntypedSchemaDef {
|
||||
tables: {
|
||||
readonly [i in keyof T]: TableToSchema<T[i]>;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface TableToSchema<T extends UntypedTableSchema>
|
||||
extends UntypedTableDef {
|
||||
@@ -88,16 +60,23 @@ export interface TableToSchema<T extends UntypedTableSchema>
|
||||
}
|
||||
|
||||
export function tablesToSchema<const T extends readonly UntypedTableSchema[]>(
|
||||
ctx: ModuleContext,
|
||||
tables: T
|
||||
): TablesToSchema<T> {
|
||||
return { tables: tables.map(tableToSchema) as TablesToSchema<T>['tables'] };
|
||||
return {
|
||||
tables: tables.map(schema =>
|
||||
tableToSchema(ctx, schema)
|
||||
) as TablesToSchema<T>['tables'],
|
||||
};
|
||||
}
|
||||
|
||||
function tableToSchema<T extends UntypedTableSchema>(
|
||||
ctx: ModuleContext,
|
||||
schema: T
|
||||
): TableToSchema<T> {
|
||||
const getColName = (i: number) =>
|
||||
schema.rowType.algebraicType.value.elements[i].name;
|
||||
const tableDef = schema.tableDef(ctx);
|
||||
|
||||
type AllowedCol = keyof T['rowType']['row'] & string;
|
||||
return {
|
||||
@@ -105,7 +84,7 @@ function tableToSchema<T extends UntypedTableSchema>(
|
||||
accessorName: toCamelCase(schema.tableName as T['tableName']),
|
||||
columns: schema.rowType.row, // typed as T[i]['rowType']['row'] under TablesToSchema<T>
|
||||
rowType: schema.rowSpacetimeType,
|
||||
constraints: schema.tableDef.constraints.map(c => ({
|
||||
constraints: tableDef.constraints.map(c => ({
|
||||
name: c.name,
|
||||
constraint: 'unique',
|
||||
columns: c.data.value.columns.map(getColName) as [string],
|
||||
@@ -114,14 +93,14 @@ function tableToSchema<T extends UntypedTableSchema>(
|
||||
// by casting it to an `Array<IndexOpts>` as `TableToSchema` expects.
|
||||
// This is then used in `TableCacheImpl.constructor` and who knows where else.
|
||||
// We should stop lying about our types.
|
||||
indexes: schema.tableDef.indexes.map((idx): UntypedIndex<AllowedCol> => {
|
||||
indexes: tableDef.indexes.map((idx): UntypedIndex<AllowedCol> => {
|
||||
const columnIds =
|
||||
idx.algorithm.tag === 'Direct'
|
||||
? [idx.algorithm.value]
|
||||
: idx.algorithm.value;
|
||||
return {
|
||||
name: idx.accessorName!,
|
||||
unique: schema.tableDef.constraints.some(c =>
|
||||
unique: tableDef.constraints.some(c =>
|
||||
c.data.value.columns.every(col => columnIds.includes(col))
|
||||
),
|
||||
algorithm: idx.algorithm.tag.toLowerCase() as 'btree',
|
||||
@@ -131,145 +110,159 @@ function tableToSchema<T extends UntypedTableSchema>(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The global module definition that gets populated by calls to `reducer()` and lifecycle hooks.
|
||||
*/
|
||||
export const MODULE_DEF: Infer<typeof RawModuleDefV9> = {
|
||||
typespace: { types: [] },
|
||||
tables: [],
|
||||
reducers: [],
|
||||
types: [],
|
||||
miscExports: [],
|
||||
rowLevelSecurity: [],
|
||||
};
|
||||
|
||||
const COMPOUND_TYPES = new Map<
|
||||
type CompoundTypeCache = Map<
|
||||
AlgebraicTypeVariants.Product | AlgebraicTypeVariants.Sum,
|
||||
RefBuilder<any, any>
|
||||
>();
|
||||
>;
|
||||
|
||||
/**
|
||||
* Resolves the actual type of a TypeBuilder by following its references until it reaches a non-ref type.
|
||||
* @param typespace The typespace to resolve types against.
|
||||
* @param typeBuilder The TypeBuilder to resolve.
|
||||
* @returns The resolved algebraic type.
|
||||
*/
|
||||
export function resolveType<AT extends AlgebraicTypeType>(
|
||||
typespace: Infer<typeof Typespace>,
|
||||
typeBuilder: RefBuilder<any, AT>
|
||||
): AT {
|
||||
let ty: AlgebraicType = typeBuilder.algebraicType;
|
||||
while (ty.tag === 'Ref') {
|
||||
ty = typespace.types[ty.value];
|
||||
}
|
||||
return ty as AT;
|
||||
}
|
||||
type ModuleDef = Infer<typeof RawModuleDefV9>;
|
||||
|
||||
/**
|
||||
* Adds a type to the module definition's typespace as a `Ref` if it is a named compound type (Product or Sum).
|
||||
* Otherwise, returns the type as is.
|
||||
* @param name
|
||||
* @param ty
|
||||
* @returns
|
||||
*/
|
||||
export function registerTypesRecursively<
|
||||
T extends TypeBuilder<any, AlgebraicType>,
|
||||
>(
|
||||
typeBuilder: T
|
||||
): T extends SumBuilder<any> | ProductBuilder<any> | RowBuilder<any>
|
||||
? RefBuilder<Infer<T>, InferSpacetimeTypeOfTypeBuilder<T>>
|
||||
: T {
|
||||
if (
|
||||
(typeBuilder instanceof ProductBuilder && !isUnit(typeBuilder)) ||
|
||||
typeBuilder instanceof SumBuilder ||
|
||||
typeBuilder instanceof RowBuilder
|
||||
) {
|
||||
return registerCompoundTypeRecursively(typeBuilder) as any;
|
||||
} else if (typeBuilder instanceof OptionBuilder) {
|
||||
return new OptionBuilder(
|
||||
registerTypesRecursively(typeBuilder.value)
|
||||
) as any;
|
||||
} else if (typeBuilder instanceof ResultBuilder) {
|
||||
return new ResultBuilder(
|
||||
registerTypesRecursively(typeBuilder.ok),
|
||||
registerTypesRecursively(typeBuilder.err)
|
||||
) as any;
|
||||
} else if (typeBuilder instanceof ArrayBuilder) {
|
||||
return new ArrayBuilder(
|
||||
registerTypesRecursively(typeBuilder.element)
|
||||
) as any;
|
||||
} else {
|
||||
return typeBuilder as any;
|
||||
}
|
||||
}
|
||||
export class ModuleContext {
|
||||
#compoundTypes: CompoundTypeCache = new Map();
|
||||
/**
|
||||
* The global module definition that gets populated by calls to `reducer()` and lifecycle hooks.
|
||||
*/
|
||||
#moduleDef: ModuleDef = {
|
||||
typespace: { types: [] },
|
||||
tables: [],
|
||||
reducers: [],
|
||||
types: [],
|
||||
miscExports: [],
|
||||
rowLevelSecurity: [],
|
||||
};
|
||||
|
||||
function registerCompoundTypeRecursively<
|
||||
T extends
|
||||
| SumBuilder<VariantsObj>
|
||||
| ProductBuilder<ElementsObj>
|
||||
| RowBuilder<RowObj>,
|
||||
>(typeBuilder: T): RefBuilder<Infer<T>, InferSpacetimeTypeOfTypeBuilder<T>> {
|
||||
const ty = typeBuilder.algebraicType;
|
||||
// NB! You must ensure that all TypeBuilder passed into this function
|
||||
// have a name. This function ensures that nested types always have a
|
||||
// name by assigning them one if they are missing it.
|
||||
const name = typeBuilder.typeName;
|
||||
if (name === undefined) {
|
||||
throw new Error(
|
||||
`Missing type name for ${typeBuilder.constructor.name ?? 'TypeBuilder'} ${JSON.stringify(typeBuilder)}`
|
||||
);
|
||||
get moduleDef() {
|
||||
return this.#moduleDef;
|
||||
}
|
||||
|
||||
let r = COMPOUND_TYPES.get(ty);
|
||||
if (r != null) {
|
||||
// Already added to typespace
|
||||
get typespace() {
|
||||
return this.#moduleDef.typespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the actual type of a TypeBuilder by following its references until it reaches a non-ref type.
|
||||
* @param typespace The typespace to resolve types against.
|
||||
* @param typeBuilder The TypeBuilder to resolve.
|
||||
* @returns The resolved algebraic type.
|
||||
*/
|
||||
public resolveType<AT extends AlgebraicTypeType>(
|
||||
typeBuilder: RefBuilder<any, AT>
|
||||
): AT {
|
||||
let ty: AlgebraicType = typeBuilder.algebraicType;
|
||||
while (ty.tag === 'Ref') {
|
||||
ty = this.typespace.types[ty.value];
|
||||
}
|
||||
return ty as AT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a type to the module definition's typespace as a `Ref` if it is a named compound type (Product or Sum).
|
||||
* Otherwise, returns the type as is.
|
||||
* @param name
|
||||
* @param ty
|
||||
* @returns
|
||||
*/
|
||||
public registerTypesRecursively<T extends TypeBuilder<any, AlgebraicType>>(
|
||||
typeBuilder: T
|
||||
): T extends SumBuilder<any> | ProductBuilder<any> | RowBuilder<any>
|
||||
? RefBuilder<Infer<T>, InferSpacetimeTypeOfTypeBuilder<T>>
|
||||
: T {
|
||||
if (
|
||||
(typeBuilder instanceof ProductBuilder && !isUnit(typeBuilder)) ||
|
||||
typeBuilder instanceof SumBuilder ||
|
||||
typeBuilder instanceof RowBuilder
|
||||
) {
|
||||
return this.#registerCompoundTypeRecursively(typeBuilder) as any;
|
||||
} else if (typeBuilder instanceof OptionBuilder) {
|
||||
return new OptionBuilder(
|
||||
this.registerTypesRecursively(typeBuilder.value)
|
||||
) as any;
|
||||
} else if (typeBuilder instanceof ResultBuilder) {
|
||||
return new ResultBuilder(
|
||||
this.registerTypesRecursively(typeBuilder.ok),
|
||||
this.registerTypesRecursively(typeBuilder.err)
|
||||
) as any;
|
||||
} else if (typeBuilder instanceof ArrayBuilder) {
|
||||
return new ArrayBuilder(
|
||||
this.registerTypesRecursively(typeBuilder.element)
|
||||
) as any;
|
||||
} else {
|
||||
return typeBuilder as any;
|
||||
}
|
||||
}
|
||||
|
||||
#registerCompoundTypeRecursively<
|
||||
T extends
|
||||
| SumBuilder<VariantsObj>
|
||||
| ProductBuilder<ElementsObj>
|
||||
| RowBuilder<RowObj>,
|
||||
>(typeBuilder: T): RefBuilder<Infer<T>, InferSpacetimeTypeOfTypeBuilder<T>> {
|
||||
const ty = typeBuilder.algebraicType;
|
||||
// NB! You must ensure that all TypeBuilder passed into this function
|
||||
// have a name. This function ensures that nested types always have a
|
||||
// name by assigning them one if they are missing it.
|
||||
const name = typeBuilder.typeName;
|
||||
if (name === undefined) {
|
||||
throw new Error(
|
||||
`Missing type name for ${typeBuilder.constructor.name ?? 'TypeBuilder'} ${JSON.stringify(typeBuilder)}`
|
||||
);
|
||||
}
|
||||
|
||||
let r = this.#compoundTypes.get(ty);
|
||||
if (r != null) {
|
||||
// Already added to typespace
|
||||
return r;
|
||||
}
|
||||
|
||||
// Recursively register nested compound types
|
||||
const newTy =
|
||||
typeBuilder instanceof RowBuilder || typeBuilder instanceof ProductBuilder
|
||||
? ({
|
||||
tag: 'Product',
|
||||
value: { elements: [] },
|
||||
} as AlgebraicTypeVariants.Product)
|
||||
: ({
|
||||
tag: 'Sum',
|
||||
value: { variants: [] },
|
||||
} as AlgebraicTypeVariants.Sum);
|
||||
|
||||
r = new RefBuilder(this.#moduleDef.typespace.types.length);
|
||||
this.#moduleDef.typespace.types.push(newTy);
|
||||
|
||||
this.#compoundTypes.set(ty, r);
|
||||
|
||||
if (typeBuilder instanceof RowBuilder) {
|
||||
for (const [name, elem] of Object.entries(typeBuilder.row)) {
|
||||
(newTy.value as ProductType).elements.push({
|
||||
name,
|
||||
algebraicType: this.registerTypesRecursively(elem.typeBuilder)
|
||||
.algebraicType,
|
||||
});
|
||||
}
|
||||
} else if (typeBuilder instanceof ProductBuilder) {
|
||||
for (const [name, elem] of Object.entries(typeBuilder.elements)) {
|
||||
(newTy.value as ProductType).elements.push({
|
||||
name,
|
||||
algebraicType: this.registerTypesRecursively(elem).algebraicType,
|
||||
});
|
||||
}
|
||||
} else if (typeBuilder instanceof SumBuilder) {
|
||||
for (const [name, variant] of Object.entries(typeBuilder.variants)) {
|
||||
(newTy.value as SumType).variants.push({
|
||||
name,
|
||||
algebraicType: this.registerTypesRecursively(variant).algebraicType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.#moduleDef.types.push({
|
||||
name: splitName(name),
|
||||
ty: r.ref,
|
||||
customOrdering: true,
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// Recursively register nested compound types
|
||||
const newTy =
|
||||
typeBuilder instanceof RowBuilder || typeBuilder instanceof ProductBuilder
|
||||
? ({
|
||||
tag: 'Product',
|
||||
value: { elements: [] },
|
||||
} as AlgebraicTypeVariants.Product)
|
||||
: ({ tag: 'Sum', value: { variants: [] } } as AlgebraicTypeVariants.Sum);
|
||||
|
||||
r = new RefBuilder(MODULE_DEF.typespace.types.length);
|
||||
MODULE_DEF.typespace.types.push(newTy);
|
||||
|
||||
COMPOUND_TYPES.set(ty, r);
|
||||
|
||||
if (typeBuilder instanceof RowBuilder) {
|
||||
for (const [name, elem] of Object.entries(typeBuilder.row)) {
|
||||
(newTy.value as ProductType).elements.push({
|
||||
name,
|
||||
algebraicType: registerTypesRecursively(elem.typeBuilder).algebraicType,
|
||||
});
|
||||
}
|
||||
} else if (typeBuilder instanceof ProductBuilder) {
|
||||
for (const [name, elem] of Object.entries(typeBuilder.elements)) {
|
||||
(newTy.value as ProductType).elements.push({
|
||||
name,
|
||||
algebraicType: registerTypesRecursively(elem).algebraicType,
|
||||
});
|
||||
}
|
||||
} else if (typeBuilder instanceof SumBuilder) {
|
||||
for (const [name, variant] of Object.entries(typeBuilder.variants)) {
|
||||
(newTy.value as SumType).variants.push({
|
||||
name,
|
||||
algebraicType: registerTypesRecursively(variant).algebraicType,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MODULE_DEF.types.push({
|
||||
name: splitName(name),
|
||||
ty: r.ref,
|
||||
customOrdering: true,
|
||||
});
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
function isUnit(typeBuilder: ProductBuilder<ElementsObj>): boolean {
|
||||
@@ -283,383 +276,3 @@ export function splitName(name: string): Infer<typeof RawScopedTypeNameV9> {
|
||||
const scope = name.split('.');
|
||||
return { name: scope.pop()!, scope };
|
||||
}
|
||||
|
||||
/**
|
||||
* The Schema class represents the database schema for a SpacetimeDB application.
|
||||
* It encapsulates the table definitions and typespace, and provides methods to define
|
||||
* reducers and lifecycle hooks.
|
||||
*
|
||||
* Schema has a generic parameter S which represents the inferred schema type. This type
|
||||
* is automatically inferred when creating a schema using the `schema()` function and is
|
||||
* used to type the database view in reducer contexts.
|
||||
*
|
||||
* The methods on this class are used to register reducers and lifecycle hooks
|
||||
* with the SpacetimeDB runtime. Theey forward to free functions that handle the actual
|
||||
* registration logic, but having them as methods on the Schema class helps with type inference.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const spacetime = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* spacetime.reducer(
|
||||
* 'create_user',
|
||||
* { username: t.string(), email: t.string() },
|
||||
* (ctx, { username, email }) => {
|
||||
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
||||
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
// TODO(cloutiertyler): It might be nice to have a way to access the types
|
||||
// for the tables from the schema object, e.g. `spacetimedb.user.type` would
|
||||
// be the type of the user table.
|
||||
class Schema<S extends UntypedSchemaDef> {
|
||||
readonly tablesDef: { tables: Infer<typeof RawTableDefV9>[] };
|
||||
readonly typespace: Infer<typeof Typespace>;
|
||||
readonly schemaType: S;
|
||||
|
||||
constructor(
|
||||
tables: Infer<typeof RawTableDefV9>[],
|
||||
typespace: Infer<typeof Typespace>,
|
||||
handles: readonly UntypedTableSchema[]
|
||||
) {
|
||||
this.tablesDef = { tables };
|
||||
this.typespace = typespace;
|
||||
// TODO: TableSchema and TableDef should really be unified
|
||||
this.schemaType = tablesToSchema(handles) as S;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a SpacetimeDB reducer function.
|
||||
*
|
||||
* Reducers are the primary way to modify the state of your SpacetimeDB application.
|
||||
* They are atomic, meaning that either all operations within a reducer succeed,
|
||||
* or none of them do.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the reducer.
|
||||
*
|
||||
* @param {string} name - The name of the reducer. This name will be used to call the reducer from clients.
|
||||
* @param {Params} params - An object defining the parameters that the reducer accepts.
|
||||
* Each key-value pair represents a parameter name and its corresponding
|
||||
* {@link TypeBuilder} or {@link ColumnBuilder}.
|
||||
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
|
||||
* spacetime.reducer(
|
||||
* 'create_user',
|
||||
* {
|
||||
* username: t.string(),
|
||||
* email: t.string(),
|
||||
* },
|
||||
* (ctx, { username, email }) => {
|
||||
* // Access the 'user' table from the database view in the context
|
||||
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
||||
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
reducer<Params extends ParamsObj>(
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): Reducer<S, Params>;
|
||||
reducer(name: string, fn: Reducer<S, {}>): Reducer<S, {}>;
|
||||
reducer<Params extends ParamsObj>(
|
||||
name: string,
|
||||
paramsOrFn: Params | Reducer<S, any>,
|
||||
fn?: Reducer<S, Params>
|
||||
): Reducer<S, Params> {
|
||||
if (typeof paramsOrFn === 'function') {
|
||||
// This is the case where params are omitted.
|
||||
// The second argument is the reducer function.
|
||||
// We pass an empty object for the params.
|
||||
reducer(name, {}, paramsOrFn);
|
||||
return paramsOrFn;
|
||||
} else {
|
||||
// This is the case where params are provided.
|
||||
// The second argument is the params object, and the third is the function.
|
||||
// The `fn` parameter is guaranteed to be defined here.
|
||||
reducer(name, paramsOrFn, fn!);
|
||||
return fn!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an initialization reducer that runs when the SpacetimeDB module is published
|
||||
* for the first time.
|
||||
*
|
||||
* This function is useful to set up any initial state of your database that is guaranteed
|
||||
* to run only once, and before any other reducers or client connections.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @param {Reducer<S, {}>} fn - The initialization reducer function.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.init((ctx) => {
|
||||
* ctx.db.user.insert({ username: 'admin', email: 'admin@example.com' });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
init(fn: Reducer<S, {}>): void;
|
||||
init(name: string, fn: Reducer<S, {}>): void;
|
||||
init(nameOrFn: any, maybeFn?: Reducer<S, {}>): void {
|
||||
const [name, fn] =
|
||||
typeof nameOrFn === 'string' ? [nameOrFn, maybeFn] : ['init', nameOrFn];
|
||||
init(name, {}, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a new client establishes a connection.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
*
|
||||
* @param fn - The reducer function to execute on client connection.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.clientConnected(
|
||||
* (ctx) => {
|
||||
* console.log(`Client ${ctx.connectionId} connected`);
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
clientConnected(fn: Reducer<S, {}>): void;
|
||||
clientConnected(name: string, fn: Reducer<S, {}>): void;
|
||||
clientConnected(nameOrFn: any, maybeFn?: Reducer<S, {}>): void {
|
||||
const [name, fn] =
|
||||
typeof nameOrFn === 'string'
|
||||
? [nameOrFn, maybeFn]
|
||||
: ['on_connect', nameOrFn];
|
||||
clientConnected(name, {}, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a client disconnects.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
*
|
||||
* @param fn - The reducer function to execute on client disconnection.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.clientDisconnected(
|
||||
* (ctx) => {
|
||||
* console.log(`Client ${ctx.connectionId} disconnected`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
clientDisconnected(fn: Reducer<S, {}>): void;
|
||||
clientDisconnected(name: string, fn: Reducer<S, {}>): void;
|
||||
clientDisconnected(nameOrFn: any, maybeFn?: Reducer<S, {}>): void {
|
||||
const [name, fn] =
|
||||
typeof nameOrFn === 'string'
|
||||
? [nameOrFn, maybeFn]
|
||||
: ['on_disconnect', nameOrFn];
|
||||
clientDisconnected(name, {}, fn);
|
||||
}
|
||||
|
||||
view<Ret extends ViewReturnTypeBuilder>(
|
||||
opts: ViewOpts,
|
||||
ret: Ret,
|
||||
fn: ViewFn<S, {}, Ret>
|
||||
): void {
|
||||
defineView(opts, false, {}, ret, fn);
|
||||
}
|
||||
|
||||
// TODO: re-enable once parameterized views are supported in SQL
|
||||
// view<Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// ret: Ret,
|
||||
// fn: ViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// params: Params,
|
||||
// ret: Ret,
|
||||
// fn: ViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// paramsOrRet: Ret | Params,
|
||||
// retOrFn: ViewFn<S, {}, Ret> | Ret,
|
||||
// maybeFn?: ViewFn<S, Params, Ret>
|
||||
// ): void {
|
||||
// if (typeof retOrFn === 'function') {
|
||||
// defineView(name, false, {}, paramsOrRet as Ret, retOrFn);
|
||||
// } else {
|
||||
// defineView(name, false, paramsOrRet as Params, retOrFn, maybeFn!);
|
||||
// }
|
||||
// }
|
||||
|
||||
anonymousView<Ret extends ViewReturnTypeBuilder>(
|
||||
opts: ViewOpts,
|
||||
ret: Ret,
|
||||
fn: AnonymousViewFn<S, {}, Ret>
|
||||
): void {
|
||||
defineView(opts, true, {}, ret, fn);
|
||||
}
|
||||
|
||||
// TODO: re-enable once parameterized views are supported in SQL
|
||||
// anonymousView<Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// ret: Ret,
|
||||
// fn: AnonymousViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// params: Params,
|
||||
// ret: Ret,
|
||||
// fn: AnonymousViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// paramsOrRet: Ret | Params,
|
||||
// retOrFn: AnonymousViewFn<S, {}, Ret> | Ret,
|
||||
// maybeFn?: AnonymousViewFn<S, Params, Ret>
|
||||
// ): void {
|
||||
// if (typeof retOrFn === 'function') {
|
||||
// defineView(name, true, {}, paramsOrRet as Ret, retOrFn);
|
||||
// } else {
|
||||
// defineView(name, true, paramsOrRet as Params, retOrFn, maybeFn!);
|
||||
// }
|
||||
// }
|
||||
|
||||
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
||||
name: string,
|
||||
params: Params,
|
||||
ret: Ret,
|
||||
fn: ProcedureFn<S, Params, Ret>
|
||||
): ProcedureFn<S, Params, Ret>;
|
||||
procedure<Ret extends TypeBuilder<any, any>>(
|
||||
name: string,
|
||||
ret: Ret,
|
||||
fn: ProcedureFn<S, {}, Ret>
|
||||
): ProcedureFn<S, {}, Ret>;
|
||||
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
||||
name: string,
|
||||
paramsOrRet: Ret | Params,
|
||||
retOrFn: ProcedureFn<S, {}, Ret> | Ret,
|
||||
maybeFn?: ProcedureFn<S, Params, Ret>
|
||||
): ProcedureFn<S, Params, Ret> {
|
||||
if (typeof retOrFn === 'function') {
|
||||
procedure(name, {}, paramsOrRet as Ret, retOrFn);
|
||||
return retOrFn;
|
||||
} else {
|
||||
procedure(name, paramsOrRet as Params, retOrFn, maybeFn!);
|
||||
return maybeFn!;
|
||||
}
|
||||
}
|
||||
|
||||
clientVisibilityFilter = {
|
||||
sql(filter: string): void {
|
||||
MODULE_DEF.rowLevelSecurity.push({ sql: filter });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the inferred schema type from a Schema instance
|
||||
*/
|
||||
export type InferSchema<SchemaDef extends Schema<any>> =
|
||||
SchemaDef extends Schema<infer S> ? S : never;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
...handles: H
|
||||
): Schema<TablesToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions (array overload)
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
handles: H
|
||||
): Schema<TablesToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param args - Either an array of table handles or a variadic list of table handles
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
...args: [H] | H
|
||||
): Schema<TablesToSchema<H>> {
|
||||
const handles = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
const tableDefs = handles.map(h => h.tableDef);
|
||||
|
||||
// Side-effect:
|
||||
// Modify the `MODULE_DEF` which will be read by
|
||||
// __describe_module__
|
||||
MODULE_DEF.tables.push(...tableDefs);
|
||||
REGISTERED_SCHEMA = {
|
||||
tables: handles.map(handle => ({
|
||||
name: handle.tableName,
|
||||
accessorName: handle.tableName,
|
||||
columns: handle.rowType.row,
|
||||
rowType: handle.rowSpacetimeType,
|
||||
indexes: handle.idxs,
|
||||
constraints: handle.constraints,
|
||||
})),
|
||||
};
|
||||
// MODULE_DEF.typespace = typespace;
|
||||
// throw new Error(
|
||||
// MODULE_DEF.tables
|
||||
// .map(t => {
|
||||
// const p = MODULE_DEF.typespace.types[t.productTypeRef];
|
||||
// return `${t.name}: ${t.productTypeRef} ${p && (p as AlgebraicTypeVariants.Product).value.elements.map(x => x.name)}`;
|
||||
// })
|
||||
// .join('\n')
|
||||
// );
|
||||
|
||||
return new Schema(tableDefs, MODULE_DEF.typespace, handles);
|
||||
}
|
||||
|
||||
type HasAccessor = { accessorName: PropertyKey };
|
||||
|
||||
export type ConvertToAccessorMap<TableDefs extends readonly HasAccessor[]> = {
|
||||
[Tbl in TableDefs[number] as Tbl['accessorName']]: Tbl;
|
||||
};
|
||||
|
||||
export function convertToAccessorMap<T extends readonly HasAccessor[]>(
|
||||
arr: T
|
||||
): ConvertToAccessorMap<T> {
|
||||
return Object.fromEntries(
|
||||
arr.map(v => [v.accessorName, v])
|
||||
) as ConvertToAccessorMap<T>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { errors } from '../server/errors';
|
||||
import type RawConstraintDefV9 from './autogen/raw_constraint_def_v_9_type';
|
||||
import RawIndexAlgorithm from './autogen/raw_index_algorithm_type';
|
||||
import type RawIndexDefV9 from './autogen/raw_index_def_v_9_type';
|
||||
@@ -12,7 +13,7 @@ import type {
|
||||
ReadonlyIndexes,
|
||||
} from './indexes';
|
||||
import ScheduleAt from './schedule_at';
|
||||
import { registerTypesRecursively } from './schema';
|
||||
import type { ModuleContext } from './schema';
|
||||
import type { TableSchema } from './table_schema';
|
||||
import {
|
||||
RowBuilder,
|
||||
@@ -24,9 +25,9 @@ import {
|
||||
type TypeBuilder,
|
||||
} from './type_builders';
|
||||
import type {
|
||||
InvalidColumnMetadata,
|
||||
Prettify,
|
||||
ValidateColumnMetadata,
|
||||
InvalidColumnMetadata,
|
||||
} from './type_util';
|
||||
import { toPascalCase } from './util';
|
||||
|
||||
@@ -218,8 +219,8 @@ export interface TableMethods<TableDef extends UntypedTableDef>
|
||||
* Insert and return the inserted row (auto-increment fields filled).
|
||||
*
|
||||
* May throw on error:
|
||||
* * If there are any unique or primary key columns in this table, may throw {@link UniqueAlreadyExists}.
|
||||
* * If there are any auto-incrementing columns in this table, may throw {@link AutoIncOverflow}.
|
||||
* * If there are any unique or primary key columns in this table, may throw {@link errors.UniqueAlreadyExists}.
|
||||
* * If there are any auto-incrementing columns in this table, may throw {@link errors.AutoIncOverflow}.
|
||||
* */
|
||||
insert(row: Prettify<RowType<TableDef>>): Prettify<RowType<TableDef>>;
|
||||
|
||||
@@ -300,8 +301,6 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
|
||||
row.typeName = toPascalCase(name);
|
||||
}
|
||||
|
||||
const rowTypeRef = registerTypesRecursively(row);
|
||||
|
||||
row.algebraicType.value.elements.forEach((elem, i) => {
|
||||
colIds.set(elem.name, i);
|
||||
colNameList.push(elem.name);
|
||||
@@ -419,9 +418,9 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
|
||||
// Temporarily set the type ref to 0. We will set this later
|
||||
// in the schema function.
|
||||
|
||||
const tableDef: Infer<typeof RawTableDefV9> = {
|
||||
const tableDef = (ctx: ModuleContext): Infer<typeof RawTableDefV9> => ({
|
||||
name,
|
||||
productTypeRef: rowTypeRef.ref,
|
||||
productTypeRef: ctx.registerTypesRecursively(row).ref,
|
||||
primaryKey: pk,
|
||||
indexes,
|
||||
constraints,
|
||||
@@ -436,7 +435,7 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
|
||||
: undefined,
|
||||
tableType: { tag: 'User' },
|
||||
tableAccess: { tag: isPublic ? 'Public' : 'Private' },
|
||||
};
|
||||
});
|
||||
|
||||
const productType = row.algebraicType.value as RowBuilder<
|
||||
CoerceRow<Row>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ProductType } from './algebraic_type';
|
||||
import type RawTableDefV9 from './autogen/raw_table_def_v_9_type';
|
||||
import type { IndexOpts } from './indexes';
|
||||
import type { ModuleContext } from './schema';
|
||||
import type { ColumnBuilder, Infer, RowBuilder } from './type_builders';
|
||||
|
||||
/**
|
||||
@@ -28,7 +30,7 @@ export type TableSchema<
|
||||
/**
|
||||
* The {@link RawTableDefV9} of the configured table
|
||||
*/
|
||||
readonly tableDef: Infer<typeof RawTableDefV9>;
|
||||
tableDef(ctx: ModuleContext): Infer<typeof RawTableDefV9>;
|
||||
|
||||
/**
|
||||
* The indexes defined on the table.
|
||||
|
||||
@@ -3874,7 +3874,7 @@ export const t = {
|
||||
enum: enumImpl,
|
||||
|
||||
/**
|
||||
* This is a special helper function for conveniently creating {@link Product} type columns with no fields.
|
||||
* This is a special helper function for conveniently creating `Product` type columns with no fields.
|
||||
*
|
||||
* @returns A new {@link ProductBuilder} instance with no fields.
|
||||
*/
|
||||
@@ -4002,10 +4002,10 @@ export const t = {
|
||||
},
|
||||
|
||||
/**
|
||||
* This is a convenience method for creating a column with the {@link ByteArray} type.
|
||||
* This is a convenience method for creating a column with the `ByteArray` type.
|
||||
* You can create a column of the same type by constructing an `array` of `u8`.
|
||||
* The TypeScript representation is {@link Uint8Array}.
|
||||
* @returns A new {@link ByteArrayBuilder} instance with the {@link ByteArray} type.
|
||||
* @returns A new {@link ByteArrayBuilder} instance with the `ByteArray` type.
|
||||
*/
|
||||
byteArray: (): ByteArrayBuilder => {
|
||||
return new ByteArrayBuilder();
|
||||
|
||||
@@ -198,7 +198,7 @@ export class DbConnectionBuilder<DbConnection extends DbConnectionImpl<any>> {
|
||||
|
||||
/**
|
||||
* Registers a callback to run when a {@link DbConnection} whose connection initially succeeded
|
||||
* is disconnected, either after a {@link DbConnection.disconnect} call or due to an error.
|
||||
* is disconnected, either after a {@link DbConnection.disconnect()} call or due to an error.
|
||||
*
|
||||
* If the connection ended because of an error, the error is passed to the callback.
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@ export { type ClientTable } from './client_table.ts';
|
||||
export { type RemoteModule } from './spacetime_module.ts';
|
||||
export { type SetReducerFlags } from './reducers.ts';
|
||||
export * from '../lib/type_builders.ts';
|
||||
export { schema, convertToAccessorMap } from '../lib/schema.ts';
|
||||
export { schema, convertToAccessorMap } from './schema.ts';
|
||||
export { table } from '../lib/table.ts';
|
||||
export { reducerSchema, reducers } from '../lib/reducers.ts';
|
||||
export { procedureSchema, procedures } from '../lib/procedures.ts';
|
||||
export { reducerSchema, reducers } from './reducers.ts';
|
||||
export { procedureSchema, procedures } from './procedures.ts';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { Infer, InferTypeOfRow } from '../lib/type_builders';
|
||||
import type { ParamsObj } from '../lib/reducers';
|
||||
import type { Infer, InferTypeOfRow, TypeBuilder } from '../lib/type_builders';
|
||||
import type { CamelCase } from '../lib/type_util';
|
||||
import { coerceParams, toCamelCase, type CoerceParams } from '../lib/util';
|
||||
import type { UntypedRemoteModule } from './spacetime_module';
|
||||
|
||||
// Utility: detect 'any'
|
||||
@@ -25,3 +27,59 @@ export type ProceduresView<RemoteModule> = IfAny<
|
||||
}
|
||||
: never
|
||||
>;
|
||||
|
||||
export type UntypedProcedureDef = {
|
||||
name: string;
|
||||
accessorName: string;
|
||||
params: CoerceParams<ParamsObj>;
|
||||
returnType: TypeBuilder<any, any>;
|
||||
};
|
||||
|
||||
export type UntypedProceduresDef = {
|
||||
procedures: readonly UntypedProcedureDef[];
|
||||
};
|
||||
|
||||
export function procedures<const H extends readonly UntypedProcedureDef[]>(
|
||||
...handles: H
|
||||
): { procedures: H };
|
||||
|
||||
export function procedures<const H extends readonly UntypedProcedureDef[]>(
|
||||
handles: H
|
||||
): { procedures: H };
|
||||
|
||||
export function procedures<const H extends readonly UntypedProcedureDef[]>(
|
||||
...args: [H] | H
|
||||
): { procedures: H } {
|
||||
const procedures = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
return { procedures };
|
||||
}
|
||||
|
||||
type ProcedureDef<
|
||||
Name extends string,
|
||||
Params extends ParamsObj,
|
||||
ReturnType extends TypeBuilder<any, any>,
|
||||
> = {
|
||||
name: Name;
|
||||
accessorName: CamelCase<Name>;
|
||||
params: CoerceParams<Params>;
|
||||
returnType: ReturnType;
|
||||
};
|
||||
|
||||
export function procedureSchema<
|
||||
ProcedureName extends string,
|
||||
Params extends ParamsObj,
|
||||
ReturnType extends TypeBuilder<any, any>,
|
||||
>(
|
||||
name: ProcedureName,
|
||||
params: Params,
|
||||
returnType: ReturnType
|
||||
): ProcedureDef<ProcedureName, Params, ReturnType> {
|
||||
return {
|
||||
name,
|
||||
accessorName: toCamelCase(name),
|
||||
params: coerceParams(params),
|
||||
returnType,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { ProductType } from '../lib/algebraic_type';
|
||||
import type { ReducerSchema } from '../lib/reducer_schema';
|
||||
import type { ParamsObj } from '../lib/reducers';
|
||||
import type { CoerceRow } from '../lib/table';
|
||||
import type { InferTypeOfRow } from '../lib/type_builders';
|
||||
import { RowBuilder, type InferTypeOfRow } from '../lib/type_builders';
|
||||
import type { CamelCase, PascalCase } from '../lib/type_util';
|
||||
import { toCamelCase } from '../lib/util';
|
||||
import type { CallReducerFlags } from './db_connection_impl';
|
||||
import type { UntypedRemoteModule } from './spacetime_module';
|
||||
import type {
|
||||
ReducerEventContextInterface,
|
||||
SubscriptionEventContextInterface,
|
||||
} from './event_context';
|
||||
import type { UntypedRemoteModule } from './spacetime_module';
|
||||
|
||||
export type ReducerEventCallback<
|
||||
RemoteModule extends UntypedRemoteModule,
|
||||
@@ -90,3 +92,110 @@ export type SetReducerFlags<R extends UntypedReducersDef> = {
|
||||
flags: CallReducerFlags
|
||||
) => void;
|
||||
};
|
||||
|
||||
class Reducers<ReducersDef extends UntypedReducersDef> {
|
||||
reducersType: ReducersDef;
|
||||
|
||||
constructor(handles: readonly ReducerSchema<any, any>[]) {
|
||||
this.reducersType = reducersToSchema(handles) as ReducersDef;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper type to convert an array of TableSchema into a schema definition
|
||||
*/
|
||||
type ReducersToSchema<T extends readonly ReducerSchema<any, any>[]> = {
|
||||
reducers: {
|
||||
/** @type {UntypedReducerDef} */
|
||||
readonly [i in keyof T]: {
|
||||
name: T[i]['reducerName'];
|
||||
accessorName: CamelCase<T[i]['accessorName']>;
|
||||
params: T[i]['params']['row'];
|
||||
paramsType: T[i]['paramsSpacetimeType'];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export function reducersToSchema<
|
||||
const T extends readonly ReducerSchema<any, any>[],
|
||||
>(reducers: T): ReducersToSchema<T> {
|
||||
const mapped = reducers.map(r => {
|
||||
const paramsRow = r.params.row;
|
||||
|
||||
return {
|
||||
name: r.reducerName,
|
||||
// Prefer the schema's own accessorName if present at runtime; otherwise derive it.
|
||||
accessorName: r.accessorName,
|
||||
params: paramsRow,
|
||||
paramsType: r.paramsSpacetimeType,
|
||||
} as const;
|
||||
}) as {
|
||||
readonly [I in keyof T]: {
|
||||
name: T[I]['reducerName'];
|
||||
accessorName: T[I]['accessorName'];
|
||||
params: T[I]['params']['row'];
|
||||
paramsType: T[I]['paramsSpacetimeType'];
|
||||
};
|
||||
};
|
||||
|
||||
const result = { reducers: mapped } satisfies ReducersToSchema<T>;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function reducers<const H extends readonly ReducerSchema<any, any>[]>(
|
||||
...handles: H
|
||||
): Reducers<ReducersToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions (array overload)
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
*/
|
||||
export function reducers<const H extends readonly ReducerSchema<any, any>[]>(
|
||||
handles: H
|
||||
): Reducers<ReducersToSchema<H>>;
|
||||
|
||||
export function reducers<const H extends readonly ReducerSchema<any, any>[]>(
|
||||
...args: [H] | H
|
||||
): Reducers<ReducersToSchema<H>> {
|
||||
const handles = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
return new Reducers(handles);
|
||||
}
|
||||
|
||||
export function reducerSchema<
|
||||
ReducerName extends string,
|
||||
Params extends ParamsObj,
|
||||
>(name: ReducerName, params: Params): ReducerSchema<ReducerName, Params> {
|
||||
const paramType: ProductType = {
|
||||
elements: Object.entries(params).map(([n, c]) => ({
|
||||
name: n,
|
||||
algebraicType:
|
||||
'typeBuilder' in c ? c.typeBuilder.algebraicType : c.algebraicType,
|
||||
})),
|
||||
};
|
||||
return {
|
||||
reducerName: name,
|
||||
accessorName: toCamelCase(name),
|
||||
params: new RowBuilder<Params>(params),
|
||||
paramsSpacetimeType: paramType,
|
||||
reducerDef: {
|
||||
name,
|
||||
params: paramType,
|
||||
lifecycle: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
ModuleContext,
|
||||
tablesToSchema,
|
||||
type TablesToSchema,
|
||||
type UntypedSchemaDef,
|
||||
} from '../lib/schema';
|
||||
import type { UntypedTableSchema } from '../lib/table_schema';
|
||||
|
||||
class Tables<S extends UntypedSchemaDef> {
|
||||
constructor(readonly schemaType: S) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
...handles: H
|
||||
): Tables<TablesToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions (array overload)
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
handles: H
|
||||
): Tables<TablesToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param args - Either an array of table handles or a variadic list of table handles
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
...args: [H] | H
|
||||
): Tables<TablesToSchema<H>> {
|
||||
const handles = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
|
||||
const ctx = new ModuleContext();
|
||||
|
||||
return new Tables(tablesToSchema(ctx, handles));
|
||||
}
|
||||
|
||||
type HasAccessor = { accessorName: PropertyKey };
|
||||
|
||||
export type ConvertToAccessorMap<TableDefs extends readonly HasAccessor[]> = {
|
||||
[Tbl in TableDefs[number] as Tbl['accessorName']]: Tbl;
|
||||
};
|
||||
|
||||
export function convertToAccessorMap<T extends readonly HasAccessor[]>(
|
||||
arr: T
|
||||
): ConvertToAccessorMap<T> {
|
||||
return Object.fromEntries(
|
||||
arr.map(v => [v.accessorName, v])
|
||||
) as ConvertToAccessorMap<T>;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { UntypedProceduresDef } from '../lib/procedures';
|
||||
import type { UntypedProceduresDef } from './procedures';
|
||||
import type { UntypedSchemaDef } from '../lib/schema';
|
||||
import type { UntypedReducersDef } from './reducers';
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
export * from '../lib/type_builders';
|
||||
export { schema, type InferSchema } from '../lib/schema';
|
||||
export { schema, type InferSchema } from './schema';
|
||||
export { table } from '../lib/table';
|
||||
export { reducers } from '../lib/reducers';
|
||||
export { SenderError, SpacetimeHostError, errors } from './errors';
|
||||
export { type Reducer, type ReducerCtx } from '../lib/reducers';
|
||||
export { type DbView } from './db_view';
|
||||
export * from './query';
|
||||
export type { ProcedureCtx, TransactionCtx } from '../lib/procedures';
|
||||
export type { ProcedureCtx, TransactionCtx } from './procedures';
|
||||
export { toCamelCase } from '../lib/util';
|
||||
export { type Uuid } from '../lib/uuid';
|
||||
export { type Random } from './rng';
|
||||
|
||||
@@ -1,21 +1,102 @@
|
||||
import {
|
||||
AlgebraicType,
|
||||
ProductType,
|
||||
type Deserializer,
|
||||
type Serializer,
|
||||
} from '../lib/algebraic_type';
|
||||
import BinaryReader from '../lib/binary_reader';
|
||||
import BinaryWriter from '../lib/binary_writer';
|
||||
import type { ConnectionId } from '../lib/connection_id';
|
||||
import { Identity } from '../lib/identity';
|
||||
import {
|
||||
PROCEDURES,
|
||||
type ProcedureCtx,
|
||||
type TransactionCtx,
|
||||
} from '../lib/procedures';
|
||||
import type { ParamsObj, ReducerCtx } from '../lib/reducers';
|
||||
import { type UntypedSchemaDef } from '../lib/schema';
|
||||
import { Timestamp } from '../lib/timestamp';
|
||||
import {
|
||||
type Infer,
|
||||
type InferTypeOfRow,
|
||||
type TypeBuilder,
|
||||
} from '../lib/type_builders';
|
||||
import { bsatnBaseSize } from '../lib/util';
|
||||
import { Uuid } from '../lib/uuid';
|
||||
import type { HttpClient } from '../server/http_internal';
|
||||
import { httpClient } from './http_internal';
|
||||
import { callUserFunction, ReducerCtxImpl, sys } from './runtime';
|
||||
import type { SchemaInner } from './schema';
|
||||
|
||||
const { freeze } = Object;
|
||||
|
||||
export type ProcedureFn<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
Ret extends TypeBuilder<any, any>,
|
||||
> = (ctx: ProcedureCtx<S>, args: InferTypeOfRow<Params>) => Infer<Ret>;
|
||||
|
||||
export interface ProcedureCtx<S extends UntypedSchemaDef> {
|
||||
readonly sender: Identity;
|
||||
readonly identity: Identity;
|
||||
readonly timestamp: Timestamp;
|
||||
readonly connectionId: ConnectionId | null;
|
||||
readonly http: HttpClient;
|
||||
readonly counter_uuid: { value: number };
|
||||
withTx<T>(body: (ctx: TransactionCtx<S>) => T): T;
|
||||
newUuidV4(): Uuid;
|
||||
newUuidV7(): Uuid;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface TransactionCtx<S extends UntypedSchemaDef>
|
||||
extends ReducerCtx<S> {}
|
||||
|
||||
export function procedure<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
Ret extends TypeBuilder<any, any>,
|
||||
>(
|
||||
ctx: SchemaInner,
|
||||
name: string,
|
||||
params: Params,
|
||||
ret: Ret,
|
||||
fn: ProcedureFn<S, Params, Ret>
|
||||
) {
|
||||
ctx.defineFunction(name);
|
||||
const paramsType: ProductType = {
|
||||
elements: Object.entries(params).map(([n, c]) => ({
|
||||
name: n,
|
||||
algebraicType: ctx.registerTypesRecursively(
|
||||
'typeBuilder' in c ? c.typeBuilder : c
|
||||
).algebraicType,
|
||||
})),
|
||||
};
|
||||
const returnType = ctx.registerTypesRecursively(ret).algebraicType;
|
||||
|
||||
ctx.moduleDef.miscExports.push({
|
||||
tag: 'Procedure',
|
||||
value: {
|
||||
name,
|
||||
params: paramsType,
|
||||
returnType,
|
||||
},
|
||||
});
|
||||
|
||||
const { typespace } = ctx;
|
||||
|
||||
ctx.procedures.push({
|
||||
fn,
|
||||
deserializeArgs: ProductType.makeDeserializer(paramsType, typespace),
|
||||
serializeReturn: AlgebraicType.makeSerializer(returnType, typespace),
|
||||
returnTypeBaseSize: bsatnBaseSize(typespace, returnType),
|
||||
});
|
||||
}
|
||||
|
||||
export type Procedures = Array<{
|
||||
fn: ProcedureFn<any, any, any>;
|
||||
deserializeArgs: Deserializer<any>;
|
||||
serializeReturn: Serializer<any>;
|
||||
returnTypeBaseSize: number;
|
||||
}>;
|
||||
|
||||
export function callProcedure(
|
||||
moduleCtx: SchemaInner,
|
||||
id: number,
|
||||
sender: Identity,
|
||||
connectionId: ConnectionId | null,
|
||||
@@ -23,7 +104,7 @@ export function callProcedure(
|
||||
argsBuf: Uint8Array
|
||||
): Uint8Array {
|
||||
const { fn, deserializeArgs, serializeReturn, returnTypeBaseSize } =
|
||||
PROCEDURES[id];
|
||||
moduleCtx.procedures[id];
|
||||
const args = deserializeArgs(new BinaryReader(argsBuf));
|
||||
|
||||
const ctx: ProcedureCtx<UntypedSchemaDef> = {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
import Lifecycle from '../lib/autogen/lifecycle_type';
|
||||
import type RawReducerDefV9 from '../lib/autogen/raw_reducer_def_v_9_type';
|
||||
import type {
|
||||
ParamsAsObject,
|
||||
ParamsObj,
|
||||
Reducer,
|
||||
ReducerCtx,
|
||||
} from '../lib/reducers';
|
||||
import { type UntypedSchemaDef } from '../lib/schema';
|
||||
import {
|
||||
ColumnBuilder,
|
||||
RowBuilder,
|
||||
type Infer,
|
||||
type RowObj,
|
||||
type TypeBuilder,
|
||||
} from '../lib/type_builders';
|
||||
import { toPascalCase } from '../lib/util';
|
||||
import type { SchemaInner } from './schema';
|
||||
|
||||
/**
|
||||
* internal: pushReducer() helper used by reducer() and lifecycle wrappers
|
||||
*
|
||||
* @param name - The name of the reducer.
|
||||
* @param params - The parameters for the reducer.
|
||||
* @param fn - The reducer function.
|
||||
* @param lifecycle - Optional lifecycle hooks for the reducer.
|
||||
*/
|
||||
export function pushReducer(
|
||||
ctx: SchemaInner,
|
||||
name: string,
|
||||
params: RowObj | RowBuilder<RowObj>,
|
||||
fn: Reducer<any, any>,
|
||||
lifecycle?: Infer<typeof RawReducerDefV9>['lifecycle']
|
||||
): void {
|
||||
ctx.defineFunction(name);
|
||||
|
||||
if (!(params instanceof RowBuilder)) {
|
||||
params = new RowBuilder(params);
|
||||
}
|
||||
|
||||
if (params.typeName === undefined) {
|
||||
params.typeName = toPascalCase(name);
|
||||
}
|
||||
|
||||
const ref = ctx.registerTypesRecursively(params);
|
||||
const paramsType = ctx.resolveType(ref).value;
|
||||
|
||||
ctx.moduleDef.reducers.push({
|
||||
name,
|
||||
params: paramsType,
|
||||
lifecycle, // <- lifecycle flag lands here
|
||||
});
|
||||
|
||||
// If the function isn't named (e.g. `function foobar() {}`), give it the same
|
||||
// name as the reducer so that it's clear what it is in in backtraces.
|
||||
if (!fn.name) {
|
||||
Object.defineProperty(fn, 'name', { value: name, writable: false });
|
||||
}
|
||||
|
||||
ctx.reducers.push(fn);
|
||||
}
|
||||
|
||||
export type Reducers = Reducer<any, any>[];
|
||||
|
||||
/**
|
||||
* Defines a SpacetimeDB reducer function.
|
||||
*
|
||||
* Reducers are the primary way to modify the state of your SpacetimeDB application.
|
||||
* They are atomic, meaning that either all operations within a reducer succeed,
|
||||
* or none of them do.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the reducer.
|
||||
*
|
||||
* @param {string} name - The name of the reducer. This name will be used to call the reducer from clients.
|
||||
* @param {Params} params - An object defining the parameters that the reducer accepts.
|
||||
* Each key-value pair represents a parameter name and its corresponding
|
||||
* {@link TypeBuilder} or {@link ColumnBuilder}.
|
||||
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
|
||||
* reducer(
|
||||
* 'create_user',
|
||||
* {
|
||||
* username: t.string(),
|
||||
* email: t.string(),
|
||||
* },
|
||||
* (ctx, { username, email }) => {
|
||||
* // Access the 'user' table from the database view in the context
|
||||
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
||||
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function reducer<S extends UntypedSchemaDef, Params extends ParamsObj>(
|
||||
ctx: SchemaInner,
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): void {
|
||||
pushReducer(ctx, name, params, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an initialization reducer that runs when the SpacetimeDB module is published
|
||||
* for the first time.
|
||||
* This function is useful to set up any initial state of your database that is guaranteed
|
||||
* to run only once, and before any other reducers or client connections.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the initialization reducer.
|
||||
*
|
||||
* @param params - The parameters object defining the expected input for the initialization reducer.
|
||||
* @param fn - The initialization reducer function.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
*/
|
||||
export function init<S extends UntypedSchemaDef, Params extends ParamsObj>(
|
||||
ctx: SchemaInner,
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): void {
|
||||
pushReducer(ctx, name, params, fn, Lifecycle.Init);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a new client establishes a connection.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the connection reducer.
|
||||
* @param params - The parameters object defining the expected input for the connection reducer.
|
||||
* @param fn - The connection reducer function itself.
|
||||
*/
|
||||
export function clientConnected<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
>(
|
||||
ctx: SchemaInner,
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): void {
|
||||
pushReducer(ctx, name, params, fn, Lifecycle.OnConnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a client disconnects.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the disconnection reducer.
|
||||
* @param params - The parameters object defining the expected input for the disconnection reducer.
|
||||
* @param fn - The disconnection reducer function itself.
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.clientDisconnected(
|
||||
* { reason: t.string() },
|
||||
* (ctx, { reason }) => {
|
||||
* console.log(`Client ${ctx.connection_id} disconnected: ${reason}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function clientDisconnected<
|
||||
S extends UntypedSchemaDef,
|
||||
Params extends ParamsObj,
|
||||
>(
|
||||
ctx: SchemaInner,
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): void {
|
||||
pushReducer(ctx, name, params, fn, Lifecycle.OnDisconnect);
|
||||
}
|
||||
@@ -24,35 +24,26 @@ import {
|
||||
type RangedIndex,
|
||||
type UniqueIndex,
|
||||
} from '../lib/indexes';
|
||||
import { callProcedure as callProcedure } from './procedures';
|
||||
import { callProcedure } from './procedures';
|
||||
import {
|
||||
REDUCERS,
|
||||
type AuthCtx,
|
||||
type JsonObject,
|
||||
type JwtClaims,
|
||||
type ReducerCtx,
|
||||
type ReducerCtx as IReducerCtx,
|
||||
} from '../lib/reducers';
|
||||
import {
|
||||
MODULE_DEF,
|
||||
getRegisteredSchema,
|
||||
type UntypedSchemaDef,
|
||||
} from '../lib/schema';
|
||||
import { type UntypedSchemaDef } from '../lib/schema';
|
||||
import { type RowType, type Table, type TableMethods } from '../lib/table';
|
||||
import type { Infer } from '../lib/type_builders';
|
||||
import { bsatnBaseSize, hasOwn, toCamelCase } from '../lib/util';
|
||||
import {
|
||||
ANON_VIEWS,
|
||||
VIEWS,
|
||||
type AnonymousViewCtx,
|
||||
type ViewCtx,
|
||||
} from '../lib/views';
|
||||
import { type AnonymousViewCtx, type ViewCtx } from './views';
|
||||
import { isRowTypedQuery, makeQueryBuilder, toSql } from './query';
|
||||
import type { DbView } from './db_view';
|
||||
import { SenderError, SpacetimeHostError } from './errors';
|
||||
import { Range, type Bound } from './range';
|
||||
import ViewResultHeader from '../lib/autogen/view_result_header_type';
|
||||
import { makeRandom, type Random } from './rng';
|
||||
import { getRegisteredSchema } from './schema';
|
||||
|
||||
const { freeze } = Object;
|
||||
|
||||
@@ -271,13 +262,18 @@ let reducerArgsDeserializers: Deserializer<any>[];
|
||||
export const hooks: ModuleHooks = {
|
||||
__describe_module__() {
|
||||
const writer = new BinaryWriter(128);
|
||||
RawModuleDef.serialize(writer, RawModuleDef.V9(MODULE_DEF));
|
||||
RawModuleDef.serialize(
|
||||
writer,
|
||||
RawModuleDef.V9(getRegisteredSchema().moduleDef)
|
||||
);
|
||||
return writer.getBuffer();
|
||||
},
|
||||
__call_reducer__(reducerId, sender, connId, timestamp, argsBuf) {
|
||||
const moduleCtx = getRegisteredSchema();
|
||||
if (reducerArgsDeserializers == null) {
|
||||
reducerArgsDeserializers = MODULE_DEF.reducers.map(({ params }) =>
|
||||
ProductType.makeDeserializer(params, MODULE_DEF.typespace)
|
||||
reducerArgsDeserializers = moduleCtx.moduleDef.reducers.map(
|
||||
({ params }) =>
|
||||
ProductType.makeDeserializer(params, moduleCtx.typespace)
|
||||
);
|
||||
}
|
||||
const deserializeArgs = reducerArgsDeserializers[reducerId];
|
||||
@@ -289,7 +285,11 @@ export const hooks: ModuleHooks = {
|
||||
ConnectionId.nullIfZero(new ConnectionId(connId))
|
||||
);
|
||||
try {
|
||||
return callUserFunction(REDUCERS[reducerId], ctx, args) ?? { tag: 'ok' };
|
||||
return (
|
||||
callUserFunction(moduleCtx.reducers[reducerId], ctx, args) ?? {
|
||||
tag: 'ok',
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof SenderError) {
|
||||
return { tag: 'err', value: e.message };
|
||||
@@ -301,15 +301,16 @@ export const hooks: ModuleHooks = {
|
||||
|
||||
export const hooks_v1_1: import('spacetime:sys@1.1').ModuleHooks = {
|
||||
__call_view__(id, sender, argsBuf) {
|
||||
const moduleCtx = getRegisteredSchema();
|
||||
const { fn, deserializeParams, serializeReturn, returnTypeBaseSize } =
|
||||
VIEWS[id];
|
||||
moduleCtx.views[id];
|
||||
const ctx: ViewCtx<any> = freeze({
|
||||
sender: new Identity(sender),
|
||||
// this is the non-readonly DbView, but the typing for the user will be
|
||||
// the readonly one, and if they do call mutating functions it will fail
|
||||
// at runtime
|
||||
db: getDbView(),
|
||||
from: makeQueryBuilder(getRegisteredSchema()),
|
||||
from: makeQueryBuilder(moduleCtx.schemaType),
|
||||
});
|
||||
const args = deserializeParams(new BinaryReader(argsBuf));
|
||||
const ret = callUserFunction(fn, ctx, args);
|
||||
@@ -324,14 +325,15 @@ export const hooks_v1_1: import('spacetime:sys@1.1').ModuleHooks = {
|
||||
return { data: retBuf.getBuffer() };
|
||||
},
|
||||
__call_view_anon__(id, argsBuf) {
|
||||
const moduleCtx = getRegisteredSchema();
|
||||
const { fn, deserializeParams, serializeReturn, returnTypeBaseSize } =
|
||||
ANON_VIEWS[id];
|
||||
moduleCtx.anonViews[id];
|
||||
const ctx: AnonymousViewCtx<any> = freeze({
|
||||
// this is the non-readonly DbView, but the typing for the user will be
|
||||
// the readonly one, and if they do call mutating functions it will fail
|
||||
// at runtime
|
||||
db: getDbView(),
|
||||
from: makeQueryBuilder(getRegisteredSchema()),
|
||||
from: makeQueryBuilder(moduleCtx.schemaType),
|
||||
});
|
||||
const args = deserializeParams(new BinaryReader(argsBuf));
|
||||
const ret = callUserFunction(fn, ctx, args);
|
||||
@@ -350,6 +352,7 @@ export const hooks_v1_1: import('spacetime:sys@1.1').ModuleHooks = {
|
||||
export const hooks_v1_2: import('spacetime:sys@1.2').ModuleHooks = {
|
||||
__call_procedure__(id, sender, connection_id, timestamp, args) {
|
||||
return callProcedure(
|
||||
getRegisteredSchema(),
|
||||
id,
|
||||
new Identity(sender),
|
||||
ConnectionId.nullIfZero(new ConnectionId(connection_id)),
|
||||
@@ -361,7 +364,7 @@ export const hooks_v1_2: import('spacetime:sys@1.2').ModuleHooks = {
|
||||
|
||||
let DB_VIEW: DbView<any> | null = null;
|
||||
function getDbView() {
|
||||
DB_VIEW ??= makeDbView(MODULE_DEF);
|
||||
DB_VIEW ??= makeDbView(getRegisteredSchema().moduleDef);
|
||||
return DB_VIEW;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { schema } from '../lib/schema';
|
||||
import { schema } from './schema';
|
||||
import { table } from '../lib/table';
|
||||
import t from '../lib/type_builders';
|
||||
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
import {
|
||||
type ParamsAsObject,
|
||||
type ParamsObj,
|
||||
type Reducer,
|
||||
type ReducerCtx,
|
||||
} from '../lib/reducers';
|
||||
import {
|
||||
ModuleContext,
|
||||
tablesToSchema,
|
||||
type TablesToSchema,
|
||||
type UntypedSchemaDef,
|
||||
} from '../lib/schema';
|
||||
import type { UntypedTableSchema } from '../lib/table_schema';
|
||||
import { ColumnBuilder, TypeBuilder } from '../lib/type_builders';
|
||||
import { procedure, type ProcedureFn, type Procedures } from './procedures';
|
||||
import {
|
||||
clientConnected,
|
||||
clientDisconnected,
|
||||
init,
|
||||
reducer,
|
||||
type Reducers,
|
||||
} from './reducers';
|
||||
|
||||
import {
|
||||
defineView,
|
||||
type AnonViews,
|
||||
type AnonymousViewFn,
|
||||
type ViewFn,
|
||||
type ViewOpts,
|
||||
type ViewReturnTypeBuilder,
|
||||
type Views,
|
||||
} from './views';
|
||||
|
||||
let REGISTERED_SCHEMA: SchemaInner | null = null;
|
||||
|
||||
export function getRegisteredSchema(): SchemaInner {
|
||||
if (REGISTERED_SCHEMA == null) {
|
||||
throw new Error('Schema has not been registered yet. Call schema() first.');
|
||||
}
|
||||
return REGISTERED_SCHEMA;
|
||||
}
|
||||
|
||||
export class SchemaInner<
|
||||
S extends UntypedSchemaDef = UntypedSchemaDef,
|
||||
> extends ModuleContext {
|
||||
schemaType: S;
|
||||
existingFunctions = new Set<string>();
|
||||
reducers: Reducers = [];
|
||||
procedures: Procedures = [];
|
||||
views: Views = [];
|
||||
anonViews: AnonViews = [];
|
||||
|
||||
constructor(getSchemaType: (ctx: ModuleContext) => S) {
|
||||
super();
|
||||
this.schemaType = getSchemaType(this);
|
||||
}
|
||||
|
||||
defineFunction(name: string) {
|
||||
if (this.existingFunctions.has(name)) {
|
||||
throw new TypeError(
|
||||
`There is already a reducer or procedure with the name '${name}'`
|
||||
);
|
||||
}
|
||||
this.existingFunctions.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Schema class represents the database schema for a SpacetimeDB application.
|
||||
* It encapsulates the table definitions and typespace, and provides methods to define
|
||||
* reducers and lifecycle hooks.
|
||||
*
|
||||
* Schema has a generic parameter S which represents the inferred schema type. This type
|
||||
* is automatically inferred when creating a schema using the `schema()` function and is
|
||||
* used to type the database view in reducer contexts.
|
||||
*
|
||||
* The methods on this class are used to register reducers and lifecycle hooks
|
||||
* with the SpacetimeDB runtime. Theey forward to free functions that handle the actual
|
||||
* registration logic, but having them as methods on the Schema class helps with type inference.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const spacetime = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* spacetime.reducer(
|
||||
* 'create_user',
|
||||
* { username: t.string(), email: t.string() },
|
||||
* (ctx, { username, email }) => {
|
||||
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
||||
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
// TODO(cloutiertyler): It might be nice to have a way to access the types
|
||||
// for the tables from the schema object, e.g. `spacetimedb.user.type` would
|
||||
// be the type of the user table.
|
||||
class Schema<S extends UntypedSchemaDef> {
|
||||
#ctx: SchemaInner<S>;
|
||||
|
||||
constructor(ctx: SchemaInner<S>) {
|
||||
// TODO: TableSchema and TableDef should really be unified
|
||||
this.#ctx = ctx;
|
||||
}
|
||||
|
||||
get schemaType(): S {
|
||||
return this.#ctx.schemaType;
|
||||
}
|
||||
|
||||
get moduleDef() {
|
||||
return this.#ctx.moduleDef;
|
||||
}
|
||||
|
||||
get typespace() {
|
||||
return this.#ctx.typespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a SpacetimeDB reducer function.
|
||||
*
|
||||
* Reducers are the primary way to modify the state of your SpacetimeDB application.
|
||||
* They are atomic, meaning that either all operations within a reducer succeed,
|
||||
* or none of them do.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @template Params - The type of the parameters object expected by the reducer.
|
||||
*
|
||||
* @param {string} name - The name of the reducer. This name will be used to call the reducer from clients.
|
||||
* @param {Params} params - An object defining the parameters that the reducer accepts.
|
||||
* Each key-value pair represents a parameter name and its corresponding
|
||||
* {@link TypeBuilder} or {@link ColumnBuilder}.
|
||||
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
|
||||
* spacetime.reducer(
|
||||
* 'create_user',
|
||||
* {
|
||||
* username: t.string(),
|
||||
* email: t.string(),
|
||||
* },
|
||||
* (ctx, { username, email }) => {
|
||||
* // Access the 'user' table from the database view in the context
|
||||
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
||||
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
reducer<Params extends ParamsObj>(
|
||||
name: string,
|
||||
params: Params,
|
||||
fn: Reducer<S, Params>
|
||||
): Reducer<S, Params>;
|
||||
reducer(name: string, fn: Reducer<S, {}>): Reducer<S, {}>;
|
||||
reducer<Params extends ParamsObj>(
|
||||
name: string,
|
||||
paramsOrFn: Params | Reducer<S, any>,
|
||||
fn?: Reducer<S, Params>
|
||||
): Reducer<S, Params> {
|
||||
if (typeof paramsOrFn === 'function') {
|
||||
// This is the case where params are omitted.
|
||||
// The second argument is the reducer function.
|
||||
// We pass an empty object for the params.
|
||||
reducer(this.#ctx, name, {}, paramsOrFn);
|
||||
return paramsOrFn;
|
||||
} else {
|
||||
// This is the case where params are provided.
|
||||
// The second argument is the params object, and the third is the function.
|
||||
// The `fn` parameter is guaranteed to be defined here.
|
||||
reducer(this.#ctx, name, paramsOrFn, fn!);
|
||||
return fn!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an initialization reducer that runs when the SpacetimeDB module is published
|
||||
* for the first time.
|
||||
*
|
||||
* This function is useful to set up any initial state of your database that is guaranteed
|
||||
* to run only once, and before any other reducers or client connections.
|
||||
*
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
* @param {Reducer<S, {}>} fn - The initialization reducer function.
|
||||
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.init((ctx) => {
|
||||
* ctx.db.user.insert({ username: 'admin', email: 'admin@example.com' });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
init(fn: Reducer<S, {}>): void;
|
||||
init(name: string, fn: Reducer<S, {}>): void;
|
||||
init(nameOrFn: any, maybeFn?: Reducer<S, {}>): void {
|
||||
const [name, fn] =
|
||||
typeof nameOrFn === 'string' ? [nameOrFn, maybeFn] : ['init', nameOrFn];
|
||||
init(this.#ctx, name, {}, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a new client establishes a connection.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
*
|
||||
* @param fn - The reducer function to execute on client connection.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.clientConnected(
|
||||
* (ctx) => {
|
||||
* console.log(`Client ${ctx.connectionId} connected`);
|
||||
* }
|
||||
* );
|
||||
*/
|
||||
clientConnected(fn: Reducer<S, {}>): void;
|
||||
clientConnected(name: string, fn: Reducer<S, {}>): void;
|
||||
clientConnected(nameOrFn: any, maybeFn?: Reducer<S, {}>): void {
|
||||
const [name, fn] =
|
||||
typeof nameOrFn === 'string'
|
||||
? [nameOrFn, maybeFn]
|
||||
: ['on_connect', nameOrFn];
|
||||
clientConnected(this.#ctx, name, {}, fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
|
||||
* This function allows you to define custom logic that should execute
|
||||
* whenever a client disconnects.
|
||||
* @template S - The inferred schema type of the SpacetimeDB module.
|
||||
*
|
||||
* @param fn - The reducer function to execute on client disconnection.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* spacetime.clientDisconnected(
|
||||
* (ctx) => {
|
||||
* console.log(`Client ${ctx.connectionId} disconnected`);
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
clientDisconnected(fn: Reducer<S, {}>): void;
|
||||
clientDisconnected(name: string, fn: Reducer<S, {}>): void;
|
||||
clientDisconnected(nameOrFn: any, maybeFn?: Reducer<S, {}>): void {
|
||||
const [name, fn] =
|
||||
typeof nameOrFn === 'string'
|
||||
? [nameOrFn, maybeFn]
|
||||
: ['on_disconnect', nameOrFn];
|
||||
clientDisconnected(this.#ctx, name, {}, fn);
|
||||
}
|
||||
|
||||
view<Ret extends ViewReturnTypeBuilder>(
|
||||
opts: ViewOpts,
|
||||
ret: Ret,
|
||||
fn: ViewFn<S, {}, Ret>
|
||||
): void {
|
||||
defineView(this.#ctx, opts, false, {}, ret, fn);
|
||||
}
|
||||
|
||||
// TODO: re-enable once parameterized views are supported in SQL
|
||||
// view<Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// ret: Ret,
|
||||
// fn: ViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// params: Params,
|
||||
// ret: Ret,
|
||||
// fn: ViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// paramsOrRet: Ret | Params,
|
||||
// retOrFn: ViewFn<S, {}, Ret> | Ret,
|
||||
// maybeFn?: ViewFn<S, Params, Ret>
|
||||
// ): void {
|
||||
// if (typeof retOrFn === 'function') {
|
||||
// defineView(name, false, {}, paramsOrRet as Ret, retOrFn);
|
||||
// } else {
|
||||
// defineView(name, false, paramsOrRet as Params, retOrFn, maybeFn!);
|
||||
// }
|
||||
// }
|
||||
|
||||
anonymousView<Ret extends ViewReturnTypeBuilder>(
|
||||
opts: ViewOpts,
|
||||
ret: Ret,
|
||||
fn: AnonymousViewFn<S, {}, Ret>
|
||||
): void {
|
||||
defineView(this.#ctx, opts, true, {}, ret, fn);
|
||||
}
|
||||
|
||||
// TODO: re-enable once parameterized views are supported in SQL
|
||||
// anonymousView<Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// ret: Ret,
|
||||
// fn: AnonymousViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// params: Params,
|
||||
// ret: Ret,
|
||||
// fn: AnonymousViewFn<S, {}, Ret>
|
||||
// ): void;
|
||||
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
||||
// opts: ViewOpts,
|
||||
// paramsOrRet: Ret | Params,
|
||||
// retOrFn: AnonymousViewFn<S, {}, Ret> | Ret,
|
||||
// maybeFn?: AnonymousViewFn<S, Params, Ret>
|
||||
// ): void {
|
||||
// if (typeof retOrFn === 'function') {
|
||||
// defineView(name, true, {}, paramsOrRet as Ret, retOrFn);
|
||||
// } else {
|
||||
// defineView(name, true, paramsOrRet as Params, retOrFn, maybeFn!);
|
||||
// }
|
||||
// }
|
||||
|
||||
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
||||
name: string,
|
||||
params: Params,
|
||||
ret: Ret,
|
||||
fn: ProcedureFn<S, Params, Ret>
|
||||
): ProcedureFn<S, Params, Ret>;
|
||||
procedure<Ret extends TypeBuilder<any, any>>(
|
||||
name: string,
|
||||
ret: Ret,
|
||||
fn: ProcedureFn<S, {}, Ret>
|
||||
): ProcedureFn<S, {}, Ret>;
|
||||
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
||||
name: string,
|
||||
paramsOrRet: Ret | Params,
|
||||
retOrFn: ProcedureFn<S, {}, Ret> | Ret,
|
||||
maybeFn?: ProcedureFn<S, Params, Ret>
|
||||
): ProcedureFn<S, Params, Ret> {
|
||||
if (typeof retOrFn === 'function') {
|
||||
procedure(this.#ctx, name, {}, paramsOrRet as Ret, retOrFn);
|
||||
return retOrFn;
|
||||
} else {
|
||||
procedure(this.#ctx, name, paramsOrRet as Params, retOrFn, maybeFn!);
|
||||
return maybeFn!;
|
||||
}
|
||||
}
|
||||
|
||||
clientVisibilityFilter = {
|
||||
sql: (filter: string) => {
|
||||
this.#ctx.moduleDef.rowLevelSecurity.push({ sql: filter });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the inferred schema type from a Schema instance
|
||||
*/
|
||||
export type InferSchema<SchemaDef extends Schema<any>> =
|
||||
SchemaDef extends Schema<infer S> ? S : never;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
...handles: H
|
||||
): Schema<TablesToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions (array overload)
|
||||
* @param handles - Array of table handles created by table() function
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
handles: H
|
||||
): Schema<TablesToSchema<H>>;
|
||||
|
||||
/**
|
||||
* Creates a schema from table definitions
|
||||
* @param args - Either an array of table handles or a variadic list of table handles
|
||||
* @returns ColumnBuilder representing the complete database schema
|
||||
* @example
|
||||
* ```ts
|
||||
* const s = schema(
|
||||
* table({ name: 'user' }, userType),
|
||||
* table({ name: 'post' }, postType)
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export function schema<const H extends readonly UntypedTableSchema[]>(
|
||||
...args: [H] | H
|
||||
): Schema<TablesToSchema<H>> {
|
||||
const handles = (
|
||||
args.length === 1 && Array.isArray(args[0]) ? args[0] : args
|
||||
) as H;
|
||||
|
||||
const ctx = new SchemaInner(ctx => {
|
||||
const tableDefs = handles.map(h => h.tableDef(ctx));
|
||||
ctx.moduleDef.tables.push(...tableDefs);
|
||||
|
||||
return tablesToSchema(ctx, handles);
|
||||
});
|
||||
|
||||
REGISTERED_SCHEMA = ctx;
|
||||
|
||||
return new Schema(ctx);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { schema } from '../lib/schema';
|
||||
import { schema } from './schema';
|
||||
import { table } from '../lib/table';
|
||||
import t from '../lib/type_builders';
|
||||
|
||||
|
||||
+16
-20
@@ -7,23 +7,19 @@ import {
|
||||
} from '../lib/algebraic_type';
|
||||
import type { Identity } from '../lib/identity';
|
||||
import type { OptionAlgebraicType } from '../lib/option';
|
||||
import type { ParamsObj } from './reducers';
|
||||
import {
|
||||
MODULE_DEF,
|
||||
registerTypesRecursively,
|
||||
resolveType,
|
||||
type UntypedSchemaDef,
|
||||
} from './schema';
|
||||
import type { ReadonlyTable } from './table';
|
||||
import type { ParamsObj } from '../lib/reducers';
|
||||
import { type UntypedSchemaDef } from '../lib/schema';
|
||||
import type { ReadonlyTable } from '../lib/table';
|
||||
import {
|
||||
RowBuilder,
|
||||
type Infer,
|
||||
type InferSpacetimeTypeOfTypeBuilder,
|
||||
type InferTypeOfRow,
|
||||
type TypeBuilder,
|
||||
} from './type_builders';
|
||||
import { bsatnBaseSize, toPascalCase } from './util';
|
||||
} from '../lib/type_builders';
|
||||
import { bsatnBaseSize, toPascalCase } from '../lib/util';
|
||||
import { type QueryBuilder, type RowTypedQuery } from './query';
|
||||
import type { SchemaInner } from './schema';
|
||||
|
||||
export type ViewCtx<S extends UntypedSchemaDef> = Readonly<{
|
||||
sender: Identity;
|
||||
@@ -90,6 +86,7 @@ export function defineView<
|
||||
Params extends ParamsObj,
|
||||
Ret extends ViewReturnTypeBuilder,
|
||||
>(
|
||||
ctx: SchemaInner,
|
||||
opts: ViewOpts,
|
||||
anon: Anonymous,
|
||||
params: Params,
|
||||
@@ -101,20 +98,19 @@ export function defineView<
|
||||
const paramsBuilder = new RowBuilder(params, toPascalCase(opts.name));
|
||||
|
||||
// Register return types if they are product types
|
||||
let returnType = registerTypesRecursively(ret).algebraicType;
|
||||
let returnType = ctx.registerTypesRecursively(ret).algebraicType;
|
||||
|
||||
const { typespace } = MODULE_DEF;
|
||||
const { typespace } = ctx;
|
||||
|
||||
const { value: paramType } = resolveType(
|
||||
typespace,
|
||||
registerTypesRecursively(paramsBuilder)
|
||||
const { value: paramType } = ctx.resolveType(
|
||||
ctx.registerTypesRecursively(paramsBuilder)
|
||||
);
|
||||
|
||||
MODULE_DEF.miscExports.push({
|
||||
ctx.moduleDef.miscExports.push({
|
||||
tag: 'View',
|
||||
value: {
|
||||
name: opts.name,
|
||||
index: (anon ? ANON_VIEWS : VIEWS).length,
|
||||
index: (anon ? ctx.anonViews : ctx.views).length,
|
||||
isPublic: opts.public,
|
||||
isAnonymous: anon,
|
||||
params: paramType,
|
||||
@@ -134,7 +130,7 @@ export function defineView<
|
||||
);
|
||||
}
|
||||
|
||||
(anon ? ANON_VIEWS : VIEWS).push({
|
||||
(anon ? ctx.anonViews : ctx.views).push({
|
||||
fn,
|
||||
deserializeParams: ProductType.makeDeserializer(paramType, typespace),
|
||||
serializeReturn: AlgebraicType.makeSerializer(returnType, typespace),
|
||||
@@ -149,8 +145,8 @@ type ViewInfo<F> = {
|
||||
returnTypeBaseSize: number;
|
||||
};
|
||||
|
||||
export const VIEWS: ViewInfo<ViewFn<any, any, any>>[] = [];
|
||||
export const ANON_VIEWS: ViewInfo<AnonymousViewFn<any, any, any>>[] = [];
|
||||
export type Views = ViewInfo<ViewFn<any, any, any>>[];
|
||||
export type AnonViews = ViewInfo<AnonymousViewFn<any, any, any>>[];
|
||||
|
||||
// A helper to get the product type out of a type builder.
|
||||
// This is only non-never if the type builder is an array.
|
||||
+32
-14
@@ -3,12 +3,18 @@ import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||
import { jsdoc } from 'eslint-plugin-jsdoc';
|
||||
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export default tseslint.config(
|
||||
jsdoc({
|
||||
rules: {
|
||||
'jsdoc/no-undefined-types': 'error',
|
||||
},
|
||||
}),
|
||||
{
|
||||
ignores: ['**/dist/**', '**/build/**', '**/coverage/**'],
|
||||
},
|
||||
@@ -48,7 +54,7 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: "off",
|
||||
reportUnusedDisableDirectives: 'off',
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tseslint.plugin,
|
||||
@@ -58,25 +64,37 @@ export default tseslint.config(
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-namespace': 'error',
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"destructuredArrayIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
destructuredArrayIgnorePattern: '^_',
|
||||
caughtErrorsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-restricted-syntax': [
|
||||
'error',
|
||||
{ selector: 'TSEnumDeclaration', message: 'Do not use enums; stick to JS-compatible types.' },
|
||||
{ selector: 'TSEnumDeclaration[const=true]', message: 'Do not use const enum; use unions or objects.' },
|
||||
{
|
||||
selector: 'TSEnumDeclaration',
|
||||
message: 'Do not use enums; stick to JS-compatible types.',
|
||||
},
|
||||
{
|
||||
selector: 'TSEnumDeclaration[const=true]',
|
||||
message: 'Do not use const enum; use unions or objects.',
|
||||
},
|
||||
{ selector: 'Decorator', message: 'Do not use decorators.' },
|
||||
],
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
"eslint-comments/no-unused-disable": "off",
|
||||
"@typescript-eslint/no-empty-object-type": ['error', { allowObjectTypes: 'always' }],
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
'eslint-comments/no-unused-disable': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': [
|
||||
'error',
|
||||
{ allowObjectTypes: 'always' },
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
+11
-6
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.7.0",
|
||||
"engines": { "node": ">=18.0.0", "pnpm": ">=9.0.0" },
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=9.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"format": "pnpm --filter ./crates/bindings-typescript run format && pnpm --filter ./docs run format && pnpm --filter ./crates/bindings-typescript/test-app run format && pnpm -r --filter './templates/**' run format",
|
||||
"lint": "pnpm --filter ./crates/bindings-typescript run lint && pnpm --filter ./docs run lint && pnpm --filter ./crates/bindings-typescript/test-app run lint && pnpm -r --filter './templates/**' run lint",
|
||||
"build": "pnpm --filter ./crates/bindings-typescript run build && pnpm --filter ./docs run build && pnpm --filter ./crates/bindings-typescript/test-app run build && pnpm -r --filter './templates/**' run build",
|
||||
"test": "pnpm --filter ./crates/bindings-typescript run test && pnpm --filter ./docs run test && pnpm --filter ./crates/bindings-typescript/examples/quickstart-chat run test && pnpm --filter ./crates/bindings-typescript/test-app run test",
|
||||
"generate": "pnpm --filter ./crates/bindings-typescript run generate && pnpm --filter ./docs run generate && pnpm --filter ./crates/bindings-typescript/test-app run generate && pnpm -r --filter './templates/**' run generate",
|
||||
"run-all": "pnpm -F ./crates/bindings-typescript -F ./crates/bindings-typescript/examples/quickstart-chat -F ./crates/bindings-typescript/examples/quickstart-chat -F ./crates/bindings-typescript/test-app -F ./docs run",
|
||||
"format": "pnpm run-all format && prettier eslint.config.js --write",
|
||||
"lint": "pnpm run-all lint && prettier eslint.config.js --check",
|
||||
"build": "pnpm run-all build",
|
||||
"test": "pnpm run-all test",
|
||||
"generate": "pnpm run-all generate",
|
||||
"clean": "pnpm -r exec rimraf dist .tsbuildinfo coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -17,6 +21,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||
"@typescript-eslint/parser": "^8.18.2",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-jsdoc": "^61.5.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
|
||||
Generated
+142
@@ -23,6 +23,9 @@ importers:
|
||||
eslint:
|
||||
specifier: ^9.17.0
|
||||
version: 9.33.0(jiti@2.5.1)
|
||||
eslint-plugin-jsdoc:
|
||||
specifier: ^61.5.0
|
||||
version: 61.5.0(eslint@9.33.0(jiti@2.5.1))
|
||||
eslint-plugin-react-hooks:
|
||||
specifier: ^5.0.0
|
||||
version: 5.2.0(eslint@9.33.0(jiti@2.5.1))
|
||||
@@ -108,6 +111,9 @@ importers:
|
||||
eslint:
|
||||
specifier: ^9.33.0
|
||||
version: 9.33.0(jiti@2.5.1)
|
||||
eslint-plugin-jsdoc:
|
||||
specifier: ^61.5.0
|
||||
version: 61.5.0(eslint@9.33.0(jiti@2.5.1))
|
||||
globals:
|
||||
specifier: ^15.14.0
|
||||
version: 15.15.0
|
||||
@@ -1714,6 +1720,14 @@ packages:
|
||||
'@emotion/memoize@0.7.4':
|
||||
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||
|
||||
'@es-joy/jsdoccomment@0.76.0':
|
||||
resolution: {integrity: sha512-g+RihtzFgGTx2WYCuTHbdOXJeAlGnROws0TeALx9ow/ZmOROOZkVg5wp/B44n0WJgI4SQFP1eWM2iRPlU2Y14w==}
|
||||
engines: {node: '>=20.11.0'}
|
||||
|
||||
'@es-joy/resolve.exports@1.2.0':
|
||||
resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.9':
|
||||
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3104,6 +3118,10 @@ packages:
|
||||
'@sinclair/typebox@0.27.8':
|
||||
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
|
||||
|
||||
'@sindresorhus/base62@1.0.0':
|
||||
resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sindresorhus/is@4.6.0':
|
||||
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3537,6 +3555,10 @@ packages:
|
||||
resolution: {integrity: sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/types@8.50.0':
|
||||
resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.40.0':
|
||||
resolution: {integrity: sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@@ -4254,6 +4276,10 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
are-docs-informative@0.0.2:
|
||||
resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
|
||||
@@ -4630,6 +4656,10 @@ packages:
|
||||
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
comment-parser@1.4.1:
|
||||
resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
common-path-prefix@3.0.0:
|
||||
resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==}
|
||||
|
||||
@@ -5154,6 +5184,12 @@ packages:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
eslint-plugin-jsdoc@61.5.0:
|
||||
resolution: {integrity: sha512-PR81eOGq4S7diVnV9xzFSBE4CDENRQGP0Lckkek8AdHtbj+6Bm0cItwlFnxsLFriJHspiE3mpu8U20eODyToIg==}
|
||||
engines: {node: '>=20.11.0'}
|
||||
peerDependencies:
|
||||
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0
|
||||
|
||||
eslint-plugin-react-hooks@5.2.0:
|
||||
resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -5696,6 +5732,9 @@ packages:
|
||||
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
html-entities@2.6.0:
|
||||
resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
@@ -6099,6 +6138,10 @@ packages:
|
||||
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
|
||||
hasBin: true
|
||||
|
||||
jsdoc-type-pratt-parser@6.10.0:
|
||||
resolution: {integrity: sha512-+LexoTRyYui5iOhJGn13N9ZazL23nAHGkXsa1p/C8yeq79WRfLBag6ZZ0FQG2aRoc9yfo59JT9EYCQonOkHKkQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
jsdom@26.1.0:
|
||||
resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -6805,6 +6848,9 @@ packages:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
object-deep-merge@2.0.0:
|
||||
resolution: {integrity: sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -6942,6 +6988,9 @@ packages:
|
||||
parse-entities@4.0.2:
|
||||
resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
|
||||
|
||||
parse-imports-exports@0.2.4:
|
||||
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
|
||||
|
||||
parse-json@5.2.0:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -6949,6 +6998,9 @@ packages:
|
||||
parse-numeric-range@1.3.0:
|
||||
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
||||
|
||||
parse-statements@1.0.11:
|
||||
resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
|
||||
|
||||
parse5-htmlparser2-tree-adapter@7.1.0:
|
||||
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
|
||||
|
||||
@@ -7880,6 +7932,10 @@ packages:
|
||||
requires-port@1.0.0:
|
||||
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
|
||||
|
||||
reserved-identifiers@1.2.0:
|
||||
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
resolve-alpn@1.2.1:
|
||||
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
|
||||
|
||||
@@ -8000,6 +8056,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
semver@7.7.3:
|
||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
send@0.19.0:
|
||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -8138,6 +8199,15 @@ packages:
|
||||
space-separated-tokens@2.0.2:
|
||||
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
|
||||
|
||||
spdx-exceptions@2.5.0:
|
||||
resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==}
|
||||
|
||||
spdx-expression-parse@4.0.0:
|
||||
resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==}
|
||||
|
||||
spdx-license-ids@3.0.22:
|
||||
resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==}
|
||||
|
||||
spdy-transport@3.0.0:
|
||||
resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==}
|
||||
|
||||
@@ -8390,6 +8460,10 @@ packages:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
|
||||
to-valid-identifier@1.0.0:
|
||||
resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -11515,6 +11589,16 @@ snapshots:
|
||||
'@emotion/memoize@0.7.4':
|
||||
optional: true
|
||||
|
||||
'@es-joy/jsdoccomment@0.76.0':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
'@typescript-eslint/types': 8.50.0
|
||||
comment-parser: 1.4.1
|
||||
esquery: 1.6.0
|
||||
jsdoc-type-pratt-parser: 6.10.0
|
||||
|
||||
'@es-joy/resolve.exports@1.2.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.9':
|
||||
optional: true
|
||||
|
||||
@@ -13077,6 +13161,8 @@ snapshots:
|
||||
|
||||
'@sinclair/typebox@0.27.8': {}
|
||||
|
||||
'@sindresorhus/base62@1.0.0': {}
|
||||
|
||||
'@sindresorhus/is@4.6.0': {}
|
||||
|
||||
'@sindresorhus/is@5.6.0': {}
|
||||
@@ -13655,6 +13741,8 @@ snapshots:
|
||||
|
||||
'@typescript-eslint/types@8.40.0': {}
|
||||
|
||||
'@typescript-eslint/types@8.50.0': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.40.0(typescript@5.6.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.40.0(typescript@5.6.3)
|
||||
@@ -15035,6 +15123,8 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
are-docs-informative@0.0.2: {}
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
arg@5.0.2: {}
|
||||
@@ -15440,6 +15530,8 @@ snapshots:
|
||||
|
||||
commander@8.3.0: {}
|
||||
|
||||
comment-parser@1.4.1: {}
|
||||
|
||||
common-path-prefix@3.0.0: {}
|
||||
|
||||
compressible@2.0.18:
|
||||
@@ -15963,6 +16055,26 @@ snapshots:
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
eslint-plugin-jsdoc@61.5.0(eslint@9.33.0(jiti@2.5.1)):
|
||||
dependencies:
|
||||
'@es-joy/jsdoccomment': 0.76.0
|
||||
'@es-joy/resolve.exports': 1.2.0
|
||||
are-docs-informative: 0.0.2
|
||||
comment-parser: 1.4.1
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
espree: 10.4.0
|
||||
esquery: 1.6.0
|
||||
html-entities: 2.6.0
|
||||
object-deep-merge: 2.0.0
|
||||
parse-imports-exports: 0.2.4
|
||||
semver: 7.7.3
|
||||
spdx-expression-parse: 4.0.0
|
||||
to-valid-identifier: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-react-hooks@5.2.0(eslint@9.33.0(jiti@2.5.1)):
|
||||
dependencies:
|
||||
eslint: 9.33.0(jiti@2.5.1)
|
||||
@@ -16696,6 +16808,8 @@ snapshots:
|
||||
dependencies:
|
||||
whatwg-encoding: 3.1.1
|
||||
|
||||
html-entities@2.6.0: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-minifier-terser@6.1.0:
|
||||
@@ -17072,6 +17186,8 @@ snapshots:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
jsdoc-type-pratt-parser@6.10.0: {}
|
||||
|
||||
jsdom@26.1.0:
|
||||
dependencies:
|
||||
cssstyle: 4.6.0
|
||||
@@ -18102,6 +18218,8 @@ snapshots:
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
object-deep-merge@2.0.0: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
object-keys@1.1.1: {}
|
||||
@@ -18261,6 +18379,10 @@ snapshots:
|
||||
is-decimal: 2.0.1
|
||||
is-hexadecimal: 2.0.1
|
||||
|
||||
parse-imports-exports@0.2.4:
|
||||
dependencies:
|
||||
parse-statements: 1.0.11
|
||||
|
||||
parse-json@5.2.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
@@ -18270,6 +18392,8 @@ snapshots:
|
||||
|
||||
parse-numeric-range@1.3.0: {}
|
||||
|
||||
parse-statements@1.0.11: {}
|
||||
|
||||
parse5-htmlparser2-tree-adapter@7.1.0:
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
@@ -19316,6 +19440,8 @@ snapshots:
|
||||
|
||||
requires-port@1.0.0: {}
|
||||
|
||||
reserved-identifiers@1.2.0: {}
|
||||
|
||||
resolve-alpn@1.2.1: {}
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
@@ -19445,6 +19571,8 @@ snapshots:
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
send@0.19.0:
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
@@ -19633,6 +19761,15 @@ snapshots:
|
||||
|
||||
space-separated-tokens@2.0.2: {}
|
||||
|
||||
spdx-exceptions@2.5.0: {}
|
||||
|
||||
spdx-expression-parse@4.0.0:
|
||||
dependencies:
|
||||
spdx-exceptions: 2.5.0
|
||||
spdx-license-ids: 3.0.22
|
||||
|
||||
spdx-license-ids@3.0.22: {}
|
||||
|
||||
spdy-transport@3.0.0:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
@@ -19896,6 +20033,11 @@ snapshots:
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
|
||||
to-valid-identifier@1.0.0:
|
||||
dependencies:
|
||||
'@sindresorhus/base62': 1.0.0
|
||||
reserved-identifiers: 1.2.0
|
||||
|
||||
toidentifier@1.0.1: {}
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
||||
Reference in New Issue
Block a user