Skip to content

Commit 1a1faaa

Browse files
feat(angular): angular integration refinements (convert chat app -> basic app, add quickstart docs, sdk improvements)
1 parent f162f85 commit 1a1faaa

50 files changed

Lines changed: 3809 additions & 6507 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { InjectionToken, type WritableSignal } from '@angular/core';
2+
import type { ConnectionId } from '../lib/connection_id';
3+
import type { Identity } from '../lib/identity';
4+
import type { DbConnectionImpl } from '../sdk/db_connection_impl';
5+
6+
export interface ConnectionState {
7+
isActive: boolean;
8+
identity?: Identity;
9+
token?: string;
10+
connectionId: ConnectionId;
11+
connectionError?: Error;
12+
getConnection<
13+
DbConnection extends DbConnectionImpl<any>,
14+
>(): DbConnection | null;
15+
}
16+
17+
export const SPACETIMEDB_CONNECTION = new InjectionToken<
18+
WritableSignal<ConnectionState>
19+
>('SpacetimeDB Connection State');
Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,4 @@
1-
export * from './injectors';
2-
export * from './providers';
3-
export {
4-
type Value,
5-
type Expr,
6-
eq,
7-
and,
8-
or,
9-
isEq,
10-
isAnd,
11-
isOr,
12-
where,
13-
} from '../lib/filter';
1+
export type { ConnectionState } from './connection_state.ts';
2+
export * from './injectors/index.ts';
3+
export * from './providers/index.ts';
4+
export { eq, where } from '../lib/filter.ts';
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { injectSpacetimeDB } from './inject-spacetimedb';
2-
export { injectTable, type TableRows } from './inject-table';
3-
export { injectSpacetimeDBConnected } from './inject-spacetimedb-connected';
4-
export { injectReducer } from './inject-reducer';
1+
export { injectSpacetimeDB } from './inject-spacetimedb.ts';
2+
export { injectTable, type TableRows } from './inject-table.ts';
3+
export { injectSpacetimeDBConnected } from './inject-spacetimedb-connected.ts';
4+
export { injectReducer } from './inject-reducer.ts';

crates/bindings-typescript/src/angular/injectors/inject-reducer.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
1+
import { assertInInjectionContext, inject, effect } from '@angular/core';
2+
import { SPACETIMEDB_CONNECTION } from '../connection_state';
13
import type { ParamsType } from '../../sdk';
24
import type { UntypedReducerDef } from '../../sdk/reducers';
3-
import { injectSpacetimeDB } from './inject-spacetimedb';
4-
import { injectSpacetimeDBConnected } from './inject-spacetimedb-connected';
5-
import { DestroyRef, effect, inject } from '@angular/core';
65

