Add Angular integration (#4139)

# Description of Changes

- Add Angular integration
- Add Angular quickstart chat template

# API and ABI breaking changes

None.

# Expected complexity level and risk

2

# Testing

Ran the template

---------

Co-authored-by: Tien Pham <tien@clockworklabs.io>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
This commit is contained in:
Julien Lavocat
2026-02-16 11:21:52 +09:00
committed by GitHub
parent 4c962b9170
commit 83851fe9df
54 changed files with 5124 additions and 292 deletions
+17
View File
@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false
+14 -2
View File
@@ -89,6 +89,12 @@
"import": "./dist/svelte/index.mjs",
"require": "./dist/svelte/index.cjs",
"default": "./dist/svelte/index.mjs"
},
"./angular": {
"types": "./dist/angular/index.d.ts",
"import": "./dist/angular/index.mjs",
"require": "./dist/angular/index.cjs",
"default": "./dist/angular/index.mjs"
}
},
"size-limit": [
@@ -183,7 +189,8 @@
"react": "^18.0.0 || ^19.0.0-0 || ^19.0.0",
"svelte": "^4.0.0 || ^5.0.0",
"undici": "^6.19.2",
"vue": "^3.3.0"
"vue": "^3.3.0",
"@angular/core": ">=17.0.0"
},
"peerDependenciesMeta": {
"@tanstack/react-query": {
@@ -200,6 +207,9 @@
},
"vue": {
"optional": true
},
"@angular/core": {
"optional": true
}
},
"devDependencies": {
@@ -224,6 +234,8 @@
"typescript": "^5.9.3",
"typescript-eslint": "^8.18.2",
"vite": "^7.1.5",
"vitest": "^3.2.4"
"vitest": "^3.2.4",
"@angular/core": "^21.1.0",
"@angular/compiler": "^21.1.0"
}
}
@@ -0,0 +1,19 @@
import { InjectionToken, type WritableSignal } from '@angular/core';
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<any>,
>(): DbConnection | null;
}
export const SPACETIMEDB_CONNECTION = new InjectionToken<
WritableSignal<ConnectionState>
>('SpacetimeDB Connection State');
@@ -0,0 +1,3 @@
export type { ConnectionState } from './connection_state.ts';
export * from './injectors/index.ts';
export * from './providers/index.ts';
@@ -0,0 +1,4 @@
export { injectSpacetimeDB } from './inject-spacetimedb.ts';
export { injectTable, type TableRows } from './inject-table.ts';
export { injectSpacetimeDBConnected } from './inject-spacetimedb-connected.ts';
export { injectReducer } from './inject-reducer.ts';
@@ -0,0 +1,62 @@
import { assertInInjectionContext, inject, effect } from '@angular/core';
import { SPACETIMEDB_CONNECTION } from '../connection_state';
import type { ParamsType } from '../../sdk';
import type { UntypedReducerDef } from '../../sdk/reducers';
export function injectReducer<ReducerDef extends UntypedReducerDef>(
reducerDef: ReducerDef
): (...params: ParamsType<ReducerDef>) => void {
assertInInjectionContext(injectReducer);
const connState = inject(SPACETIMEDB_CONNECTION);
const queue: ParamsType<ReducerDef>[] = [];
const reducerName = reducerDef.accessorName;
// flush queued calls when connection becomes active
effect((onCleanup: (fn: () => void) => void) => {
const state = connState();
if (!state.isActive) {
return;
}
const connection = state.getConnection();
if (!connection) {
return;
}
const callReducer = (connection.reducers as any)[reducerName] as (
...p: ParamsType<ReducerDef>
) => void;
if (queue.length) {
const pending = queue.splice(0);
for (const params of pending) {
callReducer(...params);
}
}
onCleanup(() => {
queue.splice(0);
});
});
return (...params: ParamsType<ReducerDef>) => {
const state = connState();
if (!state.isActive) {
queue.push(params);
return;
}
const connection = state.getConnection();
if (!connection) {
queue.push(params);
return;
}
const callReducer = (connection.reducers as any)[reducerName] as (
...p: ParamsType<ReducerDef>
) => void;
return callReducer(...params);
};
}
@@ -0,0 +1,13 @@
import {
assertInInjectionContext,
inject,
computed,
type Signal,
} from '@angular/core';
import { SPACETIMEDB_CONNECTION } from '../connection_state';
export function injectSpacetimeDBConnected(): Signal<boolean> {
assertInInjectionContext(injectSpacetimeDBConnected);
const state = inject(SPACETIMEDB_CONNECTION);
return computed(() => state().isActive);
}
@@ -0,0 +1,10 @@
import { assertInInjectionContext, inject, type Signal } from '@angular/core';
import {
SPACETIMEDB_CONNECTION,
type ConnectionState,
} from '../connection_state';
export function injectSpacetimeDB(): Signal<ConnectionState> {
assertInInjectionContext(injectSpacetimeDB);
return inject(SPACETIMEDB_CONNECTION).asReadonly();
}
@@ -0,0 +1,234 @@
import {
assertInInjectionContext,
inject,
signal,
effect,
type Signal,
} from '@angular/core';
import type { RowType, UntypedTableDef } from '../../lib/table';
import type { Prettify } from '../../lib/type_util';
import { SPACETIMEDB_CONNECTION } from '../connection_state';
import {
type Query,
type BooleanExpr,
toSql,
evaluateBooleanExpr,
getQueryAccessorName,
getQueryWhereClause,
} from '../../lib/query';
import type { EventContextInterface } from '../../sdk';
import type { UntypedRemoteModule } from '../../sdk/spacetime_module';
export type RowTypeDef<TableDef extends UntypedTableDef> = Prettify<
RowType<TableDef>
>;
export interface TableRows<TableDef extends UntypedTableDef> {
rows: readonly RowTypeDef<TableDef>[];
isLoading: boolean;
}
export interface InjectTableCallbacks<RowType> {
onInsert?: (row: RowType) => void;
onDelete?: (row: RowType) => void;
onUpdate?: (oldRow: RowType, newRow: RowType) => void;
}
type MembershipChange = 'enter' | 'leave' | 'stayIn' | 'stayOut';
function classifyMembership(
whereExpr: BooleanExpr<any> | undefined,
oldRow: Record<string, any>,
newRow: Record<string, any>
): MembershipChange {
if (!whereExpr) return 'stayIn';
const oldIn = evaluateBooleanExpr(whereExpr, oldRow);
const newIn = evaluateBooleanExpr(whereExpr, newRow);
if (oldIn && !newIn) return 'leave';
if (!oldIn && newIn) return 'enter';
if (oldIn && newIn) return 'stayIn';
return 'stayOut';
}
/**
* Angular injection function to subscribe to a table in SpacetimeDB and receive live updates.
*
* Must be called within an injection context (component field initializer or constructor).
*
* Accepts a query builder expression as the first argument:
* - `tables.user` — subscribe to all rows
* - `tables.user.where(r => r.online.eq(true))` — subscribe with a filter
*
* @template TableDef The table definition type.
*
* @param query - A query builder expression (table reference or filtered query).
* @param callbacks - Optional callbacks for row insert, delete, and update events.
*
* @returns A signal containing the current rows and loading state.
*
* @example
* ```typescript
* export class UsersComponent {
* users = injectTable(tables.user);
*
* // With a filter:
* onlineUsers = injectTable(
* tables.user.where(r => r.online.eq(true)),
* {
* onInsert: (row) => console.log('Inserted:', row),
* onDelete: (row) => console.log('Deleted:', row),
* onUpdate: (oldRow, newRow) => console.log('Updated:', oldRow, newRow),
* }
* );
*
* // In template: {{ users().rows.length }} users
* // Loading state: {{ users().isLoading }}
* }
* ```
*/
export function injectTable<TableDef extends UntypedTableDef>(
query: Query<TableDef>,
callbacks?: InjectTableCallbacks<RowTypeDef<TableDef>>
): Signal<TableRows<TableDef>> {
assertInInjectionContext(injectTable);
const connState = inject(SPACETIMEDB_CONNECTION);
const accessorName = getQueryAccessorName(query);
const whereExpr = getQueryWhereClause(query);
const querySql = toSql(query);
const tableSignal = signal<TableRows<TableDef>>({
isLoading: true,
rows: [],
});
let latestTransactionEvent: any = null;
let subscribeApplied = false;
// Note: this code is mostly derived from the React useTable implementation
// in order to keep behavior consistent across frameworks.
const computeSnapshot = (): readonly RowTypeDef<TableDef>[] => {
const state = connState();
if (!state.isActive) {
return [];
}
const connection = state.getConnection();
if (!connection) {
return [];
}
const table = connection.db[accessorName];
if (whereExpr) {
return Array.from(table.iter()).filter(row =>
evaluateBooleanExpr(whereExpr, row as Record<string, any>)
) as RowTypeDef<TableDef>[];
}
return Array.from(table.iter()) as RowTypeDef<TableDef>[];
};
const updateSnapshot = () => {
tableSignal.set({
rows: computeSnapshot(),
isLoading: !subscribeApplied,
});
};
effect((onCleanup: (fn: () => void) => void) => {
const state = connState();
if (!state.isActive) {
return;
}
const connection = state.getConnection();
if (!connection) {
return;
}
const table = connection.db[accessorName];
const onInsert = (
ctx: EventContextInterface<UntypedRemoteModule>,
row: any
) => {
if (whereExpr && !evaluateBooleanExpr(whereExpr, row)) {
return;
}
callbacks?.onInsert?.(row);
if (ctx.event !== latestTransactionEvent || !latestTransactionEvent) {
latestTransactionEvent = ctx.event;
updateSnapshot();
}
};
const onDelete = (
ctx: EventContextInterface<UntypedRemoteModule>,
row: any
) => {
if (whereExpr && !evaluateBooleanExpr(whereExpr, row)) {
return;
}
callbacks?.onDelete?.(row);
if (ctx.event !== latestTransactionEvent || !latestTransactionEvent) {
latestTransactionEvent = ctx.event;
updateSnapshot();
}
};
const onUpdate = (
ctx: EventContextInterface<UntypedRemoteModule>,
oldRow: any,
newRow: any
) => {
const change = classifyMembership(whereExpr, 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 (ctx.event !== latestTransactionEvent || !latestTransactionEvent) {
latestTransactionEvent = ctx.event;
updateSnapshot();
}
};
table.onInsert(onInsert);
table.onDelete(onDelete);
table.onUpdate?.(onUpdate);
const subscription = connection
.subscriptionBuilder()
.onApplied(() => {
subscribeApplied = true;
updateSnapshot();
})
.subscribe(querySql);
onCleanup(() => {
table.removeOnInsert(onInsert);
table.removeOnDelete(onDelete);
table.removeOnUpdate?.(onUpdate);
subscription.unsubscribe();
});
});
return tableSignal.asReadonly();
}
@@ -0,0 +1 @@
export { provideSpacetimeDB } from './provide-spacetimedb.ts';
@@ -0,0 +1,96 @@
import {
makeEnvironmentProviders,
provideAppInitializer,
signal,
type EnvironmentProviders,
} from '@angular/core';
import type {
DbConnectionBuilder,
DbConnectionImpl,
ErrorContextInterface,
RemoteModuleOf,
} from '../../sdk/db_connection_impl';
import {
SPACETIMEDB_CONNECTION,
type ConnectionState,
} from '../connection_state';
import { ConnectionId } from '../../lib/connection_id';
let connRef: DbConnectionImpl<any> | null = null;
export function provideSpacetimeDB<DbConnection extends DbConnectionImpl<any>>(
connectionBuilder: DbConnectionBuilder<DbConnection>
): EnvironmentProviders {
const state = signal<ConnectionState>({
isActive: false,
identity: undefined,
token: undefined,
connectionId: ConnectionId.random(),
connectionError: undefined,
getConnection: () => null,
});
return makeEnvironmentProviders([
{ provide: SPACETIMEDB_CONNECTION, useValue: state },
provideAppInitializer(() => {
if (typeof window === 'undefined') {
return;
}
const getConnection = <T extends DbConnectionImpl<any>>() =>
connRef as T | null;
if (!connRef) {
connRef = connectionBuilder.build();
}
const onConnect = (conn: DbConnection) => {
state.set({
...state(),
isActive: conn.isActive,
identity: conn.identity,
token: conn.token,
connectionId: conn.connectionId,
getConnection,
});
};
const onDisconnect = (
ctx: ErrorContextInterface<RemoteModuleOf<DbConnection>>
) => {
state.set({
...state(),
isActive: ctx.isActive,
});
};
const onConnectError = (
ctx: ErrorContextInterface<RemoteModuleOf<DbConnection>>,
err: Error
) => {
state.set({
...state(),
isActive: ctx.isActive,
connectionError: err,
});
};
connectionBuilder.onConnect(onConnect);
connectionBuilder.onDisconnect(onDisconnect);
connectionBuilder.onConnectError(onConnectError);
// sync initial state if already connected
const conn = connRef;
if (conn) {
state.set({
...state(),
isActive: conn.isActive,
identity: conn.identity,
token: conn.token,
connectionId: conn.connectionId,
getConnection,
});
}
}),
]);
}
@@ -0,0 +1,184 @@
import type { RowType, UntypedTableDef } from './table';
import { Uuid } from './uuid';
export type Value = string | number | boolean | Uuid;
export type Expr<Column extends string> =
| { type: 'eq'; key: Column; value: Value }
| { type: 'and'; children: Expr<Column>[] }
| { type: 'or'; children: Expr<Column>[] };
export const eq = <Column extends string>(
key: Column,
value: Value
): Expr<Column> => ({ type: 'eq', key, value });
export const and = <Column extends string>(
...children: Expr<Column>[]
): Expr<Column> => {
const flat: Expr<Column>[] = [];
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 = <Column extends string>(
...children: Expr<Column>[]
): Expr<Column> => {
const flat: Expr<Column>[] = [];
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 = <Column extends string>(
e: Expr<Column>
): e is Extract<Expr<Column>, { type: 'eq' }> => e.type === 'eq';
export const isAnd = <Column extends string>(
e: Expr<Column>
): e is Extract<Expr<Column>, { type: 'and' }> => e.type === 'and';
export const isOr = <Column extends string>(
e: Expr<Column>
): e is Extract<Expr<Column>, { type: 'or' }> => e.type === 'or';
export function evaluate<Column extends string>(
expr: Expr<Column>,
row: Record<Column, any>
): boolean {
switch (expr.type) {
case 'eq': {
// The actual value of the Column
const v = row[expr.key];
if (
typeof v === 'string' ||
typeof v === 'number' ||
typeof v === 'boolean'
) {
return v === expr.value;
}
if (typeof v === 'object') {
// Value of the Column and passed Value are both a Uuid so do an integer comparison.
if (v instanceof Uuid && expr.value instanceof Uuid) {
return v.asBigInt() === expr.value.asBigInt();
}
// Value of the Column is a Uuid but passed Value is a String so compare them via string.
if (v instanceof Uuid && typeof expr.value === 'string') {
return v.toString() === 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';
case 'object': {
if (v instanceof Uuid) {
return `'${v.toString()}'`;
}
return '';
}
}
}
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 extends UntypedTableDef>(
tableDef: TableDef,
expr: Expr<ColumnsFromRow<RowType<TableDef>>>
): 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<Column extends string>(expr: Expr<Column>): Expr<Column> {
return expr;
}
type MembershipChange = 'enter' | 'leave' | 'stayIn' | 'stayOut';
export function classifyMembership<
Col extends string,
R extends Record<string, unknown>,
>(where: Expr<Col> | 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.
*/
export type ColumnsFromRow<R> = {
[K in keyof R]-?: R[K] extends Value | undefined ? K : never;
}[keyof R] &
string;
@@ -1,15 +1,7 @@
import { useCallback, useEffect, useRef } from 'react';
import type { InferTypeOfRow } from '../lib/type_builders';
import type { UntypedReducerDef } from '../sdk/reducers';
import { useSpacetimeDB } from './useSpacetimeDB';
import type { Prettify } from '../lib/type_util';
type IsEmptyObject<T> = [keyof T] extends [never] ? true : false;
type MaybeParams<T> = IsEmptyObject<T> extends true ? [] : [params: T];
type ParamsType<R extends UntypedReducerDef> = MaybeParams<
Prettify<InferTypeOfRow<R['params']>>
>;
import type { ParamsType } from '../sdk';
export function useReducer<ReducerDef extends UntypedReducerDef>(
reducerDef: ReducerDef
@@ -9,3 +9,4 @@ export { schema, convertToAccessorMap } from './schema.ts';
export { table } from '../lib/table.ts';
export { reducerSchema, reducers } from './reducers.ts';
export { procedureSchema, procedures } from './procedures.ts';
export * from './type_utils.ts';
@@ -0,0 +1,10 @@
import type { InferTypeOfRow } from '.';
import type { Prettify } from '../lib/type_util';
import type { UntypedReducerDef } from './reducers';
export type IsEmptyObject<T> = [keyof T] extends [never] ? true : false;
export type MaybeParams<T> = IsEmptyObject<T> extends true ? [] : [params: T];
export type ParamsType<R extends UntypedReducerDef> = MaybeParams<
Prettify<InferTypeOfRow<R['params']>>
>;
@@ -1,15 +1,7 @@
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<T> = [keyof T] extends [never] ? true : false;
type MaybeParams<T> = IsEmptyObject<T> extends true ? [] : [params: T];
type ParamsType<R extends UntypedReducerDef> = MaybeParams<
Prettify<InferTypeOfRow<R['params']>>
>;
import type { ParamsType } from '../sdk';
export function useReducer<ReducerDef extends UntypedReducerDef>(
reducerDef: ReducerDef
+32
View File
@@ -140,6 +140,38 @@ export default defineConfig([
esbuildOptions: commonEsbuildTweaks(),
},
// Angular subpath (SSR-friendly): dist/angular/index.{mjs,cjs}
{
entry: { index: 'src/angular/index.ts' },
format: ['esm', 'cjs'],
target: 'es2022',
outDir: 'dist/angular',
dts: false,
sourcemap: true,
clean: true,
platform: 'neutral',
treeshake: 'smallest',
external: ['@angular/core'],
outExtension,
esbuildOptions: commonEsbuildTweaks(),
},
// Angular subpath (browser ESM): dist/browser/angular/index.mjs
{
entry: { index: 'src/angular/index.ts' },
format: ['esm'],
target: 'es2022',
outDir: 'dist/browser/angular',
dts: false,
sourcemap: true,
clean: true,
platform: 'browser',
treeshake: 'smallest',
external: ['@angular/core'],
outExtension,
esbuildOptions: commonEsbuildTweaks(),
},
// SDK subpath (SSR-friendly): dist/sdk/index.{mjs,cjs}
{
entry: { index: 'src/sdk/index.ts' },
@@ -0,0 +1,130 @@
---
title: Angular Quickstart
sidebar_label: Angular
slug: /quickstarts/angular
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 Angular app running in under 5 minutes.
## Prerequisites
- [Node.js](https://nodejs.org/) 18+ installed
- [SpacetimeDB CLI](https://spacetimedb.com/install) installed
<InstallCardLink />
---
<StepByStep>
<Step title="Create your project">
<StepText>
Run the `spacetime dev` command to create a new project with a SpacetimeDB module and Angular client.
This will start the local SpacetimeDB server, publish your module, generate TypeScript bindings, and start the Angular development server.
</StepText>
<StepCode>
```bash
spacetime dev --template angular-ts
```
</StepCode>
</Step>
<Step title="Open your app">
<StepText>
Navigate to [http://localhost:4200](http://localhost:4200) to see your app running.
The template includes a basic Angular app connected to SpacetimeDB.
</StepText>
</Step>
<Step title="Explore the project structure">
<StepText>
Your project contains both server and client code.
Edit `spacetimedb/src/index.ts` to add tables and reducers. Edit `src/app/app.component.ts` to build your UI.
</StepText>
<StepCode>
```
my-spacetime-app/
├── spacetimedb/ # Your SpacetimeDB module
│ └── src/
│ └── index.ts # Server-side logic
├── src/ # Angular frontend
│ └── app/
│ ├── app.component.ts
│ ├── app.config.ts
│ └── module_bindings/ # Auto-generated types
├── angular.json
└── package.json
```
</StepCode>
</Step>
<Step title="Understand tables and reducers">
<StepText>
Open `spacetimedb/src/index.ts` to see the module code. The template includes a `person` table and two reducers: `add` to insert a person, and `say_hello` to greet everyone.
Tables store your data. Reducers are functions that modify data — they're the only way to write to the database.
</StepText>
<StepCode>
```typescript
import { schema, table, t } from 'spacetimedb/server';
export const spacetimedb = schema(
table(
{ name: 'person', public: true },
{
name: t.string(),
}
)
);
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!');
});
```
</StepCode>
</Step>
<Step title="Test with the CLI">
<StepText>
Use the SpacetimeDB CLI to call reducers and query your data directly.
</StepText>
<StepCode>
```bash
# Call the add reducer to insert a person
spacetime call <database-name> add Alice
# Query the person table
spacetime sql <database-name> "SELECT * FROM person"
name
---------
"Alice"
# Call say_hello to greet everyone
spacetime call <database-name> say_hello
# View the module logs
spacetime logs <database-name>
2025-01-13T12:00:00.000000Z INFO: Hello, Alice!
2025-01-13T12:00:00.000000Z INFO: Hello, World!
```
</StepCode>
</Step>
</StepByStep>
## Next steps
- Read the [TypeScript SDK Reference](/sdks/typescript) for detailed API docs
+26 -1
View File
@@ -16,7 +16,12 @@ export default tseslint.config(
},
}),
{
ignores: ['**/dist/**', '**/build/**', '**/coverage/**'],
ignores: [
'**/dist/**',
'**/build/**',
'**/coverage/**',
'**/templates/angular-ts/.angular/**',
],
},
js.configs.recommended,
{
@@ -47,6 +52,7 @@ export default tseslint.config(
'./templates/react-ts/tsconfig.json',
'./templates/chat-react-ts/tsconfig.json',
'./templates/basic-ts/tsconfig.json',
'./templates/angular-ts/tsconfig.app.json',
'./docs/tsconfig.json',
],
projectService: true,
@@ -96,5 +102,24 @@ export default tseslint.config(
{ allowObjectTypes: 'always' },
],
},
},
{
files: ['templates/angular-ts/src/**/*.ts'],
rules: {
'no-restricted-syntax': [
'error',
{
selector: 'TSEnumDeclaration',
message: 'Do not use enums; stick to JS-compatible types.',
},
{
selector: 'TSEnumDeclaration[const=true]',
message: 'Do not use const enum; use unions or objects.',
},
],
'react-hooks/rules-of-hooks': 'off',
'react-hooks/exhaustive-deps': 'off',
'react-refresh/only-export-components': 'off',
},
}
);
+3515 -271
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -8,6 +8,7 @@ packages:
- 'templates/tanstack-ts'
- 'templates/browser-ts'
- 'templates/svelte-ts'
- 'templates/angular-ts'
- 'templates/nuxt-ts'
- 'modules/benchmarks-ts'
- 'modules/module-test-ts'
+44
View File
@@ -0,0 +1,44 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/mcp.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/
# System files
.DS_Store
Thumbs.db
+5
View File
@@ -0,0 +1,5 @@
{
"description": "Angular web app with TypeScript server",
"client_lang": "typescript",
"server_lang": "typescript"
}
+68
View File
@@ -0,0 +1,68 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-ts": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": ["src/styles.css"]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "angular-ts:build:production"
},
"development": {
"buildTarget": "angular-ts:build:development"
}
},
"defaultConfiguration": "development"
}
}
}
},
"cli": {
"analytics": false
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "@clockworklabs/angular-ts",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "ng serve",
"build": "ng build",
"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": {
"@angular/common": "^21.1.1",
"@angular/compiler": "^21.1.1",
"@angular/core": "^21.1.1",
"@angular/platform-browser": "^21.1.1",
"rxjs": "~7.8.0",
"spacetimedb": "workspace:*",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular/build": "^21.1.1",
"@angular/cli": "^21.1.1",
"@angular/compiler-cli": "^21.1.0",
"typescript": "~5.9.2"
}
}
@@ -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": "workspace:*"
}
}
@@ -0,0 +1,37 @@
import { schema, table, t } from 'spacetimedb/server';
const spacetimedb = schema({
person: table(
{ public: true },
{
name: t.string(),
}
),
});
export default spacetimedb;
export const init = spacetimedb.init(_ctx => {
// Called when the module is initially published
});
export const onConnect = spacetimedb.clientConnected(_ctx => {
// Called every time a new client connects
});
export const onDisconnect = spacetimedb.clientDisconnected(_ctx => {
// Called every time a client disconnects
});
export const add = spacetimedb.reducer(
{ name: t.string() },
(ctx, { name }) => {
ctx.db.person.insert({ name });
}
);
export const say_hello = spacetimedb.reducer(ctx => {
for (const person of ctx.db.person.iter()) {
console.info(`Hello, ${person.name}!`);
}
console.info('Hello, World!');
});
@@ -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",
"jsx": "react-jsx",
/* The following options are required by SpacetimeDB
* and should not be modified
*/
"target": "ESNext",
"lib": ["ES2021", "dom"],
"module": "ESNext",
"isolatedModules": true,
"noEmit": true
},
"include": ["./**/*"]
}
@@ -0,0 +1,69 @@
import { Component } from '@angular/core';
import {
injectSpacetimeDB,
injectTable,
injectReducer,
} from 'spacetimedb/angular';
import { tables, reducers } from '../module_bindings';
@Component({
selector: 'app-root',
template: `
<div style="padding: 2rem">
<h1>SpacetimeDB Angular App</h1>
<div style="margin-bottom: 1rem">
Status:
<strong [style.color]="conn().isActive ? 'green' : 'red'">
{{ conn().isActive ? 'Connected' : 'Disconnected' }}
</strong>
</div>
<form (submit)="addPerson($event)" style="margin-bottom: 2rem">
<input
type="text"
placeholder="Enter name"
[value]="name"
(input)="name = $any($event.target).value"
style="padding: 0.5rem; margin-right: 0.5rem"
[disabled]="!conn().isActive"
/>
<button
type="submit"
style="padding: 0.5rem 1rem"
[disabled]="!conn().isActive"
>
Add Person
</button>
</form>
<div>
<h2>People ({{ people().rows.length }})</h2>
@if (people().rows.length === 0) {
<p>No people yet. Add someone above!</p>
} @else {
<ul>
@for (person of people().rows; track $index) {
<li>{{ person.name }}</li>
}
</ul>
}
</div>
</div>
`,
})
export class App {
protected conn = injectSpacetimeDB();
protected people = injectTable(tables.person);
private addReducer = injectReducer(reducers.add);
protected name = '';
addPerson(event: Event) {
event.preventDefault();
if (!this.name.trim() || !this.conn().isActive) return;
// Call the add reducer
this.addReducer({ name: this.name });
this.name = '';
}
}
@@ -0,0 +1,41 @@
import {
ApplicationConfig,
provideBrowserGlobalErrorListeners,
} from '@angular/core';
import { provideSpacetimeDB } from 'spacetimedb/angular';
import { DbConnection, ErrorContext } from '../module_bindings';
import { Identity } from 'spacetimedb';
const HOST = import.meta.env.VITE_SPACETIMEDB_HOST ?? 'ws://localhost:3000';
const DB_NAME = import.meta.env.VITE_SPACETIMEDB_DB_NAME ?? 'angular-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);
};
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideSpacetimeDB(
DbConnection.builder()
.withUri(HOST)
.withDatabaseName(DB_NAME)
.withToken(localStorage.getItem('auth_token') || undefined)
.onConnect(onConnect)
.onDisconnect(onDisconnect)
.onConnectError(onConnectError)
),
],
};
+10
View File
@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_SPACETIMEDB_HOST: string;
readonly VITE_SPACETIMEDB_DB_NAME: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SpacetimeDB Angular App</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<app-root></app-root>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app.component';
bootstrapApplication(App, appConfig).catch(err => console.error(err));
+15
View File
@@ -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(),
};
+15
View File
@@ -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(),
});
+129
View File
@@ -0,0 +1,129 @@
// 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 2.0.0 (commit 4cf57e2fe6ba480834ee0bb2f6aefa4482550164).
/* eslint-disable */
/* tslint:disable */
import {
DbConnectionBuilder as __DbConnectionBuilder,
DbConnectionImpl as __DbConnectionImpl,
SubscriptionBuilderImpl as __SubscriptionBuilderImpl,
TypeBuilder as __TypeBuilder,
Uuid as __Uuid,
convertToAccessorMap as __convertToAccessorMap,
makeQueryBuilder as __makeQueryBuilder,
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 QueryBuilder as __QueryBuilder,
type ReducerEventContextInterface as __ReducerEventContextInterface,
type RemoteModule as __RemoteModule,
type SubscriptionEventContextInterface as __SubscriptionEventContextInterface,
type SubscriptionHandleImpl as __SubscriptionHandleImpl,
} from 'spacetimedb';
// Import all reducer arg schemas
import AddReducer from './add_reducer';
import SayHelloReducer from './say_hello_reducer';
// Import all procedure arg schemas
// Import all table schema definitions
import PersonRow from './person_table';
/** Type-only namespace exports for generated type groups. */
/** 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({
person: __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: '2.0.0' 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. Each table reference doubles as a query builder. */
export const tables: __QueryBuilder<typeof tablesSchema.schemaType> =
__makeQueryBuilder(tablesSchema.schemaType);
/** 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<typeof REMOTE_MODULE>;
/** 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<typeof REMOTE_MODULE>;
/** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */
export type SubscriptionHandle = __SubscriptionHandleImpl<typeof REMOTE_MODULE>;
/** 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<DbConnection> {}
/** 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<typeof REMOTE_MODULE> {
/** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */
static builder = (): DbConnectionBuilder => {
return new DbConnectionBuilder(
REMOTE_MODULE,
(config: __DbConnectionConfig<typeof REMOTE_MODULE>) =>
new DbConnection(config)
);
};
/** Creates a new {@link SubscriptionBuilder} to configure a subscription to the remote SpacetimeDB instance. */
override subscriptionBuilder = (): SubscriptionBuilder => {
return new SubscriptionBuilder(this);
};
}
+13
View File
@@ -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', {});
@@ -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 {};
@@ -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', {});
@@ -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 {};
@@ -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', {});
+15
View File
@@ -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(),
});
+15
View File
@@ -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(),
});
+8
View File
@@ -0,0 +1,8 @@
// 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 { type Infer as __Infer } from 'spacetimedb';
// Import all procedure arg schemas
+17
View File
@@ -0,0 +1,17 @@
// 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 { type Infer as __Infer } from 'spacetimedb';
// Import all reducer arg schemas
import OnConnectReducer from './on_connect_reducer';
import OnDisconnectReducer from './on_disconnect_reducer';
import AddReducer from './add_reducer';
import SayHelloReducer from './say_hello_reducer';
export type OnConnectArgs = __Infer<typeof OnConnectReducer>;
export type OnDisconnectArgs = __Infer<typeof OnDisconnectReducer>;
export type AddArgs = __Infer<typeof AddReducer>;
export type SayHelloArgs = __Infer<typeof SayHelloReducer>;
@@ -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 {};
@@ -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', {});
+11
View File
@@ -0,0 +1,11 @@
// 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 { type Infer as __Infer } from 'spacetimedb';
// Import all non-reducer types
import Person from './person_type';
export type Person = __Infer<typeof Person>;
+11
View File
@@ -0,0 +1,11 @@
// 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 { type Infer as __Infer } from 'spacetimedb';
// Import all non-reducer types
import Person from '../person_type';
export type Person = __Infer<typeof Person>;
@@ -0,0 +1,8 @@
// 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 { type Infer as __Infer } from 'spacetimedb';
// Import all procedure arg schemas
@@ -0,0 +1,17 @@
// 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 { type Infer as __Infer } from 'spacetimedb';
// Import all reducer arg schemas
import AddReducer from '../add_reducer';
import OnConnectReducer from '../on_connect_reducer';
import OnDisconnectReducer from '../on_disconnect_reducer';
import SayHelloReducer from '../say_hello_reducer';
export type AddParams = __Infer<typeof AddReducer>;
export type OnConnectParams = __Infer<typeof OnConnectReducer>;
export type OnDisconnectParams = __Infer<typeof OnDisconnectReducer>;
export type SayHelloParams = __Infer<typeof SayHelloReducer>;
+1
View File
@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */
+15
View File
@@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
]
}
+24
View File
@@ -0,0 +1,24 @@
{
"compileOnSave": false,
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
},
"files": [],
"references": [{ "path": "./tsconfig.app.json" }]
}