spacetimedb 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +2 -2
- package/dist/angular/index.cjs +3 -0
- package/dist/angular/index.cjs.map +1 -1
- package/dist/angular/index.mjs +3 -0
- package/dist/angular/index.mjs.map +1 -1
- package/dist/browser/angular/index.mjs +3 -0
- package/dist/browser/angular/index.mjs.map +1 -1
- package/dist/browser/react/index.mjs +6 -0
- package/dist/browser/react/index.mjs.map +1 -1
- package/dist/browser/svelte/index.mjs +3 -0
- package/dist/browser/svelte/index.mjs.map +1 -1
- package/dist/browser/vue/index.mjs +3 -0
- package/dist/browser/vue/index.mjs.map +1 -1
- package/dist/index.browser.mjs +133 -100
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.cjs +133 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +133 -100
- package/dist/index.mjs.map +1 -1
- package/dist/lib/binary_writer.d.ts +1 -0
- package/dist/lib/binary_writer.d.ts.map +1 -1
- package/dist/lib/indexes.d.ts +1 -1
- package/dist/lib/indexes.d.ts.map +1 -1
- package/dist/lib/query.d.ts +4 -2
- package/dist/lib/query.d.ts.map +1 -1
- package/dist/lib/schema.d.ts +2 -0
- package/dist/lib/schema.d.ts.map +1 -1
- package/dist/lib/table.d.ts +19 -1
- package/dist/lib/table.d.ts.map +1 -1
- package/dist/lib/util.d.ts +11 -11
- package/dist/lib/util.d.ts.map +1 -1
- package/dist/min/index.browser.mjs +1 -1
- package/dist/min/index.browser.mjs.map +1 -1
- package/dist/min/react/index.mjs +1 -1
- package/dist/min/react/index.mjs.map +1 -1
- package/dist/min/sdk/index.browser.mjs +1 -1
- package/dist/min/sdk/index.browser.mjs.map +1 -1
- package/dist/react/index.cjs +6 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.mjs +6 -0
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useTable.d.ts.map +1 -1
- package/dist/sdk/db_connection_impl.d.ts.map +1 -1
- package/dist/sdk/index.browser.mjs +133 -100
- package/dist/sdk/index.browser.mjs.map +1 -1
- package/dist/sdk/index.cjs +133 -100
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.mjs +133 -100
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/sdk/procedures.d.ts +1 -1
- package/dist/sdk/procedures.d.ts.map +1 -1
- package/dist/sdk/reducers.d.ts +2 -3
- package/dist/sdk/reducers.d.ts.map +1 -1
- package/dist/sdk/table_cache.d.ts.map +1 -1
- package/dist/sdk/websocket_decompress_adapter.d.ts +17 -7
- package/dist/sdk/websocket_decompress_adapter.d.ts.map +1 -1
- package/dist/sdk/websocket_test_adapter.d.ts +3 -2
- package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
- package/dist/server/index.mjs +72 -29
- package/dist/server/index.mjs.map +1 -1
- package/dist/svelte/index.cjs +3 -0
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.mjs +3 -0
- package/dist/svelte/index.mjs.map +1 -1
- package/dist/tanstack/SpacetimeDBQueryClient.d.ts +3 -3
- package/dist/tanstack/SpacetimeDBQueryClient.d.ts.map +1 -1
- package/dist/tanstack/hooks.d.ts +3 -6
- package/dist/tanstack/hooks.d.ts.map +1 -1
- package/dist/tanstack/index.cjs +28 -1
- package/dist/tanstack/index.cjs.map +1 -1
- package/dist/tanstack/index.mjs +28 -1
- package/dist/tanstack/index.mjs.map +1 -1
- package/dist/vue/index.cjs +3 -0
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.mjs +3 -0
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/binary_writer.ts +4 -0
- package/src/lib/indexes.ts +1 -1
- package/src/lib/query.ts +30 -6
- package/src/lib/schema.ts +66 -24
- package/src/lib/table.ts +41 -9
- package/src/lib/util.ts +18 -21
- package/src/react/useTable.ts +7 -0
- package/src/sdk/db_connection_impl.ts +38 -43
- package/src/sdk/procedures.ts +1 -3
- package/src/sdk/reducers.ts +5 -7
- package/src/sdk/table_cache.ts +14 -11
- package/src/sdk/websocket_decompress_adapter.ts +42 -45
- package/src/sdk/websocket_test_adapter.ts +3 -2
- package/src/server/runtime.ts +7 -3
- package/src/server/schema.test-d.ts +37 -0
- package/src/server/view.test-d.ts +2 -0
- package/src/tanstack/SpacetimeDBQueryClient.ts +28 -2
- package/src/tanstack/hooks.ts +3 -2
|
@@ -1,48 +1,55 @@
|
|
|
1
1
|
import { decompress } from './decompress';
|
|
2
2
|
import { resolveWS } from './ws';
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const buffer = new Uint8Array(msg.data);
|
|
14
|
-
let decompressed: Uint8Array;
|
|
15
|
-
|
|
16
|
-
if (buffer[0] === 0) {
|
|
17
|
-
decompressed = buffer.slice(1);
|
|
18
|
-
} else if (buffer[0] === 1) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
'Brotli Compression not supported. Please use gzip or none compression in withCompression method on DbConnection.'
|
|
21
|
-
);
|
|
22
|
-
} else if (buffer[0] === 2) {
|
|
23
|
-
decompressed = await decompress(buffer.slice(1), 'gzip');
|
|
24
|
-
} else {
|
|
25
|
-
throw new Error(
|
|
26
|
-
'Unexpected Compression Algorithm. Please use `gzip` or `none`'
|
|
27
|
-
);
|
|
28
|
-
}
|
|
4
|
+
export interface WebsocketAdapter {
|
|
5
|
+
send(msg: Uint8Array): void;
|
|
6
|
+
close(): void;
|
|
7
|
+
|
|
8
|
+
set onclose(handler: (ev: CloseEvent) => void);
|
|
9
|
+
set onopen(handler: () => void);
|
|
10
|
+
set onmessage(handler: (msg: { data: Uint8Array }) => void);
|
|
11
|
+
set onerror(handler: (msg: ErrorEvent) => void);
|
|
12
|
+
}
|
|
29
13
|
|
|
30
|
-
|
|
14
|
+
export class WebsocketDecompressAdapter implements WebsocketAdapter {
|
|
15
|
+
set onclose(handler: (ev: CloseEvent) => void) {
|
|
16
|
+
this.#ws.onclose = handler;
|
|
31
17
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
this.onopen?.(msg);
|
|
18
|
+
set onopen(handler: () => void) {
|
|
19
|
+
this.#ws.onopen = handler;
|
|
35
20
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
set onmessage(handler: (msg: { data: Uint8Array }) => void) {
|
|
22
|
+
this.#ws.onmessage = async (msg: MessageEvent<ArrayBuffer>) => {
|
|
23
|
+
const data = await this.#decompress(new Uint8Array(msg.data));
|
|
24
|
+
handler({ data });
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
set onerror(handler: (msg: ErrorEvent) => void) {
|
|
28
|
+
this.#ws.onerror = handler as (msg: Event) => void;
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
#
|
|
42
|
-
|
|
31
|
+
#ws: WebSocket;
|
|
32
|
+
|
|
33
|
+
async #decompress(buffer: Uint8Array): Promise<Uint8Array> {
|
|
34
|
+
const tag = buffer[0];
|
|
35
|
+
const data = buffer.subarray(1);
|
|
36
|
+
switch (tag) {
|
|
37
|
+
case 0:
|
|
38
|
+
return data;
|
|
39
|
+
case 1:
|
|
40
|
+
throw new Error(
|
|
41
|
+
'Brotli Compression not supported. Please use gzip or none compression in withCompression method on DbConnection.'
|
|
42
|
+
);
|
|
43
|
+
case 2:
|
|
44
|
+
return await decompress(data, 'gzip');
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(
|
|
47
|
+
'Unexpected Compression Algorithm. Please use `gzip` or `none`'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
send(msg:
|
|
52
|
+
send(msg: Uint8Array): void {
|
|
46
53
|
this.#ws.send(msg);
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -51,16 +58,6 @@ export class WebsocketDecompressAdapter {
|
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
constructor(ws: WebSocket) {
|
|
54
|
-
this.onmessage = undefined;
|
|
55
|
-
this.onopen = undefined;
|
|
56
|
-
this.onmessage = undefined;
|
|
57
|
-
this.onerror = undefined;
|
|
58
|
-
|
|
59
|
-
ws.onmessage = this.#handleOnMessage.bind(this);
|
|
60
|
-
ws.onerror = this.#handleOnError.bind(this);
|
|
61
|
-
ws.onclose = this.#handleOnClose.bind(this);
|
|
62
|
-
ws.onopen = this.#handleOnOpen.bind(this);
|
|
63
|
-
|
|
64
61
|
ws.binaryType = 'arraybuffer';
|
|
65
62
|
|
|
66
63
|
this.#ws = ws;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { BinaryReader, BinaryWriter } from '../';
|
|
2
2
|
import { ClientMessage, ServerMessage } from './client_api/types';
|
|
3
|
+
import type { WebsocketAdapter } from './websocket_decompress_adapter';
|
|
3
4
|
|
|
4
|
-
class WebsocketTestAdapter {
|
|
5
|
+
class WebsocketTestAdapter implements WebsocketAdapter {
|
|
5
6
|
onclose: any;
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
7
|
-
onopen!:
|
|
8
|
+
onopen!: () => void;
|
|
8
9
|
onmessage: any;
|
|
9
10
|
onerror: any;
|
|
10
11
|
|
package/src/server/runtime.ts
CHANGED
|
@@ -516,6 +516,7 @@ function makeTableView(
|
|
|
516
516
|
) as Table<any>;
|
|
517
517
|
|
|
518
518
|
for (const indexDef of table.indexes) {
|
|
519
|
+
const accessorName = indexDef.accessorName!;
|
|
519
520
|
const index_id = sys.index_id_from_name(indexDef.sourceName!);
|
|
520
521
|
|
|
521
522
|
let column_ids: number[];
|
|
@@ -795,10 +796,13 @@ function makeTableView(
|
|
|
795
796
|
} as RangedIndex<any, any>;
|
|
796
797
|
}
|
|
797
798
|
|
|
798
|
-
|
|
799
|
-
|
|
799
|
+
// IMPORTANT: duplicate accessor handling.
|
|
800
|
+
// When multiple raw indexes share the same accessor name, we merge index
|
|
801
|
+
// methods onto a single accessor object instead of throwing.
|
|
802
|
+
if (Object.hasOwn(tableView, accessorName)) {
|
|
803
|
+
freeze(Object.assign((tableView as any)[accessorName], index));
|
|
800
804
|
} else {
|
|
801
|
-
tableView[
|
|
805
|
+
(tableView as any)[accessorName] = freeze(index);
|
|
802
806
|
}
|
|
803
807
|
}
|
|
804
808
|
|
|
@@ -60,3 +60,40 @@ spacetimedb.init(ctx => {
|
|
|
60
60
|
// @ts-expect-error update() is NOT allowed on non-PK unique indexes
|
|
61
61
|
const _update = ctx.db.person.name2.update;
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Regression coverage for the declared-vs-resolved index split:
|
|
66
|
+
* - declared table-level indexes must still produce typed accessors
|
|
67
|
+
* - field-level indexes must still produce typed accessors
|
|
68
|
+
* - non-indexed fields must not accidentally become index accessors
|
|
69
|
+
*/
|
|
70
|
+
const account = table(
|
|
71
|
+
{
|
|
72
|
+
indexes: [
|
|
73
|
+
{
|
|
74
|
+
accessor: 'byEmailAndOrg',
|
|
75
|
+
algorithm: 'btree',
|
|
76
|
+
columns: ['email', 'orgId'] as const,
|
|
77
|
+
},
|
|
78
|
+
] as const,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
accountId: t.u32(),
|
|
82
|
+
email: t.string().index('hash'),
|
|
83
|
+
orgId: t.u32(),
|
|
84
|
+
nickname: t.string(),
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const spacetimedbIndexSplit = schema({ account });
|
|
89
|
+
|
|
90
|
+
spacetimedbIndexSplit.init(ctx => {
|
|
91
|
+
// Explicit table-level index accessor from `table({ indexes: [...] })`.
|
|
92
|
+
ctx.db.account.byEmailAndOrg.filter(['a@example.com', 1]);
|
|
93
|
+
|
|
94
|
+
// Field-level index accessor derived from column metadata.
|
|
95
|
+
ctx.db.account.email.filter('a@example.com');
|
|
96
|
+
|
|
97
|
+
// @ts-expect-error `nickname` is not indexed, so no index accessor should exist.
|
|
98
|
+
const _nickname = ctx.db.account.nickname;
|
|
99
|
+
});
|
|
@@ -7,6 +7,7 @@ const person = table(
|
|
|
7
7
|
name: 'person',
|
|
8
8
|
indexes: [
|
|
9
9
|
{
|
|
10
|
+
accessor: 'name_id_idx',
|
|
10
11
|
name: 'name_id_idx',
|
|
11
12
|
algorithm: 'btree',
|
|
12
13
|
columns: ['name', 'id'] as const,
|
|
@@ -48,6 +49,7 @@ const order = table(
|
|
|
48
49
|
name: 'order',
|
|
49
50
|
indexes: [
|
|
50
51
|
{
|
|
52
|
+
accessor: 'id_person_id',
|
|
51
53
|
name: 'id_person_id', // We are adding this to make sure `person_id` still isn't considered indexed.
|
|
52
54
|
algorithm: 'btree',
|
|
53
55
|
columns: ['id', 'person_id'] as const,
|
|
@@ -4,13 +4,15 @@ import type {
|
|
|
4
4
|
QueryFunction,
|
|
5
5
|
} from '@tanstack/react-query';
|
|
6
6
|
import {
|
|
7
|
+
type Query,
|
|
8
|
+
toSql,
|
|
7
9
|
type BooleanExpr,
|
|
8
10
|
evaluateBooleanExpr,
|
|
9
11
|
getQueryAccessorName,
|
|
10
12
|
getQueryWhereClause,
|
|
11
13
|
} from '../lib/query';
|
|
12
14
|
|
|
13
|
-
type QueryInput =
|
|
15
|
+
type QueryInput = Query<any>;
|
|
14
16
|
|
|
15
17
|
const queryRegistry = new Map<
|
|
16
18
|
string,
|
|
@@ -51,7 +53,7 @@ export function spacetimeDBQuery(
|
|
|
51
53
|
const query = queryOrSkip;
|
|
52
54
|
const accessorName = getQueryAccessorName(query);
|
|
53
55
|
const whereExpr = getQueryWhereClause(query);
|
|
54
|
-
const querySql =
|
|
56
|
+
const querySql = toSql(query);
|
|
55
57
|
|
|
56
58
|
queryRegistry.set(querySql, { accessorName, whereExpr });
|
|
57
59
|
|
|
@@ -94,6 +96,7 @@ export class SpacetimeDBQueryClient {
|
|
|
94
96
|
setConnection(connection: SpacetimeConnection): void {
|
|
95
97
|
this.connection = connection;
|
|
96
98
|
this.processPendingQueries();
|
|
99
|
+
this.hydrateSubscriptions();
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
connect(queryClient: QueryClient): void {
|
|
@@ -287,6 +290,29 @@ export class SpacetimeDBQueryClient {
|
|
|
287
290
|
}
|
|
288
291
|
}
|
|
289
292
|
|
|
293
|
+
// subscribe to queries with SSR cached data but no active subscription
|
|
294
|
+
private hydrateSubscriptions(): void {
|
|
295
|
+
if (!this.connection || !this.queryClient) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const [querySql, { accessorName, whereExpr }] of queryRegistry) {
|
|
300
|
+
const queryKey = ['spacetimedb', accessorName, querySql] as const;
|
|
301
|
+
const keyStr = JSON.stringify(queryKey);
|
|
302
|
+
|
|
303
|
+
if (this.subscriptions.has(keyStr)) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (this.queryClient.getQueryData(queryKey) === undefined) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.setupSubscription(queryKey, accessorName, querySql, whereExpr).catch(
|
|
311
|
+
() => {}
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
290
316
|
// clean up all subscriptions and disconnect
|
|
291
317
|
disconnect(): void {
|
|
292
318
|
if (this.cacheUnsubscribe) {
|
package/src/tanstack/hooks.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
UseSuspenseQueryResult,
|
|
7
7
|
} from '@tanstack/react-query';
|
|
8
8
|
import type { UntypedTableDef, RowType } from '../lib/table';
|
|
9
|
+
import type { Query } from '../lib/query';
|
|
9
10
|
import { spacetimeDBQuery } from './SpacetimeDBQueryClient';
|
|
10
11
|
|
|
11
12
|
export type UseSpacetimeDBQueryResult<T> = [
|
|
@@ -29,7 +30,7 @@ export type UseSpacetimeDBSuspenseQueryResult<T> = [
|
|
|
29
30
|
// useSpacetimeDBQuery(tables.user.where(r => r.online.eq(true)))
|
|
30
31
|
// useSpacetimeDBQuery(condition ? tables.user : 'skip')
|
|
31
32
|
export function useSpacetimeDBQuery<TableDef extends UntypedTableDef>(
|
|
32
|
-
queryOrSkip:
|
|
33
|
+
queryOrSkip: Query<TableDef> | 'skip',
|
|
33
34
|
// any useQuery option (e.g. enabled, refetchInterval, select, placeholderData),
|
|
34
35
|
// except queryKey, queryFn, and meta (managed internally)
|
|
35
36
|
options?: Omit<
|
|
@@ -60,7 +61,7 @@ export function useSpacetimeDBQuery<TableDef extends UntypedTableDef>(
|
|
|
60
61
|
// until data is ready, a parent <Suspense fallback={…}> handles the loading UI.
|
|
61
62
|
// does not support 'skip' because useSuspenseQuery must always resolve
|
|
62
63
|
export function useSpacetimeDBSuspenseQuery<TableDef extends UntypedTableDef>(
|
|
63
|
-
query:
|
|
64
|
+
query: Query<TableDef>,
|
|
64
65
|
options?: Omit<
|
|
65
66
|
UseSuspenseQueryOptions<
|
|
66
67
|
RowType<TableDef>[],
|