From 53af8c1e913e5fe0625f35567476d784b375601a Mon Sep 17 00:00:00 2001 From: Tien Pham Date: Wed, 14 Jan 2026 15:55:14 +0200 Subject: [PATCH] feat(vue): create Vue framework sdk --- crates/bindings-typescript/package.json | 10 + .../src/vue/SpacetimeDBProvider.ts | 156 +++++++ .../src/vue/connection_state.ts | 19 + crates/bindings-typescript/src/vue/index.ts | 4 + .../bindings-typescript/src/vue/useReducer.ts | 56 +++ .../src/vue/useSpacetimeDB.ts | 18 + .../bindings-typescript/src/vue/useTable.ts | 379 ++++++++++++++++++ crates/bindings-typescript/tsup.config.ts | 32 ++ .../00200-quickstarts/00150-vue.md | 70 ++++ pnpm-lock.yaml | 360 +++++++++++++---- pnpm-workspace.yaml | 1 + templates/templates-list.json | 11 +- templates/vue-ts/.template.json | 5 + templates/vue-ts/LICENSE | 1 + templates/vue-ts/env.d.ts | 10 + templates/vue-ts/index.html | 12 + templates/vue-ts/package.json | 25 ++ templates/vue-ts/spacetimedb/package.json | 15 + templates/vue-ts/spacetimedb/src/index.ts | 33 ++ templates/vue-ts/spacetimedb/tsconfig.json | 23 ++ templates/vue-ts/src/App.vue | 61 +++ templates/vue-ts/src/main.ts | 36 ++ .../vue-ts/src/module_bindings/add_reducer.ts | 15 + .../vue-ts/src/module_bindings/add_type.ts | 15 + templates/vue-ts/src/module_bindings/index.ts | 145 +++++++ .../vue-ts/src/module_bindings/init_type.ts | 13 + .../src/module_bindings/on_connect_reducer.ts | 13 + .../src/module_bindings/on_connect_type.ts | 13 + .../module_bindings/on_disconnect_reducer.ts | 13 + .../src/module_bindings/on_disconnect_type.ts | 13 + .../src/module_bindings/person_table.ts | 15 + .../vue-ts/src/module_bindings/person_type.ts | 15 + .../src/module_bindings/say_hello_reducer.ts | 13 + .../src/module_bindings/say_hello_type.ts | 13 + templates/vue-ts/tsconfig.json | 24 ++ templates/vue-ts/vite.config.ts | 7 + 36 files changed, 1593 insertions(+), 71 deletions(-) create mode 100644 crates/bindings-typescript/src/vue/SpacetimeDBProvider.ts create mode 100644 crates/bindings-typescript/src/vue/connection_state.ts create mode 100644 crates/bindings-typescript/src/vue/index.ts create mode 100644 crates/bindings-typescript/src/vue/useReducer.ts create mode 100644 crates/bindings-typescript/src/vue/useSpacetimeDB.ts create mode 100644 crates/bindings-typescript/src/vue/useTable.ts create mode 100644 docs/docs/00100-intro/00200-quickstarts/00150-vue.md create mode 100644 templates/vue-ts/.template.json create mode 120000 templates/vue-ts/LICENSE create mode 100644 templates/vue-ts/env.d.ts create mode 100644 templates/vue-ts/index.html create mode 100644 templates/vue-ts/package.json create mode 100644 templates/vue-ts/spacetimedb/package.json create mode 100644 templates/vue-ts/spacetimedb/src/index.ts create mode 100644 templates/vue-ts/spacetimedb/tsconfig.json create mode 100644 templates/vue-ts/src/App.vue create mode 100644 templates/vue-ts/src/main.ts create mode 100644 templates/vue-ts/src/module_bindings/add_reducer.ts create mode 100644 templates/vue-ts/src/module_bindings/add_type.ts create mode 100644 templates/vue-ts/src/module_bindings/index.ts create mode 100644 templates/vue-ts/src/module_bindings/init_type.ts create mode 100644 templates/vue-ts/src/module_bindings/on_connect_reducer.ts create mode 100644 templates/vue-ts/src/module_bindings/on_connect_type.ts create mode 100644 templates/vue-ts/src/module_bindings/on_disconnect_reducer.ts create mode 100644 templates/vue-ts/src/module_bindings/on_disconnect_type.ts create mode 100644 templates/vue-ts/src/module_bindings/person_table.ts create mode 100644 templates/vue-ts/src/module_bindings/person_type.ts create mode 100644 templates/vue-ts/src/module_bindings/say_hello_reducer.ts create mode 100644 templates/vue-ts/src/module_bindings/say_hello_type.ts create mode 100644 templates/vue-ts/tsconfig.json create mode 100644 templates/vue-ts/vite.config.ts diff --git a/crates/bindings-typescript/package.json b/crates/bindings-typescript/package.json index 7b83e8e1c4f..26dae2c6fde 100644 --- a/crates/bindings-typescript/package.json +++ b/crates/bindings-typescript/package.json @@ -71,6 +71,12 @@ "import": "./dist/server/index.mjs", "require": "./dist/server/index.cjs", "default": "./dist/server/index.mjs" + }, + "./vue": { + "types": "./dist/vue/index.d.ts", + "import": "./dist/vue/index.mjs", + "require": "./dist/vue/index.cjs", + "default": "./dist/vue/index.mjs" } }, "size-limit": [ @@ -161,12 +167,16 @@ }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0", + "vue": "^3.3.0", "undici": "^6.19.2" }, "peerDependenciesMeta": { "react": { "optional": true }, + "vue": { + "optional": true + }, "undici": { "optional": true } diff --git a/crates/bindings-typescript/src/vue/SpacetimeDBProvider.ts b/crates/bindings-typescript/src/vue/SpacetimeDBProvider.ts new file mode 100644 index 00000000000..d0d19542948 --- /dev/null +++ b/crates/bindings-typescript/src/vue/SpacetimeDBProvider.ts @@ -0,0 +1,156 @@ +import { + defineComponent, + onMounted, + onUnmounted, + provide, + reactive, + shallowRef, + type PropType, + type Slot, +} from 'vue'; +import { + DbConnectionBuilder, + type DbConnectionImpl, + type ErrorContextInterface, + type RemoteModuleOf, +} from '../sdk/db_connection_impl'; +import { ConnectionId } from '../lib/connection_id'; +import { + SPACETIMEDB_INJECTION_KEY, + type ConnectionState, +} from './connection_state'; + +export interface SpacetimeDBProviderProps< + DbConnection extends DbConnectionImpl, +> { + connectionBuilder: DbConnectionBuilder; +} + +function setupConnection>( + connectionBuilder: DbConnectionBuilder +): { + state: ConnectionState; + cleanup: () => void; +} { + const connRef = shallowRef(null); + let cleanupTimeoutId: ReturnType | null = null; + + const state = reactive({ + isActive: false, + identity: undefined, + token: undefined, + connectionId: ConnectionId.random(), + connectionError: undefined, + getConnection: >() => + connRef.value as T | null, + }); + + provide(SPACETIMEDB_INJECTION_KEY, state); + + let onConnectCallback: ((conn: DbConnection) => void) | null = null; + let onDisconnectCallback: + | ((ctx: ErrorContextInterface>) => void) + | null = null; + let onConnectErrorCallback: + | (( + ctx: ErrorContextInterface>, + err: Error + ) => void) + | null = null; + + onMounted(() => { + if (cleanupTimeoutId) { + clearTimeout(cleanupTimeoutId); + cleanupTimeoutId = null; + } + + if (!connRef.value) { + connRef.value = connectionBuilder.build(); + } + + onConnectCallback = (conn: DbConnection) => { + state.isActive = conn.isActive; + state.identity = conn.identity; + state.token = conn.token; + state.connectionId = conn.connectionId; + state.connectionError = undefined; + }; + + onDisconnectCallback = ( + ctx: ErrorContextInterface> + ) => { + state.isActive = ctx.isActive; + }; + + onConnectErrorCallback = ( + ctx: ErrorContextInterface>, + err: Error + ) => { + state.isActive = ctx.isActive; + state.connectionError = err; + }; + + connectionBuilder.onConnect(onConnectCallback); + connectionBuilder.onDisconnect(onDisconnectCallback); + connectionBuilder.onConnectError(onConnectErrorCallback); + + const conn = connRef.value; + if (conn) { + state.isActive = conn.isActive; + state.identity = conn.identity; + state.token = conn.token; + state.connectionId = conn.connectionId; + } + }); + + const cleanup = () => { + if (connRef.value) { + if (onConnectCallback) { + connRef.value.removeOnConnect?.(onConnectCallback as any); + } + if (onDisconnectCallback) { + connRef.value.removeOnDisconnect?.(onDisconnectCallback as any); + } + if (onConnectErrorCallback) { + connRef.value.removeOnConnectError?.(onConnectErrorCallback as any); + } + + cleanupTimeoutId = setTimeout(() => { + connRef.value?.disconnect(); + connRef.value = null; + cleanupTimeoutId = null; + }, 0); + } + }; + + onUnmounted(cleanup); + + return { state, cleanup }; +} + +export const SpacetimeDBProvider = defineComponent({ + name: 'SpacetimeDBProvider', + + props: { + connectionBuilder: { + type: Object as PropType>, + required: true, + }, + }, + + setup(props, { slots }) { + setupConnection(props.connectionBuilder); + + return () => { + const defaultSlot = slots.default as Slot | undefined; + return defaultSlot ? defaultSlot() : null; + }; + }, +}); + +export function useSpacetimeDBProvider< + DbConnection extends DbConnectionImpl, +>(connectionBuilder: DbConnectionBuilder): ConnectionState { + const { state } = setupConnection(connectionBuilder); + return state; +} diff --git a/crates/bindings-typescript/src/vue/connection_state.ts b/crates/bindings-typescript/src/vue/connection_state.ts new file mode 100644 index 00000000000..aded21a0c52 --- /dev/null +++ b/crates/bindings-typescript/src/vue/connection_state.ts @@ -0,0 +1,19 @@ +import type { InjectionKey } from 'vue'; +import type { ConnectionId } from '../lib/connection_id'; +import type { Identity } from '../lib/identity'; +import type { DbConnectionImpl } from '../sdk/db_connection_impl'; + +export interface ConnectionState { + isActive: boolean; + identity?: Identity; + token?: string; + connectionId: ConnectionId; + connectionError?: Error; + getConnection< + DbConnection extends DbConnectionImpl, + >(): DbConnection | null; +} + +export const SPACETIMEDB_INJECTION_KEY = Symbol( + 'spacetimedb' +) as InjectionKey; diff --git a/crates/bindings-typescript/src/vue/index.ts b/crates/bindings-typescript/src/vue/index.ts new file mode 100644 index 00000000000..6bcf01a6a28 --- /dev/null +++ b/crates/bindings-typescript/src/vue/index.ts @@ -0,0 +1,4 @@ +export * from './SpacetimeDBProvider.ts'; +export { useSpacetimeDB } from './useSpacetimeDB.ts'; +export { useTable, where, eq } from './useTable.ts'; +export { useReducer } from './useReducer.ts'; diff --git a/crates/bindings-typescript/src/vue/useReducer.ts b/crates/bindings-typescript/src/vue/useReducer.ts new file mode 100644 index 00000000000..bbcb1afb625 --- /dev/null +++ b/crates/bindings-typescript/src/vue/useReducer.ts @@ -0,0 +1,56 @@ +import { shallowRef, watch, onUnmounted } from 'vue'; +import { useSpacetimeDB } from './useSpacetimeDB'; +import type { InferTypeOfRow } from '../lib/type_builders'; +import type { UntypedReducerDef } from '../sdk/reducers'; +import type { Prettify } from '../lib/type_util'; + +type IsEmptyObject = [keyof T] extends [never] ? true : false; +type MaybeParams = IsEmptyObject extends true ? [] : [params: T]; + +type ParamsType = MaybeParams< + Prettify> +>; + +export function useReducer( + reducerDef: ReducerDef +): (...params: ParamsType) => void { + const conn = useSpacetimeDB(); + const reducerName = reducerDef.accessorName; + + const queueRef = shallowRef[]>([]); + + const stopWatch = watch( + () => conn.isActive, + () => { + const connection = conn.getConnection(); + if (!connection) return; + + const fn = (connection.reducers as any)[reducerName] as ( + ...p: ParamsType + ) => void; + if (queueRef.value.length) { + const pending = queueRef.value.splice(0); + for (const params of pending) { + fn(...params); + } + } + }, + { immediate: true } + ); + + onUnmounted(() => { + stopWatch(); + }); + + return (...params: ParamsType) => { + const connection = conn.getConnection(); + if (!connection) { + queueRef.value.push(params); + return; + } + const fn = (connection.reducers as any)[reducerName] as ( + ...p: ParamsType + ) => void; + fn(...params); + }; +} diff --git a/crates/bindings-typescript/src/vue/useSpacetimeDB.ts b/crates/bindings-typescript/src/vue/useSpacetimeDB.ts new file mode 100644 index 00000000000..1d80fa8f3a3 --- /dev/null +++ b/crates/bindings-typescript/src/vue/useSpacetimeDB.ts @@ -0,0 +1,18 @@ +import { inject } from 'vue'; +import { + SPACETIMEDB_INJECTION_KEY, + type ConnectionState, +} from './connection_state'; + +export function useSpacetimeDB(): ConnectionState { + const context = inject(SPACETIMEDB_INJECTION_KEY); + + if (!context) { + throw new Error( + 'useSpacetimeDB must be used within a SpacetimeDBProvider component. ' + + 'Did you forget to add a `SpacetimeDBProvider` to your component tree?' + ); + } + + return context; +} diff --git a/crates/bindings-typescript/src/vue/useTable.ts b/crates/bindings-typescript/src/vue/useTable.ts new file mode 100644 index 00000000000..84b0d7afa2e --- /dev/null +++ b/crates/bindings-typescript/src/vue/useTable.ts @@ -0,0 +1,379 @@ +import { + onUnmounted, + readonly, + ref, + shallowRef, + watch, + type DeepReadonly, + type Ref, +} from 'vue'; +import { useSpacetimeDB } from './useSpacetimeDB'; + +import type { EventContextInterface } from '../sdk/db_connection_impl'; +import type { UntypedRemoteModule } from '../sdk/spacetime_module'; +import type { RowType, UntypedTableDef } from '../lib/table'; +import type { Prettify } from '../lib/type_util'; + +export interface UseTableCallbacks { + onInsert?: (row: RowType) => void; + onDelete?: (row: RowType) => void; + onUpdate?: (oldRow: RowType, newRow: RowType) => void; +} + +export interface UseTableResult { + rows: DeepReadonly>; + isReady: Ref; +} + +export type Value = string | number | boolean; + +export type Expr = + | { type: 'eq'; key: Column; value: Value } + | { type: 'and'; children: Expr[] } + | { type: 'or'; children: Expr[] }; + +export const eq = ( + key: Column, + value: Value +): Expr => ({ type: 'eq', key, value }); + +export const and = ( + ...children: Expr[] +): Expr => { + const flat: Expr[] = []; + for (const c of children) { + if (!c) continue; + if (c.type === 'and') flat.push(...c.children); + else flat.push(c); + } + const pruned = flat.filter(Boolean); + if (pruned.length === 0) return { type: 'and', children: [] }; + if (pruned.length === 1) return pruned[0]; + return { type: 'and', children: pruned }; +}; + +export const or = ( + ...children: Expr[] +): Expr => { + const flat: Expr[] = []; + for (const c of children) { + if (!c) continue; + if (c.type === 'or') flat.push(...c.children); + else flat.push(c); + } + const pruned = flat.filter(Boolean); + if (pruned.length === 0) return { type: 'or', children: [] }; + if (pruned.length === 1) return pruned[0]; + return { type: 'or', children: pruned }; +}; + +export const isEq = ( + e: Expr +): e is Extract, { type: 'eq' }> => e.type === 'eq'; +export const isAnd = ( + e: Expr +): e is Extract, { type: 'and' }> => e.type === 'and'; +export const isOr = ( + e: Expr +): e is Extract, { type: 'or' }> => e.type === 'or'; + +export function evaluate( + expr: Expr, + row: Record +): boolean { + switch (expr.type) { + case 'eq': { + const v = row[expr.key]; + if ( + typeof v === 'string' || + typeof v === 'number' || + typeof v === 'boolean' + ) { + return v === expr.value; + } + return false; + } + case 'and': + return ( + expr.children.length === 0 || expr.children.every(c => evaluate(c, row)) + ); + case 'or': + return ( + expr.children.length !== 0 && expr.children.some(c => evaluate(c, row)) + ); + } +} + +function formatValue(v: Value): string { + switch (typeof v) { + case 'string': + return `'${v.replace(/'/g, "''")}'`; + case 'number': + return Number.isFinite(v) ? String(v) : `'${String(v)}'`; + case 'boolean': + return v ? 'TRUE' : 'FALSE'; + } +} + +function escapeIdent(id: string): string { + if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) return id; + return `"${id.replace(/"/g, '""')}"`; +} + +function parenthesize(s: string): string { + if (!s.includes(' AND ') && !s.includes(' OR ')) return s; + return `(${s})`; +} + +export function toString( + tableDef: TableDef, + expr: Expr>> +): string { + switch (expr.type) { + case 'eq': { + const key = tableDef.columns[expr.key].columnMetadata.name ?? expr.key; + return `${escapeIdent(key)} = ${formatValue(expr.value)}`; + } + case 'and': + return parenthesize( + expr.children.map(expr => toString(tableDef, expr)).join(' AND ') + ); + case 'or': + return parenthesize( + expr.children.map(expr => toString(tableDef, expr)).join(' OR ') + ); + } +} + +/** + * This is just the identity function to make things look like SQL. + * @param expr + * @returns + */ +export function where(expr: Expr): Expr { + return expr; +} + +type MembershipChange = 'enter' | 'leave' | 'stayIn' | 'stayOut'; + +function classifyMembership< + Col extends string, + R extends Record, +>(where: Expr | undefined, oldRow: R, newRow: R): MembershipChange { + // No filter: everything is in, so updates are always "stayIn". + if (!where) { + return 'stayIn'; + } + + const oldIn = evaluate(where, oldRow); + const newIn = evaluate(where, newRow); + + if (oldIn && !newIn) { + return 'leave'; + } + if (!oldIn && newIn) { + return 'enter'; + } + if (oldIn && newIn) { + return 'stayIn'; + } + return 'stayOut'; +} + +/** + * Extracts the column names from a RowType whose values are of type Value. + * Note that this will exclude columns that are of type object, array, etc. + */ +type ColumnsFromRow = { + [K in keyof R]-?: R[K] extends Value | undefined ? K : never; +}[keyof R] & + string; + +export function useTable( + tableDef: TableDef, + where: Expr>>, + callbacks?: UseTableCallbacks>> +): UseTableResult>>; + +export function useTable( + tableDef: TableDef, + callbacks?: UseTableCallbacks>> +): UseTableResult>>; + +export function useTable( + tableDef: TableDef, + whereClauseOrCallbacks?: + | Expr>> + | UseTableCallbacks>, + callbacks?: UseTableCallbacks> +): UseTableResult>> { + type Row = RowType; + const tableName = tableDef.name; + const accessorName = tableDef.accessorName; + + let whereClause: Expr> | undefined; + if ( + whereClauseOrCallbacks && + typeof whereClauseOrCallbacks === 'object' && + 'type' in whereClauseOrCallbacks + ) { + whereClause = whereClauseOrCallbacks as Expr>; + } else { + callbacks = whereClauseOrCallbacks as UseTableCallbacks | undefined; + } + + const conn = useSpacetimeDB(); + + const rows = shallowRef[]>([]); + const isReady = ref(false); + + const query = + `SELECT * FROM ${tableName}` + + (whereClause ? ` WHERE ${toString(tableDef, whereClause)}` : ''); + + let latestTransactionEvent: any = null; + let unsubscribeFromTable: (() => void) | null = null; + let subscriptionHandle: { unsubscribe: () => void } | null = null; + + const computeFilteredRows = (): readonly Prettify[] => { + const connection = conn.getConnection(); + if (!connection) return []; + + const table = connection.db[accessorName]; + if (!table) return []; + + const allRows = Array.from(table.iter()) as Row[]; + if (whereClause) { + return allRows.filter(row => + evaluate(whereClause, row as Record) + ) as Prettify[]; + } + return allRows as Prettify[]; + }; + + const setupTableListeners = () => { + const connection = conn.getConnection(); + if (!connection) return; + + const table = connection.db[accessorName]; + if (!table) return; + + const onInsert = ( + eventCtx: EventContextInterface, + row: any + ) => { + if (whereClause && !evaluate(whereClause, row)) return; + callbacks?.onInsert?.(row); + + if ( + eventCtx.event !== latestTransactionEvent || + !latestTransactionEvent + ) { + latestTransactionEvent = eventCtx.event; + rows.value = computeFilteredRows(); + } + }; + + const onDelete = ( + eventCtx: EventContextInterface, + row: any + ) => { + if (whereClause && !evaluate(whereClause, row)) return; + callbacks?.onDelete?.(row); + + if ( + eventCtx.event !== latestTransactionEvent || + !latestTransactionEvent + ) { + latestTransactionEvent = eventCtx.event; + rows.value = computeFilteredRows(); + } + }; + + const onUpdate = ( + eventCtx: EventContextInterface, + oldRow: any, + newRow: any + ) => { + const change = classifyMembership(whereClause, oldRow, newRow); + + switch (change) { + case 'leave': + callbacks?.onDelete?.(oldRow); + break; + case 'enter': + callbacks?.onInsert?.(newRow); + break; + case 'stayIn': + callbacks?.onUpdate?.(oldRow, newRow); + break; + case 'stayOut': + return; + } + + if ( + eventCtx.event !== latestTransactionEvent || + !latestTransactionEvent + ) { + latestTransactionEvent = eventCtx.event; + rows.value = computeFilteredRows(); + } + }; + + table.onInsert(onInsert); + table.onDelete(onDelete); + table.onUpdate?.(onUpdate); + + return () => { + table.removeOnInsert(onInsert); + table.removeOnDelete(onDelete); + table.removeOnUpdate?.(onUpdate); + }; + }; + + const setupSubscription = () => { + const connection = conn.getConnection(); + if (!connection) return; + + subscriptionHandle = connection + .subscriptionBuilder() + .onApplied(() => { + isReady.value = true; + rows.value = computeFilteredRows(); + }) + .subscribe(query); + }; + + watch( + () => conn.isActive, + isActive => { + // Clean up existing listeners and subscriptions first + if (unsubscribeFromTable) { + unsubscribeFromTable(); + unsubscribeFromTable = null; + } + if (subscriptionHandle) { + subscriptionHandle.unsubscribe(); + subscriptionHandle = null; + } + + if (isActive) { + unsubscribeFromTable = setupTableListeners() || null; + setupSubscription(); + rows.value = computeFilteredRows(); + } else { + isReady.value = false; + rows.value = []; + } + }, + { immediate: true } + ); + + onUnmounted(() => { + unsubscribeFromTable?.(); + subscriptionHandle?.unsubscribe(); + latestTransactionEvent = null; + }); + + return { rows: readonly(rows), isReady }; +} diff --git a/crates/bindings-typescript/tsup.config.ts b/crates/bindings-typescript/tsup.config.ts index f88f703b9ec..dc277bff62e 100644 --- a/crates/bindings-typescript/tsup.config.ts +++ b/crates/bindings-typescript/tsup.config.ts @@ -76,6 +76,38 @@ export default defineConfig([ esbuildOptions: commonEsbuildTweaks(), }, + // Vue subpath (SSR-friendly): dist/vue/index.{mjs,cjs} + { + entry: { index: 'src/vue/index.ts' }, + format: ['esm', 'cjs'], + target: 'es2022', + outDir: 'dist/vue', + dts: false, + sourcemap: true, + clean: true, + platform: 'neutral', + treeshake: 'smallest', + external: ['vue'], + outExtension, + esbuildOptions: commonEsbuildTweaks(), + }, + + // Vue subpath (browser ESM): dist/browser/vue/index.mjs + { + entry: { index: 'src/vue/index.ts' }, + format: ['esm'], + target: 'es2022', + outDir: 'dist/browser/vue', + dts: false, + sourcemap: true, + clean: true, + platform: 'browser', + treeshake: 'smallest', + external: ['vue'], + outExtension, + esbuildOptions: commonEsbuildTweaks(), + }, + // SDK subpath (SSR-friendly): dist/sdk/index.{mjs,cjs} { entry: { index: 'src/sdk/index.ts' }, diff --git a/docs/docs/00100-intro/00200-quickstarts/00150-vue.md b/docs/docs/00100-intro/00200-quickstarts/00150-vue.md new file mode 100644 index 00000000000..bd12ffc33ce --- /dev/null +++ b/docs/docs/00100-intro/00200-quickstarts/00150-vue.md @@ -0,0 +1,70 @@ +--- +title: Vue Quickstart +sidebar_label: Vue +slug: /quickstarts/vue +hide_table_of_contents: true +--- + +import { InstallCardLink } from "@site/src/components/InstallCardLink"; +import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps"; + + +Get a SpacetimeDB Vue app running in under 5 minutes. + +## Prerequisites + +- [Node.js](https://nodejs.org/) 18+ installed +- [SpacetimeDB CLI](https://spacetimedb.com/install) installed + + + +--- + + + + + Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Vue client. + + This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Vue development server. + + +```bash +spacetime dev --template vue-ts my-spacetime-app +``` + + + + + + Navigate to [http://localhost:5173](http://localhost:5173) to see your app running. + + The template includes a basic Vue app connected to SpacetimeDB. + + + + + + Your project contains both server and client code. + + Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `client/src/App.vue` to build your UI. + + +``` +my-spacetime-app/ +├── spacetimedb/ # Your SpacetimeDB module +│ └── src/ +│ └── index.ts # Server-side logic +├── client/ # Vue frontend +│ └── src/ +│ ├── App.vue +│ └── module_bindings/ # Auto-generated types +└── package.json +``` + + + + +## Next steps + +- See the [Chat App Tutorial](/tutorials/chat-app) for a complete example +- Read the [TypeScript SDK Reference](/sdks/typescript) for detailed API docs diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd8b2c2a44f..978cbebd125 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -71,6 +71,9 @@ importers: url-polyfill: specifier: ^1.1.14 version: 1.1.14 + vue: + specifier: ^3.3.0 + version: 3.5.26(typescript@5.9.3) devDependencies: '@eslint/js': specifier: ^9.17.0 @@ -162,7 +165,7 @@ importers: version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-docs': specifier: 3.9.2 - version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + version: 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/preset-classic': specifier: 3.9.2 version: 3.9.2(@algolia/client-search@5.39.0)(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3)(typescript@5.6.3) @@ -369,6 +372,28 @@ importers: specifier: workspace:^ version: link:../../../crates/bindings-typescript + templates/vue-ts: + dependencies: + spacetimedb: + specifier: workspace:* + version: link:../../crates/bindings-typescript + vue: + specifier: ^3.5.13 + version: 3.5.26(typescript@5.6.3) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.0 + version: 6.0.3(vite@7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4))(vue@3.5.26(typescript@5.6.3)) + 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) + vue-tsc: + specifier: ^2.2.0 + version: 2.2.12(typescript@5.6.3) + packages: '@adobe/css-tools@4.4.4': @@ -599,6 +624,10 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} @@ -621,6 +650,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} engines: {node: '>=6.9.0'} @@ -1099,6 +1133,10 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -2894,6 +2932,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.34': resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + '@rollup/rollup-android-arm-eabi@4.50.2': resolution: {integrity: sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==} cpu: [arm] @@ -3484,6 +3525,13 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitejs/plugin-vue@6.0.3': + resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vue: ^3.2.25 + '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -3522,21 +3570,70 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@volar/language-core@2.4.15': + resolution: {integrity: sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==} + + '@volar/source-map@2.4.15': + resolution: {integrity: sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==} + + '@volar/typescript@2.4.15': + resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} + '@vue/compiler-core@3.5.22': resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} + '@vue/compiler-core@3.5.26': + resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + '@vue/compiler-dom@3.5.22': resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} + '@vue/compiler-dom@3.5.26': + resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + '@vue/compiler-sfc@3.5.22': resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==} + '@vue/compiler-sfc@3.5.26': + resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + '@vue/compiler-ssr@3.5.22': resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} + '@vue/compiler-ssr@3.5.26': + resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/reactivity@3.5.26': + resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + + '@vue/runtime-core@3.5.26': + resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + + '@vue/runtime-dom@3.5.26': + resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + + '@vue/server-renderer@3.5.26': + resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + peerDependencies: + vue: 3.5.26 + '@vue/shared@3.5.22': resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} + '@vue/shared@3.5.26': + resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -4060,6 +4157,9 @@ packages: resolution: {integrity: sha512-DzTfhUxzg9QBNGzU/0kZkxEV72TeA4MmPJ7RVfLnQwHNhhliPo7ynglEWJS791rNlLFoTyrKvkapwr/P3EXV9A==} engines: {node: '>= 14.0.0'} + alien-signals@1.0.13: + resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} + altcha-lib@1.3.0: resolution: {integrity: sha512-PpFg/JPuR+Jiud7Vs54XSDqDxvylcp+0oDa/i1ARxBA/iKDqLeNlO8PorQbfuDTMVLYRypAa/2VDK3nbBTAu5A==} @@ -4702,10 +4802,16 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -4929,6 +5035,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.0: + resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} + engines: {node: '>=0.12'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -6113,6 +6223,9 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} @@ -6526,6 +6639,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + multicast-dns@7.2.5: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true @@ -8654,6 +8770,23 @@ packages: jsdom: optional: true + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-tsc@2.2.12: + resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.26: + resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} @@ -9267,6 +9400,8 @@ snapshots: '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.3': @@ -9290,6 +9425,10 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.3)': dependencies: '@babel/core': 7.28.3 @@ -9900,6 +10039,11 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@bcoe/v8-coverage@1.0.2': {} '@clack/core@0.3.5': @@ -10466,7 +10610,7 @@ snapshots: '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.9.2 '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -10503,46 +10647,6 @@ snapshots: - webpack-cli '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': - dependencies: - '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/logger': 3.9.2 - '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/react-router-config': 5.0.11 - combine-promises: 1.2.0 - fs-extra: 11.3.2 - js-yaml: 4.1.0 - lodash: 4.17.21 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - schema-dts: 1.1.5 - tslib: 2.8.1 - utility-types: 3.11.0 - webpack: 5.102.0 - transitivePeerDependencies: - - '@docusaurus/faster' - - '@mdx-js/react' - - '@parcel/css' - - '@rspack/core' - - '@swc/core' - - '@swc/css' - - bufferutil - - csso - - debug - - esbuild - - lightningcss - - supports-color - - typescript - - uglify-js - - utf-8-validate - - webpack-cli - - '@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': dependencies: '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.9.2 @@ -10811,7 +10915,7 @@ snapshots: dependencies: '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-css-cascade-layers': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-debug': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) @@ -10859,7 +10963,7 @@ snapshots: '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/plugin-content-blog': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/plugin-content-pages': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-translations': 3.9.2 @@ -10899,35 +11003,11 @@ snapshots: - utf-8-validate - webpack-cli - '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) - '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/history': 4.7.11 - '@types/react': 18.3.23 - '@types/react-router-config': 5.0.11 - clsx: 2.1.1 - parse-numeric-range: 1.3.0 - prism-react-renderer: 2.4.1(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.8.1 - utility-types: 3.11.0 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - supports-color - - uglify-js - - webpack-cli - '@docusaurus/theme-common@3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@docusaurus/mdx-loader': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/module-type-aliases': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/utils-common': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 @@ -10952,7 +11032,7 @@ snapshots: '@docsearch/react': 4.2.0(@algolia/client-search@5.39.0)(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.3) '@docusaurus/core': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/logger': 3.9.2 - '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) + '@docusaurus/plugin-content-docs': 3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(debug@4.4.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3) '@docusaurus/theme-common': 3.9.2(@docusaurus/plugin-content-docs@3.9.2(@mdx-js/react@3.1.1(@types/react@18.3.23)(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-translations': 3.9.2 '@docusaurus/utils': 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -12680,6 +12760,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.34': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} + '@rollup/rollup-android-arm-eabi@4.50.2': optional: true @@ -13434,6 +13516,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-vue@6.0.3(vite@7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4))(vue@3.5.26(typescript@5.6.3))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.53 + vite: 7.1.5(@types/node@24.3.0)(jiti@2.5.1)(terser@5.43.1)(tsx@4.20.4) + vue: 3.5.26(typescript@5.6.3) + '@vitest/coverage-v8@3.2.4(vitest@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))': dependencies: '@ampproject/remapping': 2.3.0 @@ -13503,6 +13591,18 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 + '@volar/language-core@2.4.15': + dependencies: + '@volar/source-map': 2.4.15 + + '@volar/source-map@2.4.15': {} + + '@volar/typescript@2.4.15': + dependencies: + '@volar/language-core': 2.4.15 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + '@vue/compiler-core@3.5.22': dependencies: '@babel/parser': 7.28.4 @@ -13511,11 +13611,24 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.26': + dependencies: + '@babel/parser': 7.28.5 + '@vue/shared': 3.5.26 + entities: 7.0.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.22': dependencies: '@vue/compiler-core': 3.5.22 '@vue/shared': 3.5.22 + '@vue/compiler-dom@3.5.26': + dependencies: + '@vue/compiler-core': 3.5.26 + '@vue/shared': 3.5.26 + '@vue/compiler-sfc@3.5.22': dependencies: '@babel/parser': 7.28.4 @@ -13528,13 +13641,78 @@ snapshots: postcss: 8.5.6 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.5.26': + dependencies: + '@babel/parser': 7.28.5 + '@vue/compiler-core': 3.5.26 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.5.22': dependencies: '@vue/compiler-dom': 3.5.22 '@vue/shared': 3.5.22 + '@vue/compiler-ssr@3.5.26': + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.12(typescript@5.6.3)': + dependencies: + '@volar/language-core': 2.4.15 + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.26 + alien-signals: 1.0.13 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.6.3 + + '@vue/reactivity@3.5.26': + dependencies: + '@vue/shared': 3.5.26 + + '@vue/runtime-core@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/shared': 3.5.26 + + '@vue/runtime-dom@3.5.26': + dependencies: + '@vue/reactivity': 3.5.26 + '@vue/runtime-core': 3.5.26 + '@vue/shared': 3.5.26 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.6.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.6.3) + + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.5.26 + '@vue/shared': 3.5.26 + vue: 3.5.26(typescript@5.9.3) + '@vue/shared@3.5.22': {} + '@vue/shared@3.5.26': {} + '@webassemblyjs/ast@1.14.1': dependencies: '@webassemblyjs/helper-numbers': 1.13.2 @@ -14597,6 +14775,8 @@ snapshots: '@algolia/requester-fetch': 5.39.0 '@algolia/requester-node-http': 5.39.0 + alien-signals@1.0.13: {} + altcha-lib@1.3.0: {} ansi-align@3.0.1: @@ -15278,11 +15458,15 @@ snapshots: csstype@3.1.3: {} + csstype@3.2.3: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 + de-indent@1.0.2: {} + debounce@1.2.1: {} debug@2.6.9: @@ -15470,6 +15654,8 @@ snapshots: entities@6.0.1: {} + entities@7.0.0: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -16817,6 +17003,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: dependencies: '@babel/parser': 7.28.3 @@ -17592,6 +17782,8 @@ snapshots: ms@2.1.3: {} + muggle-string@0.4.1: {} + multicast-dns@7.2.5: dependencies: dns-packet: 5.6.1 @@ -19994,6 +20186,34 @@ snapshots: - tsx - yaml + vscode-uri@3.1.0: {} + + vue-tsc@2.2.12(typescript@5.6.3): + dependencies: + '@volar/typescript': 2.4.15 + '@vue/language-core': 2.2.12(typescript@5.6.3) + typescript: 5.6.3 + + vue@3.5.26(typescript@5.6.3): + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.6.3)) + '@vue/shared': 3.5.26 + optionalDependencies: + typescript: 5.6.3 + + vue@3.5.26(typescript@5.9.3): + dependencies: + '@vue/compiler-dom': 3.5.26 + '@vue/compiler-sfc': 3.5.26 + '@vue/runtime-dom': 3.5.26 + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 + optionalDependencies: + typescript: 5.9.3 + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0b6b411ea9e..9fcab4319ca 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,7 @@ packages: - 'crates/bindings-typescript/test-app' - 'templates/quickstart-chat-typescript' - 'templates/basic-react' + - 'templates/vue-ts' - 'templates/basic-typescript' - 'modules/benchmarks-ts' - 'modules/module-test-ts' diff --git a/templates/templates-list.json b/templates/templates-list.json index 05f8c035e5c..e17626132cf 100644 --- a/templates/templates-list.json +++ b/templates/templates-list.json @@ -1,6 +1,7 @@ { "highlights": [ - { "name": "React", "template_id": "basic-react" } + { "name": "React", "template_id": "basic-react" }, + { "name": "Vue", "template_id": "vue-ts" } ], "templates": [ { @@ -58,6 +59,14 @@ "client_source": "quickstart-chat-ts", "server_lang": "typescript", "client_lang": "typescript" + }, + { + "id": "vue-ts", + "description": "Vue.js web app with TypeScript server", + "server_source": "vue-ts/spacetimedb", + "client_source": "vue-ts", + "server_lang": "typescript", + "client_lang": "typescript" } ] } diff --git a/templates/vue-ts/.template.json b/templates/vue-ts/.template.json new file mode 100644 index 00000000000..ea14a0674ad --- /dev/null +++ b/templates/vue-ts/.template.json @@ -0,0 +1,5 @@ +{ + "description": "Vue.js web app with TypeScript server", + "client_lang": "typescript", + "server_lang": "typescript" +} diff --git a/templates/vue-ts/LICENSE b/templates/vue-ts/LICENSE new file mode 120000 index 00000000000..039e117dde2 --- /dev/null +++ b/templates/vue-ts/LICENSE @@ -0,0 +1 @@ +../../licenses/apache2.txt \ No newline at end of file diff --git a/templates/vue-ts/env.d.ts b/templates/vue-ts/env.d.ts new file mode 100644 index 00000000000..5c930383177 --- /dev/null +++ b/templates/vue-ts/env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_SPACETIMEDB_HOST: string; + readonly VITE_SPACETIMEDB_DB_NAME: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/templates/vue-ts/index.html b/templates/vue-ts/index.html new file mode 100644 index 00000000000..c553efb834b --- /dev/null +++ b/templates/vue-ts/index.html @@ -0,0 +1,12 @@ + + + + + + SpacetimeDB Vue App + + +
+ + + diff --git a/templates/vue-ts/package.json b/templates/vue-ts/package.json new file mode 100644 index 00000000000..a3d0c159ad1 --- /dev/null +++ b/templates/vue-ts/package.json @@ -0,0 +1,25 @@ +{ + "name": "@clockworklabs/vue-ts", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview", + "generate": "pnpm --dir spacetimedb install --ignore-workspace && cargo run -p gen-bindings -- --out-dir src/module_bindings --project-path spacetimedb && 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 spacetimedb --server local", + "spacetime:publish": "spacetime publish --project-path spacetimedb --server maincloud" + }, + "dependencies": { + "spacetimedb": "workspace:*", + "vue": "^3.5.13" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.0", + "typescript": "~5.6.2", + "vite": "^7.1.5", + "vue-tsc": "^2.2.0" + } +} diff --git a/templates/vue-ts/spacetimedb/package.json b/templates/vue-ts/spacetimedb/package.json new file mode 100644 index 00000000000..214ccc569bf --- /dev/null +++ b/templates/vue-ts/spacetimedb/package.json @@ -0,0 +1,15 @@ +{ + "name": "spacetime-module", + "version": "1.0.0", + "description": "", + "scripts": { + "build": "spacetime build", + "publish": "spacetime publish" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "spacetimedb": "1.*" + } +} diff --git a/templates/vue-ts/spacetimedb/src/index.ts b/templates/vue-ts/spacetimedb/src/index.ts new file mode 100644 index 00000000000..900cb1bf2e9 --- /dev/null +++ b/templates/vue-ts/spacetimedb/src/index.ts @@ -0,0 +1,33 @@ +import { schema, table, t } from 'spacetimedb/server'; + +export const spacetimedb = schema( + table( + { name: 'person', public: true }, + { + name: t.string(), + } + ) +); + +spacetimedb.init((_ctx) => { + // Called when the module is initially published +}); + +spacetimedb.clientConnected((_ctx) => { + // Called every time a new client connects +}); + +spacetimedb.clientDisconnected((_ctx) => { + // Called every time a client disconnects +}); + +spacetimedb.reducer('add', { name: t.string() }, (ctx, { name }) => { + ctx.db.person.insert({ name }); +}); + +spacetimedb.reducer('say_hello', (ctx) => { + for (const person of ctx.db.person.iter()) { + console.info(`Hello, ${person.name}!`); + } + console.info('Hello, World!'); +}); diff --git a/templates/vue-ts/spacetimedb/tsconfig.json b/templates/vue-ts/spacetimedb/tsconfig.json new file mode 100644 index 00000000000..c97c980cf80 --- /dev/null +++ b/templates/vue-ts/spacetimedb/tsconfig.json @@ -0,0 +1,23 @@ + +/* + * This tsconfig is used for TypeScript projects created with `spacetimedb init + * --lang typescript`. You can modify it as needed for your project, although + * some options are required by SpacetimeDB. + */ +{ + "compilerOptions": { + "strict": true, + "skipLibCheck": true, + "moduleResolution": "bundler", + + /* The following options are required by SpacetimeDB + * and should not be modified + */ + "target": "ESNext", + "lib": ["ES2021", "dom"], + "module": "ESNext", + "isolatedModules": true, + "noEmit": true + }, + "include": ["./**/*"] +} diff --git a/templates/vue-ts/src/App.vue b/templates/vue-ts/src/App.vue new file mode 100644 index 00000000000..78300f718f7 --- /dev/null +++ b/templates/vue-ts/src/App.vue @@ -0,0 +1,61 @@ + + + diff --git a/templates/vue-ts/src/main.ts b/templates/vue-ts/src/main.ts new file mode 100644 index 00000000000..4f71c579ae4 --- /dev/null +++ b/templates/vue-ts/src/main.ts @@ -0,0 +1,36 @@ +import { createApp, h } from 'vue'; +import App from './App.vue'; +import { Identity } from 'spacetimedb'; +import { SpacetimeDBProvider } from 'spacetimedb/vue'; +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 ?? 'vue-ts'; + +const onConnect = (_conn: DbConnection, identity: Identity, token: string) => { + localStorage.setItem('auth_token', token); + console.log( + 'Connected to SpacetimeDB with identity:', + identity.toHexString() + ); +}; + +const onDisconnect = () => { + console.log('Disconnected from SpacetimeDB'); +}; + +const onConnectError = (_ctx: ErrorContext, err: Error) => { + console.log('Error connecting to SpacetimeDB:', err); +}; + +const connectionBuilder = DbConnection.builder() + .withUri(HOST) + .withModuleName(DB_NAME) + .withToken(localStorage.getItem('auth_token') || undefined) + .onConnect(onConnect) + .onDisconnect(onDisconnect) + .onConnectError(onConnectError); + +createApp({ + render: () => h(SpacetimeDBProvider, { connectionBuilder }, () => h(App)), +}).mount('#app'); diff --git a/templates/vue-ts/src/module_bindings/add_reducer.ts b/templates/vue-ts/src/module_bindings/add_reducer.ts new file mode 100644 index 00000000000..85081559c7d --- /dev/null +++ b/templates/vue-ts/src/module_bindings/add_reducer.ts @@ -0,0 +1,15 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default { + name: __t.string(), +}; diff --git a/templates/vue-ts/src/module_bindings/add_type.ts b/templates/vue-ts/src/module_bindings/add_type.ts new file mode 100644 index 00000000000..638f62cea39 --- /dev/null +++ b/templates/vue-ts/src/module_bindings/add_type.ts @@ -0,0 +1,15 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.object('Add', { + name: __t.string(), +}); diff --git a/templates/vue-ts/src/module_bindings/index.ts b/templates/vue-ts/src/module_bindings/index.ts new file mode 100644 index 00000000000..3c2919c1917 --- /dev/null +++ b/templates/vue-ts/src/module_bindings/index.ts @@ -0,0 +1,145 @@ +// 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.11.2 (commit 5508f620e2fd5a4d8a3b7aaf5303776d127f5cbd). + +/* eslint-disable */ +/* tslint:disable */ +import { + DbConnectionBuilder as __DbConnectionBuilder, + DbConnectionImpl as __DbConnectionImpl, + SubscriptionBuilderImpl as __SubscriptionBuilderImpl, + TypeBuilder as __TypeBuilder, + Uuid as __Uuid, + convertToAccessorMap as __convertToAccessorMap, + procedureSchema as __procedureSchema, + procedures as __procedures, + reducerSchema as __reducerSchema, + reducers as __reducers, + schema as __schema, + t as __t, + table as __table, + type AlgebraicTypeType as __AlgebraicTypeType, + type DbConnectionConfig as __DbConnectionConfig, + type ErrorContextInterface as __ErrorContextInterface, + type Event as __Event, + type EventContextInterface as __EventContextInterface, + type Infer as __Infer, + type ReducerEventContextInterface as __ReducerEventContextInterface, + type RemoteModule as __RemoteModule, + type SubscriptionEventContextInterface as __SubscriptionEventContextInterface, + type SubscriptionHandleImpl as __SubscriptionHandleImpl, +} from 'spacetimedb'; + +// Import and reexport all reducer arg types +import OnConnectReducer from './on_connect_reducer'; +export { OnConnectReducer }; +import OnDisconnectReducer from './on_disconnect_reducer'; +export { OnDisconnectReducer }; +import AddReducer from './add_reducer'; +export { AddReducer }; +import SayHelloReducer from './say_hello_reducer'; +export { SayHelloReducer }; + +// Import and reexport all procedure arg types + +// Import and reexport all table handle types +import PersonRow from './person_table'; +export { PersonRow }; + +// Import and reexport all types +import Add from './add_type'; +export { Add }; +import Init from './init_type'; +export { Init }; +import OnConnect from './on_connect_type'; +export { OnConnect }; +import OnDisconnect from './on_disconnect_type'; +export { OnDisconnect }; +import Person from './person_type'; +export { Person }; +import SayHello from './say_hello_type'; +export { SayHello }; + +/** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */ +const tablesSchema = __schema( + __table( + { + name: 'person', + indexes: [], + constraints: [], + }, + PersonRow + ) +); + +/** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */ +const reducersSchema = __reducers( + __reducerSchema('add', AddReducer), + __reducerSchema('say_hello', SayHelloReducer) +); + +/** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ +const proceduresSchema = __procedures(); + +/** The remote SpacetimeDB module schema, both runtime and type information. */ +const REMOTE_MODULE = { + versionInfo: { + cliVersion: '1.11.2' as const, + }, + tables: tablesSchema.schemaType.tables, + reducers: reducersSchema.reducersType.reducers, + ...proceduresSchema, +} satisfies __RemoteModule< + typeof tablesSchema.schemaType, + typeof reducersSchema.reducersType, + typeof proceduresSchema +>; + +/** The tables available in this remote SpacetimeDB module. */ +export const tables = __convertToAccessorMap(tablesSchema.schemaType.tables); + +/** The reducers available in this remote SpacetimeDB module. */ +export const reducers = __convertToAccessorMap( + reducersSchema.reducersType.reducers +); + +/** The context type returned in callbacks for all possible events. */ +export type EventContext = __EventContextInterface; +/** The context type returned in callbacks for reducer events. */ +export type ReducerEventContext = __ReducerEventContextInterface< + typeof REMOTE_MODULE +>; +/** The context type returned in callbacks for subscription events. */ +export type SubscriptionEventContext = __SubscriptionEventContextInterface< + typeof REMOTE_MODULE +>; +/** The context type returned in callbacks for error events. */ +export type ErrorContext = __ErrorContextInterface; +/** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ +export type SubscriptionHandle = __SubscriptionHandleImpl; + +/** Builder class to configure a new subscription to the remote SpacetimeDB instance. */ +export class SubscriptionBuilder extends __SubscriptionBuilderImpl< + typeof REMOTE_MODULE +> {} + +/** Builder class to configure a new database connection to the remote SpacetimeDB instance. */ +export class DbConnectionBuilder extends __DbConnectionBuilder {} + +/** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ +export class DbConnection extends __DbConnectionImpl { + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ + static builder = (): DbConnectionBuilder => { + return new DbConnectionBuilder( + REMOTE_MODULE, + (config: __DbConnectionConfig) => + new DbConnection(config) + ); + }; + + /** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */ + override subscriptionBuilder = (): SubscriptionBuilder => { + return new SubscriptionBuilder(this); + }; +} diff --git a/templates/vue-ts/src/module_bindings/init_type.ts b/templates/vue-ts/src/module_bindings/init_type.ts new file mode 100644 index 00000000000..52ed691ed94 --- /dev/null +++ b/templates/vue-ts/src/module_bindings/init_type.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.object('Init', {}); diff --git a/templates/vue-ts/src/module_bindings/on_connect_reducer.ts b/templates/vue-ts/src/module_bindings/on_connect_reducer.ts new file mode 100644 index 00000000000..2ca99c88fea --- /dev/null +++ b/templates/vue-ts/src/module_bindings/on_connect_reducer.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default {}; diff --git a/templates/vue-ts/src/module_bindings/on_connect_type.ts b/templates/vue-ts/src/module_bindings/on_connect_type.ts new file mode 100644 index 00000000000..d36362515de --- /dev/null +++ b/templates/vue-ts/src/module_bindings/on_connect_type.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.object('OnConnect', {}); diff --git a/templates/vue-ts/src/module_bindings/on_disconnect_reducer.ts b/templates/vue-ts/src/module_bindings/on_disconnect_reducer.ts new file mode 100644 index 00000000000..2ca99c88fea --- /dev/null +++ b/templates/vue-ts/src/module_bindings/on_disconnect_reducer.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default {}; diff --git a/templates/vue-ts/src/module_bindings/on_disconnect_type.ts b/templates/vue-ts/src/module_bindings/on_disconnect_type.ts new file mode 100644 index 00000000000..efda71ebcfd --- /dev/null +++ b/templates/vue-ts/src/module_bindings/on_disconnect_type.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.object('OnDisconnect', {}); diff --git a/templates/vue-ts/src/module_bindings/person_table.ts b/templates/vue-ts/src/module_bindings/person_table.ts new file mode 100644 index 00000000000..0f70f74f617 --- /dev/null +++ b/templates/vue-ts/src/module_bindings/person_table.ts @@ -0,0 +1,15 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.row({ + name: __t.string(), +}); diff --git a/templates/vue-ts/src/module_bindings/person_type.ts b/templates/vue-ts/src/module_bindings/person_type.ts new file mode 100644 index 00000000000..1156775a3cf --- /dev/null +++ b/templates/vue-ts/src/module_bindings/person_type.ts @@ -0,0 +1,15 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.object('Person', { + name: __t.string(), +}); diff --git a/templates/vue-ts/src/module_bindings/say_hello_reducer.ts b/templates/vue-ts/src/module_bindings/say_hello_reducer.ts new file mode 100644 index 00000000000..2ca99c88fea --- /dev/null +++ b/templates/vue-ts/src/module_bindings/say_hello_reducer.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default {}; diff --git a/templates/vue-ts/src/module_bindings/say_hello_type.ts b/templates/vue-ts/src/module_bindings/say_hello_type.ts new file mode 100644 index 00000000000..6293ca6bd09 --- /dev/null +++ b/templates/vue-ts/src/module_bindings/say_hello_type.ts @@ -0,0 +1,13 @@ +// 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 { + TypeBuilder as __TypeBuilder, + t as __t, + type AlgebraicTypeType as __AlgebraicTypeType, + type Infer as __Infer, +} from 'spacetimedb'; + +export default __t.object('SayHello', {}); diff --git a/templates/vue-ts/tsconfig.json b/templates/vue-ts/tsconfig.json new file mode 100644 index 00000000000..aef60866ded --- /dev/null +++ b/templates/vue-ts/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "module": "ESNext", + "skipLibCheck": true, + + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["env.d.ts", "src", "vite.config.ts"] +} diff --git a/templates/vue-ts/vite.config.ts b/templates/vue-ts/vite.config.ts new file mode 100644 index 00000000000..37d3d6f22bd --- /dev/null +++ b/templates/vue-ts/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +});