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.
Files changed (180) hide show
  1. package/LICENSE.txt +759 -759
  2. package/README.md +211 -211
  3. package/dist/angular/index.cjs.map +1 -1
  4. package/dist/angular/index.mjs.map +1 -1
  5. package/dist/browser/angular/index.mjs.map +1 -1
  6. package/dist/browser/react/index.mjs +129 -57
  7. package/dist/browser/react/index.mjs.map +1 -1
  8. package/dist/browser/solid/index.mjs +120 -50
  9. package/dist/browser/solid/index.mjs.map +1 -1
  10. package/dist/browser/svelte/index.mjs.map +1 -1
  11. package/dist/browser/vue/index.mjs.map +1 -1
  12. package/dist/index.browser.mjs +10 -2
  13. package/dist/index.browser.mjs.map +1 -1
  14. package/dist/index.cjs +10 -2
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.mjs +10 -2
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/min/index.browser.mjs +1 -1
  19. package/dist/min/index.browser.mjs.map +1 -1
  20. package/dist/min/react/index.mjs +1 -1
  21. package/dist/min/react/index.mjs.map +1 -1
  22. package/dist/min/sdk/index.browser.mjs +1 -1
  23. package/dist/min/sdk/index.browser.mjs.map +1 -1
  24. package/dist/react/index.cjs +129 -57
  25. package/dist/react/index.cjs.map +1 -1
  26. package/dist/react/index.mjs +129 -57
  27. package/dist/react/index.mjs.map +1 -1
  28. package/dist/react/useTable.d.ts.map +1 -1
  29. package/dist/sdk/connection_manager.d.ts +8 -0
  30. package/dist/sdk/connection_manager.d.ts.map +1 -1
  31. package/dist/sdk/db_connection_impl.d.ts +7 -0
  32. package/dist/sdk/db_connection_impl.d.ts.map +1 -1
  33. package/dist/sdk/index.browser.mjs +10 -2
  34. package/dist/sdk/index.browser.mjs.map +1 -1
  35. package/dist/sdk/index.cjs +10 -2
  36. package/dist/sdk/index.cjs.map +1 -1
  37. package/dist/sdk/index.mjs +10 -2
  38. package/dist/sdk/index.mjs.map +1 -1
  39. package/dist/sdk/websocket_test_adapter.d.ts +2 -1
  40. package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
  41. package/dist/server/index.mjs.map +1 -1
  42. package/dist/server/runtime.d.ts.map +1 -1
  43. package/dist/solid/index.cjs +120 -50
  44. package/dist/solid/index.cjs.map +1 -1
  45. package/dist/solid/index.mjs +120 -50
  46. package/dist/solid/index.mjs.map +1 -1
  47. package/dist/svelte/index.cjs.map +1 -1
  48. package/dist/svelte/index.mjs.map +1 -1
  49. package/dist/tanstack/index.cjs +120 -50
  50. package/dist/tanstack/index.cjs.map +1 -1
  51. package/dist/tanstack/index.mjs +120 -50
  52. package/dist/tanstack/index.mjs.map +1 -1
  53. package/dist/vue/index.cjs.map +1 -1
  54. package/dist/vue/index.mjs.map +1 -1
  55. package/package.json +1 -1
  56. package/src/angular/connection_state.ts +19 -19
  57. package/src/angular/index.ts +3 -3
  58. package/src/angular/injectors/index.ts +4 -4
  59. package/src/angular/injectors/inject-reducer.ts +62 -62
  60. package/src/angular/injectors/inject-spacetimedb-connected.ts +13 -13
  61. package/src/angular/injectors/inject-spacetimedb.ts +10 -10
  62. package/src/angular/injectors/inject-table.ts +234 -234
  63. package/src/angular/providers/index.ts +1 -1
  64. package/src/angular/providers/provide-spacetimedb.ts +96 -96
  65. package/src/index.ts +16 -16
  66. package/src/lib/algebraic_type.ts +819 -819
  67. package/src/lib/algebraic_type_variants.ts +26 -26
  68. package/src/lib/algebraic_value.ts +10 -10
  69. package/src/lib/autogen/types.ts +746 -746
  70. package/src/lib/binary_reader.ts +188 -188
  71. package/src/lib/binary_writer.ts +213 -213
  72. package/src/lib/connection_id.ts +102 -102
  73. package/src/lib/constraints.ts +48 -48
  74. package/src/lib/errors.ts +26 -26
  75. package/src/lib/filter.ts +195 -195
  76. package/src/lib/identity.ts +83 -83
  77. package/src/lib/indexes.ts +251 -251
  78. package/src/lib/option.ts +34 -34
  79. package/src/lib/query.ts +1019 -1019
  80. package/src/lib/reducer_schema.ts +38 -38
  81. package/src/lib/reducers.ts +116 -116
  82. package/src/lib/result.ts +36 -36
  83. package/src/lib/schedule_at.ts +86 -86
  84. package/src/lib/schema.ts +420 -420
  85. package/src/lib/table.ts +548 -548
  86. package/src/lib/table_schema.ts +64 -64
  87. package/src/lib/time_duration.ts +77 -77
  88. package/src/lib/timestamp.ts +148 -148
  89. package/src/lib/type_builders.test-d.ts +128 -128
  90. package/src/lib/type_builders.ts +4014 -4014
  91. package/src/lib/type_util.ts +124 -124
  92. package/src/lib/util.ts +196 -196
  93. package/src/lib/uuid.ts +337 -337
  94. package/src/react/SpacetimeDBProvider.ts +84 -84
  95. package/src/react/connection_state.ts +6 -6
  96. package/src/react/index.ts +5 -5
  97. package/src/react/useProcedure.ts +60 -60
  98. package/src/react/useReducer.ts +53 -53
  99. package/src/react/useSpacetimeDB.ts +18 -18
  100. package/src/react/useTable.ts +256 -251
  101. package/src/sdk/client_api/index.ts +114 -114
  102. package/src/sdk/client_api/types/procedures.ts +8 -8
  103. package/src/sdk/client_api/types/reducers.ts +8 -8
  104. package/src/sdk/client_api/types.ts +288 -288
  105. package/src/sdk/client_cache.ts +129 -129
  106. package/src/sdk/client_table.ts +179 -179
  107. package/src/sdk/connection_manager.ts +352 -237
  108. package/src/sdk/db_connection_builder.ts +290 -290
  109. package/src/sdk/db_connection_impl.ts +1356 -1347
  110. package/src/sdk/db_context.ts +28 -28
  111. package/src/sdk/db_view.ts +12 -12
  112. package/src/sdk/decompress.ts +51 -51
  113. package/src/sdk/event.ts +18 -18
  114. package/src/sdk/event_context.ts +51 -51
  115. package/src/sdk/event_emitter.ts +32 -32
  116. package/src/sdk/index.ts +14 -14
  117. package/src/sdk/internal.ts +2 -2
  118. package/src/sdk/json_api.ts +46 -46
  119. package/src/sdk/logger.ts +134 -134
  120. package/src/sdk/message_types.ts +46 -46
  121. package/src/sdk/procedures.ts +83 -83
  122. package/src/sdk/reducer_event.ts +20 -20
  123. package/src/sdk/reducer_handle.ts +12 -12
  124. package/src/sdk/reducers.ts +159 -159
  125. package/src/sdk/schema.ts +45 -45
  126. package/src/sdk/spacetime_module.ts +28 -28
  127. package/src/sdk/subscription_builder_impl.ts +275 -275
  128. package/src/sdk/table_cache.ts +581 -581
  129. package/src/sdk/type_utils.ts +19 -19
  130. package/src/sdk/version.ts +133 -133
  131. package/src/sdk/websocket_decompress_adapter.ts +63 -63
  132. package/src/sdk/websocket_protocols.ts +25 -25
  133. package/src/sdk/websocket_test_adapter.ts +107 -100
  134. package/src/sdk/websocket_v3_frames.ts +126 -126
  135. package/src/sdk/ws.ts +105 -105
  136. package/src/server/console.ts +81 -81
  137. package/src/server/db_view.ts +21 -21
  138. package/src/server/errors.ts +138 -138
  139. package/src/server/http.test-d.ts +80 -80
  140. package/src/server/http.ts +14 -14
  141. package/src/server/http_handlers.ts +413 -413
  142. package/src/server/http_internal.ts +79 -79
  143. package/src/server/http_shared.ts +186 -186
  144. package/src/server/index.ts +37 -37
  145. package/src/server/polyfills.ts +4 -4
  146. package/src/server/procedures.ts +239 -239
  147. package/src/server/query.ts +1 -1
  148. package/src/server/range.ts +53 -53
  149. package/src/server/reducers.ts +113 -113
  150. package/src/server/rng.ts +113 -113
  151. package/src/server/runtime.ts +1102 -1102
  152. package/src/server/schema.test-d.ts +99 -99
  153. package/src/server/schema.ts +663 -663
  154. package/src/server/sys.d.ts +125 -125
  155. package/src/server/view.test-d.ts +194 -194
  156. package/src/server/views.ts +340 -340
  157. package/src/solid/SpacetimeDBProvider.ts +97 -97
  158. package/src/solid/connection_state.ts +6 -6
  159. package/src/solid/index.ts +5 -5
  160. package/src/solid/useProcedure.ts +57 -57
  161. package/src/solid/useReducer.ts +50 -50
  162. package/src/solid/useSpacetimeDB.ts +18 -18
  163. package/src/solid/useTable.ts +203 -203
  164. package/src/svelte/SpacetimeDBProvider.ts +101 -101
  165. package/src/svelte/connection_state.ts +16 -16
  166. package/src/svelte/index.ts +4 -4
  167. package/src/svelte/useReducer.ts +61 -61
  168. package/src/svelte/useSpacetimeDB.ts +22 -22
  169. package/src/svelte/useTable.ts +218 -218
  170. package/src/tanstack/SpacetimeDBQueryClient.ts +330 -330
  171. package/src/tanstack/hooks.ts +83 -83
  172. package/src/tanstack/index.ts +16 -16
  173. package/src/util-stub.ts +1 -1
  174. package/src/vue/SpacetimeDBProvider.ts +157 -157
  175. package/src/vue/connection_state.ts +19 -19
  176. package/src/vue/index.ts +5 -5
  177. package/src/vue/useProcedure.ts +62 -62
  178. package/src/vue/useReducer.ts +55 -55
  179. package/src/vue/useSpacetimeDB.ts +18 -18
  180. package/src/vue/useTable.ts +229 -229