76
export function injectReducer<ReducerDef extends UntypedReducerDef>(
87
reducerDef: ReducerDef
9-
) {
10-
const conn = injectSpacetimeDB();
11-
const isActive = injectSpacetimeDBConnected();
12-
const destroyRef = inject(DestroyRef);
8+
): (...params: ParamsType<ReducerDef>) => void {
9+
assertInInjectionContext(injectReducer);
1310

11+
const connState = inject(SPACETIMEDB_CONNECTION);
1412
const queue: ParamsType<ReducerDef>[] = [];
1513
const reducerName = reducerDef.accessorName;
1614

17-
effect(() => {
18-
if (!isActive()) {
15+
// flush queued calls when connection becomes active
16+
effect(onCleanup => {
17+
const state = connState();
18+
if (!state.isActive) {
1919
return;
2020
}
2121

22-
const callReducer = (conn.reducers as any)[reducerName] as (
22+
const connection = state.getConnection();
23+
if (!connection) {
24+
return;
25+
}
26+
27+
const callReducer = (connection.reducers as any)[reducerName] as (
2328
...p: ParamsType<ReducerDef>
2429
) => void;
2530

@@ -29,19 +34,26 @@ export function injectReducer<ReducerDef extends UntypedReducerDef>(
2934
callReducer(...params);
3035
}
3136
}
32-
});
3337

34-
destroyRef.onDestroy(() => {
35-
queue.splice(0);
38+
onCleanup(() => {
39+
queue.splice(0);
40+
});
3641
});
3742

3843
return (...params: ParamsType<ReducerDef>) => {
39-
if (!isActive()) {
44+
const state = connState();
45+
if (!state.isActive) {
46+
queue.push(params);
47+
return;
48+
}
49+
50+
const connection = state.getConnection();
51+
if (!connection) {
4052
queue.push(params);
4153
return;
4254
}
4355

44-
const callReducer = (conn.reducers as any)[reducerName] as (
56+
const callReducer = (connection.reducers as any)[reducerName] as (
4557
...p: ParamsType<ReducerDef>
4658
) => void;
4759

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
1-
import { effect, signal, type Signal } from '@angular/core';
2-
import { injectSpacetimeDB } from './inject-spacetimedb';
1+
import {
2+
assertInInjectionContext,
3+
inject,
4+
computed,
5+
type Signal,
6+
} from '@angular/core';
7+
import { SPACETIMEDB_CONNECTION } from '../connection_state';
38

49
export function injectSpacetimeDBConnected(): Signal<boolean> {
5-
const conn = injectSpacetimeDB();
6-
7-
const connectedSignal = signal<boolean>(conn.isActive);
8-
9-
// FIXME: Bit of a dirty hack for now, we need to change injectSpacetimeDB
10-
// to return a signal so we can react to changes in connection state properly.
11-
effect(onCleanup => {
12-
const interval = setInterval(() => {
13-
connectedSignal.set(conn.isActive);
14-
}, 100);
15-
16-
onCleanup(() => {
17-
clearInterval(interval);
18-
});
19-
});
20-
21-
return connectedSignal.asReadonly();
10+
assertInInjectionContext(injectSpacetimeDBConnected);
11+
const state = inject(SPACETIMEDB_CONNECTION);
12+
return computed(() => state().isActive);
2213
}
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { assertInInjectionContext, inject } from '@angular/core';
2-
import type { DbConnectionImpl } from '../../sdk';
3-
import { SPACETIMEDB_TOKEN } from '../token';
1+
import { assertInInjectionContext, inject, type Signal } from '@angular/core';
2+
import {
3+
SPACETIMEDB_CONNECTION,
4+
type ConnectionState,
5+
} from '../connection_state';
46

5-
export function injectSpacetimeDB<T extends DbConnectionImpl<any>>(): T {
7+
export function injectSpacetimeDB(): Signal<ConnectionState> {
68
assertInInjectionContext(injectSpacetimeDB);
7-
const spacetimedb = inject(SPACETIMEDB_TOKEN);
8-
return spacetimedb as T;
9+
return inject(SPACETIMEDB_CONNECTION).asReadonly();
910
}

crates/bindings-typescript/src/angular/injectors/inject-table.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
1-
import { type Signal, signal, effect } from '@angular/core';
1+
import {
2+
assertInInjectionContext,
3+
inject,
4+
signal,
5+
effect,
6+
type Signal,
7+
} from '@angular/core';
28
import type { RowType, UntypedTableDef } from '../../lib/table';
39
import type { Prettify } from '../../lib/type_util';
4-
import { injectSpacetimeDB } from './inject-spacetimedb';
10+
import { SPACETIMEDB_CONNECTION } from '../connection_state';
511
import {
12+
type Value,
613
type Expr,
714
type ColumnsFromRow,
15+
eq,
16+
and,
17+
or,
18+
isEq,
19+
isAnd,
20+
isOr,
821
evaluate,
922
toString,
23+
where,
1024
classifyMembership,
1125
} from '../../lib/filter';
1226
import type { EventContextInterface } from '../../sdk';
1327
import type { UntypedRemoteModule } from '../../sdk/spacetime_module';
1428

29+
export { eq, and, or, isEq, isAnd, isOr, where };
30+
export type { Value, Expr };
31+
1532
export type RowTypeDef<TableDef extends UntypedTableDef> = Prettify<
1633
RowType<TableDef>
1734
>;
1835

1936
export interface TableRows<TableDef extends UntypedTableDef> {
2037
rows: readonly RowTypeDef<TableDef>[];
2138
isLoading: boolean;
22-
error?: Error;
2339
}
2440

2541
export interface InjectTableCallbacks<RowType> {
@@ -36,9 +52,7 @@ export interface InjectTableOptions<TableDef extends UntypedTableDef> {
3652
/**
3753
* Angular injection function to subscribe to a table in SpacetimeDB and receive live updates.
3854
*
39-
* This function returns a signal containing the table's rows, filtered by an optional `where` clause,
40-
* and provides a loading state until the initial subscription is applied. It also allows you to specify
41-
* callbacks for row insertions, deletions, and updates.
55+
* Must be called within an injection context (component field initializer or constructor).
4256
*
4357
* @template TableDef The table definition type.
4458
*
@@ -68,9 +82,11 @@ export function injectTable<TableDef extends UntypedTableDef>(
6882
tableDef: TableDef,
6983
options?: InjectTableOptions<TableDef>
7084
): Signal<TableRows<TableDef>> {
85+
assertInInjectionContext(injectTable);
86+
7187
type UseTableRowType = RowType<TableDef>;
7288

73-
const conn = injectSpacetimeDB();
89+
const connState = inject(SPACETIMEDB_CONNECTION);
7490

7591
const tableName = tableDef.name;
7692
const accessorName = tableDef.accessorName;
@@ -93,11 +109,17 @@ export function injectTable<TableDef extends UntypedTableDef>(
93109
// in order to keep behavior consistent across frameworks.
94110

95111
const computeSnapshot = (): readonly RowTypeDef<TableDef>[] => {
96-
if (!conn.isActive) {
112+
const state = connState();
113+
if (!state.isActive) {
114+
return [];
115+
}
116+
117+
const connection = state.getConnection();
118+
if (!connection) {
97119
return [];
98120
}
99121

100-
const table = conn.db[accessorName];
122+
const table = connection.db[accessorName];
101123

102124
if (whereClause) {
103125
return Array.from(table.iter()).filter(row =>
@@ -116,11 +138,17 @@ export function injectTable<TableDef extends UntypedTableDef>(
116138
};
117139

118140
effect(onCleanup => {
119-
if (!conn.isActive) {
141+
const state = connState();
142+
if (!state.isActive) {
120143
return;
121144
}
122145

123-
const table = conn.db[accessorName];
146+
const connection = state.getConnection();
147+
if (!connection) {
148+
return;
149+
}
150+
151+
const table = connection.db[accessorName];
124152

125153
const onInsert = (
126154
ctx: EventContextInterface<UntypedRemoteModule>,
@@ -185,19 +213,12 @@ export function injectTable<TableDef extends UntypedTableDef>(
185213
table.onDelete(onDelete);
186214
table.onUpdate?.(onUpdate);
187215

188-
const subscription = conn
216+
const subscription = connection
189217
.subscriptionBuilder()
190218
.onApplied(() => {
191219
subscribeApplied = true;
192220
updateSnapshot();
193221
})
194-
.onError(err => {
195-
tableSignal.set({
196-
rows: [],
197-
isLoading: false,
198-
error: err.event,
199-
});
200-
})
201222
.subscribe(query);
202223

203224
onCleanup(() => {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { provideSpacetimeDB } from './provide-spacetimedb';
1+
export { provideSpacetimeDB } from './provide-spacetimedb.ts';

0 commit comments

Comments
 (0)