spacetimedb 2.5.0 → 2.6.0
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 +759 -759
- package/README.md +211 -211
- package/dist/angular/index.cjs.map +1 -1
- package/dist/angular/index.mjs.map +1 -1
- package/dist/browser/angular/index.mjs.map +1 -1
- package/dist/browser/react/index.mjs +129 -57
- package/dist/browser/react/index.mjs.map +1 -1
- package/dist/browser/solid/index.mjs +120 -50
- package/dist/browser/solid/index.mjs.map +1 -1
- package/dist/browser/svelte/index.mjs.map +1 -1
- package/dist/browser/vue/index.mjs.map +1 -1
- package/dist/index.browser.mjs +10 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.cjs +10 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/index.mjs.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 +129 -57
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.mjs +129 -57
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useTable.d.ts.map +1 -1
- package/dist/sdk/connection_manager.d.ts +8 -0
- package/dist/sdk/connection_manager.d.ts.map +1 -1
- package/dist/sdk/db_connection_impl.d.ts +7 -0
- package/dist/sdk/db_connection_impl.d.ts.map +1 -1
- package/dist/sdk/index.browser.mjs +10 -2
- package/dist/sdk/index.browser.mjs.map +1 -1
- package/dist/sdk/index.cjs +10 -2
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.mjs +10 -2
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/sdk/websocket_test_adapter.d.ts +2 -1
- package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/solid/index.cjs +120 -50
- package/dist/solid/index.cjs.map +1 -1
- package/dist/solid/index.mjs +120 -50
- package/dist/solid/index.mjs.map +1 -1
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.mjs.map +1 -1
- package/dist/tanstack/index.cjs +120 -50
- package/dist/tanstack/index.cjs.map +1 -1
- package/dist/tanstack/index.mjs +120 -50
- package/dist/tanstack/index.mjs.map +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/angular/connection_state.ts +19 -19
- package/src/angular/index.ts +3 -3
- package/src/angular/injectors/index.ts +4 -4
- package/src/angular/injectors/inject-reducer.ts +62 -62
- package/src/angular/injectors/inject-spacetimedb-connected.ts +13 -13
- package/src/angular/injectors/inject-spacetimedb.ts +10 -10
- package/src/angular/injectors/inject-table.ts +234 -234
- package/src/angular/providers/index.ts +1 -1
- package/src/angular/providers/provide-spacetimedb.ts +96 -96
- package/src/index.ts +16 -16
- package/src/lib/algebraic_type.ts +819 -819
- package/src/lib/algebraic_type_variants.ts +26 -26
- package/src/lib/algebraic_value.ts +10 -10
- package/src/lib/autogen/types.ts +746 -746
- package/src/lib/binary_reader.ts +188 -188
- package/src/lib/binary_writer.ts +213 -213
- package/src/lib/connection_id.ts +102 -102
- package/src/lib/constraints.ts +48 -48
- package/src/lib/errors.ts +26 -26
- package/src/lib/filter.ts +195 -195
- package/src/lib/identity.ts +83 -83
- package/src/lib/indexes.ts +251 -251
- package/src/lib/option.ts +34 -34
- package/src/lib/query.ts +1019 -1019
- package/src/lib/reducer_schema.ts +38 -38
- package/src/lib/reducers.ts +116 -116
- package/src/lib/result.ts +36 -36
- package/src/lib/schedule_at.ts +86 -86
- package/src/lib/schema.ts +420 -420
- package/src/lib/table.ts +548 -548
- package/src/lib/table_schema.ts +64 -64
- package/src/lib/time_duration.ts +77 -77
- package/src/lib/timestamp.ts +148 -148
- package/src/lib/type_builders.test-d.ts +128 -128
- package/src/lib/type_builders.ts +4014 -4014
- package/src/lib/type_util.ts +124 -124
- package/src/lib/util.ts +196 -196
- package/src/lib/uuid.ts +337 -337
- package/src/react/SpacetimeDBProvider.ts +84 -84
- package/src/react/connection_state.ts +6 -6
- package/src/react/index.ts +5 -5
- package/src/react/useProcedure.ts +60 -60
- package/src/react/useReducer.ts +53 -53
- package/src/react/useSpacetimeDB.ts +18 -18
- package/src/react/useTable.ts +256 -251
- package/src/sdk/client_api/index.ts +114 -114
- package/src/sdk/client_api/types/procedures.ts +8 -8
- package/src/sdk/client_api/types/reducers.ts +8 -8
- package/src/sdk/client_api/types.ts +288 -288
- package/src/sdk/client_cache.ts +129 -129
- package/src/sdk/client_table.ts +179 -179
- package/src/sdk/connection_manager.ts +352 -237
- package/src/sdk/db_connection_builder.ts +290 -290
- package/src/sdk/db_connection_impl.ts +1356 -1347
- package/src/sdk/db_context.ts +28 -28
- package/src/sdk/db_view.ts +12 -12
- package/src/sdk/decompress.ts +51 -51
- package/src/sdk/event.ts +18 -18
- package/src/sdk/event_context.ts +51 -51
- package/src/sdk/event_emitter.ts +32 -32
- package/src/sdk/index.ts +14 -14
- package/src/sdk/internal.ts +2 -2
- package/src/sdk/json_api.ts +46 -46
- package/src/sdk/logger.ts +134 -134
- package/src/sdk/message_types.ts +46 -46
- package/src/sdk/procedures.ts +83 -83
- package/src/sdk/reducer_event.ts +20 -20
- package/src/sdk/reducer_handle.ts +12 -12
- package/src/sdk/reducers.ts +159 -159
- package/src/sdk/schema.ts +45 -45
- package/src/sdk/spacetime_module.ts +28 -28
- package/src/sdk/subscription_builder_impl.ts +275 -275
- package/src/sdk/table_cache.ts +581 -581
- package/src/sdk/type_utils.ts +19 -19
- package/src/sdk/version.ts +133 -133
- package/src/sdk/websocket_decompress_adapter.ts +63 -63
- package/src/sdk/websocket_protocols.ts +25 -25
- package/src/sdk/websocket_test_adapter.ts +107 -100
- package/src/sdk/websocket_v3_frames.ts +126 -126
- package/src/sdk/ws.ts +105 -105
- package/src/server/console.ts +81 -81
- package/src/server/db_view.ts +21 -21
- package/src/server/errors.ts +138 -138
- package/src/server/http.test-d.ts +80 -80
- package/src/server/http.ts +14 -14
- package/src/server/http_handlers.ts +413 -413
- package/src/server/http_internal.ts +79 -79
- package/src/server/http_shared.ts +186 -186
- package/src/server/index.ts +37 -37
- package/src/server/polyfills.ts +4 -4
- package/src/server/procedures.ts +239 -239
- package/src/server/query.ts +1 -1
- package/src/server/range.ts +53 -53
- package/src/server/reducers.ts +113 -113
- package/src/server/rng.ts +113 -113
- package/src/server/runtime.ts +1102 -1102
- package/src/server/schema.test-d.ts +99 -99
- package/src/server/schema.ts +663 -663
- package/src/server/sys.d.ts +125 -125
- package/src/server/view.test-d.ts +194 -194
- package/src/server/views.ts +340 -340
- package/src/solid/SpacetimeDBProvider.ts +97 -97
- package/src/solid/connection_state.ts +6 -6
- package/src/solid/index.ts +5 -5
- package/src/solid/useProcedure.ts +57 -57
- package/src/solid/useReducer.ts +50 -50
- package/src/solid/useSpacetimeDB.ts +18 -18
- package/src/solid/useTable.ts +203 -203
- package/src/svelte/SpacetimeDBProvider.ts +101 -101
- package/src/svelte/connection_state.ts +16 -16
- package/src/svelte/index.ts +4 -4
- package/src/svelte/useReducer.ts +61 -61
- package/src/svelte/useSpacetimeDB.ts +22 -22
- package/src/svelte/useTable.ts +218 -218
- package/src/tanstack/SpacetimeDBQueryClient.ts +330 -330
- package/src/tanstack/hooks.ts +83 -83
- package/src/tanstack/index.ts +16 -16
- package/src/util-stub.ts +1 -1
- package/src/vue/SpacetimeDBProvider.ts +157 -157
- package/src/vue/connection_state.ts +19 -19
- package/src/vue/index.ts +5 -5
- package/src/vue/useProcedure.ts +62 -62
- package/src/vue/useReducer.ts +55 -55
- package/src/vue/useSpacetimeDB.ts +18 -18
- package/src/vue/useTable.ts +229 -229
package/src/server/schema.ts
CHANGED
|
@@ -1,663 +1,663 @@
|
|
|
1
|
-
import { moduleHooks, type ModuleDefaultExport } from 'spacetime:sys@2.0';
|
|
2
|
-
import {
|
|
3
|
-
CaseConversionPolicy,
|
|
4
|
-
Lifecycle,
|
|
5
|
-
type MethodOrAny,
|
|
6
|
-
} from '../lib/autogen/types';
|
|
7
|
-
import {
|
|
8
|
-
type ParamsAsObject,
|
|
9
|
-
type ParamsObj,
|
|
10
|
-
type Reducer,
|
|
11
|
-
type ReducerCtx,
|
|
12
|
-
} from '../lib/reducers';
|
|
13
|
-
import {
|
|
14
|
-
ModuleContext,
|
|
15
|
-
tableToSchema,
|
|
16
|
-
type TablesToSchema,
|
|
17
|
-
type UntypedSchemaDef,
|
|
18
|
-
} from '../lib/schema';
|
|
19
|
-
import type { UntypedTableSchema } from '../lib/table_schema';
|
|
20
|
-
import { ColumnBuilder, TypeBuilder } from '../lib/type_builders';
|
|
21
|
-
import {
|
|
22
|
-
Router,
|
|
23
|
-
type HandlerFn,
|
|
24
|
-
type HttpHandlerExport,
|
|
25
|
-
type HttpHandlerOpts,
|
|
26
|
-
makeHttpHandlerExport,
|
|
27
|
-
makeHttpRouterExport,
|
|
28
|
-
} from './http_handlers';
|
|
29
|
-
import {
|
|
30
|
-
makeProcedureExport,
|
|
31
|
-
type ProcedureExport,
|
|
32
|
-
type ProcedureFn,
|
|
33
|
-
type ProcedureOpts,
|
|
34
|
-
type Procedures,
|
|
35
|
-
} from './procedures';
|
|
36
|
-
import {
|
|
37
|
-
makeReducerExport,
|
|
38
|
-
type ReducerExport,
|
|
39
|
-
type ReducerOpts,
|
|
40
|
-
type Reducers,
|
|
41
|
-
} from './reducers';
|
|
42
|
-
import { makeHooks } from './runtime';
|
|
43
|
-
|
|
44
|
-
import {
|
|
45
|
-
makeAnonViewExport,
|
|
46
|
-
makeViewExport,
|
|
47
|
-
type AnonViews,
|
|
48
|
-
type AnonymousViewFn,
|
|
49
|
-
type ViewExport,
|
|
50
|
-
type ViewFn,
|
|
51
|
-
type ViewOpts,
|
|
52
|
-
type ViewReturnTypeBuilder,
|
|
53
|
-
type ValidateViewPrimaryKey,
|
|
54
|
-
type Views,
|
|
55
|
-
} from './views';
|
|
56
|
-
import type { UntypedTableDef } from '../lib/table';
|
|
57
|
-
|
|
58
|
-
export class SchemaInner<
|
|
59
|
-
S extends UntypedSchemaDef = UntypedSchemaDef,
|
|
60
|
-
> extends ModuleContext {
|
|
61
|
-
schemaType: S;
|
|
62
|
-
existingFunctions = new Set<string>();
|
|
63
|
-
existingHttpHandlers = new Set<string>();
|
|
64
|
-
reducers: Reducers = [];
|
|
65
|
-
procedures: Procedures = [];
|
|
66
|
-
views: Views = [];
|
|
67
|
-
anonViews: AnonViews = [];
|
|
68
|
-
httpHandlers: HandlerFn[] = [];
|
|
69
|
-
/**
|
|
70
|
-
* Maps ReducerExport objects to the name of the reducer.
|
|
71
|
-
* Used for resolving the reducers of scheduled tables.
|
|
72
|
-
*/
|
|
73
|
-
functionExports: Map<
|
|
74
|
-
| ReducerExport<UntypedSchemaDef, any>
|
|
75
|
-
| ProcedureExport<UntypedSchemaDef, any, any>,
|
|
76
|
-
string
|
|
77
|
-
> = new Map();
|
|
78
|
-
httpHandlerExports: Map<HttpHandlerExport<UntypedSchemaDef>, string> =
|
|
79
|
-
new Map();
|
|
80
|
-
pendingSchedules: PendingSchedule[] = [];
|
|
81
|
-
pendingHttpRoutes: PendingHttpRoute[] = [];
|
|
82
|
-
|
|
83
|
-
constructor(getSchemaType: (ctx: SchemaInner<S>) => S) {
|
|
84
|
-
super();
|
|
85
|
-
this.schemaType = getSchemaType(this);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
defineFunction(name: string) {
|
|
89
|
-
if (this.existingFunctions.has(name)) {
|
|
90
|
-
throw new TypeError(
|
|
91
|
-
`There is already a reducer, procedure, or view with the name '${name}'`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
this.existingFunctions.add(name);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
defineHttpHandler(name: string) {
|
|
98
|
-
if (this.existingHttpHandlers.has(name)) {
|
|
99
|
-
throw new TypeError(
|
|
100
|
-
`There is already an HTTP handler with the name '${name}'`
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
this.existingHttpHandlers.add(name);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
resolveSchedules() {
|
|
107
|
-
for (const { reducer, scheduleAtCol, tableName } of this.pendingSchedules) {
|
|
108
|
-
const functionName = this.functionExports.get(reducer());
|
|
109
|
-
if (functionName === undefined) {
|
|
110
|
-
const msg = `Table ${tableName} defines a schedule, but it seems like the associated function was not exported.`;
|
|
111
|
-
throw new TypeError(msg);
|
|
112
|
-
}
|
|
113
|
-
this.moduleDef.schedules.push({
|
|
114
|
-
sourceName: undefined,
|
|
115
|
-
tableName,
|
|
116
|
-
scheduleAtCol,
|
|
117
|
-
functionName,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
resolveHttpRoutes() {
|
|
123
|
-
for (const route of this.pendingHttpRoutes) {
|
|
124
|
-
const handlerFunction = this.httpHandlerExports.get(route.handler);
|
|
125
|
-
if (handlerFunction === undefined) {
|
|
126
|
-
throw new TypeError(
|
|
127
|
-
`HTTP route for path '${route.path}' refers to a handler that was not exported.`
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
this.moduleDef.httpRoutes.push({
|
|
131
|
-
handlerFunction,
|
|
132
|
-
method: route.method,
|
|
133
|
-
path: route.path,
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
type PendingSchedule = UntypedTableSchema['schedule'] & { tableName: string };
|
|
140
|
-
type PendingHttpRoute = {
|
|
141
|
-
handler: HttpHandlerExport<UntypedSchemaDef>;
|
|
142
|
-
method: MethodOrAny;
|
|
143
|
-
path: string;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* The Schema class represents the database schema for a SpacetimeDB application.
|
|
148
|
-
* It encapsulates the table definitions and typespace, and provides methods to define
|
|
149
|
-
* reducers and lifecycle hooks.
|
|
150
|
-
*
|
|
151
|
-
* Schema has a generic parameter S which represents the inferred schema type. This type
|
|
152
|
-
* is automatically inferred when creating a schema using the `schema()` function and is
|
|
153
|
-
* used to type the database view in reducer contexts.
|
|
154
|
-
*
|
|
155
|
-
* The methods on this class are used to register reducers and lifecycle hooks
|
|
156
|
-
* with the SpacetimeDB runtime. Theey forward to free functions that handle the actual
|
|
157
|
-
* registration logic, but having them as methods on the Schema class helps with type inference.
|
|
158
|
-
*
|
|
159
|
-
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* ```typescript
|
|
163
|
-
* const spacetimedb = schema({
|
|
164
|
-
* user: table({}, userType),
|
|
165
|
-
* post: table({}, postType)
|
|
166
|
-
* });
|
|
167
|
-
* spacetimedb.reducer(
|
|
168
|
-
* 'create_user',
|
|
169
|
-
* { username: t.string(), email: t.string() },
|
|
170
|
-
* (ctx, { username, email }) => {
|
|
171
|
-
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
|
172
|
-
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
|
173
|
-
* }
|
|
174
|
-
* );
|
|
175
|
-
* ```
|
|
176
|
-
*/
|
|
177
|
-
// TODO(cloutiertyler): It might be nice to have a way to access the types
|
|
178
|
-
// for the tables from the schema object, e.g. `spacetimedb.user.type` would
|
|
179
|
-
// be the type of the user table.
|
|
180
|
-
export class Schema<S extends UntypedSchemaDef> implements ModuleDefaultExport {
|
|
181
|
-
#ctx: SchemaInner<S>;
|
|
182
|
-
|
|
183
|
-
constructor(ctx: SchemaInner<S>) {
|
|
184
|
-
// TODO: TableSchema and TableDef should really be unified
|
|
185
|
-
this.#ctx = ctx;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
[moduleHooks](exports: object) {
|
|
189
|
-
// if (!(hasOwn(exports, 'default') && exports.default instanceof Schema)) {
|
|
190
|
-
// throw new TypeError('must export schema as default export');
|
|
191
|
-
// }
|
|
192
|
-
const registeredSchema = this.#ctx;
|
|
193
|
-
for (const [name, moduleExport] of Object.entries(exports)) {
|
|
194
|
-
if (name === 'default') continue;
|
|
195
|
-
if (!isModuleExport(moduleExport)) {
|
|
196
|
-
throw new TypeError(
|
|
197
|
-
'exporting something that is not a spacetime export'
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
checkExportContext(moduleExport, registeredSchema);
|
|
201
|
-
moduleExport[registerExport](registeredSchema, name);
|
|
202
|
-
}
|
|
203
|
-
registeredSchema.resolveSchedules();
|
|
204
|
-
registeredSchema.resolveHttpRoutes();
|
|
205
|
-
return makeHooks(registeredSchema);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
get schemaType(): S {
|
|
209
|
-
return this.#ctx.schemaType;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
get moduleDef() {
|
|
213
|
-
return this.#ctx.moduleDef;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
get typespace() {
|
|
217
|
-
return this.#ctx.typespace;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Defines a SpacetimeDB reducer function.
|
|
222
|
-
*
|
|
223
|
-
* Reducers are the primary way to modify the state of your SpacetimeDB application.
|
|
224
|
-
* They are atomic, meaning that either all operations within a reducer succeed,
|
|
225
|
-
* or none of them do.
|
|
226
|
-
*
|
|
227
|
-
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
228
|
-
* @template Params - The type of the parameters object expected by the reducer.
|
|
229
|
-
*
|
|
230
|
-
* @param {Params} params - An object defining the parameters that the reducer accepts.
|
|
231
|
-
* Each key-value pair represents a parameter name and its corresponding
|
|
232
|
-
* {@link TypeBuilder} or {@link ColumnBuilder}.
|
|
233
|
-
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
|
|
234
|
-
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
|
235
|
-
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* ```typescript
|
|
239
|
-
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
|
|
240
|
-
* export const create_user = spacetime.reducer(
|
|
241
|
-
* {
|
|
242
|
-
* username: t.string(),
|
|
243
|
-
* email: t.string(),
|
|
244
|
-
* },
|
|
245
|
-
* (ctx, { username, email }) => {
|
|
246
|
-
* // Access the 'user' table from the database view in the context
|
|
247
|
-
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
|
248
|
-
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
|
249
|
-
* }
|
|
250
|
-
* );
|
|
251
|
-
* ```
|
|
252
|
-
*/
|
|
253
|
-
reducer<Params extends ParamsObj>(
|
|
254
|
-
params: Params,
|
|
255
|
-
fn: Reducer<S, Params>
|
|
256
|
-
): ReducerExport<S, Params>;
|
|
257
|
-
reducer(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
258
|
-
reducer<Params extends ParamsObj>(
|
|
259
|
-
opts: ReducerOpts,
|
|
260
|
-
params: Params,
|
|
261
|
-
fn: Reducer<S, Params>
|
|
262
|
-
): ReducerExport<S, Params>;
|
|
263
|
-
reducer(opts: ReducerOpts, fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
264
|
-
reducer<Params extends ParamsObj>(
|
|
265
|
-
...args:
|
|
266
|
-
| [Params, Reducer<S, Params>]
|
|
267
|
-
| [Reducer<S, {}>]
|
|
268
|
-
| [ReducerOpts, Params, Reducer<S, Params>]
|
|
269
|
-
| [ReducerOpts, Reducer<S, {}>]
|
|
270
|
-
): ReducerExport<S, Params> {
|
|
271
|
-
let opts: ReducerOpts | undefined,
|
|
272
|
-
params: Params = {} as Params,
|
|
273
|
-
fn: Reducer<S, Params>;
|
|
274
|
-
switch (args.length) {
|
|
275
|
-
case 1:
|
|
276
|
-
[fn] = args;
|
|
277
|
-
break;
|
|
278
|
-
case 2: {
|
|
279
|
-
let arg1;
|
|
280
|
-
[arg1, fn] = args;
|
|
281
|
-
if (typeof arg1.name === 'string') opts = arg1 as ReducerOpts;
|
|
282
|
-
else params = arg1 as Params;
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
case 3:
|
|
286
|
-
[opts, params, fn] = args;
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
return makeReducerExport(this.#ctx, opts, params, fn);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Registers an initialization reducer that runs when the SpacetimeDB module is published
|
|
294
|
-
* for the first time.
|
|
295
|
-
*
|
|
296
|
-
* This function is useful to set up any initial state of your database that is guaranteed
|
|
297
|
-
* to run only once, and before any other reducers or client connections.
|
|
298
|
-
*
|
|
299
|
-
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
300
|
-
* @param {Reducer<S, {}>} fn - The initialization reducer function.
|
|
301
|
-
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
|
302
|
-
* @example
|
|
303
|
-
* ```typescript
|
|
304
|
-
* export const init = spacetime.init((ctx) => {
|
|
305
|
-
* ctx.db.user.insert({ username: 'admin', email: 'admin@example.com' });
|
|
306
|
-
* });
|
|
307
|
-
* ```
|
|
308
|
-
*/
|
|
309
|
-
init(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
310
|
-
init(opts: ReducerOpts, fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
311
|
-
init(
|
|
312
|
-
...args: [Reducer<S, {}>] | [ReducerOpts, Reducer<S, {}>]
|
|
313
|
-
): ReducerExport<S, {}> {
|
|
314
|
-
let opts: ReducerOpts | undefined, fn: Reducer<S, {}>;
|
|
315
|
-
switch (args.length) {
|
|
316
|
-
case 1:
|
|
317
|
-
[fn] = args;
|
|
318
|
-
break;
|
|
319
|
-
case 2:
|
|
320
|
-
[opts, fn] = args;
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
return makeReducerExport(this.#ctx, opts, {}, fn, Lifecycle.Init);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
|
|
328
|
-
* This function allows you to define custom logic that should execute
|
|
329
|
-
* whenever a new client establishes a connection.
|
|
330
|
-
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
331
|
-
*
|
|
332
|
-
* @param fn - The reducer function to execute on client connection.
|
|
333
|
-
*
|
|
334
|
-
* @example
|
|
335
|
-
* ```typescript
|
|
336
|
-
* export const onConnect = spacetime.clientConnected(
|
|
337
|
-
* (ctx) => {
|
|
338
|
-
* console.log(`Client ${ctx.connectionId} connected`);
|
|
339
|
-
* }
|
|
340
|
-
* );
|
|
341
|
-
*/
|
|
342
|
-
clientConnected(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
343
|
-
clientConnected(opts: ReducerOpts, fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
344
|
-
clientConnected(
|
|
345
|
-
...args: [Reducer<S, {}>] | [ReducerOpts, Reducer<S, {}>]
|
|
346
|
-
): ReducerExport<S, {}> {
|
|
347
|
-
let opts: ReducerOpts | undefined, fn: Reducer<S, {}>;
|
|
348
|
-
switch (args.length) {
|
|
349
|
-
case 1:
|
|
350
|
-
[fn] = args;
|
|
351
|
-
break;
|
|
352
|
-
case 2:
|
|
353
|
-
[opts, fn] = args;
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
return makeReducerExport(this.#ctx, opts, {}, fn, Lifecycle.OnConnect);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
|
|
361
|
-
* This function allows you to define custom logic that should execute
|
|
362
|
-
* whenever a client disconnects.
|
|
363
|
-
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
364
|
-
*
|
|
365
|
-
* @param fn - The reducer function to execute on client disconnection.
|
|
366
|
-
*
|
|
367
|
-
* @example
|
|
368
|
-
* ```typescript
|
|
369
|
-
* export const onDisconnect = spacetime.clientDisconnected(
|
|
370
|
-
* (ctx) => {
|
|
371
|
-
* console.log(`Client ${ctx.connectionId} disconnected`);
|
|
372
|
-
* }
|
|
373
|
-
* );
|
|
374
|
-
* ```
|
|
375
|
-
*/
|
|
376
|
-
clientDisconnected(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
377
|
-
clientDisconnected(
|
|
378
|
-
opts: ReducerOpts,
|
|
379
|
-
fn: Reducer<S, {}>
|
|
380
|
-
): ReducerExport<S, {}>;
|
|
381
|
-
clientDisconnected(
|
|
382
|
-
...args: [Reducer<S, {}>] | [ReducerOpts, Reducer<S, {}>]
|
|
383
|
-
): ReducerExport<S, {}> {
|
|
384
|
-
let opts: ReducerOpts | undefined, fn: Reducer<S, {}>;
|
|
385
|
-
switch (args.length) {
|
|
386
|
-
case 1:
|
|
387
|
-
[fn] = args;
|
|
388
|
-
break;
|
|
389
|
-
case 2:
|
|
390
|
-
[opts, fn] = args;
|
|
391
|
-
break;
|
|
392
|
-
}
|
|
393
|
-
return makeReducerExport(this.#ctx, opts, {}, fn, Lifecycle.OnDisconnect);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
view<Ret extends ViewReturnTypeBuilder, F extends ViewFn<S, {}, Ret>>(
|
|
397
|
-
opts: ViewOpts,
|
|
398
|
-
ret: Ret,
|
|
399
|
-
fn: F,
|
|
400
|
-
// Compile-time-only guard: this rest parameter is `[]` for valid return
|
|
401
|
-
// builders, but becomes a required error tuple when a returned row builder
|
|
402
|
-
// marks more than one column with `.primaryKey()`.
|
|
403
|
-
..._: ValidateViewPrimaryKey<Ret>
|
|
404
|
-
): ViewExport<F> {
|
|
405
|
-
return makeViewExport<S, {}, Ret, F>(this.#ctx, opts, {}, ret, fn);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// TODO: re-enable once parameterized views are supported in SQL
|
|
409
|
-
// view<Ret extends ViewReturnTypeBuilder>(
|
|
410
|
-
// opts: ViewOpts,
|
|
411
|
-
// ret: Ret,
|
|
412
|
-
// fn: ViewFn<S, {}, Ret>
|
|
413
|
-
// ): void;
|
|
414
|
-
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
415
|
-
// opts: ViewOpts,
|
|
416
|
-
// params: Params,
|
|
417
|
-
// ret: Ret,
|
|
418
|
-
// fn: ViewFn<S, {}, Ret>
|
|
419
|
-
// ): void;
|
|
420
|
-
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
421
|
-
// opts: ViewOpts,
|
|
422
|
-
// paramsOrRet: Ret | Params,
|
|
423
|
-
// retOrFn: ViewFn<S, {}, Ret> | Ret,
|
|
424
|
-
// maybeFn?: ViewFn<S, Params, Ret>
|
|
425
|
-
// ): void {
|
|
426
|
-
// if (typeof retOrFn === 'function') {
|
|
427
|
-
// defineView(name, false, {}, paramsOrRet as Ret, retOrFn);
|
|
428
|
-
// } else {
|
|
429
|
-
// defineView(name, false, paramsOrRet as Params, retOrFn, maybeFn!);
|
|
430
|
-
// }
|
|
431
|
-
// }
|
|
432
|
-
|
|
433
|
-
anonymousView<
|
|
434
|
-
Ret extends ViewReturnTypeBuilder,
|
|
435
|
-
F extends AnonymousViewFn<S, {}, Ret>,
|
|
436
|
-
>(
|
|
437
|
-
opts: ViewOpts,
|
|
438
|
-
ret: Ret,
|
|
439
|
-
fn: F,
|
|
440
|
-
// Compile-time-only guard: this rest parameter is `[]` for valid return
|
|
441
|
-
// builders, but becomes a required error tuple when a returned row builder
|
|
442
|
-
// marks more than one column with `.primaryKey()`.
|
|
443
|
-
..._: ValidateViewPrimaryKey<Ret>
|
|
444
|
-
): ViewExport<F> {
|
|
445
|
-
return makeAnonViewExport<S, {}, Ret, F>(this.#ctx, opts, {}, ret, fn);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// TODO: re-enable once parameterized views are supported in SQL
|
|
449
|
-
// anonymousView<Ret extends ViewReturnTypeBuilder>(
|
|
450
|
-
// opts: ViewOpts,
|
|
451
|
-
// ret: Ret,
|
|
452
|
-
// fn: AnonymousViewFn<S, {}, Ret>
|
|
453
|
-
// ): void;
|
|
454
|
-
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
455
|
-
// opts: ViewOpts,
|
|
456
|
-
// params: Params,
|
|
457
|
-
// ret: Ret,
|
|
458
|
-
// fn: AnonymousViewFn<S, {}, Ret>
|
|
459
|
-
// ): void;
|
|
460
|
-
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
461
|
-
// opts: ViewOpts,
|
|
462
|
-
// paramsOrRet: Ret | Params,
|
|
463
|
-
// retOrFn: AnonymousViewFn<S, {}, Ret> | Ret,
|
|
464
|
-
// maybeFn?: AnonymousViewFn<S, Params, Ret>
|
|
465
|
-
// ): void {
|
|
466
|
-
// if (typeof retOrFn === 'function') {
|
|
467
|
-
// defineView(name, true, {}, paramsOrRet as Ret, retOrFn);
|
|
468
|
-
// } else {
|
|
469
|
-
// defineView(name, true, paramsOrRet as Params, retOrFn, maybeFn!);
|
|
470
|
-
// }
|
|
471
|
-
// }
|
|
472
|
-
|
|
473
|
-
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
|
474
|
-
params: Params,
|
|
475
|
-
ret: Ret,
|
|
476
|
-
fn: ProcedureFn<S, Params, Ret>
|
|
477
|
-
): ProcedureFn<S, Params, Ret>;
|
|
478
|
-
procedure<Ret extends TypeBuilder<any, any>>(
|
|
479
|
-
ret: Ret,
|
|
480
|
-
fn: ProcedureFn<S, {}, Ret>
|
|
481
|
-
): ProcedureFn<S, {}, Ret>;
|
|
482
|
-
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
|
483
|
-
opts: ProcedureOpts,
|
|
484
|
-
params: Params,
|
|
485
|
-
ret: Ret,
|
|
486
|
-
fn: ProcedureFn<S, Params, Ret>
|
|
487
|
-
): ProcedureFn<S, Params, Ret>;
|
|
488
|
-
procedure<Ret extends TypeBuilder<any, any>>(
|
|
489
|
-
opts: ProcedureOpts,
|
|
490
|
-
ret: Ret,
|
|
491
|
-
fn: ProcedureFn<S, {}, Ret>
|
|
492
|
-
): ProcedureFn<S, {}, Ret>;
|
|
493
|
-
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
|
494
|
-
...args:
|
|
495
|
-
| [Params, Ret, ProcedureFn<S, Params, Ret>]
|
|
496
|
-
| [Ret, ProcedureFn<S, Params, Ret>]
|
|
497
|
-
| [ProcedureOpts, Params, Ret, ProcedureFn<S, Params, Ret>]
|
|
498
|
-
| [ProcedureOpts, Ret, ProcedureFn<S, Params, Ret>]
|
|
499
|
-
): ProcedureExport<S, Params, Ret> {
|
|
500
|
-
let opts: ProcedureOpts | undefined,
|
|
501
|
-
params: Params = {} as Params,
|
|
502
|
-
ret: Ret,
|
|
503
|
-
fn: ProcedureFn<S, Params, Ret>;
|
|
504
|
-
switch (args.length) {
|
|
505
|
-
case 2:
|
|
506
|
-
[ret, fn] = args;
|
|
507
|
-
break;
|
|
508
|
-
case 3: {
|
|
509
|
-
let arg1;
|
|
510
|
-
[arg1, ret, fn] = args;
|
|
511
|
-
if (typeof arg1.name === 'string') opts = arg1 as ProcedureOpts;
|
|
512
|
-
else params = arg1 as Params;
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
case 4:
|
|
516
|
-
[opts, params, ret, fn] = args;
|
|
517
|
-
break;
|
|
518
|
-
}
|
|
519
|
-
return makeProcedureExport(this.#ctx, opts, params, ret, fn);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
httpHandler(fn: HandlerFn<S>): HttpHandlerExport<S>;
|
|
523
|
-
httpHandler(opts: HttpHandlerOpts, fn: HandlerFn<S>): HttpHandlerExport<S>;
|
|
524
|
-
httpHandler(
|
|
525
|
-
...args: [HandlerFn<S>] | [HttpHandlerOpts, HandlerFn<S>]
|
|
526
|
-
): HttpHandlerExport<S> {
|
|
527
|
-
let opts: HttpHandlerOpts | undefined, fn: HandlerFn<S>;
|
|
528
|
-
switch (args.length) {
|
|
529
|
-
case 1:
|
|
530
|
-
[fn] = args;
|
|
531
|
-
break;
|
|
532
|
-
case 2:
|
|
533
|
-
[opts, fn] = args;
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
return makeHttpHandlerExport(this.#ctx, opts, fn);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
httpRouter(router: Router): ModuleExport {
|
|
540
|
-
return makeHttpRouterExport(this.#ctx, router);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
/**
|
|
544
|
-
* Bundle multiple reducers, procedures, etc into one value to export.
|
|
545
|
-
* The name they will be exported with is their corresponding key in the `exports` argument.
|
|
546
|
-
*/
|
|
547
|
-
exportGroup(exports: Record<string, ModuleExport>): ModuleExport {
|
|
548
|
-
return {
|
|
549
|
-
[exportContext]: this.#ctx,
|
|
550
|
-
[registerExport](ctx, _exportName) {
|
|
551
|
-
for (const [exportName, moduleExport] of Object.entries(exports)) {
|
|
552
|
-
checkExportContext(moduleExport, ctx);
|
|
553
|
-
moduleExport[registerExport](ctx, exportName);
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
clientVisibilityFilter = {
|
|
560
|
-
sql: (filter: string): ModuleExport => ({
|
|
561
|
-
[exportContext]: this.#ctx,
|
|
562
|
-
[registerExport](ctx, _exportName) {
|
|
563
|
-
ctx.moduleDef.rowLevelSecurity.push({ sql: filter });
|
|
564
|
-
},
|
|
565
|
-
}),
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
export const registerExport = Symbol('SpacetimeDB.registerExport');
|
|
570
|
-
export const exportContext = Symbol('SpacetimeDB.exportContext');
|
|
571
|
-
|
|
572
|
-
export interface ModuleExport {
|
|
573
|
-
[registerExport](ctx: SchemaInner, exportName: string): void;
|
|
574
|
-
[exportContext]?: SchemaInner;
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function isModuleExport(x: unknown): x is ModuleExport {
|
|
578
|
-
return (
|
|
579
|
-
(typeof x === 'function' || typeof x === 'object') &&
|
|
580
|
-
x !== null &&
|
|
581
|
-
registerExport in x
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/** Verify that the ModuleContext that `exp` comes from is the same as `schema` */
|
|
586
|
-
function checkExportContext(exp: ModuleExport, schema: SchemaInner) {
|
|
587
|
-
if (exp[exportContext] != null && exp[exportContext] !== schema) {
|
|
588
|
-
throw new TypeError('multiple schemas are not supported');
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Extracts the inferred schema type from a Schema instance
|
|
594
|
-
*/
|
|
595
|
-
export type InferSchema<SchemaDef extends Schema<any>> =
|
|
596
|
-
SchemaDef extends Schema<infer S> ? S : never;
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Creates a schema from table definitions
|
|
600
|
-
* @param handles - Array of table handles created by table() function
|
|
601
|
-
* @returns ColumnBuilder representing the complete database schema
|
|
602
|
-
* @example
|
|
603
|
-
* ```ts
|
|
604
|
-
* const spacetimedb = schema({
|
|
605
|
-
* user: table({}, userType),
|
|
606
|
-
* post: table({}, postType)
|
|
607
|
-
* });
|
|
608
|
-
* ```
|
|
609
|
-
*/
|
|
610
|
-
/**
|
|
611
|
-
* Module-level settings that can be passed to `schema()`.
|
|
612
|
-
*/
|
|
613
|
-
export interface ModuleSettings {
|
|
614
|
-
/**
|
|
615
|
-
* The case conversion policy for this module.
|
|
616
|
-
* Defaults to `SnakeCase` if not specified.
|
|
617
|
-
*
|
|
618
|
-
* @example
|
|
619
|
-
* ```ts
|
|
620
|
-
* export default schema({
|
|
621
|
-
* player,
|
|
622
|
-
* }, { CASE_CONVERSION_POLICY: CaseConversionPolicy.None });
|
|
623
|
-
* ```
|
|
624
|
-
*/
|
|
625
|
-
CASE_CONVERSION_POLICY?: CaseConversionPolicy;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
export function schema<const H extends Record<string, UntypedTableSchema>>(
|
|
629
|
-
tables: H,
|
|
630
|
-
moduleSettings?: ModuleSettings
|
|
631
|
-
): Schema<TablesToSchema<H>> {
|
|
632
|
-
const ctx = new SchemaInner<TablesToSchema<H>>(ctx => {
|
|
633
|
-
// Apply module settings.
|
|
634
|
-
if (moduleSettings?.CASE_CONVERSION_POLICY != null) {
|
|
635
|
-
ctx.setCaseConversionPolicy(moduleSettings.CASE_CONVERSION_POLICY);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const tableSchemas: Record<string, UntypedTableDef> = {};
|
|
639
|
-
for (const [accName, table] of Object.entries(tables)) {
|
|
640
|
-
const tableDef = table.tableDef(ctx, accName);
|
|
641
|
-
tableSchemas[accName] = tableToSchema(accName, table, tableDef);
|
|
642
|
-
ctx.moduleDef.tables.push(tableDef);
|
|
643
|
-
if (table.schedule) {
|
|
644
|
-
ctx.pendingSchedules.push({
|
|
645
|
-
...table.schedule,
|
|
646
|
-
tableName: tableDef.sourceName,
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
if (table.tableName) {
|
|
650
|
-
ctx.moduleDef.explicitNames.entries.push({
|
|
651
|
-
tag: 'Table',
|
|
652
|
-
value: {
|
|
653
|
-
sourceName: accName,
|
|
654
|
-
canonicalName: table.tableName,
|
|
655
|
-
},
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return { tables: tableSchemas } as TablesToSchema<H>;
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
return new Schema(ctx);
|
|
663
|
-
}
|
|
1
|
+
import { moduleHooks, type ModuleDefaultExport } from 'spacetime:sys@2.0';
|
|
2
|
+
import {
|
|
3
|
+
CaseConversionPolicy,
|
|
4
|
+
Lifecycle,
|
|
5
|
+
type MethodOrAny,
|
|
6
|
+
} from '../lib/autogen/types';
|
|
7
|
+
import {
|
|
8
|
+
type ParamsAsObject,
|
|
9
|
+
type ParamsObj,
|
|
10
|
+
type Reducer,
|
|
11
|
+
type ReducerCtx,
|
|
12
|
+
} from '../lib/reducers';
|
|
13
|
+
import {
|
|
14
|
+
ModuleContext,
|
|
15
|
+
tableToSchema,
|
|
16
|
+
type TablesToSchema,
|
|
17
|
+
type UntypedSchemaDef,
|
|
18
|
+
} from '../lib/schema';
|
|
19
|
+
import type { UntypedTableSchema } from '../lib/table_schema';
|
|
20
|
+
import { ColumnBuilder, TypeBuilder } from '../lib/type_builders';
|
|
21
|
+
import {
|
|
22
|
+
Router,
|
|
23
|
+
type HandlerFn,
|
|
24
|
+
type HttpHandlerExport,
|
|
25
|
+
type HttpHandlerOpts,
|
|
26
|
+
makeHttpHandlerExport,
|
|
27
|
+
makeHttpRouterExport,
|
|
28
|
+
} from './http_handlers';
|
|
29
|
+
import {
|
|
30
|
+
makeProcedureExport,
|
|
31
|
+
type ProcedureExport,
|
|
32
|
+
type ProcedureFn,
|
|
33
|
+
type ProcedureOpts,
|
|
34
|
+
type Procedures,
|
|
35
|
+
} from './procedures';
|
|
36
|
+
import {
|
|
37
|
+
makeReducerExport,
|
|
38
|
+
type ReducerExport,
|
|
39
|
+
type ReducerOpts,
|
|
40
|
+
type Reducers,
|
|
41
|
+
} from './reducers';
|
|
42
|
+
import { makeHooks } from './runtime';
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
makeAnonViewExport,
|
|
46
|
+
makeViewExport,
|
|
47
|
+
type AnonViews,
|
|
48
|
+
type AnonymousViewFn,
|
|
49
|
+
type ViewExport,
|
|
50
|
+
type ViewFn,
|
|
51
|
+
type ViewOpts,
|
|
52
|
+
type ViewReturnTypeBuilder,
|
|
53
|
+
type ValidateViewPrimaryKey,
|
|
54
|
+
type Views,
|
|
55
|
+
} from './views';
|
|
56
|
+
import type { UntypedTableDef } from '../lib/table';
|
|
57
|
+
|
|
58
|
+
export class SchemaInner<
|
|
59
|
+
S extends UntypedSchemaDef = UntypedSchemaDef,
|
|
60
|
+
> extends ModuleContext {
|
|
61
|
+
schemaType: S;
|
|
62
|
+
existingFunctions = new Set<string>();
|
|
63
|
+
existingHttpHandlers = new Set<string>();
|
|
64
|
+
reducers: Reducers = [];
|
|
65
|
+
procedures: Procedures = [];
|
|
66
|
+
views: Views = [];
|
|
67
|
+
anonViews: AnonViews = [];
|
|
68
|
+
httpHandlers: HandlerFn[] = [];
|
|
69
|
+
/**
|
|
70
|
+
* Maps ReducerExport objects to the name of the reducer.
|
|
71
|
+
* Used for resolving the reducers of scheduled tables.
|
|
72
|
+
*/
|
|
73
|
+
functionExports: Map<
|
|
74
|
+
| ReducerExport<UntypedSchemaDef, any>
|
|
75
|
+
| ProcedureExport<UntypedSchemaDef, any, any>,
|
|
76
|
+
string
|
|
77
|
+
> = new Map();
|
|
78
|
+
httpHandlerExports: Map<HttpHandlerExport<UntypedSchemaDef>, string> =
|
|
79
|
+
new Map();
|
|
80
|
+
pendingSchedules: PendingSchedule[] = [];
|
|
81
|
+
pendingHttpRoutes: PendingHttpRoute[] = [];
|
|
82
|
+
|
|
83
|
+
constructor(getSchemaType: (ctx: SchemaInner<S>) => S) {
|
|
84
|
+
super();
|
|
85
|
+
this.schemaType = getSchemaType(this);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
defineFunction(name: string) {
|
|
89
|
+
if (this.existingFunctions.has(name)) {
|
|
90
|
+
throw new TypeError(
|
|
91
|
+
`There is already a reducer, procedure, or view with the name '${name}'`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
this.existingFunctions.add(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
defineHttpHandler(name: string) {
|
|
98
|
+
if (this.existingHttpHandlers.has(name)) {
|
|
99
|
+
throw new TypeError(
|
|
100
|
+
`There is already an HTTP handler with the name '${name}'`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
this.existingHttpHandlers.add(name);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resolveSchedules() {
|
|
107
|
+
for (const { reducer, scheduleAtCol, tableName } of this.pendingSchedules) {
|
|
108
|
+
const functionName = this.functionExports.get(reducer());
|
|
109
|
+
if (functionName === undefined) {
|
|
110
|
+
const msg = `Table ${tableName} defines a schedule, but it seems like the associated function was not exported.`;
|
|
111
|
+
throw new TypeError(msg);
|
|
112
|
+
}
|
|
113
|
+
this.moduleDef.schedules.push({
|
|
114
|
+
sourceName: undefined,
|
|
115
|
+
tableName,
|
|
116
|
+
scheduleAtCol,
|
|
117
|
+
functionName,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
resolveHttpRoutes() {
|
|
123
|
+
for (const route of this.pendingHttpRoutes) {
|
|
124
|
+
const handlerFunction = this.httpHandlerExports.get(route.handler);
|
|
125
|
+
if (handlerFunction === undefined) {
|
|
126
|
+
throw new TypeError(
|
|
127
|
+
`HTTP route for path '${route.path}' refers to a handler that was not exported.`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
this.moduleDef.httpRoutes.push({
|
|
131
|
+
handlerFunction,
|
|
132
|
+
method: route.method,
|
|
133
|
+
path: route.path,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
type PendingSchedule = UntypedTableSchema['schedule'] & { tableName: string };
|
|
140
|
+
type PendingHttpRoute = {
|
|
141
|
+
handler: HttpHandlerExport<UntypedSchemaDef>;
|
|
142
|
+
method: MethodOrAny;
|
|
143
|
+
path: string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The Schema class represents the database schema for a SpacetimeDB application.
|
|
148
|
+
* It encapsulates the table definitions and typespace, and provides methods to define
|
|
149
|
+
* reducers and lifecycle hooks.
|
|
150
|
+
*
|
|
151
|
+
* Schema has a generic parameter S which represents the inferred schema type. This type
|
|
152
|
+
* is automatically inferred when creating a schema using the `schema()` function and is
|
|
153
|
+
* used to type the database view in reducer contexts.
|
|
154
|
+
*
|
|
155
|
+
* The methods on this class are used to register reducers and lifecycle hooks
|
|
156
|
+
* with the SpacetimeDB runtime. Theey forward to free functions that handle the actual
|
|
157
|
+
* registration logic, but having them as methods on the Schema class helps with type inference.
|
|
158
|
+
*
|
|
159
|
+
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* const spacetimedb = schema({
|
|
164
|
+
* user: table({}, userType),
|
|
165
|
+
* post: table({}, postType)
|
|
166
|
+
* });
|
|
167
|
+
* spacetimedb.reducer(
|
|
168
|
+
* 'create_user',
|
|
169
|
+
* { username: t.string(), email: t.string() },
|
|
170
|
+
* (ctx, { username, email }) => {
|
|
171
|
+
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
|
172
|
+
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
|
173
|
+
* }
|
|
174
|
+
* );
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
// TODO(cloutiertyler): It might be nice to have a way to access the types
|
|
178
|
+
// for the tables from the schema object, e.g. `spacetimedb.user.type` would
|
|
179
|
+
// be the type of the user table.
|
|
180
|
+
export class Schema<S extends UntypedSchemaDef> implements ModuleDefaultExport {
|
|
181
|
+
#ctx: SchemaInner<S>;
|
|
182
|
+
|
|
183
|
+
constructor(ctx: SchemaInner<S>) {
|
|
184
|
+
// TODO: TableSchema and TableDef should really be unified
|
|
185
|
+
this.#ctx = ctx;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
[moduleHooks](exports: object) {
|
|
189
|
+
// if (!(hasOwn(exports, 'default') && exports.default instanceof Schema)) {
|
|
190
|
+
// throw new TypeError('must export schema as default export');
|
|
191
|
+
// }
|
|
192
|
+
const registeredSchema = this.#ctx;
|
|
193
|
+
for (const [name, moduleExport] of Object.entries(exports)) {
|
|
194
|
+
if (name === 'default') continue;
|
|
195
|
+
if (!isModuleExport(moduleExport)) {
|
|
196
|
+
throw new TypeError(
|
|
197
|
+
'exporting something that is not a spacetime export'
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
checkExportContext(moduleExport, registeredSchema);
|
|
201
|
+
moduleExport[registerExport](registeredSchema, name);
|
|
202
|
+
}
|
|
203
|
+
registeredSchema.resolveSchedules();
|
|
204
|
+
registeredSchema.resolveHttpRoutes();
|
|
205
|
+
return makeHooks(registeredSchema);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get schemaType(): S {
|
|
209
|
+
return this.#ctx.schemaType;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get moduleDef() {
|
|
213
|
+
return this.#ctx.moduleDef;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get typespace() {
|
|
217
|
+
return this.#ctx.typespace;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Defines a SpacetimeDB reducer function.
|
|
222
|
+
*
|
|
223
|
+
* Reducers are the primary way to modify the state of your SpacetimeDB application.
|
|
224
|
+
* They are atomic, meaning that either all operations within a reducer succeed,
|
|
225
|
+
* or none of them do.
|
|
226
|
+
*
|
|
227
|
+
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
228
|
+
* @template Params - The type of the parameters object expected by the reducer.
|
|
229
|
+
*
|
|
230
|
+
* @param {Params} params - An object defining the parameters that the reducer accepts.
|
|
231
|
+
* Each key-value pair represents a parameter name and its corresponding
|
|
232
|
+
* {@link TypeBuilder} or {@link ColumnBuilder}.
|
|
233
|
+
* @param {(ctx: ReducerCtx<S>, payload: ParamsAsObject<Params>) => void} fn - The reducer function itself.
|
|
234
|
+
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
|
235
|
+
* - `payload`: An object containing the arguments passed to the reducer, typed according to `params`.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```typescript
|
|
239
|
+
* // Define a reducer named 'create_user' that takes 'username' (string) and 'email' (string)
|
|
240
|
+
* export const create_user = spacetime.reducer(
|
|
241
|
+
* {
|
|
242
|
+
* username: t.string(),
|
|
243
|
+
* email: t.string(),
|
|
244
|
+
* },
|
|
245
|
+
* (ctx, { username, email }) => {
|
|
246
|
+
* // Access the 'user' table from the database view in the context
|
|
247
|
+
* ctx.db.user.insert({ username, email, created_at: ctx.timestamp });
|
|
248
|
+
* console.log(`User ${username} created by ${ctx.sender.identityId}`);
|
|
249
|
+
* }
|
|
250
|
+
* );
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
reducer<Params extends ParamsObj>(
|
|
254
|
+
params: Params,
|
|
255
|
+
fn: Reducer<S, Params>
|
|
256
|
+
): ReducerExport<S, Params>;
|
|
257
|
+
reducer(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
258
|
+
reducer<Params extends ParamsObj>(
|
|
259
|
+
opts: ReducerOpts,
|
|
260
|
+
params: Params,
|
|
261
|
+
fn: Reducer<S, Params>
|
|
262
|
+
): ReducerExport<S, Params>;
|
|
263
|
+
reducer(opts: ReducerOpts, fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
264
|
+
reducer<Params extends ParamsObj>(
|
|
265
|
+
...args:
|
|
266
|
+
| [Params, Reducer<S, Params>]
|
|
267
|
+
| [Reducer<S, {}>]
|
|
268
|
+
| [ReducerOpts, Params, Reducer<S, Params>]
|
|
269
|
+
| [ReducerOpts, Reducer<S, {}>]
|
|
270
|
+
): ReducerExport<S, Params> {
|
|
271
|
+
let opts: ReducerOpts | undefined,
|
|
272
|
+
params: Params = {} as Params,
|
|
273
|
+
fn: Reducer<S, Params>;
|
|
274
|
+
switch (args.length) {
|
|
275
|
+
case 1:
|
|
276
|
+
[fn] = args;
|
|
277
|
+
break;
|
|
278
|
+
case 2: {
|
|
279
|
+
let arg1;
|
|
280
|
+
[arg1, fn] = args;
|
|
281
|
+
if (typeof arg1.name === 'string') opts = arg1 as ReducerOpts;
|
|
282
|
+
else params = arg1 as Params;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
case 3:
|
|
286
|
+
[opts, params, fn] = args;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
return makeReducerExport(this.#ctx, opts, params, fn);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Registers an initialization reducer that runs when the SpacetimeDB module is published
|
|
294
|
+
* for the first time.
|
|
295
|
+
*
|
|
296
|
+
* This function is useful to set up any initial state of your database that is guaranteed
|
|
297
|
+
* to run only once, and before any other reducers or client connections.
|
|
298
|
+
*
|
|
299
|
+
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
300
|
+
* @param {Reducer<S, {}>} fn - The initialization reducer function.
|
|
301
|
+
* - `ctx`: The reducer context, providing access to `sender`, `timestamp`, `connection_id`, and `db`.
|
|
302
|
+
* @example
|
|
303
|
+
* ```typescript
|
|
304
|
+
* export const init = spacetime.init((ctx) => {
|
|
305
|
+
* ctx.db.user.insert({ username: 'admin', email: 'admin@example.com' });
|
|
306
|
+
* });
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
init(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
310
|
+
init(opts: ReducerOpts, fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
311
|
+
init(
|
|
312
|
+
...args: [Reducer<S, {}>] | [ReducerOpts, Reducer<S, {}>]
|
|
313
|
+
): ReducerExport<S, {}> {
|
|
314
|
+
let opts: ReducerOpts | undefined, fn: Reducer<S, {}>;
|
|
315
|
+
switch (args.length) {
|
|
316
|
+
case 1:
|
|
317
|
+
[fn] = args;
|
|
318
|
+
break;
|
|
319
|
+
case 2:
|
|
320
|
+
[opts, fn] = args;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
return makeReducerExport(this.#ctx, opts, {}, fn, Lifecycle.Init);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Registers a reducer to be called when a client connects to the SpacetimeDB module.
|
|
328
|
+
* This function allows you to define custom logic that should execute
|
|
329
|
+
* whenever a new client establishes a connection.
|
|
330
|
+
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
331
|
+
*
|
|
332
|
+
* @param fn - The reducer function to execute on client connection.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```typescript
|
|
336
|
+
* export const onConnect = spacetime.clientConnected(
|
|
337
|
+
* (ctx) => {
|
|
338
|
+
* console.log(`Client ${ctx.connectionId} connected`);
|
|
339
|
+
* }
|
|
340
|
+
* );
|
|
341
|
+
*/
|
|
342
|
+
clientConnected(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
343
|
+
clientConnected(opts: ReducerOpts, fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
344
|
+
clientConnected(
|
|
345
|
+
...args: [Reducer<S, {}>] | [ReducerOpts, Reducer<S, {}>]
|
|
346
|
+
): ReducerExport<S, {}> {
|
|
347
|
+
let opts: ReducerOpts | undefined, fn: Reducer<S, {}>;
|
|
348
|
+
switch (args.length) {
|
|
349
|
+
case 1:
|
|
350
|
+
[fn] = args;
|
|
351
|
+
break;
|
|
352
|
+
case 2:
|
|
353
|
+
[opts, fn] = args;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
return makeReducerExport(this.#ctx, opts, {}, fn, Lifecycle.OnConnect);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Registers a reducer to be called when a client disconnects from the SpacetimeDB module.
|
|
361
|
+
* This function allows you to define custom logic that should execute
|
|
362
|
+
* whenever a client disconnects.
|
|
363
|
+
* @template S - The inferred schema type of the SpacetimeDB module.
|
|
364
|
+
*
|
|
365
|
+
* @param fn - The reducer function to execute on client disconnection.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* export const onDisconnect = spacetime.clientDisconnected(
|
|
370
|
+
* (ctx) => {
|
|
371
|
+
* console.log(`Client ${ctx.connectionId} disconnected`);
|
|
372
|
+
* }
|
|
373
|
+
* );
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
clientDisconnected(fn: Reducer<S, {}>): ReducerExport<S, {}>;
|
|
377
|
+
clientDisconnected(
|
|
378
|
+
opts: ReducerOpts,
|
|
379
|
+
fn: Reducer<S, {}>
|
|
380
|
+
): ReducerExport<S, {}>;
|
|
381
|
+
clientDisconnected(
|
|
382
|
+
...args: [Reducer<S, {}>] | [ReducerOpts, Reducer<S, {}>]
|
|
383
|
+
): ReducerExport<S, {}> {
|
|
384
|
+
let opts: ReducerOpts | undefined, fn: Reducer<S, {}>;
|
|
385
|
+
switch (args.length) {
|
|
386
|
+
case 1:
|
|
387
|
+
[fn] = args;
|
|
388
|
+
break;
|
|
389
|
+
case 2:
|
|
390
|
+
[opts, fn] = args;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
return makeReducerExport(this.#ctx, opts, {}, fn, Lifecycle.OnDisconnect);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
view<Ret extends ViewReturnTypeBuilder, F extends ViewFn<S, {}, Ret>>(
|
|
397
|
+
opts: ViewOpts,
|
|
398
|
+
ret: Ret,
|
|
399
|
+
fn: F,
|
|
400
|
+
// Compile-time-only guard: this rest parameter is `[]` for valid return
|
|
401
|
+
// builders, but becomes a required error tuple when a returned row builder
|
|
402
|
+
// marks more than one column with `.primaryKey()`.
|
|
403
|
+
..._: ValidateViewPrimaryKey<Ret>
|
|
404
|
+
): ViewExport<F> {
|
|
405
|
+
return makeViewExport<S, {}, Ret, F>(this.#ctx, opts, {}, ret, fn);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// TODO: re-enable once parameterized views are supported in SQL
|
|
409
|
+
// view<Ret extends ViewReturnTypeBuilder>(
|
|
410
|
+
// opts: ViewOpts,
|
|
411
|
+
// ret: Ret,
|
|
412
|
+
// fn: ViewFn<S, {}, Ret>
|
|
413
|
+
// ): void;
|
|
414
|
+
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
415
|
+
// opts: ViewOpts,
|
|
416
|
+
// params: Params,
|
|
417
|
+
// ret: Ret,
|
|
418
|
+
// fn: ViewFn<S, {}, Ret>
|
|
419
|
+
// ): void;
|
|
420
|
+
// view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
421
|
+
// opts: ViewOpts,
|
|
422
|
+
// paramsOrRet: Ret | Params,
|
|
423
|
+
// retOrFn: ViewFn<S, {}, Ret> | Ret,
|
|
424
|
+
// maybeFn?: ViewFn<S, Params, Ret>
|
|
425
|
+
// ): void {
|
|
426
|
+
// if (typeof retOrFn === 'function') {
|
|
427
|
+
// defineView(name, false, {}, paramsOrRet as Ret, retOrFn);
|
|
428
|
+
// } else {
|
|
429
|
+
// defineView(name, false, paramsOrRet as Params, retOrFn, maybeFn!);
|
|
430
|
+
// }
|
|
431
|
+
// }
|
|
432
|
+
|
|
433
|
+
anonymousView<
|
|
434
|
+
Ret extends ViewReturnTypeBuilder,
|
|
435
|
+
F extends AnonymousViewFn<S, {}, Ret>,
|
|
436
|
+
>(
|
|
437
|
+
opts: ViewOpts,
|
|
438
|
+
ret: Ret,
|
|
439
|
+
fn: F,
|
|
440
|
+
// Compile-time-only guard: this rest parameter is `[]` for valid return
|
|
441
|
+
// builders, but becomes a required error tuple when a returned row builder
|
|
442
|
+
// marks more than one column with `.primaryKey()`.
|
|
443
|
+
..._: ValidateViewPrimaryKey<Ret>
|
|
444
|
+
): ViewExport<F> {
|
|
445
|
+
return makeAnonViewExport<S, {}, Ret, F>(this.#ctx, opts, {}, ret, fn);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// TODO: re-enable once parameterized views are supported in SQL
|
|
449
|
+
// anonymousView<Ret extends ViewReturnTypeBuilder>(
|
|
450
|
+
// opts: ViewOpts,
|
|
451
|
+
// ret: Ret,
|
|
452
|
+
// fn: AnonymousViewFn<S, {}, Ret>
|
|
453
|
+
// ): void;
|
|
454
|
+
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
455
|
+
// opts: ViewOpts,
|
|
456
|
+
// params: Params,
|
|
457
|
+
// ret: Ret,
|
|
458
|
+
// fn: AnonymousViewFn<S, {}, Ret>
|
|
459
|
+
// ): void;
|
|
460
|
+
// anonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
|
|
461
|
+
// opts: ViewOpts,
|
|
462
|
+
// paramsOrRet: Ret | Params,
|
|
463
|
+
// retOrFn: AnonymousViewFn<S, {}, Ret> | Ret,
|
|
464
|
+
// maybeFn?: AnonymousViewFn<S, Params, Ret>
|
|
465
|
+
// ): void {
|
|
466
|
+
// if (typeof retOrFn === 'function') {
|
|
467
|
+
// defineView(name, true, {}, paramsOrRet as Ret, retOrFn);
|
|
468
|
+
// } else {
|
|
469
|
+
// defineView(name, true, paramsOrRet as Params, retOrFn, maybeFn!);
|
|
470
|
+
// }
|
|
471
|
+
// }
|
|
472
|
+
|
|
473
|
+
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
|
474
|
+
params: Params,
|
|
475
|
+
ret: Ret,
|
|
476
|
+
fn: ProcedureFn<S, Params, Ret>
|
|
477
|
+
): ProcedureFn<S, Params, Ret>;
|
|
478
|
+
procedure<Ret extends TypeBuilder<any, any>>(
|
|
479
|
+
ret: Ret,
|
|
480
|
+
fn: ProcedureFn<S, {}, Ret>
|
|
481
|
+
): ProcedureFn<S, {}, Ret>;
|
|
482
|
+
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
|
483
|
+
opts: ProcedureOpts,
|
|
484
|
+
params: Params,
|
|
485
|
+
ret: Ret,
|
|
486
|
+
fn: ProcedureFn<S, Params, Ret>
|
|
487
|
+
): ProcedureFn<S, Params, Ret>;
|
|
488
|
+
procedure<Ret extends TypeBuilder<any, any>>(
|
|
489
|
+
opts: ProcedureOpts,
|
|
490
|
+
ret: Ret,
|
|
491
|
+
fn: ProcedureFn<S, {}, Ret>
|
|
492
|
+
): ProcedureFn<S, {}, Ret>;
|
|
493
|
+
procedure<Params extends ParamsObj, Ret extends TypeBuilder<any, any>>(
|
|
494
|
+
...args:
|
|
495
|
+
| [Params, Ret, ProcedureFn<S, Params, Ret>]
|
|
496
|
+
| [Ret, ProcedureFn<S, Params, Ret>]
|
|
497
|
+
| [ProcedureOpts, Params, Ret, ProcedureFn<S, Params, Ret>]
|
|
498
|
+
| [ProcedureOpts, Ret, ProcedureFn<S, Params, Ret>]
|
|
499
|
+
): ProcedureExport<S, Params, Ret> {
|
|
500
|
+
let opts: ProcedureOpts | undefined,
|
|
501
|
+
params: Params = {} as Params,
|
|
502
|
+
ret: Ret,
|
|
503
|
+
fn: ProcedureFn<S, Params, Ret>;
|
|
504
|
+
switch (args.length) {
|
|
505
|
+
case 2:
|
|
506
|
+
[ret, fn] = args;
|
|
507
|
+
break;
|
|
508
|
+
case 3: {
|
|
509
|
+
let arg1;
|
|
510
|
+
[arg1, ret, fn] = args;
|
|
511
|
+
if (typeof arg1.name === 'string') opts = arg1 as ProcedureOpts;
|
|
512
|
+
else params = arg1 as Params;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case 4:
|
|
516
|
+
[opts, params, ret, fn] = args;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
return makeProcedureExport(this.#ctx, opts, params, ret, fn);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
httpHandler(fn: HandlerFn<S>): HttpHandlerExport<S>;
|
|
523
|
+
httpHandler(opts: HttpHandlerOpts, fn: HandlerFn<S>): HttpHandlerExport<S>;
|
|
524
|
+
httpHandler(
|
|
525
|
+
...args: [HandlerFn<S>] | [HttpHandlerOpts, HandlerFn<S>]
|
|
526
|
+
): HttpHandlerExport<S> {
|
|
527
|
+
let opts: HttpHandlerOpts | undefined, fn: HandlerFn<S>;
|
|
528
|
+
switch (args.length) {
|
|
529
|
+
case 1:
|
|
530
|
+
[fn] = args;
|
|
531
|
+
break;
|
|
532
|
+
case 2:
|
|
533
|
+
[opts, fn] = args;
|
|
534
|
+
break;
|
|
535
|
+
}
|
|
536
|
+
return makeHttpHandlerExport(this.#ctx, opts, fn);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
httpRouter(router: Router): ModuleExport {
|
|
540
|
+
return makeHttpRouterExport(this.#ctx, router);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Bundle multiple reducers, procedures, etc into one value to export.
|
|
545
|
+
* The name they will be exported with is their corresponding key in the `exports` argument.
|
|
546
|
+
*/
|
|
547
|
+
exportGroup(exports: Record<string, ModuleExport>): ModuleExport {
|
|
548
|
+
return {
|
|
549
|
+
[exportContext]: this.#ctx,
|
|
550
|
+
[registerExport](ctx, _exportName) {
|
|
551
|
+
for (const [exportName, moduleExport] of Object.entries(exports)) {
|
|
552
|
+
checkExportContext(moduleExport, ctx);
|
|
553
|
+
moduleExport[registerExport](ctx, exportName);
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
clientVisibilityFilter = {
|
|
560
|
+
sql: (filter: string): ModuleExport => ({
|
|
561
|
+
[exportContext]: this.#ctx,
|
|
562
|
+
[registerExport](ctx, _exportName) {
|
|
563
|
+
ctx.moduleDef.rowLevelSecurity.push({ sql: filter });
|
|
564
|
+
},
|
|
565
|
+
}),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export const registerExport = Symbol('SpacetimeDB.registerExport');
|
|
570
|
+
export const exportContext = Symbol('SpacetimeDB.exportContext');
|
|
571
|
+
|
|
572
|
+
export interface ModuleExport {
|
|
573
|
+
[registerExport](ctx: SchemaInner, exportName: string): void;
|
|
574
|
+
[exportContext]?: SchemaInner;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function isModuleExport(x: unknown): x is ModuleExport {
|
|
578
|
+
return (
|
|
579
|
+
(typeof x === 'function' || typeof x === 'object') &&
|
|
580
|
+
x !== null &&
|
|
581
|
+
registerExport in x
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** Verify that the ModuleContext that `exp` comes from is the same as `schema` */
|
|
586
|
+
function checkExportContext(exp: ModuleExport, schema: SchemaInner) {
|
|
587
|
+
if (exp[exportContext] != null && exp[exportContext] !== schema) {
|
|
588
|
+
throw new TypeError('multiple schemas are not supported');
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Extracts the inferred schema type from a Schema instance
|
|
594
|
+
*/
|
|
595
|
+
export type InferSchema<SchemaDef extends Schema<any>> =
|
|
596
|
+
SchemaDef extends Schema<infer S> ? S : never;
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Creates a schema from table definitions
|
|
600
|
+
* @param handles - Array of table handles created by table() function
|
|
601
|
+
* @returns ColumnBuilder representing the complete database schema
|
|
602
|
+
* @example
|
|
603
|
+
* ```ts
|
|
604
|
+
* const spacetimedb = schema({
|
|
605
|
+
* user: table({}, userType),
|
|
606
|
+
* post: table({}, postType)
|
|
607
|
+
* });
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
/**
|
|
611
|
+
* Module-level settings that can be passed to `schema()`.
|
|
612
|
+
*/
|
|
613
|
+
export interface ModuleSettings {
|
|
614
|
+
/**
|
|
615
|
+
* The case conversion policy for this module.
|
|
616
|
+
* Defaults to `SnakeCase` if not specified.
|
|
617
|
+
*
|
|
618
|
+
* @example
|
|
619
|
+
* ```ts
|
|
620
|
+
* export default schema({
|
|
621
|
+
* player,
|
|
622
|
+
* }, { CASE_CONVERSION_POLICY: CaseConversionPolicy.None });
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
CASE_CONVERSION_POLICY?: CaseConversionPolicy;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function schema<const H extends Record<string, UntypedTableSchema>>(
|
|
629
|
+
tables: H,
|
|
630
|
+
moduleSettings?: ModuleSettings
|
|
631
|
+
): Schema<TablesToSchema<H>> {
|
|
632
|
+
const ctx = new SchemaInner<TablesToSchema<H>>(ctx => {
|
|
633
|
+
// Apply module settings.
|
|
634
|
+
if (moduleSettings?.CASE_CONVERSION_POLICY != null) {
|
|
635
|
+
ctx.setCaseConversionPolicy(moduleSettings.CASE_CONVERSION_POLICY);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const tableSchemas: Record<string, UntypedTableDef> = {};
|
|
639
|
+
for (const [accName, table] of Object.entries(tables)) {
|
|
640
|
+
const tableDef = table.tableDef(ctx, accName);
|
|
641
|
+
tableSchemas[accName] = tableToSchema(accName, table, tableDef);
|
|
642
|
+
ctx.moduleDef.tables.push(tableDef);
|
|
643
|
+
if (table.schedule) {
|
|
644
|
+
ctx.pendingSchedules.push({
|
|
645
|
+
...table.schedule,
|
|
646
|
+
tableName: tableDef.sourceName,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
if (table.tableName) {
|
|
650
|
+
ctx.moduleDef.explicitNames.entries.push({
|
|
651
|
+
tag: 'Table',
|
|
652
|
+
value: {
|
|
653
|
+
sourceName: accName,
|
|
654
|
+
canonicalName: table.tableName,
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return { tables: tableSchemas } as TablesToSchema<H>;
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
return new Schema(ctx);
|
|
663
|
+
}
|