diff --git a/Cargo.lock b/Cargo.lock index ed929d1e017..96b3e83cf87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -616,6 +616,16 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -2350,6 +2360,24 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gen-bindings" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.50", + "replace-spacetimedb", +] + +[[package]] +name = "generate-client-api" +version = "0.1.0" +dependencies = [ + "anyhow", + "replace-spacetimedb", + "tempfile", +] + [[package]] name = "generator" version = "0.8.7" @@ -2439,6 +2467,19 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "gzip-header" version = "1.0.0" @@ -3061,6 +3102,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "imara-diff" version = "0.1.8" @@ -5755,6 +5812,16 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" +[[package]] +name = "replace-spacetimedb" +version = "0.1.0" +dependencies = [ + "clap 4.5.50", + "ignore", + "memchr", + "regex", +] + [[package]] name = "reqwest" version = "0.11.27" diff --git a/Cargo.toml b/Cargo.toml index 5bb1d180aed..d50806764db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,9 @@ members = [ "sdks/rust/tests/connect_disconnect_client", "tools/upgrade-version", "tools/license-check", + "tools/replace-spacetimedb", + "tools/generate-client-api", + "tools/gen-bindings", "crates/bindings-typescript/test-app/server", "crates/bindings-typescript/test-react-router-app/server", ] diff --git a/crates/bindings-typescript/examples/basic-react/package.json b/crates/bindings-typescript/examples/basic-react/package.json index cb30120ed8f..b9f51487874 100644 --- a/crates/bindings-typescript/examples/basic-react/package.json +++ b/crates/bindings-typescript/examples/basic-react/package.json @@ -7,13 +7,15 @@ "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", - "local": "spacetime publish --project-path spacetimedb --server local && spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb", - "deploy": "spacetime publish --project-path spacetimedb && spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb" + "generate": "cargo run -p gen-bindings -- --out-dir src/module_bindings --project-path ../../../cli/templates/basic-typescript/server && prettier --write src/module_bindings", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb", + "spacetime:publish:local": "spacetime publish --project-path server --server local", + "spacetime:publish": "spacetime publish --project-path server --server testnet" }, "dependencies": { "spacetimedb": "workspace:*", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1" }, "devDependencies": { "@types/react": "^18.3.18", diff --git a/crates/bindings-typescript/examples/basic-react/src/main.tsx b/crates/bindings-typescript/examples/basic-react/src/main.tsx index f176f6ef300..0cfb34e144b 100644 --- a/crates/bindings-typescript/examples/basic-react/src/main.tsx +++ b/crates/bindings-typescript/examples/basic-react/src/main.tsx @@ -8,7 +8,7 @@ import { DbConnection, ErrorContext } from './module_bindings/index.ts'; const HOST = import.meta.env.VITE_SPACETIMEDB_HOST ?? 'ws://localhost:3000'; const DB_NAME = import.meta.env.VITE_SPACETIMEDB_DB_NAME ?? 'my-db'; -const onConnect = (conn: DbConnection, identity: Identity, token: string) => { +const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { localStorage.setItem('auth_token', token); console.log( 'Connected to SpacetimeDB with identity:', diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/add_reducer.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/add_reducer.ts new file mode 100644 index 00000000000..bc8fe325253 --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/add_reducer.ts @@ -0,0 +1,70 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type Add = { + name: string; +}; +let _cached_Add_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const Add = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_Add_type_value) return _cached_Add_type_value; + _cached_Add_type_value = __AlgebraicTypeValue.Product({ elements: [] }); + _cached_Add_type_value.value.elements.push({ + name: 'name', + algebraicType: __AlgebraicTypeValue.String, + }); + return _cached_Add_type_value; + }, + + serialize(writer: __BinaryWriter, value: Add): void { + __AlgebraicTypeValue.serializeValue( + writer, + Add.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): Add { + return __AlgebraicTypeValue.deserializeValue( + reader, + Add.getTypeScriptAlgebraicType() + ); + }, +}; + +export default Add; diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/client_connected_reducer.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/client_connected_reducer.ts new file mode 100644 index 00000000000..a8b7233cef8 --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/client_connected_reducer.ts @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type ClientConnected = {}; +let _cached_ClientConnected_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const ClientConnected = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_ClientConnected_type_value) + return _cached_ClientConnected_type_value; + _cached_ClientConnected_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_ClientConnected_type_value.value.elements.push(); + return _cached_ClientConnected_type_value; + }, + + serialize(writer: __BinaryWriter, value: ClientConnected): void { + __AlgebraicTypeValue.serializeValue( + writer, + ClientConnected.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): ClientConnected { + return __AlgebraicTypeValue.deserializeValue( + reader, + ClientConnected.getTypeScriptAlgebraicType() + ); + }, +}; + +export default ClientConnected; diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/client_disconnected_reducer.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/client_disconnected_reducer.ts new file mode 100644 index 00000000000..385acd9bd3c --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/client_disconnected_reducer.ts @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type ClientDisconnected = {}; +let _cached_ClientDisconnected_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const ClientDisconnected = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_ClientDisconnected_type_value) + return _cached_ClientDisconnected_type_value; + _cached_ClientDisconnected_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_ClientDisconnected_type_value.value.elements.push(); + return _cached_ClientDisconnected_type_value; + }, + + serialize(writer: __BinaryWriter, value: ClientDisconnected): void { + __AlgebraicTypeValue.serializeValue( + writer, + ClientDisconnected.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): ClientDisconnected { + return __AlgebraicTypeValue.deserializeValue( + reader, + ClientDisconnected.getTypeScriptAlgebraicType() + ); + }, +}; + +export default ClientDisconnected; diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/index.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/index.ts new file mode 100644 index 00000000000..08fef081923 --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/index.ts @@ -0,0 +1,303 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.6.0 (commit 64812908d1dd2fb0ac1a44ae8c669306ce3f001e). + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +// Import and reexport all reducer arg types +import { Init } from './init_reducer.ts'; +export { Init }; +import { ClientConnected } from './client_connected_reducer.ts'; +export { ClientConnected }; +import { ClientDisconnected } from './client_disconnected_reducer.ts'; +export { ClientDisconnected }; +import { Add } from './add_reducer.ts'; +export { Add }; +import { SayHello } from './say_hello_reducer.ts'; +export { SayHello }; + +// Import and reexport all table handle types +import { PersonTableHandle } from './person_table.ts'; +export { PersonTableHandle }; + +// Import and reexport all types +import { Person } from './person_type.ts'; +export { Person }; + +const REMOTE_MODULE = { + tables: { + person: { + tableName: 'person' as const, + rowType: Person.getTypeScriptAlgebraicType(), + }, + }, + reducers: { + init: { + reducerName: 'init', + argsType: Init.getTypeScriptAlgebraicType(), + }, + client_connected: { + reducerName: 'client_connected', + argsType: ClientConnected.getTypeScriptAlgebraicType(), + }, + client_disconnected: { + reducerName: 'client_disconnected', + argsType: ClientDisconnected.getTypeScriptAlgebraicType(), + }, + add: { + reducerName: 'add', + argsType: Add.getTypeScriptAlgebraicType(), + }, + say_hello: { + reducerName: 'say_hello', + argsType: SayHello.getTypeScriptAlgebraicType(), + }, + }, + versionInfo: { + cliVersion: '1.6.0', + }, + // Constructors which are used by the DbConnectionImpl to + // extract type information from the generated RemoteModule. + // + // NOTE: This is not strictly necessary for `eventContextConstructor` because + // all we do is build a TypeScript object which we could have done inside the + // SDK, but if in the future we wanted to create a class this would be + // necessary because classes have methods, so we'll keep it. + eventContextConstructor: ( + imp: __DbConnectionImpl, + event: __Event + ) => { + return { + ...(imp as DbConnection), + event, + }; + }, + dbViewConstructor: (imp: __DbConnectionImpl) => { + return new RemoteTables(imp); + }, + reducersConstructor: ( + imp: __DbConnectionImpl, + setReducerFlags: SetReducerFlags + ) => { + return new RemoteReducers(imp, setReducerFlags); + }, + setReducerFlagsConstructor: () => { + return new SetReducerFlags(); + }, +}; + +// A type representing all the possible variants of a reducer. +export type Reducer = + | never + | { name: 'Init'; args: Init } + | { name: 'ClientConnected'; args: ClientConnected } + | { name: 'ClientDisconnected'; args: ClientDisconnected } + | { name: 'Add'; args: Add } + | { name: 'SayHello'; args: SayHello }; + +export class RemoteReducers { + constructor( + private connection: __DbConnectionImpl, + private setCallReducerFlags: SetReducerFlags + ) {} + + init() { + this.connection.callReducer( + 'init', + new Uint8Array(0), + this.setCallReducerFlags.initFlags + ); + } + + onInit(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('init', callback); + } + + removeOnInit(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('init', callback); + } + + clientConnected() { + this.connection.callReducer( + 'client_connected', + new Uint8Array(0), + this.setCallReducerFlags.clientConnectedFlags + ); + } + + onClientConnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('client_connected', callback); + } + + removeOnClientConnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('client_connected', callback); + } + + clientDisconnected() { + this.connection.callReducer( + 'client_disconnected', + new Uint8Array(0), + this.setCallReducerFlags.clientDisconnectedFlags + ); + } + + onClientDisconnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('client_disconnected', callback); + } + + removeOnClientDisconnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('client_disconnected', callback); + } + + add(name: string) { + const __args = { name }; + let __writer = new __BinaryWriter(1024); + Add.serialize(__writer, __args); + let __argsBuffer = __writer.getBuffer(); + this.connection.callReducer( + 'add', + __argsBuffer, + this.setCallReducerFlags.addFlags + ); + } + + onAdd(callback: (ctx: ReducerEventContext, name: string) => void) { + this.connection.onReducer('add', callback); + } + + removeOnAdd(callback: (ctx: ReducerEventContext, name: string) => void) { + this.connection.offReducer('add', callback); + } + + sayHello() { + this.connection.callReducer( + 'say_hello', + new Uint8Array(0), + this.setCallReducerFlags.sayHelloFlags + ); + } + + onSayHello(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('say_hello', callback); + } + + removeOnSayHello(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('say_hello', callback); + } +} + +export class SetReducerFlags { + initFlags: __CallReducerFlags = 'FullUpdate'; + init(flags: __CallReducerFlags) { + this.initFlags = flags; + } + + clientConnectedFlags: __CallReducerFlags = 'FullUpdate'; + clientConnected(flags: __CallReducerFlags) { + this.clientConnectedFlags = flags; + } + + clientDisconnectedFlags: __CallReducerFlags = 'FullUpdate'; + clientDisconnected(flags: __CallReducerFlags) { + this.clientDisconnectedFlags = flags; + } + + addFlags: __CallReducerFlags = 'FullUpdate'; + add(flags: __CallReducerFlags) { + this.addFlags = flags; + } + + sayHelloFlags: __CallReducerFlags = 'FullUpdate'; + sayHello(flags: __CallReducerFlags) { + this.sayHelloFlags = flags; + } +} + +export class RemoteTables { + constructor(private connection: __DbConnectionImpl) {} + + get person(): PersonTableHandle<'person'> { + // clientCache is a private property + return new PersonTableHandle( + ( + this.connection as unknown as { clientCache: __ClientCache } + ).clientCache.getOrCreateTable(REMOTE_MODULE.tables.person) + ); + } +} + +export class SubscriptionBuilder extends __SubscriptionBuilderImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> {} + +export class DbConnection extends __DbConnectionImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> { + static builder = (): __DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + > => { + return new __DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + >(REMOTE_MODULE, (imp: __DbConnectionImpl) => imp as DbConnection); + }; + subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} + +export type EventContext = __EventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type ReducerEventContext = __ReducerEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type SubscriptionEventContext = __SubscriptionEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; +export type ErrorContext = __ErrorContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/init_reducer.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/init_reducer.ts new file mode 100644 index 00000000000..12e0782fc1a --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/init_reducer.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type Init = {}; +let _cached_Init_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const Init = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_Init_type_value) return _cached_Init_type_value; + _cached_Init_type_value = __AlgebraicTypeValue.Product({ elements: [] }); + _cached_Init_type_value.value.elements.push(); + return _cached_Init_type_value; + }, + + serialize(writer: __BinaryWriter, value: Init): void { + __AlgebraicTypeValue.serializeValue( + writer, + Init.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): Init { + return __AlgebraicTypeValue.deserializeValue( + reader, + Init.getTypeScriptAlgebraicType() + ); + }, +}; + +export default Init; diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/person_table.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/person_table.ts new file mode 100644 index 00000000000..c50f51f9ff0 --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/person_table.ts @@ -0,0 +1,83 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; +import { Person } from './person_type'; +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; +declare type __keep = [EventContext, Reducer, RemoteReducers, RemoteTables]; + +/** + * Table handle for the table `person`. + * + * Obtain a handle from the [`person`] property on [`RemoteTables`], + * like `ctx.db.person`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.person.on_insert(...)`. + */ +export class PersonTableHandle + implements __TableHandle +{ + // phantom type to track the table name + readonly tableName!: TableName; + tableCache: __TableCache; + + constructor(tableCache: __TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + + onInsert = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.removeOnDelete(cb); + }; +} diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/person_type.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/person_type.ts new file mode 100644 index 00000000000..c3085f4f3ba --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/person_type.ts @@ -0,0 +1,70 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type Person = { + name: string; +}; +let _cached_Person_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const Person = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_Person_type_value) return _cached_Person_type_value; + _cached_Person_type_value = __AlgebraicTypeValue.Product({ elements: [] }); + _cached_Person_type_value.value.elements.push({ + name: 'name', + algebraicType: __AlgebraicTypeValue.String, + }); + return _cached_Person_type_value; + }, + + serialize(writer: __BinaryWriter, value: Person): void { + __AlgebraicTypeValue.serializeValue( + writer, + Person.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): Person { + return __AlgebraicTypeValue.deserializeValue( + reader, + Person.getTypeScriptAlgebraicType() + ); + }, +}; + +export default Person; diff --git a/crates/bindings-typescript/examples/basic-react/src/module_bindings/say_hello_reducer.ts b/crates/bindings-typescript/examples/basic-react/src/module_bindings/say_hello_reducer.ts new file mode 100644 index 00000000000..e0cdd2d5259 --- /dev/null +++ b/crates/bindings-typescript/examples/basic-react/src/module_bindings/say_hello_reducer.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type SayHello = {}; +let _cached_SayHello_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const SayHello = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_SayHello_type_value) return _cached_SayHello_type_value; + _cached_SayHello_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_SayHello_type_value.value.elements.push(); + return _cached_SayHello_type_value; + }, + + serialize(writer: __BinaryWriter, value: SayHello): void { + __AlgebraicTypeValue.serializeValue( + writer, + SayHello.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): SayHello { + return __AlgebraicTypeValue.deserializeValue( + reader, + SayHello.getTypeScriptAlgebraicType() + ); + }, +}; + +export default SayHello; diff --git a/crates/bindings-typescript/examples/basic-react/tsconfig.json b/crates/bindings-typescript/examples/basic-react/tsconfig.json index c7224f57541..46f00dbd6d1 100644 --- a/crates/bindings-typescript/examples/basic-react/tsconfig.json +++ b/crates/bindings-typescript/examples/basic-react/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["vite/client"], "module": "ESNext", "skipLibCheck": true, diff --git a/crates/bindings-typescript/examples/empty/package.json b/crates/bindings-typescript/examples/empty/package.json index 78d933bf832..eab974922a6 100644 --- a/crates/bindings-typescript/examples/empty/package.json +++ b/crates/bindings-typescript/examples/empty/package.json @@ -6,7 +6,11 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "preview": "vite preview" + "preview": "vite preview", + "generate": "cargo run -p gen-bindings -- --out-dir src/module_bindings --project-path ../../../cli/templates/basic-typescript/server && prettier --write src/module_bindings", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb", + "spacetime:publish:local": "spacetime publish --project-path server --server local", + "spacetime:publish": "spacetime publish --project-path server --server testnet" }, "dependencies": { "spacetimedb": "^1.5.0" diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/add_reducer.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/add_reducer.ts new file mode 100644 index 00000000000..bc8fe325253 --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/add_reducer.ts @@ -0,0 +1,70 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type Add = { + name: string; +}; +let _cached_Add_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const Add = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_Add_type_value) return _cached_Add_type_value; + _cached_Add_type_value = __AlgebraicTypeValue.Product({ elements: [] }); + _cached_Add_type_value.value.elements.push({ + name: 'name', + algebraicType: __AlgebraicTypeValue.String, + }); + return _cached_Add_type_value; + }, + + serialize(writer: __BinaryWriter, value: Add): void { + __AlgebraicTypeValue.serializeValue( + writer, + Add.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): Add { + return __AlgebraicTypeValue.deserializeValue( + reader, + Add.getTypeScriptAlgebraicType() + ); + }, +}; + +export default Add; diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/client_connected_reducer.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/client_connected_reducer.ts new file mode 100644 index 00000000000..a8b7233cef8 --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/client_connected_reducer.ts @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type ClientConnected = {}; +let _cached_ClientConnected_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const ClientConnected = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_ClientConnected_type_value) + return _cached_ClientConnected_type_value; + _cached_ClientConnected_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_ClientConnected_type_value.value.elements.push(); + return _cached_ClientConnected_type_value; + }, + + serialize(writer: __BinaryWriter, value: ClientConnected): void { + __AlgebraicTypeValue.serializeValue( + writer, + ClientConnected.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): ClientConnected { + return __AlgebraicTypeValue.deserializeValue( + reader, + ClientConnected.getTypeScriptAlgebraicType() + ); + }, +}; + +export default ClientConnected; diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/client_disconnected_reducer.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/client_disconnected_reducer.ts new file mode 100644 index 00000000000..385acd9bd3c --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/client_disconnected_reducer.ts @@ -0,0 +1,68 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type ClientDisconnected = {}; +let _cached_ClientDisconnected_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const ClientDisconnected = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_ClientDisconnected_type_value) + return _cached_ClientDisconnected_type_value; + _cached_ClientDisconnected_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_ClientDisconnected_type_value.value.elements.push(); + return _cached_ClientDisconnected_type_value; + }, + + serialize(writer: __BinaryWriter, value: ClientDisconnected): void { + __AlgebraicTypeValue.serializeValue( + writer, + ClientDisconnected.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): ClientDisconnected { + return __AlgebraicTypeValue.deserializeValue( + reader, + ClientDisconnected.getTypeScriptAlgebraicType() + ); + }, +}; + +export default ClientDisconnected; diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/index.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/index.ts new file mode 100644 index 00000000000..08fef081923 --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/index.ts @@ -0,0 +1,303 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +// This was generated using spacetimedb cli version 1.6.0 (commit 64812908d1dd2fb0ac1a44ae8c669306ce3f001e). + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +// Import and reexport all reducer arg types +import { Init } from './init_reducer.ts'; +export { Init }; +import { ClientConnected } from './client_connected_reducer.ts'; +export { ClientConnected }; +import { ClientDisconnected } from './client_disconnected_reducer.ts'; +export { ClientDisconnected }; +import { Add } from './add_reducer.ts'; +export { Add }; +import { SayHello } from './say_hello_reducer.ts'; +export { SayHello }; + +// Import and reexport all table handle types +import { PersonTableHandle } from './person_table.ts'; +export { PersonTableHandle }; + +// Import and reexport all types +import { Person } from './person_type.ts'; +export { Person }; + +const REMOTE_MODULE = { + tables: { + person: { + tableName: 'person' as const, + rowType: Person.getTypeScriptAlgebraicType(), + }, + }, + reducers: { + init: { + reducerName: 'init', + argsType: Init.getTypeScriptAlgebraicType(), + }, + client_connected: { + reducerName: 'client_connected', + argsType: ClientConnected.getTypeScriptAlgebraicType(), + }, + client_disconnected: { + reducerName: 'client_disconnected', + argsType: ClientDisconnected.getTypeScriptAlgebraicType(), + }, + add: { + reducerName: 'add', + argsType: Add.getTypeScriptAlgebraicType(), + }, + say_hello: { + reducerName: 'say_hello', + argsType: SayHello.getTypeScriptAlgebraicType(), + }, + }, + versionInfo: { + cliVersion: '1.6.0', + }, + // Constructors which are used by the DbConnectionImpl to + // extract type information from the generated RemoteModule. + // + // NOTE: This is not strictly necessary for `eventContextConstructor` because + // all we do is build a TypeScript object which we could have done inside the + // SDK, but if in the future we wanted to create a class this would be + // necessary because classes have methods, so we'll keep it. + eventContextConstructor: ( + imp: __DbConnectionImpl, + event: __Event + ) => { + return { + ...(imp as DbConnection), + event, + }; + }, + dbViewConstructor: (imp: __DbConnectionImpl) => { + return new RemoteTables(imp); + }, + reducersConstructor: ( + imp: __DbConnectionImpl, + setReducerFlags: SetReducerFlags + ) => { + return new RemoteReducers(imp, setReducerFlags); + }, + setReducerFlagsConstructor: () => { + return new SetReducerFlags(); + }, +}; + +// A type representing all the possible variants of a reducer. +export type Reducer = + | never + | { name: 'Init'; args: Init } + | { name: 'ClientConnected'; args: ClientConnected } + | { name: 'ClientDisconnected'; args: ClientDisconnected } + | { name: 'Add'; args: Add } + | { name: 'SayHello'; args: SayHello }; + +export class RemoteReducers { + constructor( + private connection: __DbConnectionImpl, + private setCallReducerFlags: SetReducerFlags + ) {} + + init() { + this.connection.callReducer( + 'init', + new Uint8Array(0), + this.setCallReducerFlags.initFlags + ); + } + + onInit(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('init', callback); + } + + removeOnInit(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('init', callback); + } + + clientConnected() { + this.connection.callReducer( + 'client_connected', + new Uint8Array(0), + this.setCallReducerFlags.clientConnectedFlags + ); + } + + onClientConnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('client_connected', callback); + } + + removeOnClientConnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('client_connected', callback); + } + + clientDisconnected() { + this.connection.callReducer( + 'client_disconnected', + new Uint8Array(0), + this.setCallReducerFlags.clientDisconnectedFlags + ); + } + + onClientDisconnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('client_disconnected', callback); + } + + removeOnClientDisconnected(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('client_disconnected', callback); + } + + add(name: string) { + const __args = { name }; + let __writer = new __BinaryWriter(1024); + Add.serialize(__writer, __args); + let __argsBuffer = __writer.getBuffer(); + this.connection.callReducer( + 'add', + __argsBuffer, + this.setCallReducerFlags.addFlags + ); + } + + onAdd(callback: (ctx: ReducerEventContext, name: string) => void) { + this.connection.onReducer('add', callback); + } + + removeOnAdd(callback: (ctx: ReducerEventContext, name: string) => void) { + this.connection.offReducer('add', callback); + } + + sayHello() { + this.connection.callReducer( + 'say_hello', + new Uint8Array(0), + this.setCallReducerFlags.sayHelloFlags + ); + } + + onSayHello(callback: (ctx: ReducerEventContext) => void) { + this.connection.onReducer('say_hello', callback); + } + + removeOnSayHello(callback: (ctx: ReducerEventContext) => void) { + this.connection.offReducer('say_hello', callback); + } +} + +export class SetReducerFlags { + initFlags: __CallReducerFlags = 'FullUpdate'; + init(flags: __CallReducerFlags) { + this.initFlags = flags; + } + + clientConnectedFlags: __CallReducerFlags = 'FullUpdate'; + clientConnected(flags: __CallReducerFlags) { + this.clientConnectedFlags = flags; + } + + clientDisconnectedFlags: __CallReducerFlags = 'FullUpdate'; + clientDisconnected(flags: __CallReducerFlags) { + this.clientDisconnectedFlags = flags; + } + + addFlags: __CallReducerFlags = 'FullUpdate'; + add(flags: __CallReducerFlags) { + this.addFlags = flags; + } + + sayHelloFlags: __CallReducerFlags = 'FullUpdate'; + sayHello(flags: __CallReducerFlags) { + this.sayHelloFlags = flags; + } +} + +export class RemoteTables { + constructor(private connection: __DbConnectionImpl) {} + + get person(): PersonTableHandle<'person'> { + // clientCache is a private property + return new PersonTableHandle( + ( + this.connection as unknown as { clientCache: __ClientCache } + ).clientCache.getOrCreateTable(REMOTE_MODULE.tables.person) + ); + } +} + +export class SubscriptionBuilder extends __SubscriptionBuilderImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> {} + +export class DbConnection extends __DbConnectionImpl< + RemoteTables, + RemoteReducers, + SetReducerFlags +> { + static builder = (): __DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + > => { + return new __DbConnectionBuilder< + DbConnection, + ErrorContext, + SubscriptionEventContext + >(REMOTE_MODULE, (imp: __DbConnectionImpl) => imp as DbConnection); + }; + subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} + +export type EventContext = __EventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type ReducerEventContext = __ReducerEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags, + Reducer +>; +export type SubscriptionEventContext = __SubscriptionEventContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; +export type ErrorContext = __ErrorContextInterface< + RemoteTables, + RemoteReducers, + SetReducerFlags +>; diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/init_reducer.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/init_reducer.ts new file mode 100644 index 00000000000..12e0782fc1a --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/init_reducer.ts @@ -0,0 +1,65 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type Init = {}; +let _cached_Init_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const Init = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_Init_type_value) return _cached_Init_type_value; + _cached_Init_type_value = __AlgebraicTypeValue.Product({ elements: [] }); + _cached_Init_type_value.value.elements.push(); + return _cached_Init_type_value; + }, + + serialize(writer: __BinaryWriter, value: Init): void { + __AlgebraicTypeValue.serializeValue( + writer, + Init.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): Init { + return __AlgebraicTypeValue.deserializeValue( + reader, + Init.getTypeScriptAlgebraicType() + ); + }, +}; + +export default Init; diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/person_table.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/person_table.ts new file mode 100644 index 00000000000..c50f51f9ff0 --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/person_table.ts @@ -0,0 +1,83 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; +import { Person } from './person_type'; +import { + type EventContext, + type Reducer, + RemoteReducers, + RemoteTables, +} from '.'; +declare type __keep = [EventContext, Reducer, RemoteReducers, RemoteTables]; + +/** + * Table handle for the table `person`. + * + * Obtain a handle from the [`person`] property on [`RemoteTables`], + * like `ctx.db.person`. + * + * Users are encouraged not to explicitly reference this type, + * but to directly chain method calls, + * like `ctx.db.person.on_insert(...)`. + */ +export class PersonTableHandle + implements __TableHandle +{ + // phantom type to track the table name + readonly tableName!: TableName; + tableCache: __TableCache; + + constructor(tableCache: __TableCache) { + this.tableCache = tableCache; + } + + count(): number { + return this.tableCache.count(); + } + + iter(): Iterable { + return this.tableCache.iter(); + } + + onInsert = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.onInsert(cb); + }; + + removeOnInsert = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.removeOnInsert(cb); + }; + + onDelete = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.onDelete(cb); + }; + + removeOnDelete = (cb: (ctx: EventContext, row: Person) => void) => { + return this.tableCache.removeOnDelete(cb); + }; +} diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/person_type.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/person_type.ts new file mode 100644 index 00000000000..c3085f4f3ba --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/person_type.ts @@ -0,0 +1,70 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type Person = { + name: string; +}; +let _cached_Person_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const Person = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_Person_type_value) return _cached_Person_type_value; + _cached_Person_type_value = __AlgebraicTypeValue.Product({ elements: [] }); + _cached_Person_type_value.value.elements.push({ + name: 'name', + algebraicType: __AlgebraicTypeValue.String, + }); + return _cached_Person_type_value; + }, + + serialize(writer: __BinaryWriter, value: Person): void { + __AlgebraicTypeValue.serializeValue( + writer, + Person.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): Person { + return __AlgebraicTypeValue.deserializeValue( + reader, + Person.getTypeScriptAlgebraicType() + ); + }, +}; + +export default Person; diff --git a/crates/bindings-typescript/examples/empty/src/module_bindings/say_hello_reducer.ts b/crates/bindings-typescript/examples/empty/src/module_bindings/say_hello_reducer.ts new file mode 100644 index 00000000000..e0cdd2d5259 --- /dev/null +++ b/crates/bindings-typescript/examples/empty/src/module_bindings/say_hello_reducer.ts @@ -0,0 +1,67 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ClientCache as __ClientCache, + ConnectionId as __ConnectionId, + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + Identity as __Identity, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TableCache as __TableCache, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type CallReducerFlags as __CallReducerFlags, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type TableHandle as __TableHandle, +} from 'spacetimedb'; + +export type SayHello = {}; +let _cached_SayHello_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const SayHello = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_SayHello_type_value) return _cached_SayHello_type_value; + _cached_SayHello_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_SayHello_type_value.value.elements.push(); + return _cached_SayHello_type_value; + }, + + serialize(writer: __BinaryWriter, value: SayHello): void { + __AlgebraicTypeValue.serializeValue( + writer, + SayHello.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): SayHello { + return __AlgebraicTypeValue.deserializeValue( + reader, + SayHello.getTypeScriptAlgebraicType() + ); + }, +}; + +export default SayHello; diff --git a/crates/bindings-typescript/examples/quickstart-chat/package.json b/crates/bindings-typescript/examples/quickstart-chat/package.json index 3de530f9985..26f0de419cf 100644 --- a/crates/bindings-typescript/examples/quickstart-chat/package.json +++ b/crates/bindings-typescript/examples/quickstart-chat/package.json @@ -10,10 +10,10 @@ "lint": "eslint . && prettier . --check --ignore-path ../../../../.prettierignore", "preview": "vite preview", "test": "vitest run", - "generate": "cargo build -p spacetimedb-standalone && cargo run -p spacetimedb-cli generate --lang typescript --out-dir src/module_bindings --project-path ../../../../modules/quickstart-chat && prettier --write src/module_bindings", - "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path server", - "spacetime:publish:local": "spacetime publish chat --project-path server --server local", - "spacetime:publish": "spacetime publish chat --project-path server --server testnet" + "generate": "cargo run -p gen-bindings -- --out-dir src/module_bindings --project-path ../../../../modules/quickstart-chat && prettier --write src/module_bindings", + "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path spacetimedb", + "spacetime:publish:local": "spacetime publish --project-path server --server local", + "spacetime:publish": "spacetime publish --project-path server --server testnet" }, "dependencies": { "spacetimedb": "workspace:*", diff --git a/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts b/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts index f30b1c1253b..2a73f959c72 100644 --- a/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts +++ b/crates/bindings-typescript/examples/quickstart-chat/src/module_bindings/index.ts @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 1.5.0 (commit 5bfc84351742a6a8dc717b6c0011946f2d1b632d). +// This was generated using spacetimedb cli version 1.6.0 (commit 64812908d1dd2fb0ac1a44ae8c669306ce3f001e). /* eslint-disable */ /* tslint:disable */ diff --git a/crates/bindings-typescript/examples/quickstart-chat/src/vite-env.d.ts b/crates/bindings-typescript/examples/quickstart-chat/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2a00..00000000000 --- a/crates/bindings-typescript/examples/quickstart-chat/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/crates/bindings-typescript/examples/quickstart-chat/tsconfig.app.json b/crates/bindings-typescript/examples/quickstart-chat/tsconfig.app.json index 2934471320f..11b9d355b80 100644 --- a/crates/bindings-typescript/examples/quickstart-chat/tsconfig.app.json +++ b/crates/bindings-typescript/examples/quickstart-chat/tsconfig.app.json @@ -4,6 +4,7 @@ "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["vite/client"], "module": "ESNext", "skipLibCheck": true, diff --git a/crates/bindings-typescript/package.json b/crates/bindings-typescript/package.json index b1dd6daf6a9..6fed060d097 100644 --- a/crates/bindings-typescript/package.json +++ b/crates/bindings-typescript/package.json @@ -34,9 +34,12 @@ "brotli-size": "brotli-size dist/index.js", "size": "pnpm -s build && size-limit", "generate:moduledef": "cargo run -p spacetimedb-codegen --example regen-typescript-moduledef && prettier --write src/lib/autogen", - "generate:client-api": "cargo build -p spacetimedb-standalone && cargo run -p spacetimedb-client-api-messages --example get_ws_schema > ws_schema.json && cargo run -p spacetimedb-cli generate --lang typescript --out-dir src/sdk/client_api --module-def ws_schema.json && rm ws_schema.json && find src/sdk/client_api -type f -exec perl -pi -e 's#spacetimedb#../../index#g' {} + && prettier --write src/sdk/client_api", + "generate:client-api": "cargo run -p generate-client-api && prettier --write src/sdk/client_api", "generate:test-app": "pnpm --filter @clockworklabs/test-app generate", - "generate": "pnpm generate:moduledef && pnpm generate:client-api && pnpm generate:test-app", + "generate:examples:quickstart-chat": "pnpm --filter @clockworklabs/quickstart-chat generate", + "generate:examples:basic-react": "pnpm --filter @clockworklabs/basic-react generate", + "generate:examples:empty": "pnpm --filter @clockworklabs/empty-client generate", + "generate": "pnpm generate:moduledef && pnpm generate:client-api && pnpm generate:test-app && pnpm generate:examples:quickstart-chat && pnpm generate:examples:basic-react && pnpm generate:examples:empty", "prepublishOnly": "pnpm run build && pnpm run test && pnpm run size" }, "main": "dist/index.cjs", diff --git a/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_type.ts b/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_type.ts index f8a0566672b..e10a92d8844 100644 --- a/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_type.ts +++ b/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_type.ts @@ -19,12 +19,20 @@ import { import { RawColumnDefaultValueV9 } from './raw_column_default_value_v_9_type'; // Mark import as potentially unused declare type __keep_RawColumnDefaultValueV9 = RawColumnDefaultValueV9; +import { RawProcedureDefV9 } from './raw_procedure_def_v_9_type'; +// Mark import as potentially unused +declare type __keep_RawProcedureDefV9 = RawProcedureDefV9; +import { RawViewDefV9 } from './raw_view_def_v_9_type'; +// Mark import as potentially unused +declare type __keep_RawViewDefV9 = RawViewDefV9; import * as RawMiscModuleExportV9Variants from './raw_misc_module_export_v_9_variants'; // The tagged union or sum type for the algebraic type `RawMiscModuleExportV9`. export type RawMiscModuleExportV9 = - RawMiscModuleExportV9Variants.ColumnDefaultValue; + | RawMiscModuleExportV9Variants.ColumnDefaultValue + | RawMiscModuleExportV9Variants.Procedure + | RawMiscModuleExportV9Variants.View; let _cached_RawMiscModuleExportV9_type_value: __AlgebraicTypeType | null = null; @@ -42,6 +50,13 @@ export const RawMiscModuleExportV9 = { tag: 'ColumnDefaultValue', value, }), + Procedure: ( + value: RawProcedureDefV9 + ): RawMiscModuleExportV9Variants.Procedure => ({ tag: 'Procedure', value }), + View: (value: RawViewDefV9): RawMiscModuleExportV9Variants.View => ({ + tag: 'View', + value, + }), getTypeScriptAlgebraicType(): __AlgebraicTypeType { if (_cached_RawMiscModuleExportV9_type_value) @@ -49,10 +64,17 @@ export const RawMiscModuleExportV9 = { _cached_RawMiscModuleExportV9_type_value = __AlgebraicTypeValue.Sum({ variants: [], }); - _cached_RawMiscModuleExportV9_type_value.value.variants.push({ - name: 'ColumnDefaultValue', - algebraicType: RawColumnDefaultValueV9.getTypeScriptAlgebraicType(), - }); + _cached_RawMiscModuleExportV9_type_value.value.variants.push( + { + name: 'ColumnDefaultValue', + algebraicType: RawColumnDefaultValueV9.getTypeScriptAlgebraicType(), + }, + { + name: 'Procedure', + algebraicType: RawProcedureDefV9.getTypeScriptAlgebraicType(), + }, + { name: 'View', algebraicType: RawViewDefV9.getTypeScriptAlgebraicType() } + ); return _cached_RawMiscModuleExportV9_type_value; }, diff --git a/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_variants.ts b/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_variants.ts index e65385fac01..a100dd6dc1c 100644 --- a/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_variants.ts +++ b/crates/bindings-typescript/src/lib/autogen/raw_misc_module_export_v_9_variants.ts @@ -19,8 +19,16 @@ import { import { RawColumnDefaultValueV9 as RawColumnDefaultValueV9Type } from './raw_column_default_value_v_9_type'; // Mark import as potentially unused declare type __keep_RawColumnDefaultValueV9Type = RawColumnDefaultValueV9Type; +import { RawProcedureDefV9 as RawProcedureDefV9Type } from './raw_procedure_def_v_9_type'; +// Mark import as potentially unused +declare type __keep_RawProcedureDefV9Type = RawProcedureDefV9Type; +import { RawViewDefV9 as RawViewDefV9Type } from './raw_view_def_v_9_type'; +// Mark import as potentially unused +declare type __keep_RawViewDefV9Type = RawViewDefV9Type; export type ColumnDefaultValue = { tag: 'ColumnDefaultValue'; value: RawColumnDefaultValueV9Type; }; +export type Procedure = { tag: 'Procedure'; value: RawProcedureDefV9Type }; +export type View = { tag: 'View'; value: RawViewDefV9Type }; diff --git a/crates/bindings-typescript/src/lib/autogen/raw_procedure_def_v_9_type.ts b/crates/bindings-typescript/src/lib/autogen/raw_procedure_def_v_9_type.ts new file mode 100644 index 00000000000..8c422c869be --- /dev/null +++ b/crates/bindings-typescript/src/lib/autogen/raw_procedure_def_v_9_type.ts @@ -0,0 +1,77 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ConnectionId as __ConnectionId, + Identity as __Identity, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type TableHandle as __TableHandle, +} from '../../index'; +import { AlgebraicType } from './algebraic_type_type'; +// Mark import as potentially unused +declare type __keep_AlgebraicType = AlgebraicType; +import { ProductType } from './product_type_type'; +// Mark import as potentially unused +declare type __keep_ProductType = ProductType; + +export type RawProcedureDefV9 = { + name: string; + params: ProductType; + returnType: AlgebraicType; +}; +let _cached_RawProcedureDefV9_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const RawProcedureDefV9 = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_RawProcedureDefV9_type_value) + return _cached_RawProcedureDefV9_type_value; + _cached_RawProcedureDefV9_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_RawProcedureDefV9_type_value.value.elements.push( + { name: 'name', algebraicType: __AlgebraicTypeValue.String }, + { + name: 'params', + algebraicType: ProductType.getTypeScriptAlgebraicType(), + }, + { + name: 'returnType', + algebraicType: AlgebraicType.getTypeScriptAlgebraicType(), + } + ); + return _cached_RawProcedureDefV9_type_value; + }, + + serialize(writer: __BinaryWriter, value: RawProcedureDefV9): void { + __AlgebraicTypeValue.serializeValue( + writer, + RawProcedureDefV9.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): RawProcedureDefV9 { + return __AlgebraicTypeValue.deserializeValue( + reader, + RawProcedureDefV9.getTypeScriptAlgebraicType() + ); + }, +}; + +export default RawProcedureDefV9; diff --git a/crates/bindings-typescript/src/lib/autogen/raw_view_def_v_9_type.ts b/crates/bindings-typescript/src/lib/autogen/raw_view_def_v_9_type.ts new file mode 100644 index 00000000000..d2386eb4301 --- /dev/null +++ b/crates/bindings-typescript/src/lib/autogen/raw_view_def_v_9_type.ts @@ -0,0 +1,80 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +/* eslint-disable */ +/* tslint:disable */ +import { + AlgebraicType as __AlgebraicTypeValue, + BinaryReader as __BinaryReader, + BinaryWriter as __BinaryWriter, + ConnectionId as __ConnectionId, + Identity as __Identity, + TimeDuration as __TimeDuration, + Timestamp as __Timestamp, + deepEqual as __deepEqual, + type AlgebraicType as __AlgebraicTypeType, + type AlgebraicTypeVariants as __AlgebraicTypeVariants, + type TableHandle as __TableHandle, +} from '../../index'; +import { AlgebraicType } from './algebraic_type_type'; +// Mark import as potentially unused +declare type __keep_AlgebraicType = AlgebraicType; +import { ProductType } from './product_type_type'; +// Mark import as potentially unused +declare type __keep_ProductType = ProductType; + +export type RawViewDefV9 = { + name: string; + isPublic: boolean; + isAnonymous: boolean; + params: ProductType; + returnType: AlgebraicType; +}; +let _cached_RawViewDefV9_type_value: __AlgebraicTypeType | null = null; + +/** + * An object for generated helper functions. + */ +export const RawViewDefV9 = { + /** + * A function which returns this type represented as an AlgebraicType. + * This function is derived from the AlgebraicType used to generate this type. + */ + getTypeScriptAlgebraicType(): __AlgebraicTypeType { + if (_cached_RawViewDefV9_type_value) return _cached_RawViewDefV9_type_value; + _cached_RawViewDefV9_type_value = __AlgebraicTypeValue.Product({ + elements: [], + }); + _cached_RawViewDefV9_type_value.value.elements.push( + { name: 'name', algebraicType: __AlgebraicTypeValue.String }, + { name: 'isPublic', algebraicType: __AlgebraicTypeValue.Bool }, + { name: 'isAnonymous', algebraicType: __AlgebraicTypeValue.Bool }, + { + name: 'params', + algebraicType: ProductType.getTypeScriptAlgebraicType(), + }, + { + name: 'returnType', + algebraicType: AlgebraicType.getTypeScriptAlgebraicType(), + } + ); + return _cached_RawViewDefV9_type_value; + }, + + serialize(writer: __BinaryWriter, value: RawViewDefV9): void { + __AlgebraicTypeValue.serializeValue( + writer, + RawViewDefV9.getTypeScriptAlgebraicType(), + value + ); + }, + + deserialize(reader: __BinaryReader): RawViewDefV9 { + return __AlgebraicTypeValue.deserializeValue( + reader, + RawViewDefV9.getTypeScriptAlgebraicType() + ); + }, +}; + +export default RawViewDefV9; diff --git a/crates/bindings-typescript/src/sdk/client_api/index.ts b/crates/bindings-typescript/src/sdk/client_api/index.ts index 5e3cc809133..946b62f91bb 100644 --- a/crates/bindings-typescript/src/sdk/client_api/index.ts +++ b/crates/bindings-typescript/src/sdk/client_api/index.ts @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using ../../index cli version 1.5.0 (commit 5bfc84351742a6a8dc717b6c0011946f2d1b632d). +// This was generated using spacetimedb cli version 1.6.0 (commit 64812908d1dd2fb0ac1a44ae8c669306ce3f001e). /* eslint-disable */ /* tslint:disable */ @@ -102,7 +102,7 @@ const REMOTE_MODULE = { tables: {}, reducers: {}, versionInfo: { - cliVersion: '1.5.0', + cliVersion: '1.6.0', }, // Constructors which are used by the DbConnectionImpl to // extract type information from the generated RemoteModule. diff --git a/crates/bindings-typescript/test-app/package.json b/crates/bindings-typescript/test-app/package.json index ea7805f3deb..28c8f27673e 100644 --- a/crates/bindings-typescript/test-app/package.json +++ b/crates/bindings-typescript/test-app/package.json @@ -12,7 +12,7 @@ "format": "prettier . --write --ignore-path ../../../.prettierignore", "lint": "eslint . && prettier . --check --ignore-path ../../../.prettierignore", "preview": "vite preview", - "generate": "cargo build -p spacetimedb-standalone && cargo run -p spacetimedb-cli generate --lang typescript --out-dir src/module_bindings --project-path server && prettier --write src/module_bindings && node ./replace-spacetimedb.js && prettier --write src/module_bindings", + "generate": "cargo run -p gen-bindings -- --replacement ../../../src/index && prettier --write src/module_bindings", "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path server", "spacetime:start": "spacetime start server", "spacetime:publish:local": "spacetime publish game --project-path server --server local", diff --git a/crates/bindings-typescript/test-app/replace-spacetimedb.js b/crates/bindings-typescript/test-app/replace-spacetimedb.js deleted file mode 100644 index e5e6329ccad..00000000000 --- a/crates/bindings-typescript/test-app/replace-spacetimedb.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node - -/** - * Cross-platform replacement script - * Equivalent to: - * find src/module_bindings -type f -exec perl -pi -e 's#spacetimedb#../../../src/index#g' {} - */ - -import fs from 'fs'; -import path from 'path'; - -const ROOT = path.resolve('src/module_bindings'); -const SEARCH = /spacetimedb/g; -const REPLACEMENT = '../../../src/index'; - -function replaceInFile(filePath) { - try { - let content = fs.readFileSync(filePath, 'utf8'); - if (SEARCH.test(content)) { - const updated = content.replace(SEARCH, REPLACEMENT); - fs.writeFileSync(filePath, updated, 'utf8'); - console.log(`✔ Updated: ${filePath}`); - } - } catch (err) { - console.error(`✖ Error processing ${filePath}:`, err); - } -} - -function walkDir(dir) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) walkDir(fullPath); - else if (entry.isFile()) replaceInFile(fullPath); - } -} - -walkDir(ROOT); -console.log('✅ Replacement complete.'); diff --git a/crates/bindings-typescript/test-app/src/module_bindings/index.ts b/crates/bindings-typescript/test-app/src/module_bindings/index.ts index 104b8f6c14c..198f7c09880 100644 --- a/crates/bindings-typescript/test-app/src/module_bindings/index.ts +++ b/crates/bindings-typescript/test-app/src/module_bindings/index.ts @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using ../../../src/index cli version 1.5.0 (commit 5bfc84351742a6a8dc717b6c0011946f2d1b632d). +// This was generated using spacetimedb cli version 1.6.0 (commit 64812908d1dd2fb0ac1a44ae8c669306ce3f001e). /* eslint-disable */ /* tslint:disable */ @@ -88,7 +88,7 @@ const REMOTE_MODULE = { }, }, versionInfo: { - cliVersion: '1.5.0', + cliVersion: '1.6.0', }, // Constructors which are used by the DbConnectionImpl to // extract type information from the generated RemoteModule. diff --git a/crates/bindings-typescript/test-react-router-app/package.json b/crates/bindings-typescript/test-react-router-app/package.json index babf93f8200..24f50b1245f 100644 --- a/crates/bindings-typescript/test-react-router-app/package.json +++ b/crates/bindings-typescript/test-react-router-app/package.json @@ -12,7 +12,7 @@ "format": "prettier . --write --ignore-path ../../../.prettierignore", "lint": "eslint . && prettier . --check --ignore-path ../../../.prettierignore", "preview": "vite preview", - "generate": "cargo build -p spacetimedb-standalone && cargo run -p spacetimedb-cli generate --lang typescript --out-dir src/module_bindings --project-path server && prettier --write src/module_bindings && node ./replace-spacetimedb.js && prettier --write src/module_bindings", + "generate": "cargo run -p gen-bindings -- --replacement ../../../src/index && prettier --write src/module_bindings", "spacetime:generate": "spacetime generate --lang typescript --out-dir src/module_bindings --project-path server", "spacetime:start": "spacetime start server", "spacetime:publish:local": "spacetime publish game --project-path server --server local", diff --git a/crates/bindings-typescript/test-react-router-app/replace-spacetimedb.js b/crates/bindings-typescript/test-react-router-app/replace-spacetimedb.js deleted file mode 100644 index e5e6329ccad..00000000000 --- a/crates/bindings-typescript/test-react-router-app/replace-spacetimedb.js +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env node - -/** - * Cross-platform replacement script - * Equivalent to: - * find src/module_bindings -type f -exec perl -pi -e 's#spacetimedb#../../../src/index#g' {} - */ - -import fs from 'fs'; -import path from 'path'; - -const ROOT = path.resolve('src/module_bindings'); -const SEARCH = /spacetimedb/g; -const REPLACEMENT = '../../../src/index'; - -function replaceInFile(filePath) { - try { - let content = fs.readFileSync(filePath, 'utf8'); - if (SEARCH.test(content)) { - const updated = content.replace(SEARCH, REPLACEMENT); - fs.writeFileSync(filePath, updated, 'utf8'); - console.log(`✔ Updated: ${filePath}`); - } - } catch (err) { - console.error(`✖ Error processing ${filePath}:`, err); - } -} - -function walkDir(dir) { - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) walkDir(fullPath); - else if (entry.isFile()) replaceInFile(fullPath); - } -} - -walkDir(ROOT); -console.log('✅ Replacement complete.'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45fcf50fc2c..d3e37b0d08a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,6 +115,47 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)(terser@5.43.1)(tsx@4.20.4) + crates/bindings-typescript/examples/basic-react: + dependencies: + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + spacetimedb: + specifier: workspace:* + version: link:../.. + devDependencies: + '@types/react': + specifier: ^18.3.18 + version: 18.3.23 + '@types/react-dom': + specifier: ^18.3.5 + version: 18.3.7(@types/react@18.3.23) + '@vitejs/plugin-react': + specifier: ^5.0.2 + version: 5.0.2(vite@7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4)) + typescript: + specifier: ~5.6.2 + version: 5.6.3 + vite: + specifier: ^7.1.5 + version: 7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4) + + crates/bindings-typescript/examples/empty: + dependencies: + spacetimedb: + specifier: ^1.5.0 + version: 1.6.2(react@19.2.0)(undici@6.21.3) + devDependencies: + typescript: + specifier: ~5.6.2 + version: 5.6.3 + vite: + specifier: ^7.1.5 + version: 7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4) + crates/bindings-typescript/examples/quickstart-chat: dependencies: react: @@ -7882,6 +7923,17 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spacetimedb@1.6.2: + resolution: {integrity: sha512-XOdIgnTT1j2wZNiPdEIiv9uzhpnbqO0Rk/kOxVA9XhMMmrbtS3bXduBf1MzcaS/gJzXFK+Tf27j1HVa5SNbhLQ==} + peerDependencies: + react: ^18.0.0 || ^19.0.0-0 || ^19.0.0 + undici: ^6.19.2 + peerDependenciesMeta: + react: + optional: true + undici: + optional: true + spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} @@ -19040,6 +19092,15 @@ snapshots: space-separated-tokens@2.0.2: {} + spacetimedb@1.6.2(react@19.2.0)(undici@6.21.3): + dependencies: + base64-js: 1.5.1 + fast-text-encoding: 1.0.6 + prettier: 3.6.2 + optionalDependencies: + react: 19.2.0 + undici: 6.21.3 + spdy-transport@3.0.0: dependencies: debug: 4.4.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cc374f6f306..44dd8100610 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,8 @@ packages: - 'crates/bindings-typescript' - 'crates/bindings-typescript/test-app' - 'crates/bindings-typescript/examples/quickstart-chat' + - 'crates/bindings-typescript/examples/basic-react' + - 'crates/bindings-typescript/examples/empty' - 'modules/benchmarks-ts' - 'modules/module-test-ts' - 'modules/quickstart-chat-ts' diff --git a/tools/gen-bindings/Cargo.toml b/tools/gen-bindings/Cargo.toml new file mode 100644 index 00000000000..343564fba45 --- /dev/null +++ b/tools/gen-bindings/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gen-bindings" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +clap.workspace = true +replace-spacetimedb = { path = "../replace-spacetimedb" } \ No newline at end of file diff --git a/tools/gen-bindings/src/main.rs b/tools/gen-bindings/src/main.rs new file mode 100644 index 00000000000..d48f3cdeea8 --- /dev/null +++ b/tools/gen-bindings/src/main.rs @@ -0,0 +1,96 @@ +#![allow(clippy::disallowed_macros)] +use anyhow::{anyhow, Context, Result}; +use clap::Parser; +use replace_spacetimedb::{replace_in_tree, ReplaceOptions}; +use std::fs; +use std::path::Path; +use std::process::{Command, Stdio}; + +#[derive(Parser, Debug)] +#[command(version, about = "Build + generate TS bindings + replace + prettier")] +struct Cli { + /// Output directory for generated code + #[arg(long, default_value = "src/module_bindings")] + out_dir: String, + + /// Project path passed to spacetimedb-cli + #[arg(long, default_value = "server")] + project_path: String, + + /// Replacement for 'spacetimedb' (relative string used in imports) + #[arg(long)] + replacement: Option, +} + +fn run_inherit(cmd: &str, args: &[&str]) -> Result<()> { + let status = Command::new(cmd) + .args(args) + .stdin(Stdio::null()) + .status() + .with_context(|| format!("Failed to start {cmd}"))?; + if !status.success() { + return Err(anyhow!("Command failed: {cmd} {args:?} (exit {status})")); + } + Ok(()) +} + +fn main() -> Result<()> { + let args = Cli::parse(); + + // 1) Build prerequisite + run_inherit("cargo", &["build", "-p", "spacetimedb-standalone"])?; + + // 2) Ensure output directory exists + if !Path::new(&args.out_dir).exists() { + fs::create_dir_all(&args.out_dir).context("create output directory")?; + } + + // 3) Generate TS client from project + run_inherit( + "cargo", + &[ + "run", + "-p", + "spacetimedb-cli", + "generate", + "--lang", + "typescript", + "--out-dir", + &args.out_dir, + "--project-path", + &args.project_path, + ], + )?; + + if let Some(replacement) = &args.replacement { + + // 5) Replace "spacetimedb" references + let opts = ReplaceOptions { + dry_run: false, + only_exts: Some(vec![ + "ts".into(), + "tsx".into(), + "js".into(), + "jsx".into(), + "mts".into(), + "cts".into(), + "json".into(), + "d.ts".into(), + ]), + follow_symlinks: false, + include_hidden: false, + ignore_globs: vec![ + "**/node_modules/**".into(), + "**/dist/**".into(), + "**/target/**".into(), + ], + }; + + let stats = replace_in_tree(&args.out_dir, replacement, &opts)?; + println!( + "Replaced 'spacetimedb' → '{}' in {} files ({} occurrences).", + replacement, stats.files_changed, stats.occurrences + ); + } + Ok(()) +} diff --git a/tools/generate-client-api/Cargo.toml b/tools/generate-client-api/Cargo.toml new file mode 100644 index 00000000000..e6645aa15b0 --- /dev/null +++ b/tools/generate-client-api/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "generate-client-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +tempfile = "3" +anyhow = "1" +replace-spacetimedb = { path = "../replace-spacetimedb" } # use the library directly diff --git a/tools/generate-client-api/src/main.rs b/tools/generate-client-api/src/main.rs new file mode 100644 index 00000000000..0fb05f0c1b8 --- /dev/null +++ b/tools/generate-client-api/src/main.rs @@ -0,0 +1,107 @@ +#![allow(clippy::disallowed_macros)] +use anyhow::{anyhow, Context, Result}; +use replace_spacetimedb::{replace_in_tree, ReplaceOptions}; +use std::fs; +use std::path::Path; +use std::process::{Command, Stdio}; +use tempfile::NamedTempFile; + +/// Run a command inheriting stdio; error if it fails. +fn run_inherit(cmd: &str, args: &[&str]) -> Result<()> { + let status = Command::new(cmd) + .args(args) + .stdin(Stdio::null()) + .status() + .with_context(|| format!("Failed to start {cmd}"))?; + if !status.success() { + return Err(anyhow!("Command failed: {cmd} {args:?} (exit {status})")); + } + Ok(()) +} + +/// Run a command and return captured stdout as UTF-8 string. +fn run_capture(cmd: &str, args: &[&str]) -> Result { + let out = Command::new(cmd) + .args(args) + .stdin(Stdio::null()) + .output() + .with_context(|| format!("Failed to start {cmd}"))?; + if !out.status.success() { + return Err(anyhow!( + "Command failed: {cmd} {args:?} (exit {})", + out.status + )); + } + Ok(String::from_utf8(out.stdout)?) +} + +fn main() -> Result<()> { + let out_dir = "src/sdk/client_api"; + let replacement = "../../index"; + + // 1) Build prerequisite + run_inherit("cargo", &["build", "-p", "spacetimedb-standalone"])?; + + // 2) Get schema to a temp file (auto-cleaned) + let mut tmp_schema = NamedTempFile::new().context("create temp schema file")?; + let schema_json = run_capture( + "cargo", + &[ + "run", + "-p", + "spacetimedb-client-api-messages", + "--example", + "get_ws_schema", + ], + )?; + use std::io::Write; + tmp_schema.write_all(schema_json.as_bytes())?; + let schema_path = tmp_schema.path(); + + // 3) Ensure output directory exists + if !Path::new(out_dir).exists() { + fs::create_dir_all(out_dir).context("create output directory")?; + } + + // 4) Generate TS client + run_inherit( + "cargo", + &[ + "run", + "-p", + "spacetimedb-cli", + "generate", + "--lang", + "typescript", + "--out-dir", + out_dir, + "--module-def", + schema_path.to_str().unwrap(), + ], + )?; + + // 5) Replace "spacetimedb" references under out_dir + let opts = ReplaceOptions { + dry_run: false, + only_exts: Some(vec![ + "ts".into(), + "tsx".into(), + "js".into(), + "jsx".into(), + "mts".into(), + "cts".into(), + "json".into(), + "d.ts".into(), + ]), + follow_symlinks: false, + include_hidden: false, + ignore_globs: vec!["**/node_modules/**".into(), "**/dist/**".into()], + }; + let stats = replace_in_tree(out_dir, replacement, &opts)?; + println!( + "Replaced {} occurrences across {} files.", + stats.occurrences, stats.files_changed + ); + + Ok(()) +} diff --git a/tools/replace-spacetimedb/Cargo.toml b/tools/replace-spacetimedb/Cargo.toml new file mode 100644 index 00000000000..9cb80ef0282 --- /dev/null +++ b/tools/replace-spacetimedb/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "replace-spacetimedb" +version = "0.1.0" +edition = "2021" + +[lib] +name = "replace_spacetimedb" +path = "src/lib.rs" + +[[bin]] +name = "replace-spacetimedb" +path = "src/main.rs" + +[dependencies] +clap.workspace = true +regex.workspace = true +ignore = "0.4" +memchr = "2" \ No newline at end of file diff --git a/tools/replace-spacetimedb/src/lib.rs b/tools/replace-spacetimedb/src/lib.rs new file mode 100644 index 00000000000..d4e4b10ff22 --- /dev/null +++ b/tools/replace-spacetimedb/src/lib.rs @@ -0,0 +1,131 @@ +#![allow(clippy::disallowed_macros)] +use ignore::{DirEntry, WalkBuilder}; +use regex::Regex; +use std::fs; +use std::io; +use std::path::{Path}; + +#[derive(Clone, Debug)] +pub struct ReplaceOptions { + pub dry_run: bool, + pub only_exts: Option>, + pub follow_symlinks: bool, + pub include_hidden: bool, + pub ignore_globs: Vec, +} + +fn is_probably_text(bytes: &[u8]) -> bool { + !bytes.contains(&0) +} + +fn should_process_file(path: &Path, only_exts: &Option>) -> bool { + if let Some(exts) = only_exts { + if let Some(ext) = path.extension().and_then(|s| s.to_str()) { + return exts.iter().any(|e| e.eq_ignore_ascii_case(ext)); + } + return false; + } + true +} + +pub struct ReplaceStats { + pub files_changed: usize, + pub occurrences: usize, +} + +/// Replace only occurrences inside `} from 'spacetimedb'` or `} from "spacetimedb"` +/// (works for both `import { ... } from ...` and `export { ... } from ...`). +pub fn replace_in_tree( + root: impl AsRef, + replacement: &str, + options: &ReplaceOptions, +) -> io::Result { + let root = root.as_ref().to_path_buf(); + if !root.is_dir() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Not a directory: {}", root.display()), + )); + } + + // Match exactly the two forms you want. No backreferences needed. + // We intentionally DO NOT include a trailing semicolon so we preserve it (or its absence). + let re_single = Regex::new(r#"}\s*from\s*'spacetimedb'"#).unwrap(); + let re_double = Regex::new(r#"}\s*from\s*"spacetimedb""#).unwrap(); + + let mut builder = WalkBuilder::new(&root); + builder + .follow_links(options.follow_symlinks) + .hidden(!options.include_hidden) + .git_exclude(true) + .git_ignore(true) + .git_global(true); + builder.add_ignore("node_modules"); + builder.add_ignore("target"); + builder.add_ignore(".git"); + for g in &options.ignore_globs { + builder.add_ignore(g); + } + + let mut files_changed = 0usize; + let mut total_matches = 0usize; + + for result in builder.build() { + let entry: DirEntry = match result { + Ok(e) => e, + Err(err) => { + eprintln!("walk error: {err}"); + continue; + } + }; + + let path = entry.path(); + if !entry.file_type().map(|t| t.is_file()).unwrap_or(false) { + continue; + } + if !should_process_file(path, &options.only_exts) { + continue; + } + + let bytes = match fs::read(path) { + Ok(b) => b, + Err(err) => { + eprintln!("read error {}: {err}", path.display()); + continue; + } + }; + if !is_probably_text(&bytes) { + continue; + } + + let content = match String::from_utf8(bytes) { + Ok(s) => s, + Err(_) => continue, + }; + + // Count before replacing + let matches = re_single.find_iter(&content).count() + + re_double.find_iter(&content).count(); + if matches == 0 { + continue; + } + + // Do the replacements, preserving quote style + let updated1 = re_single.replace_all(&content, format!("}} from '{}'", replacement)); + let updated = re_double.replace_all(&updated1, format!("}} from \"{}\"", replacement)); + + if options.dry_run { + println!("[dry-run] {} ({} matches)", path.display(), matches); + } else if let Err(err) = fs::write(path, updated.as_ref()) { + eprintln!("write error {}: {err}", path.display()); + continue; + } else { + println!("✔ {} ({} matches)", path.display(), matches); + } + + files_changed += 1; + total_matches += matches; + } + + Ok(ReplaceStats { files_changed, occurrences: total_matches }) +} \ No newline at end of file diff --git a/tools/replace-spacetimedb/src/main.rs b/tools/replace-spacetimedb/src/main.rs new file mode 100644 index 00000000000..1fea98e23a4 --- /dev/null +++ b/tools/replace-spacetimedb/src/main.rs @@ -0,0 +1,64 @@ +#![allow(clippy::disallowed_macros)] +use clap::Parser; +use replace_spacetimedb::{replace_in_tree, ReplaceOptions}; + +/// Replace all occurrences of "spacetimedb" under with . +#[derive(Parser, Debug)] +#[command(version, about)] +struct Args { + /// Directory to process (recursively). + target_dir: String, + /// Replacement string for 'spacetimedb'. + replacement: String, + + /// Only process given file extensions (comma-separated, e.g. "ts,tsx,js,json"). + #[arg(long)] + only_exts: Option, + + /// Follow symlinks. + #[arg(long)] + follow_symlinks: bool, + + /// Include hidden files/dirs. + #[arg(long)] + include_hidden: bool, + + /// Ignore globs to skip (can be used multiple times). + #[arg(long)] + ignore: Vec, + + /// Dry run: show changes without writing. + #[arg(long)] + dry_run: bool, +} + +fn main() { + let args = Args::parse(); + let only_exts = args.only_exts.map(|s| { + s.split(',') + .map(|e| e.trim().trim_start_matches('.').to_string()) + .filter(|e| !e.is_empty()) + .collect::>() + }); + + let opts = ReplaceOptions { + dry_run: args.dry_run, + only_exts, + follow_symlinks: args.follow_symlinks, + include_hidden: args.include_hidden, + ignore_globs: args.ignore, + }; + + match replace_in_tree(&args.target_dir, &args.replacement, &opts) { + Ok(stats) => { + println!( + "✅ Replacement complete. Files changed: {} | Occurrences: {}", + stats.files_changed, stats.occurrences + ); + } + Err(e) => { + eprintln!("error: {e}"); + std::process::exit(1); + } + } +}