package/src/lib/table.ts CHANGED
@@ -1,548 +1,548 @@
1
- import type { ProcedureExport, ReducerExport, t } from '../server';
2
- import type { errors } from '../server/errors';
3
- import {
4
- ExplicitNameEntry,
5
- RawColumnDefaultValueV10,
6
- RawConstraintDefV10,
7
- RawIndexAlgorithm,
8
- RawIndexDefV10,
9
- RawSequenceDefV10,
10
- RawTableDefV10,
11
- } from './autogen/types';
12
- import BinaryWriter from './binary_writer';
13
- import type { AllUnique, ConstraintOpts } from './constraints';
14
- import type {
15
- ColumnIndex,
16
- IndexColumns,
17
- Indexes,
18
- IndexOpts,
19
- ReadonlyIndexes,
20
- UntypedIndex,
21
- } from './indexes';
22
- import ScheduleAt from './schedule_at';
23
- import type { TableSchema } from './table_schema';
24
- import {
25
- RowBuilder,
26
- type ColumnBuilder,
27
- type ColumnMetadata,
28
- type InferTypeOfRow,
29
- type RowObj,
30
- type TypeBuilder,
31
- } from './type_builders';
32
- import type {
33
- InvalidColumnMetadata,
34
- Prettify,
35
- ValidateColumnMetadata,
36
- } from './type_util';
37
- import { toPascalCase } from './util';
38
-
39
- export type AlgebraicTypeRef = number;
40
- type ColId = number;
41
- type ColList = ColId[];
42
-
43
- /**
44
- * Check if any column in the row has invalid metadata.
45
- */
46
- type HasInvalidColumn<Row extends RowObj> =
47
- // this checks if Row exactly equals RowObj - if it does, we can't
48
- // do type-system-level checking, so just let it pass
49
- (<G>() => G extends Row ? 1 : 2) extends <G>() => G extends RowObj ? 1 : 2
50
- ? false
51
- : {
52
- [K in keyof Row]: Row[K] extends ColumnBuilder<any, any, infer M>
53
- ? ValidateColumnMetadata<M> extends InvalidColumnMetadata<any>
54
- ? true
55
- : false
56
- : false;
57
- }[keyof Row] extends false
58
- ? false
59
- : true;
60
-
61
- /**
62
- * Extract the names of columns that have invalid metadata.
63
- */
64
- type InvalidColumnNames<Row extends RowObj> = {
65
- [K in keyof Row]: Row[K] extends ColumnBuilder<any, any, infer M>
66
- ? ValidateColumnMetadata<M> extends InvalidColumnMetadata<any>
67
- ? K & string
68
- : never
69
- : never;
70
- }[keyof Row];
71
-
72
- /**
73
- * A descriptive error type that surfaces the validation error.
74
- * The type name itself contains the error message for better CLI output.
75
- */
76
- type ERROR_default_cannot_be_combined_with_primaryKey_unique_or_autoInc<
77
- InvalidColumns extends string,
78
- > = {
79
- _invalidColumns: InvalidColumns;
80
- _fix: 'Remove either default() or the constraint (primaryKey/unique/autoInc) from these columns';
81
- };
82
-
83
- /**
84
- * A helper type to extract the row type from a TableDef
85
- */
86
- export type RowType<TableDef extends Pick<UntypedTableDef, 'columns'>> =
87
- InferTypeOfRow<TableDef['columns']>;
88
-
89
- /**
90
- * Coerces a column which may be a TypeBuilder or ColumnBuilder into a ColumnBuilder
91
- */
92
- export type CoerceColumn<
93
- Col extends TypeBuilder<any, any> | ColumnBuilder<any, any, any>,
94
- > =
95
- Col extends TypeBuilder<infer T, infer U>
96
- ? ColumnBuilder<T, U, ColumnMetadata<any>>
97
- : Col;
98
-
99
- /**
100
- * Coerces a RowObj where TypeBuilders are replaced with ColumnBuilders
101
- */
102
- export type CoerceRow<Row extends RowObj> = {
103
- [k in keyof Row & string]: CoerceColumn<Row[k]>;
104
- };
105
-
106
- /**
107
- * Helper type to coerce an array of IndexOpts
108
- */
109
- type CoerceArray<X extends IndexOpts<any>[]> = X;
110
-
111
- /**
112
- * An untyped representation of a table's schema.
113
- */
114
- export type UntypedTableDef = {
115
- sourceName: string;
116
- accessorName: string;
117
- columns: Record<string, ColumnBuilder<any, any, ColumnMetadata<any>>>;
118
- // This is really just a ProductType where all the elements have names.
119
- rowType: RowBuilder<RowObj>['algebraicType']['value'];
120
- /**
121
- * Declarative multi-column indexes supplied by user code in `table({ indexes: [...] }, ...)`.
122
- *
123
- * This is intentionally the *declarative* shape (`IndexOpts`) because a lot of
124
- * type-level behavior is derived from these entries (for example query-builder
125
- * inference over composite indexes).
126
- */
127
- indexes: readonly IndexOpts<any>[];
128
- /**
129
- * Fully-resolved runtime indexes materialized from `RawTableDefV10`.
130
- *
131
- * This contains both:
132
- * 1) field-level indexes inferred from column metadata, and
133
- * 2) explicit table-level indexes.
134
- *
135
- * Runtime consumers like `TableCacheImpl` should use this field instead of
136
- * reinterpreting `indexes` as runtime index metadata.
137
- */
138
- resolvedIndexes: readonly UntypedIndex<any>[];
139
- constraints: readonly ConstraintOpts<any>[];
140
- tableDef: RawTableDefV10;
141
- isEvent?: boolean;
142
- };
143
-
144
- /**
145
- * A type representing the indexes defined on a table.
146
- */
147
- export type TableIndexes<TableDef extends UntypedTableDef> = {
148
- [K in keyof TableDef['columns'] & string as ColumnIndex<
149
- K,
150
- TableDef['columns'][K]['columnMetadata']
151
- > extends never
152
- ? never
153
- : K]: ColumnIndex<K, TableDef['columns'][K]['columnMetadata']>;
154
- } & {
155
- [I in TableDef['indexes'][number] as I['accessor'] & {}]: TableIndexFromDef<
156
- TableDef,
157
- I
158
- >;
159
- };
160
-
161
- type TableIndexFromDef<
162
- TableDef extends UntypedTableDef,
163
- I extends IndexOpts<keyof TableDef['columns'] & string>,
164
- > =
165
- NormalizeIndexColumns<TableDef, I> extends infer Cols extends ReadonlyArray<
166
- keyof TableDef['columns'] & string
167
- >
168
- ? {
169
- name: I['accessor'];
170
- unique: AllUnique<TableDef, Cols>;
171
- algorithm: Lowercase<I['algorithm']>;
172
- columns: Cols;
173
- }
174
- : never;
175
-
176
- type NormalizeIndexColumns<
177
- TableDef extends UntypedTableDef,
178
- I extends IndexOpts<keyof TableDef['columns'] & string>,
179
- > =
180
- IndexColumns<I> extends ReadonlyArray<keyof TableDef['columns'] & string>
181
- ? IndexColumns<I>
182
- : never;
183
-
184
- /**
185
- * Options for configuring a database table.
186
- * - `name`: The name of the table.
187
- * - `public`: Whether the table is publicly accessible. Defaults to `false`.
188
- * - `indexes`: An array of index configurations for the table.
189
- * - `constraints`: An array of constraint configurations for the table.
190
- * - `scheduled`: The name of the reducer to be executed based on the scheduled rows in this table.
191
- */
192
- export type TableOpts<Row extends RowObj> = {
193
- name?: string;
194
- public?: boolean;
195
- indexes?: IndexOpts<keyof Row & string>[]; // declarative multi‑column indexes
196
- constraints?: ConstraintOpts<keyof Row & string>[];
197
- scheduled?: () =>
198
- | ReducerExport<any, { [k: string]: RowBuilder<RowObj> }>
199
- | ProcedureExport<
200
- any,
201
- { [k: string]: RowBuilder<RowObj> },
202
- ReturnType<typeof t.unit>
203
- >;
204
- event?: boolean;
205
- };
206
-
207
- /**
208
- * Extracts the indices from TableOpts, defaulting to an empty array if none are provided.
209
- */
210
- type OptsIndices<Opts extends TableOpts<any>> = Opts extends {
211
- indexes: infer Ixs extends NonNullable<any[]>;
212
- }
213
- ? Ixs
214
- : CoerceArray<[]>;
215
-
216
- /**
217
- * Extracts the constraints from TableOpts, defaulting to an empty array if none are provided.
218
- */
219
- type OptsConstraints<Opts extends TableOpts<any>> = Opts extends {
220
- constraints: infer Constraints extends NonNullable<any[]>;
221
- }
222
- ? Constraints
223
- : CoerceArray<[]>;
224
-
225
- /**
226
- * Table<Row, UniqueConstraintViolation = never, AutoIncOverflow = never>
227
- *
228
- * - Row: row shape
229
- * - UCV: unique-constraint violation error type (never if none)
230
- * - AIO: auto-increment overflow error type (never if none)
231
- */
232
- export type Table<TableDef extends UntypedTableDef> = Prettify<
233
- TableMethods<TableDef> & Indexes<TableDef, TableIndexes<TableDef>>
234
- >;
235
-
236
- export type ReadonlyTable<TableDef extends UntypedTableDef> = Prettify<
237
- ReadonlyTableMethods<TableDef> &
238
- ReadonlyIndexes<TableDef, TableIndexes<TableDef>>
239
- >;
240
-
241
- export interface ReadonlyTableMethods<TableDef extends UntypedTableDef> {
242
- /**
243
- * Returns the number of rows in this table.
244
- *
245
- * This reads datastore metadata, so it runs in constant time.
246
- * It also takes into account modifications by the current transaction.
247
- */
248
- count(): bigint;
249
-
250
- /** Iterate over all rows in the TX state. Rust Iterator<Item=Row> → TS IterableIterator<Row>. */
251
- iter(): IteratorObject<Prettify<RowType<TableDef>>, undefined>;
252
- [Symbol.iterator](): IteratorObject<Prettify<RowType<TableDef>>, undefined>;
253
- }
254
-
255
- /**
256
- * A type representing the methods available on a table.
257
- */
258
- export interface TableMethods<TableDef extends UntypedTableDef>
259
- extends ReadonlyTableMethods<TableDef> {
260
- /**
261
- * Insert and return the inserted row (auto-increment fields filled).
262
- *
263
- * May throw on error:
264
- * * If there are any unique or primary key columns in this table, may throw {@link errors.UniqueAlreadyExists}.
265
- * * If there are any auto-incrementing columns in this table, may throw {@link errors.AutoIncOverflow}.
266
- * */
267
- insert(row: Prettify<RowType<TableDef>>): Prettify<RowType<TableDef>>;
268
-
269
- /** Delete a row equal to `row`. Returns true if something was deleted. */
270
- delete(row: Prettify<RowType<TableDef>>): boolean;
271
-
272
- /**
273
- * Clears the table of all rows.
274
- * Returns the number of rows deleted,
275
- * i.e., the return value of `this.count()` before this call.
276
- */
277
- clear(): bigint;
278
- }
279
-
280
- /**
281
- * Defines a database table with schema and options.
282
- *
283
- * @param opts - Table configuration including name, indexes, and access control
284
- * @param row - Product type defining the table's row structure
285
- * @returns Table handle for use in schema() function
286
- *
287
- * @example
288
- * ```ts
289
- * const playerTable = table(
290
- * { name: 'player', public: true },
291
- * {
292
- * id: t.u32().primaryKey(),
293
- * name: t.string().index('btree')
294
- * }
295
- * );
296
- * ```
297
- *
298
- * ## Column Validation Error
299
- *
300
- * **If you see an error like "Expected 3 arguments, but got 2"**, this means
301
- * one of your columns has an invalid combination of attributes.
302
- *
303
- * Specifically, `default()` cannot be combined with:
304
- * - `primaryKey()`
305
- * - `unique()`
306
- * - `autoInc()`
307
- *
308
- * **Example of invalid code:**
309
- * ```ts
310
- * // ERROR: default() + primaryKey() is not allowed
311
- * const badTable = table(
312
- * { name: 'bad' },
313
- * { id: t.u64().default(0n).primaryKey() } // <- This causes "Expected 3 arguments"
314
- * );
315
- * ```
316
- *
317
- * **How to fix:** Remove either `default()` or the constraint (`primaryKey`/`unique`/`autoInc`).
318
- */
319
- export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
320
- opts: Opts,
321
- row: Row | RowBuilder<Row>,
322
- // ⚠️ INTERNAL: This parameter enforces compile-time validation of column metadata.
323
- // It is never passed at runtime. If you see "Expected 3 arguments, but got 2",
324
- // it means a column has an invalid combination (e.g., default + primaryKey).
325
- // See the JSDoc above for details on how to fix this error.
326
- ..._: HasInvalidColumn<Row> extends true
327
- ? [
328
- error: ERROR_default_cannot_be_combined_with_primaryKey_unique_or_autoInc<
329
- InvalidColumnNames<Row>
330
- >,
331
- ]
332
- : []
333
- ): TableSchema<CoerceRow<Row>, OptsIndices<Opts>> {
334
- const {
335
- name,
336
- public: isPublic = false,
337
- indexes: userIndexes = [],
338
- scheduled,
339
- event: isEvent = false,
340
- } = opts;
341
-
342
- // 1. column catalogue + helpers
343
- const colIds = new Map<keyof Row & string, ColId>();
344
- const colNameList: string[] = [];
345
-
346
- if (!(row instanceof RowBuilder)) {
347
- row = new RowBuilder(row);
348
- }
349
-
350
- row.algebraicType.value.elements.forEach((elem, i) => {
351
- colIds.set(elem.name, i);
352
- colNameList.push(elem.name);
353
- });
354
-
355
- // gather primary keys, per‑column indexes, uniques, sequences
356
- const pk: ColList = [];
357
- const indexes: (RawIndexDefV10 & { canonicalName?: string })[] = [];
358
- const constraints: RawConstraintDefV10[] = [];
359
- const sequences: RawSequenceDefV10[] = [];
360
-
361
- let scheduleAtCol: ColId | undefined;
362
- const defaultValues: RawColumnDefaultValueV10[] = [];
363
-
364
- for (const [name, builder] of Object.entries(row.row)) {
365
- const meta: ColumnMetadata<any> = builder.columnMetadata;
366
-
367
- if (meta.isPrimaryKey) {
368
- pk.push(colIds.get(name)!);
369
- }
370
-
371
- const isUnique = meta.isUnique || meta.isPrimaryKey;
372
-
373
- // implicit 1‑column indexes
374
- if (meta.indexType || isUnique) {
375
- const algo = meta.indexType ?? 'btree';
376
- const id = colIds.get(name)!;
377
- let algorithm: RawIndexAlgorithm;
378
- switch (algo) {
379
- case 'btree':
380
- algorithm = RawIndexAlgorithm.BTree([id]);
381
- break;
382
- case 'hash':
383
- algorithm = RawIndexAlgorithm.Hash([id]);
384
- break;
385
- case 'direct':
386
- algorithm = RawIndexAlgorithm.Direct(id);
387
- break;
388
- }
389
- indexes.push({
390
- sourceName: undefined, // Unnamed indexes will be assigned a globally unique name
391
- accessorName: name,
392
- algorithm,
393
- });
394
- }
395
-
396
- if (isUnique) {
397
- constraints.push({
398
- sourceName: undefined,
399
- data: { tag: 'Unique', value: { columns: [colIds.get(name)!] } },
400
- });
401
- }
402
-
403
- if (meta.isAutoIncrement) {
404
- sequences.push({
405
- sourceName: undefined,
406
- start: undefined,
407
- minValue: undefined,
408
- maxValue: undefined,
409
- column: colIds.get(name)!,
410
- increment: 1n,
411
- });
412
- }
413
-
414
- // Check for defaultValue on the property to allow for 0, false, '', and undefined as defaults
415
- if (Object.prototype.hasOwnProperty.call(meta, 'defaultValue')) {
416
- const writer = new BinaryWriter(16);
417
- builder.serialize(writer, meta.defaultValue);
418
- defaultValues.push({
419
- colId: colIds.get(name)!,
420
- value: writer.getBuffer(),
421
- });
422
- }
423
-
424
- // If this column is shaped like ScheduleAtAlgebraicType, mark it as the schedule‑at column
425
- if (scheduled) {
426
- const algebraicType = builder.typeBuilder.algebraicType;
427
- if (ScheduleAt.isScheduleAt(algebraicType)) {
428
- scheduleAtCol = colIds.get(name)!;
429
- }
430
- }
431
- }
432
-
433
- // convert explicit multi‑column indexes coming from options.indexes
434
- for (const indexOpts of userIndexes ?? []) {
435
- const accessor = indexOpts.accessor;
436
- if (typeof accessor !== 'string' || accessor.length === 0) {
437
- const tableLabel = name ?? '<unnamed>';
438
- const indexLabel = indexOpts.name ?? '<unnamed>';
439
- throw new TypeError(
440
- `Index '${indexLabel}' on table '${tableLabel}' must define a non-empty 'accessor'`
441
- );
442
- }
443
- let algorithm: RawIndexAlgorithm;
444
- switch (indexOpts.algorithm) {
445
- case 'btree':
446
- algorithm = {
447
- tag: 'BTree',
448
- value: indexOpts.columns.map(c => colIds.get(c)!),
449
- };
450
- break;
451
- case 'hash':
452
- algorithm = {
453
- tag: 'Hash',
454
- value: indexOpts.columns.map(c => colIds.get(c)!),
455
- };
456
- break;
457
- case 'direct':
458
- algorithm = { tag: 'Direct', value: colIds.get(indexOpts.column)! };
459
- break;
460
- }
461
-
462
- // Unnamed indexes are assigned a globally unique source name.
463
- // `accessor` controls the TypeScript property used to access the index.
464
- // `name` (if present) is preserved as the canonical schema name.
465
- //
466
- // IMPORTANT: we intentionally do not reject duplicate accessor names here.
467
- // This preserves existing behavior for raw table definitions. Downstream
468
- // runtime consumers decide how duplicates are resolved:
469
- // - server runtime merges duplicate accessors onto one accessor object
470
- // - client cache assignment is last-write-wins for duplicate accessors
471
- indexes.push({
472
- sourceName: undefined,
473
- accessorName: accessor,
474
- algorithm,
475
- canonicalName: indexOpts.name,
476
- });
477
- }
478
-
479
- // add explicit constraints from options.constraints
480
- for (const constraintOpts of opts.constraints ?? []) {
481
- if (constraintOpts.constraint === 'unique') {
482
- const data: RawConstraintDefV10['data'] = {
483
- tag: 'Unique',
484
- value: { columns: constraintOpts.columns.map(c => colIds.get(c)!) },
485
- };
486
- constraints.push({ sourceName: constraintOpts.name, data });
487
- continue;
488
- }
489
- }
490
-
491
- const productType = row.algebraicType.value as RowBuilder<
492
- CoerceRow<Row>
493
- >['algebraicType']['value'];
494
-
495
- const schedule =
496
- scheduled && scheduleAtCol !== undefined
497
- ? { scheduleAtCol, reducer: scheduled }
498
- : undefined;
499
-
500
- return {
501
- rowType: row as RowBuilder<CoerceRow<Row>>,
502
- tableName: name,
503
- rowSpacetimeType: productType,
504
- tableDef: (ctx, accName) => {
505
- const tableName = name ?? accName;
506
- if (row.typeName === undefined) {
507
- row.typeName = toPascalCase(tableName);
508
- }
509
-
510
- // Build index source names using accName
511
- for (const index of indexes) {
512
- const cols =
513
- index.algorithm.tag === 'Direct'
514
- ? [index.algorithm.value]
515
- : index.algorithm.value;
516
-
517
- const colS = cols.map(i => colNameList[i]).join('_');
518
- const sourceName =
519
- (index.sourceName = `${accName}_${colS}_idx_${index.algorithm.tag.toLowerCase()}`);
520
-
521
- const { canonicalName } = index;
522
- if (canonicalName !== undefined) {
523
- ctx.moduleDef.explicitNames.entries.push(
524
- ExplicitNameEntry.Index({ sourceName, canonicalName })
525
- );
526
- }
527
- }
528
-
529
- return {
530
- sourceName: accName,
531
- productTypeRef: ctx.registerTypesRecursively(row).ref,
532
- primaryKey: pk,
533
- indexes,
534
- constraints,
535
- sequences,
536
- tableType: { tag: 'User' },
537
- tableAccess: { tag: isPublic ? 'Public' : 'Private' },
538
- defaultValues,
539
- isEvent,
540
- };
541
- },
542
- // Preserve the declared index options as runtime data so `tableToSchema`
543
- // can expose them without type-smuggling.
544
- idxs: userIndexes as OptsIndices<Opts>,
545
- constraints: constraints as OptsConstraints<Opts>,
546
- schedule,
547
- };
548
- }
1
+ import type { ProcedureExport, ReducerExport, t } from '../server';
2
+ import type { errors } from '../server/errors';
3
+ import {
4
+ ExplicitNameEntry,
5
+ RawColumnDefaultValueV10,
6
+ RawConstraintDefV10,
7
+ RawIndexAlgorithm,
8
+ RawIndexDefV10,
9
+ RawSequenceDefV10,
10
+ RawTableDefV10,
11
+ } from './autogen/types';
12
+ import BinaryWriter from './binary_writer';
13
+ import type { AllUnique, ConstraintOpts } from './constraints';
14
+ import type {
15
+ ColumnIndex,
16
+ IndexColumns,
17
+ Indexes,
18
+ IndexOpts,
19
+ ReadonlyIndexes,
20
+ UntypedIndex,
21
+ } from './indexes';
22
+ import ScheduleAt from './schedule_at';
23
+ import type { TableSchema } from './table_schema';
24
+ import {
25
+ RowBuilder,
26
+ type ColumnBuilder,
27
+ type ColumnMetadata,
28
+ type InferTypeOfRow,
29
+ type RowObj,
30
+ type TypeBuilder,
31
+ } from './type_builders';
32
+ import type {
33
+ InvalidColumnMetadata,
34
+ Prettify,
35
+ ValidateColumnMetadata,
36
+ } from './type_util';
37
+ import { toPascalCase } from './util';
38
+
39
+ export type AlgebraicTypeRef = number;
40
+ type ColId = number;
41
+ type ColList = ColId[];
42
+
43
+ /**
44
+ * Check if any column in the row has invalid metadata.
45
+ */
46
+ type HasInvalidColumn<Row extends RowObj> =
47
+ // this checks if Row exactly equals RowObj - if it does, we can't
48
+ // do type-system-level checking, so just let it pass
49
+ (<G>() => G extends Row ? 1 : 2) extends <G>() => G extends RowObj ? 1 : 2
50
+ ? false
51
+ : {
52
+ [K in keyof Row]: Row[K] extends ColumnBuilder<any, any, infer M>
53
+ ? ValidateColumnMetadata<M> extends InvalidColumnMetadata<any>
54
+ ? true
55
+ : false
56
+ : false;
57
+ }[keyof Row] extends false
58
+ ? false
59
+ : true;
60
+
61
+ /**
62
+ * Extract the names of columns that have invalid metadata.
63
+ */
64
+ type InvalidColumnNames<Row extends RowObj> = {
65
+ [K in keyof Row]: Row[K] extends ColumnBuilder<any, any, infer M>
66
+ ? ValidateColumnMetadata<M> extends InvalidColumnMetadata<any>
67
+ ? K & string
68
+ : never
69
+ : never;
70
+ }[keyof Row];
71
+
72
+ /**
73
+ * A descriptive error type that surfaces the validation error.
74
+ * The type name itself contains the error message for better CLI output.
75
+ */
76
+ type ERROR_default_cannot_be_combined_with_primaryKey_unique_or_autoInc<
77
+ InvalidColumns extends string,
78
+ > = {
79
+ _invalidColumns: InvalidColumns;
80
+ _fix: 'Remove either default() or the constraint (primaryKey/unique/autoInc) from these columns';
81
+ };
82
+
83
+ /**
84
+ * A helper type to extract the row type from a TableDef
85
+ */
86
+ export type RowType<TableDef extends Pick<UntypedTableDef, 'columns'>> =
87
+ InferTypeOfRow<TableDef['columns']>;
88
+
89
+ /**
90
+ * Coerces a column which may be a TypeBuilder or ColumnBuilder into a ColumnBuilder
91
+ */
92
+ export type CoerceColumn<
93
+ Col extends TypeBuilder<any, any> | ColumnBuilder<any, any, any>,
94
+ > =
95
+ Col extends TypeBuilder<infer T, infer U>
96
+ ? ColumnBuilder<T, U, ColumnMetadata<any>>
97
+ : Col;
98
+
99
+ /**
100
+ * Coerces a RowObj where TypeBuilders are replaced with ColumnBuilders
101
+ */
102
+ export type CoerceRow<Row extends RowObj> = {
103
+ [k in keyof Row & string]: CoerceColumn<Row[k]>;
104
+ };
105
+
106
+ /**
107
+ * Helper type to coerce an array of IndexOpts
108
+ */
109
+ type CoerceArray<X extends IndexOpts<any>[]> = X;
110
+
111
+ /**
112
+ * An untyped representation of a table's schema.
113
+ */
114
+ export type UntypedTableDef = {
115
+ sourceName: string;
116
+ accessorName: string;
117
+ columns: Record<string, ColumnBuilder<any, any, ColumnMetadata<any>>>;
118
+ // This is really just a ProductType where all the elements have names.
119
+ rowType: RowBuilder<RowObj>['algebraicType']['value'];
120
+ /**
121
+ * Declarative multi-column indexes supplied by user code in `table({ indexes: [...] }, ...)`.
122
+ *
123
+ * This is intentionally the *declarative* shape (`IndexOpts`) because a lot of
124
+ * type-level behavior is derived from these entries (for example query-builder
125
+ * inference over composite indexes).
126
+ */
127
+ indexes: readonly IndexOpts<any>[];
128
+ /**
129
+ * Fully-resolved runtime indexes materialized from `RawTableDefV10`.
130
+ *
131
+ * This contains both:
132
+ * 1) field-level indexes inferred from column metadata, and
133
+ * 2) explicit table-level indexes.
134
+ *
135
+ * Runtime consumers like `TableCacheImpl` should use this field instead of
136
+ * reinterpreting `indexes` as runtime index metadata.
137
+ */
138
+ resolvedIndexes: readonly UntypedIndex<any>[];
139
+ constraints: readonly ConstraintOpts<any>[];
140
+ tableDef: RawTableDefV10;
141
+ isEvent?: boolean;
142
+ };
143
+
144
+ /**
145
+ * A type representing the indexes defined on a table.
146
+ */
147
+ export type TableIndexes<TableDef extends UntypedTableDef> = {
148
+ [K in keyof TableDef['columns'] & string as ColumnIndex<
149
+ K,
150
+ TableDef['columns'][K]['columnMetadata']
151
+ > extends never
152
+ ? never
153
+ : K]: ColumnIndex<K, TableDef['columns'][K]['columnMetadata']>;
154
+ } & {
155
+ [I in TableDef['indexes'][number] as I['accessor'] & {}]: TableIndexFromDef<
156
+ TableDef,
157
+ I
158
+ >;
159
+ };
160
+
161
+ type TableIndexFromDef<
162
+ TableDef extends UntypedTableDef,
163
+ I extends IndexOpts<keyof TableDef['columns'] & string>,
164
+ > =
165
+ NormalizeIndexColumns<TableDef, I> extends infer Cols extends ReadonlyArray<
166
+ keyof TableDef['columns'] & string
167
+ >
168
+ ? {
169
+ name: I['accessor'];
170
+ unique: AllUnique<TableDef, Cols>;
171
+ algorithm: Lowercase<I['algorithm']>;
172
+ columns: Cols;
173
+ }
174
+ : never;
175
+
176
+ type NormalizeIndexColumns<
177
+ TableDef extends UntypedTableDef,
178
+ I extends IndexOpts<keyof TableDef['columns'] & string>,
179
+ > =
180
+ IndexColumns<I> extends ReadonlyArray<keyof TableDef['columns'] & string>
181
+ ? IndexColumns<I>
182
+ : never;
183
+
184
+ /**
185
+ * Options for configuring a database table.
186
+ * - `name`: The name of the table.
187
+ * - `public`: Whether the table is publicly accessible. Defaults to `false`.
188
+ * - `indexes`: An array of index configurations for the table.
189
+ * - `constraints`: An array of constraint configurations for the table.
190
+ * - `scheduled`: The name of the reducer to be executed based on the scheduled rows in this table.
191
+ */
192
+ export type TableOpts<Row extends RowObj> = {
193
+ name?: string;
194
+ public?: boolean;
195
+ indexes?: IndexOpts<keyof Row & string>[]; // declarative multi‑column indexes
196
+ constraints?: ConstraintOpts<keyof Row & string>[];
197
+ scheduled?: () =>
198
+ | ReducerExport<any, { [k: string]: RowBuilder<RowObj> }>
199
+ | ProcedureExport<
200
+ any,
201
+ { [k: string]: RowBuilder<RowObj> },
202
+ ReturnType<typeof t.unit>
203
+ >;
204
+ event?: boolean;
205
+ };
206
+
207
+ /**
208
+ * Extracts the indices from TableOpts, defaulting to an empty array if none are provided.
209
+ */
210
+ type OptsIndices<Opts extends TableOpts<any>> = Opts extends {
211
+ indexes: infer Ixs extends NonNullable<any[]>;
212
+ }
213
+ ? Ixs
214
+ : CoerceArray<[]>;
215
+
216
+ /**
217
+ * Extracts the constraints from TableOpts, defaulting to an empty array if none are provided.
218
+ */
219
+ type OptsConstraints<Opts extends TableOpts<any>> = Opts extends {
220
+ constraints: infer Constraints extends NonNullable<any[]>;
221
+ }
222
+ ? Constraints
223
+ : CoerceArray<[]>;
224
+
225
+ /**
226
+ * Table<Row, UniqueConstraintViolation = never, AutoIncOverflow = never>
227
+ *
228
+ * - Row: row shape
229
+ * - UCV: unique-constraint violation error type (never if none)
230
+ * - AIO: auto-increment overflow error type (never if none)
231
+ */
232
+ export type Table<TableDef extends UntypedTableDef> = Prettify<
233
+ TableMethods<TableDef> & Indexes<TableDef, TableIndexes<TableDef>>
234
+ >;
235
+
236
+ export type ReadonlyTable<TableDef extends UntypedTableDef> = Prettify<
237
+ ReadonlyTableMethods<TableDef> &
238
+ ReadonlyIndexes<TableDef, TableIndexes<TableDef>>
239
+ >;
240
+
241
+ export interface ReadonlyTableMethods<TableDef extends UntypedTableDef> {
242
+ /**
243
+ * Returns the number of rows in this table.
244
+ *
245
+ * This reads datastore metadata, so it runs in constant time.
246
+ * It also takes into account modifications by the current transaction.
247
+ */
248
+ count(): bigint;
249
+
250
+ /** Iterate over all rows in the TX state. Rust Iterator<Item=Row> → TS IterableIterator<Row>. */
251
+ iter(): IteratorObject<Prettify<RowType<TableDef>>, undefined>;
252
+ [Symbol.iterator](): IteratorObject<Prettify<RowType<TableDef>>, undefined>;
253
+ }
254
+
255
+ /**
256
+ * A type representing the methods available on a table.
257
+ */
258
+ export interface TableMethods<TableDef extends UntypedTableDef>
259
+ extends ReadonlyTableMethods<TableDef> {
260
+ /**
261
+ * Insert and return the inserted row (auto-increment fields filled).
262
+ *
263
+ * May throw on error:
264
+ * * If there are any unique or primary key columns in this table, may throw {@link errors.UniqueAlreadyExists}.
265
+ * * If there are any auto-incrementing columns in this table, may throw {@link errors.AutoIncOverflow}.
266
+ * */
267
+ insert(row: Prettify<RowType<TableDef>>): Prettify<RowType<TableDef>>;
268
+
269
+ /** Delete a row equal to `row`. Returns true if something was deleted. */
270
+ delete(row: Prettify<RowType<TableDef>>): boolean;
271
+
272
+ /**
273
+ * Clears the table of all rows.
274
+ * Returns the number of rows deleted,
275
+ * i.e., the return value of `this.count()` before this call.
276
+ */
277
+ clear(): bigint;
278
+ }
279
+
280
+ /**
281
+ * Defines a database table with schema and options.
282
+ *
283
+ * @param opts - Table configuration including name, indexes, and access control
284
+ * @param row - Product type defining the table's row structure
285
+ * @returns Table handle for use in schema() function
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * const playerTable = table(
290
+ * { name: 'player', public: true },
291
+ * {
292
+ * id: t.u32().primaryKey(),
293
+ * name: t.string().index('btree')
294
+ * }
295
+ * );
296
+ * ```
297
+ *
298
+ * ## Column Validation Error
299
+ *
300
+ * **If you see an error like "Expected 3 arguments, but got 2"**, this means
301
+ * one of your columns has an invalid combination of attributes.
302
+ *
303
+ * Specifically, `default()` cannot be combined with:
304
+ * - `primaryKey()`
305
+ * - `unique()`
306
+ * - `autoInc()`
307
+ *
308
+ * **Example of invalid code:**
309
+ * ```ts
310
+ * // ERROR: default() + primaryKey() is not allowed
311
+ * const badTable = table(
312
+ * { name: 'bad' },
313
+ * { id: t.u64().default(0n).primaryKey() } // <- This causes "Expected 3 arguments"
314
+ * );
315
+ * ```
316
+ *
317
+ * **How to fix:** Remove either `default()` or the constraint (`primaryKey`/`unique`/`autoInc`).
318
+ */
319
+ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
320
+ opts: Opts,
321
+ row: Row | RowBuilder<Row>,
322
+ // ⚠️ INTERNAL: This parameter enforces compile-time validation of column metadata.
323
+ // It is never passed at runtime. If you see "Expected 3 arguments, but got 2",
324
+ // it means a column has an invalid combination (e.g., default + primaryKey).
325
+ // See the JSDoc above for details on how to fix this error.
326
+ ..._: HasInvalidColumn<Row> extends true
327
+ ? [
328
+ error: ERROR_default_cannot_be_combined_with_primaryKey_unique_or_autoInc<
329
+ InvalidColumnNames<Row>
330
+ >,
331
+ ]
332
+ : []
333
+ ): TableSchema<CoerceRow<Row>, OptsIndices<Opts>> {
334
+ const {
335
+ name,
336
+ public: isPublic = false,
337
+ indexes: userIndexes = [],
338
+ scheduled,
339
+ event: isEvent = false,
340
+ } = opts;
341
+
342
+ // 1. column catalogue + helpers
343
+ const colIds = new Map<keyof Row & string, ColId>();
344
+ const colNameList: string[] = [];
345
+
346
+ if (!(row instanceof RowBuilder)) {
347
+ row = new RowBuilder(row);
348
+ }
349
+
350
+ row.algebraicType.value.elements.forEach((elem, i) => {
351
+ colIds.set(elem.name, i);
352
+ colNameList.push(elem.name);
353
+ });
354
+
355
+ // gather primary keys, per‑column indexes, uniques, sequences
356
+ const pk: ColList = [];
357
+ const indexes: (RawIndexDefV10 & { canonicalName?: string })[] = [];
358
+ const constraints: RawConstraintDefV10[] = [];
359
+ const sequences: RawSequenceDefV10[] = [];
360
+
361
+ let scheduleAtCol: ColId | undefined;
362
+ const defaultValues: RawColumnDefaultValueV10[] = [];
363
+
364
+ for (const [name, builder] of Object.entries(row.row)) {
365
+ const meta: ColumnMetadata<any> = builder.columnMetadata;
366
+
367
+ if (meta.isPrimaryKey) {
368
+ pk.push(colIds.get(name)!);
369
+ }
370
+
371
+ const isUnique = meta.isUnique || meta.isPrimaryKey;
372
+
373
+ // implicit 1‑column indexes
374
+ if (meta.indexType || isUnique) {
375
+ const algo = meta.indexType ?? 'btree';
376
+ const id = colIds.get(name)!;
377
+ let algorithm: RawIndexAlgorithm;
378
+ switch (algo) {
379
+ case 'btree':
380
+ algorithm = RawIndexAlgorithm.BTree([id]);
381
+ break;
382
+ case 'hash':
383
+ algorithm = RawIndexAlgorithm.Hash([id]);
384
+ break;
385
+ case 'direct':
386
+ algorithm = RawIndexAlgorithm.Direct(id);
387
+ break;
388
+ }
389
+ indexes.push({
390
+ sourceName: undefined, // Unnamed indexes will be assigned a globally unique name
391
+ accessorName: name,
392
+ algorithm,
393
+ });
394
+ }
395
+
396
+ if (isUnique) {
397
+ constraints.push({
398
+ sourceName: undefined,
399
+ data: { tag: 'Unique', value: { columns: [colIds.get(name)!] } },
400
+ });
401
+ }
402
+
403
+ if (meta.isAutoIncrement) {
404
+ sequences.push({
405
+ sourceName: undefined,
406
+ start: undefined,
407
+ minValue: undefined,
408
+ maxValue: undefined,
409
+ column: colIds.get(name)!,
410
+ increment: 1n,
411
+ });
412
+ }
413
+
414
+ // Check for defaultValue on the property to allow for 0, false, '', and undefined as defaults
415
+ if (Object.prototype.hasOwnProperty.call(meta, 'defaultValue')) {
416
+ const writer = new BinaryWriter(16);
417
+ builder.serialize(writer, meta.defaultValue);
418
+ defaultValues.push({
419
+ colId: colIds.get(name)!,
420
+ value: writer.getBuffer(),
421
+ });
422
+ }
423
+
424
+ // If this column is shaped like ScheduleAtAlgebraicType, mark it as the schedule‑at column
425
+ if (scheduled) {
426
+ const algebraicType = builder.typeBuilder.algebraicType;
427
+ if (ScheduleAt.isScheduleAt(algebraicType)) {
428
+ scheduleAtCol = colIds.get(name)!;
429
+ }
430
+ }
431
+ }
432
+
433
+ // convert explicit multi‑column indexes coming from options.indexes
434
+ for (const indexOpts of userIndexes ?? []) {
435
+ const accessor = indexOpts.accessor;
436
+ if (typeof accessor !== 'string' || accessor.length === 0) {
437
+ const tableLabel = name ?? '<unnamed>';
438
+ const indexLabel = indexOpts.name ?? '<unnamed>';
439
+ throw new TypeError(
440
+ `Index '${indexLabel}' on table '${tableLabel}' must define a non-empty 'accessor'`
441
+ );
442
+ }
443
+ let algorithm: RawIndexAlgorithm;
444
+ switch (indexOpts.algorithm) {
445
+ case 'btree':
446
+ algorithm = {
447
+ tag: 'BTree',
448
+ value: indexOpts.columns.map(c => colIds.get(c)!),
449
+ };
450
+ break;
451
+ case 'hash':
452
+ algorithm = {
453
+ tag: 'Hash',
454
+ value: indexOpts.columns.map(c => colIds.get(c)!),
455
+ };
456
+ break;
457
+ case 'direct':
458
+ algorithm = { tag: 'Direct', value: colIds.get(indexOpts.column)! };
459
+ break;
460
+ }
461
+
462
+ // Unnamed indexes are assigned a globally unique source name.
463
+ // `accessor` controls the TypeScript property used to access the index.
464
+ // `name` (if present) is preserved as the canonical schema name.
465
+ //
466
+ // IMPORTANT: we intentionally do not reject duplicate accessor names here.
467
+ // This preserves existing behavior for raw table definitions. Downstream
468
+ // runtime consumers decide how duplicates are resolved:
469
+ // - server runtime merges duplicate accessors onto one accessor object
470
+ // - client cache assignment is last-write-wins for duplicate accessors
471
+ indexes.push({
472
+ sourceName: undefined,
473
+ accessorName: accessor,
474
+ algorithm,
475
+ canonicalName: indexOpts.name,
476
+ });
477
+ }
478
+
479
+ // add explicit constraints from options.constraints
480
+ for (const constraintOpts of opts.constraints ?? []) {
481
+ if (constraintOpts.constraint === 'unique') {
482
+ const data: RawConstraintDefV10['data'] = {
483
+ tag: 'Unique',
484
+ value: { columns: constraintOpts.columns.map(c => colIds.get(c)!) },
485
+ };
486
+ constraints.push({ sourceName: constraintOpts.name, data });
487
+ continue;
488
+ }
489
+ }
490
+
491
+ const productType = row.algebraicType.value as RowBuilder<
492
+ CoerceRow<Row>
493
+ >['algebraicType']['value'];
494
+
495
+ const schedule =
496
+ scheduled && scheduleAtCol !== undefined
497
+ ? { scheduleAtCol, reducer: scheduled }
498
+ : undefined;
499
+
500
+ return {
501
+ rowType: row as RowBuilder<CoerceRow<Row>>,
502
+ tableName: name,
503
+ rowSpacetimeType: productType,
504
+ tableDef: (ctx, accName) => {
505
+ const tableName = name ?? accName;
506
+ if (row.typeName === undefined) {
507
+ row.typeName = toPascalCase(tableName);
508
+ }
509
+
510
+ // Build index source names using accName
511
+ for (const index of indexes) {
512
+ const cols =
513
+ index.algorithm.tag === 'Direct'
514
+ ? [index.algorithm.value]
515
+ : index.algorithm.value;
516
+
517
+ const colS = cols.map(i => colNameList[i]).join('_');
518
+ const sourceName =
519
+ (index.sourceName = `${accName}_${colS}_idx_${index.algorithm.tag.toLowerCase()}`);
520
+
521
+ const { canonicalName } = index;
522
+ if (canonicalName !== undefined) {
523
+ ctx.moduleDef.explicitNames.entries.push(
524
+ ExplicitNameEntry.Index({ sourceName, canonicalName })
525
+ );
526
+ }
527
+ }
528
+
529
+ return {
530
+ sourceName: accName,
531
+ productTypeRef: ctx.registerTypesRecursively(row).ref,
532
+ primaryKey: pk,
533
+ indexes,
534
+ constraints,
535
+ sequences,
536
+ tableType: { tag: 'User' },
537
+ tableAccess: { tag: isPublic ? 'Public' : 'Private' },
538
+ defaultValues,
539
+ isEvent,
540
+ };
541
+ },
542
+ // Preserve the declared index options as runtime data so `tableToSchema`
543
+ // can expose them without type-smuggling.
544
+ idxs: userIndexes as OptsIndices<Opts>,
545
+ constraints: constraints as OptsConstraints<Opts>,
546
+ schedule,
547
+ };
548
+ }