spacetimedb 2.4.1 → 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 (194) hide show
  1. package/LICENSE.txt +759 -759
  2. package/README.md +211 -120
  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 +1933 -0
  9. package/dist/browser/solid/index.mjs.map +1 -0
  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/SpacetimeDBProvider.d.ts +7 -0
  44. package/dist/solid/SpacetimeDBProvider.d.ts.map +1 -0
  45. package/dist/solid/connection_state.d.ts +6 -0
  46. package/dist/solid/connection_state.d.ts.map +1 -0
  47. package/dist/solid/index.cjs +1939 -0
  48. package/dist/solid/index.cjs.map +1 -0
  49. package/dist/solid/index.d.ts +6 -0
  50. package/dist/solid/index.d.ts.map +1 -0
  51. package/dist/solid/index.mjs +1933 -0
  52. package/dist/solid/index.mjs.map +1 -0
  53. package/dist/solid/useProcedure.d.ts +4 -0
  54. package/dist/solid/useProcedure.d.ts.map +1 -0
  55. package/dist/solid/useReducer.d.ts +4 -0
  56. package/dist/solid/useReducer.d.ts.map +1 -0
  57. package/dist/solid/useSpacetimeDB.d.ts +4 -0
  58. package/dist/solid/useSpacetimeDB.d.ts.map +1 -0
  59. package/dist/solid/useTable.d.ts +32 -0
  60. package/dist/solid/useTable.d.ts.map +1 -0
  61. package/dist/svelte/index.cjs.map +1 -1
  62. package/dist/svelte/index.mjs.map +1 -1
  63. package/dist/tanstack/index.cjs +120 -50
  64. package/dist/tanstack/index.cjs.map +1 -1
  65. package/dist/tanstack/index.mjs +120 -50
  66. package/dist/tanstack/index.mjs.map +1 -1
  67. package/dist/vue/index.cjs.map +1 -1
  68. package/dist/vue/index.mjs.map +1 -1
  69. package/package.json +13 -3
  70. package/src/angular/connection_state.ts +19 -19
  71. package/src/angular/index.ts +3 -3
  72. package/src/angular/injectors/index.ts +4 -4
  73. package/src/angular/injectors/inject-reducer.ts +62 -62
  74. package/src/angular/injectors/inject-spacetimedb-connected.ts +13 -13
  75. package/src/angular/injectors/inject-spacetimedb.ts +10 -10
  76. package/src/angular/injectors/inject-table.ts +234 -234
  77. package/src/angular/providers/index.ts +1 -1
  78. package/src/angular/providers/provide-spacetimedb.ts +96 -96
  79. package/src/index.ts +16 -16
  80. package/src/lib/algebraic_type.ts +819 -819
  81. package/src/lib/algebraic_type_variants.ts +26 -26
  82. package/src/lib/algebraic_value.ts +10 -10
  83. package/src/lib/autogen/types.ts +746 -746
  84. package/src/lib/binary_reader.ts +188 -188
  85. package/src/lib/binary_writer.ts +213 -213
  86. package/src/lib/connection_id.ts +102 -102
  87. package/src/lib/constraints.ts +48 -48
  88. package/src/lib/errors.ts +26 -26
  89. package/src/lib/filter.ts +195 -195
  90. package/src/lib/identity.ts +83 -83
  91. package/src/lib/indexes.ts +251 -251
  92. package/src/lib/option.ts +34 -34
  93. package/src/lib/query.ts +1019 -1019
  94. package/src/lib/reducer_schema.ts +38 -38
  95. package/src/lib/reducers.ts +116 -116
  96. package/src/lib/result.ts +36 -36
  97. package/src/lib/schedule_at.ts +86 -86
  98. package/src/lib/schema.ts +420 -420
  99. package/src/lib/table.ts +548 -548
  100. package/src/lib/table_schema.ts +64 -64
  101. package/src/lib/time_duration.ts +77 -77
  102. package/src/lib/timestamp.ts +148 -148
  103. package/src/lib/type_builders.test-d.ts +128 -128
  104. package/src/lib/type_builders.ts +4014 -4014
  105. package/src/lib/type_util.ts +124 -124
  106. package/src/lib/util.ts +196 -196
  107. package/src/lib/uuid.ts +337 -337
  108. package/src/react/SpacetimeDBProvider.ts +84 -84
  109. package/src/react/connection_state.ts +6 -6
  110. package/src/react/index.ts +5 -5
  111. package/src/react/useProcedure.ts +60 -60
  112. package/src/react/useReducer.ts +53 -53
  113. package/src/react/useSpacetimeDB.ts +18 -18
  114. package/src/react/useTable.ts +256 -251
  115. package/src/sdk/client_api/index.ts +114 -114
  116. package/src/sdk/client_api/types/procedures.ts +8 -8
  117. package/src/sdk/client_api/types/reducers.ts +8 -8
  118. package/src/sdk/client_api/types.ts +288 -288
  119. package/src/sdk/client_cache.ts +129 -129
  120. package/src/sdk/client_table.ts +179 -179
  121. package/src/sdk/connection_manager.ts +352 -237
  122. package/src/sdk/db_connection_builder.ts +290 -290
  123. package/src/sdk/db_connection_impl.ts +1356 -1347
  124. package/src/sdk/db_context.ts +28 -28
  125. package/src/sdk/db_view.ts +12 -12
  126. package/src/sdk/decompress.ts +51 -51
  127. package/src/sdk/event.ts +18 -18
  128. package/src/sdk/event_context.ts +51 -51
  129. package/src/sdk/event_emitter.ts +32 -32
  130. package/src/sdk/index.ts +14 -14
  131. package/src/sdk/internal.ts +2 -2
  132. package/src/sdk/json_api.ts +46 -46
  133. package/src/sdk/logger.ts +134 -134
  134. package/src/sdk/message_types.ts +46 -46
  135. package/src/sdk/procedures.ts +83 -83
  136. package/src/sdk/reducer_event.ts +20 -20
  137. package/src/sdk/reducer_handle.ts +12 -12
  138. package/src/sdk/reducers.ts +159 -159
  139. package/src/sdk/schema.ts +45 -45
  140. package/src/sdk/spacetime_module.ts +28 -28
  141. package/src/sdk/subscription_builder_impl.ts +275 -275
  142. package/src/sdk/table_cache.ts +581 -581
  143. package/src/sdk/type_utils.ts +19 -19
  144. package/src/sdk/version.ts +133 -133
  145. package/src/sdk/websocket_decompress_adapter.ts +63 -63
  146. package/src/sdk/websocket_protocols.ts +25 -25
  147. package/src/sdk/websocket_test_adapter.ts +107 -100
  148. package/src/sdk/websocket_v3_frames.ts +126 -126
  149. package/src/sdk/ws.ts +105 -105
  150. package/src/server/console.ts +81 -81
  151. package/src/server/db_view.ts +21 -21
  152. package/src/server/errors.ts +138 -138
  153. package/src/server/http.test-d.ts +80 -80
  154. package/src/server/http.ts +14 -14
  155. package/src/server/http_handlers.ts +413 -413
  156. package/src/server/http_internal.ts +79 -79
  157. package/src/server/http_shared.ts +186 -186
  158. package/src/server/index.ts +37 -37
  159. package/src/server/polyfills.ts +4 -4
  160. package/src/server/procedures.ts +239 -239
  161. package/src/server/query.ts +1 -1
  162. package/src/server/range.ts +53 -53
  163. package/src/server/reducers.ts +113 -113
  164. package/src/server/rng.ts +113 -113
  165. package/src/server/runtime.ts +1102 -1102
  166. package/src/server/schema.test-d.ts +99 -99
  167. package/src/server/schema.ts +663 -663
  168. package/src/server/sys.d.ts +125 -125
  169. package/src/server/view.test-d.ts +194 -194
  170. package/src/server/views.ts +340 -340
  171. package/src/solid/SpacetimeDBProvider.ts +97 -0
  172. package/src/solid/connection_state.ts +6 -0
  173. package/src/solid/index.ts +5 -0
  174. package/src/solid/useProcedure.ts +57 -0
  175. package/src/solid/useReducer.ts +50 -0
  176. package/src/solid/useSpacetimeDB.ts +18 -0
  177. package/src/solid/useTable.ts +203 -0
  178. package/src/svelte/SpacetimeDBProvider.ts +101 -101
  179. package/src/svelte/connection_state.ts +16 -16
  180. package/src/svelte/index.ts +4 -4
  181. package/src/svelte/useReducer.ts +61 -61
  182. package/src/svelte/useSpacetimeDB.ts +22 -22
  183. package/src/svelte/useTable.ts +218 -218
  184. package/src/tanstack/SpacetimeDBQueryClient.ts +330 -330
  185. package/src/tanstack/hooks.ts +83 -83
  186. package/src/tanstack/index.ts +16 -16
  187. package/src/util-stub.ts +1 -1
  188. package/src/vue/SpacetimeDBProvider.ts +157 -157
  189. package/src/vue/connection_state.ts +19 -19
  190. package/src/vue/index.ts +5 -5
  191. package/src/vue/useProcedure.ts +62 -62
  192. package/src/vue/useReducer.ts +55 -55
  193. package/src/vue/useSpacetimeDB.ts +18 -18
  194. package/src/vue/useTable.ts +229 -229
@@ -1,1102 +1,1102 @@
1
- import * as _syscalls2_0 from 'spacetime:sys@2.0';
2
- import * as _syscalls2_1 from 'spacetime:sys@2.1';
3
-
4
- import type { ModuleHooks, u128, u16, u256, u32 } from 'spacetime:sys@2.0';
5
- import {
6
- AlgebraicType,
7
- ProductType,
8
- type Deserializer,
9
- } from '../lib/algebraic_type';
10
- import {
11
- RawModuleDef,
12
- ViewResultHeader,
13
- type RawTableDefV10,
14
- type Typespace,
15
- } from '../lib/autogen/types';
16
- import { ConnectionId } from '../lib/connection_id';
17
- import { Identity } from '../lib/identity';
18
- import { Timestamp } from '../lib/timestamp';
19
- import { Uuid } from '../lib/uuid';
20
- import BinaryReader from '../lib/binary_reader';
21
- import BinaryWriter, { ResizableBuffer } from '../lib/binary_writer';
22
- import {
23
- type Index,
24
- type IndexVal,
25
- type PointIndex,
26
- type RangedIndex,
27
- type UniqueIndex,
28
- } from '../lib/indexes';
29
- import { callProcedure } from './procedures';
30
- import {
31
- type HandlerContext,
32
- Request,
33
- SyncResponse,
34
- makeRequest,
35
- } from './http_handlers';
36
- import { httpClient } from './http_internal';
37
- import {
38
- deserializeHeaders,
39
- deserializeMethod,
40
- serializeHeaders,
41
- } from './http_shared';
42
- import {
43
- type AuthCtx,
44
- type JsonObject,
45
- type JwtClaims,
46
- type ReducerCtx as IReducerCtx,
47
- } from '../lib/reducers';
48
- import { type UntypedSchemaDef } from '../lib/schema';
49
- import { type RowType, type Table, type TableMethods } from '../lib/table';
50
- import { bsatnBaseSize, hasOwn } from '../lib/util';
51
- import { type AnonymousViewCtx, type ViewCtx } from './views';
52
- import { isRowTypedQuery, makeQueryBuilder, toSql } from './query';
53
- import type { DbView } from './db_view';
54
- import { getErrorConstructor, SenderError } from './errors';
55
- import { Range, type Bound } from './range';
56
- import { makeRandom, type Random } from './rng';
57
- import type { SchemaInner } from './schema';
58
- import { HttpRequest, HttpResponse } from '../lib/autogen/types';
59
-
60
- const { freeze } = Object;
61
-
62
- export const sys = { ..._syscalls2_0, ..._syscalls2_1 };
63
-
64
- function requestFromWire(request: HttpRequest, body: Uint8Array): Request {
65
- return Request[makeRequest](body, {
66
- headers: deserializeHeaders(request.headers),
67
- method: deserializeMethod(request.method),
68
- uri: request.uri,
69
- version: request.version,
70
- });
71
- }
72
-
73
- function responseIntoWire(response: SyncResponse): [HttpResponse, Uint8Array] {
74
- return [
75
- {
76
- headers: serializeHeaders(response.headers),
77
- version: response.version,
78
- code: response.status,
79
- },
80
- response.bytes(),
81
- ];
82
- }
83
-
84
- export function parseJsonObject(json: string): JsonObject {
85
- let value: unknown;
86
-
87
- try {
88
- value = JSON.parse(json);
89
- } catch {
90
- throw new Error('Invalid JSON: failed to parse string');
91
- }
92
-
93
- if (value === null || typeof value !== 'object' || Array.isArray(value)) {
94
- throw new Error('Expected a JSON object at the top level');
95
- }
96
-
97
- // The runtime check above guarantees this cast is safe
98
- return value as JsonObject;
99
- }
100
-
101
- class JwtClaimsImpl implements JwtClaims {
102
- readonly fullPayload: JsonObject;
103
- private readonly _identity: Identity;
104
- /**
105
- * Creates a new JwtClaims instance.
106
- * @param rawPayload The JWT payload as a raw JSON string.
107
- * @param identity The identity for this JWT. We are only taking this because we don't have a blake3 implementation (which we need to compute it).
108
- */
109
- constructor(
110
- public readonly rawPayload: string,
111
- identity: Identity
112
- ) {
113
- this.fullPayload = parseJsonObject(rawPayload);
114
- this._identity = identity;
115
- }
116
- readonly [claim: string]: unknown;
117
- get identity(): Identity {
118
- return this._identity;
119
- }
120
- get subject() {
121
- return this.fullPayload['sub'] as string;
122
- }
123
- get issuer() {
124
- return this.fullPayload['iss'] as string;
125
- }
126
- get audience() {
127
- const aud = this.fullPayload['aud'];
128
- if (aud == null) {
129
- return [];
130
- }
131
- return typeof aud === 'string' ? [aud] : (aud as string[]);
132
- }
133
- }
134
-
135
- class AuthCtxImpl implements AuthCtx {
136
- public readonly isInternal: boolean;
137
-
138
- // Source of the JWT payload string, if there is one.
139
- private readonly _jwtSource: () => string | null;
140
- // Whether we have initialized the JWT claims.
141
- private _initializedJWT: boolean = false;
142
- private _jwtClaims?: JwtClaims | null;
143
- private _senderIdentity: Identity;
144
-
145
- private constructor(opts: {
146
- isInternal: boolean;
147
- jwtSource: () => string | null;
148
- senderIdentity: Identity;
149
- }) {
150
- this.isInternal = opts.isInternal;
151
- this._jwtSource = opts.jwtSource;
152
- this._senderIdentity = opts.senderIdentity;
153
- }
154
-
155
- private _initializeJWT() {
156
- if (this._initializedJWT) return;
157
- this._initializedJWT = true;
158
-
159
- const token = this._jwtSource();
160
- if (!token) {
161
- this._jwtClaims = null;
162
- } else {
163
- this._jwtClaims = new JwtClaimsImpl(token, this._senderIdentity);
164
- }
165
- // At this point we can safely freeze the object.
166
- Object.freeze(this);
167
- }
168
-
169
- /** Lazily compute whether a JWT exists and is parseable. */
170
- get hasJWT(): boolean {
171
- this._initializeJWT();
172
- return this._jwtClaims !== null;
173
- }
174
-
175
- /** Lazily parse the JwtClaims only when accessed. */
176
- get jwt(): JwtClaims | null {
177
- this._initializeJWT();
178
- return this._jwtClaims!;
179
- }
180
-
181
- /** Create a context representing internal (non-user) requests. */
182
- static internal(): AuthCtx {
183
- return new AuthCtxImpl({
184
- isInternal: true,
185
- jwtSource: () => null,
186
- senderIdentity: Identity.zero(),
187
- });
188
- }
189
-
190
- /** If there is a connection id, look up the JWT payload from the system tables. */
191
- static fromSystemTables(
192
- connectionId: ConnectionId | null,
193
- sender: Identity
194
- ): AuthCtx {
195
- if (connectionId === null) {
196
- return new AuthCtxImpl({
197
- isInternal: false,
198
- jwtSource: () => null,
199
- senderIdentity: sender,
200
- });
201
- }
202
- return new AuthCtxImpl({
203
- isInternal: false,
204
- jwtSource: () => {
205
- const payloadBuf = sys.get_jwt_payload(connectionId.__connection_id__);
206
- if (payloadBuf.length === 0) return null;
207
- const payloadStr = new TextDecoder().decode(payloadBuf);
208
- return payloadStr;
209
- },
210
- senderIdentity: sender,
211
- });
212
- }
213
- }
214
-
215
- // Using a class expression rather than declaration keeps the class out of the
216
- // type namespace, so that `ReducerCtx` still refers to the interface.
217
- export const ReducerCtxImpl = class ReducerCtx<
218
- SchemaDef extends UntypedSchemaDef,
219
- > implements IReducerCtx<SchemaDef>
220
- {
221
- #identity: Identity | undefined;
222
- #senderAuth: AuthCtx | undefined;
223
- #uuidCounter: { value: number } | undefined;
224
- #random: Random | undefined;
225
- sender: Identity;
226
- timestamp: Timestamp;
227
- connectionId: ConnectionId | null;
228
- db: DbView<SchemaDef>;
229
-
230
- constructor(
231
- sender: Identity,
232
- timestamp: Timestamp,
233
- connectionId: ConnectionId | null,
234
- dbView: DbView<any>
235
- ) {
236
- Object.seal(this);
237
- this.sender = sender;
238
- this.timestamp = timestamp;
239
- this.connectionId = connectionId;
240
- this.db = dbView;
241
- }
242
-
243
- /** Reset the `ReducerCtx` to be used for a new transaction */
244
- static reset(
245
- me: InstanceType<typeof this>,
246
- sender: Identity,
247
- timestamp: Timestamp,
248
- connectionId: ConnectionId | null
249
- ) {
250
- me.sender = sender;
251
- me.timestamp = timestamp;
252
- me.connectionId = connectionId;
253
- me.#uuidCounter = undefined;
254
- me.#senderAuth = undefined;
255
- }
256
-
257
- get databaseIdentity() {
258
- return (this.#identity ??= new Identity(sys.identity()));
259
- }
260
-
261
- get identity() {
262
- return this.databaseIdentity;
263
- }
264
-
265
- get senderAuth() {
266
- return (this.#senderAuth ??= AuthCtxImpl.fromSystemTables(
267
- this.connectionId,
268
- this.sender
269
- ));
270
- }
271
-
272
- get random() {
273
- return (this.#random ??= makeRandom(this.timestamp));
274
- }
275
-
276
- /**
277
- * Create a new random {@link Uuid} `v4` using this `ReducerCtx`'s RNG.
278
- */
279
- newUuidV4(): Uuid {
280
- const bytes = this.random.fill(new Uint8Array(16));
281
- return Uuid.fromRandomBytesV4(bytes);
282
- }
283
-
284
- /**
285
- * Create a new sortable {@link Uuid} `v7` using this `ReducerCtx`'s RNG, counter,
286
- * and timestamp.
287
- */
288
- newUuidV7(): Uuid {
289
- const bytes = this.random.fill(new Uint8Array(4));
290
- const counter = (this.#uuidCounter ??= { value: 0 });
291
- return Uuid.fromCounterV7(counter, this.timestamp, bytes);
292
- }
293
- };
294
-
295
- /**
296
- * Call into a user function `fn` - the backtrace from an exception thrown in
297
- * `fn` or one of its descendants in the callgraph will be stripped by host
298
- * code in `crates/core/src/host/v8/error.rs` such that `fn` will be shown to
299
- * be the root of the call stack.
300
- */
301
- export const callUserFunction = function __spacetimedb_end_short_backtrace<
302
- Args extends any[],
303
- R,
304
- >(fn: (...args: Args) => R, ...args: Args): R {
305
- return fn(...args);
306
- };
307
-
308
- export function runWithTx<T, Ctx>(
309
- makeCtx: (timestamp: Timestamp) => Ctx,
310
- body: (ctx: Ctx) => T
311
- ): T {
312
- const run = () => {
313
- const timestamp = sys.procedure_start_mut_tx();
314
-
315
- try {
316
- return body(makeCtx(new Timestamp(timestamp)));
317
- } catch (e) {
318
- sys.procedure_abort_mut_tx();
319
- throw e;
320
- }
321
- };
322
-
323
- let res = run();
324
- try {
325
- sys.procedure_commit_mut_tx();
326
- return res;
327
- } catch {
328
- // ignore the commit error
329
- }
330
- console.warn('committing anonymous transaction failed');
331
- res = run();
332
- try {
333
- sys.procedure_commit_mut_tx();
334
- return res;
335
- } catch (e) {
336
- throw new Error('transaction retry failed again', { cause: e });
337
- }
338
- }
339
-
340
- export const makeHooks = (schema: SchemaInner): ModuleHooks =>
341
- new ModuleHooksImpl(schema);
342
-
343
- class ModuleHooksImpl implements ModuleHooks {
344
- #schema: SchemaInner;
345
- #dbView_: DbView<any> | undefined;
346
- #reducerArgsDeserializers;
347
- /** Cache the `ReducerCtx` object to avoid allocating anew for ever reducer call. */
348
- #reducerCtx_: InstanceType<typeof ReducerCtxImpl> | undefined;
349
-
350
- constructor(schema: SchemaInner) {
351
- this.#schema = schema;
352
- this.#reducerArgsDeserializers = schema.moduleDef.reducers.map(
353
- ({ params }) => ProductType.makeDeserializer(params, schema.typespace)
354
- );
355
- }
356
-
357
- get #dbView() {
358
- return (this.#dbView_ ??= freeze(
359
- Object.fromEntries(
360
- Object.values(this.#schema.schemaType.tables).map(table => [
361
- table.accessorName,
362
- makeTableView(this.#schema.typespace, table.tableDef),
363
- ])
364
- )
365
- ));
366
- }
367
-
368
- get #reducerCtx() {
369
- return (this.#reducerCtx_ ??= new ReducerCtxImpl(
370
- Identity.zero(),
371
- Timestamp.UNIX_EPOCH,
372
- null,
373
- this.#dbView
374
- ));
375
- }
376
-
377
- __describe_module__() {
378
- const writer = new BinaryWriter(128);
379
- RawModuleDef.serialize(
380
- writer,
381
- RawModuleDef.V10(this.#schema.rawModuleDefV10())
382
- );
383
- return writer.getBuffer();
384
- }
385
-
386
- __get_error_constructor__(code: number): new (msg: string) => Error {
387
- return getErrorConstructor(code);
388
- }
389
-
390
- get __sender_error_class__() {
391
- return SenderError;
392
- }
393
-
394
- __call_reducer__(
395
- reducerId: u32,
396
- sender: u256,
397
- connId: u128,
398
- timestamp: bigint,
399
- argsBuf: DataView
400
- ): void {
401
- const moduleCtx = this.#schema;
402
- const deserializeArgs = this.#reducerArgsDeserializers[reducerId];
403
- BINARY_READER.reset(argsBuf);
404
- const args = deserializeArgs(BINARY_READER);
405
- const senderIdentity = new Identity(sender);
406
- const ctx = this.#reducerCtx;
407
- ReducerCtxImpl.reset(
408
- ctx,
409
- senderIdentity,
410
- new Timestamp(timestamp),
411
- ConnectionId.nullIfZero(new ConnectionId(connId))
412
- );
413
- callUserFunction(moduleCtx.reducers[reducerId], ctx, args);
414
- }
415
-
416
- __call_view__(
417
- id: u32,
418
- sender: u256,
419
- argsBuf: Uint8Array
420
- ): { data: Uint8Array } {
421
- const moduleCtx = this.#schema;
422
- const { fn, deserializeParams, serializeReturn, returnTypeBaseSize } =
423
- moduleCtx.views[id];
424
- const ctx: ViewCtx<any> = freeze({
425
- sender: new Identity(sender),
426
- // this is the non-readonly DbView, but the typing for the user will be
427
- // the readonly one, and if they do call mutating functions it will fail
428
- // at runtime
429
- db: this.#dbView,
430
- from: makeQueryBuilder(moduleCtx.schemaType),
431
- });
432
- const args = deserializeParams(new BinaryReader(argsBuf));
433
- const ret = callUserFunction(fn, ctx, args);
434
- const retBuf = new BinaryWriter(returnTypeBaseSize);
435
- if (isRowTypedQuery(ret)) {
436
- const query = toSql(ret);
437
- ViewResultHeader.serialize(retBuf, ViewResultHeader.RawSql(query));
438
- } else {
439
- ViewResultHeader.serialize(retBuf, ViewResultHeader.RowData);
440
- serializeReturn(retBuf, ret);
441
- }
442
- return { data: retBuf.getBuffer() };
443
- }
444
-
445
- __call_view_anon__(id: u32, argsBuf: Uint8Array): { data: Uint8Array } {
446
- const moduleCtx = this.#schema;
447
- const { fn, deserializeParams, serializeReturn, returnTypeBaseSize } =
448
- moduleCtx.anonViews[id];
449
- const ctx: AnonymousViewCtx<any> = freeze({
450
- // this is the non-readonly DbView, but the typing for the user will be
451
- // the readonly one, and if they do call mutating functions it will fail
452
- // at runtime
453
- db: this.#dbView,
454
- from: makeQueryBuilder(moduleCtx.schemaType),
455
- });
456
- const args = deserializeParams(new BinaryReader(argsBuf));
457
- const ret = callUserFunction(fn, ctx, args);
458
- const retBuf = new BinaryWriter(returnTypeBaseSize);
459
- if (isRowTypedQuery(ret)) {
460
- const query = toSql(ret);
461
- ViewResultHeader.serialize(retBuf, ViewResultHeader.RawSql(query));
462
- } else {
463
- ViewResultHeader.serialize(retBuf, ViewResultHeader.RowData);
464
- serializeReturn(retBuf, ret);
465
- }
466
- return { data: retBuf.getBuffer() };
467
- }
468
-
469
- __call_procedure__(
470
- id: u32,
471
- sender: u256,
472
- connection_id: u128,
473
- timestamp: bigint,
474
- args: Uint8Array
475
- ): Uint8Array {
476
- return callProcedure(
477
- this.#schema,
478
- id,
479
- new Identity(sender),
480
- ConnectionId.nullIfZero(new ConnectionId(connection_id)),
481
- new Timestamp(timestamp),
482
- args,
483
- () => this.#dbView
484
- );
485
- }
486
-
487
- __call_http_handler__(
488
- id: u32,
489
- timestamp: bigint,
490
- request: Uint8Array,
491
- body: Uint8Array
492
- ): [response: Uint8Array, body: Uint8Array] {
493
- const moduleCtx = this.#schema;
494
- const handler = moduleCtx.httpHandlers[id];
495
- const ctx = new HandlerContextImpl(
496
- new Timestamp(timestamp),
497
- () => this.#dbView
498
- );
499
- const requestMetadata = HttpRequest.deserialize(new BinaryReader(request));
500
- const response = callUserFunction(
501
- handler,
502
- ctx,
503
- requestFromWire(requestMetadata, body)
504
- );
505
- const [responseMetadata, responseBody] = responseIntoWire(response);
506
- const responseBuf = new BinaryWriter(
507
- bsatnBaseSize(moduleCtx.typespace, HttpResponse.algebraicType)
508
- );
509
- HttpResponse.serialize(responseBuf, responseMetadata);
510
- return [responseBuf.getBuffer(), responseBody];
511
- }
512
- }
513
-
514
- const BINARY_WRITER = new BinaryWriter(0);
515
- const BINARY_READER = new BinaryReader(new Uint8Array());
516
-
517
- class HandlerContextImpl<S extends UntypedSchemaDef = UntypedSchemaDef>
518
- implements HandlerContext<S>
519
- {
520
- #identity: Identity | undefined;
521
- #uuidCounter: { value: number } | undefined;
522
- #random: Random | undefined;
523
- #dbView: () => DbView<any>;
524
-
525
- readonly http = httpClient;
526
-
527
- constructor(
528
- readonly timestamp: Timestamp,
529
- dbView: () => DbView<any>
530
- ) {
531
- this.#dbView = dbView;
532
- }
533
-
534
- get identity() {
535
- return (this.#identity ??= new Identity(sys.identity()));
536
- }
537
-
538
- get random() {
539
- return (this.#random ??= makeRandom(this.timestamp));
540
- }
541
-
542
- withTx<T>(body: (ctx: any) => T): T {
543
- return runWithTx(
544
- timestamp =>
545
- new ReducerCtxImpl(Identity.zero(), timestamp, null, this.#dbView()),
546
- body
547
- );
548
- }
549
-
550
- newUuidV4(): Uuid {
551
- const bytes = this.random.fill(new Uint8Array(16));
552
- return Uuid.fromRandomBytesV4(bytes);
553
- }
554
-
555
- newUuidV7(): Uuid {
556
- const bytes = this.random.fill(new Uint8Array(4));
557
- const counter = (this.#uuidCounter ??= { value: 0 });
558
- return Uuid.fromCounterV7(counter, this.timestamp, bytes);
559
- }
560
- }
561
-
562
- function makeTableView(
563
- typespace: Typespace,
564
- table: RawTableDefV10
565
- ): Table<any> {
566
- const table_id = sys.table_id_from_name(table.sourceName);
567
- const rowType = typespace.types[table.productTypeRef];
568
- if (rowType.tag !== 'Product') {
569
- throw 'impossible';
570
- }
571
-
572
- const serializeRow = AlgebraicType.makeSerializer(rowType, typespace);
573
- const deserializeRow = AlgebraicType.makeDeserializer(rowType, typespace);
574
-
575
- const sequences = table.sequences.map(seq => {
576
- const col = rowType.value.elements[seq.column];
577
- const colType = col.algebraicType;
578
-
579
- // Determine the sentinel value which users will pass to as a placeholder
580
- // to cause the sequence to advance.
581
- // For small integer SATS types which fit in V8 `number`s, this is `0: number`,
582
- // and for larger integer SATS types it's `0n: BigInt`.
583
- let sequenceTrigger: bigint | number;
584
- switch (colType.tag) {
585
- case 'U8':
586
- case 'I8':
587
- case 'U16':
588
- case 'I16':
589
- case 'U32':
590
- case 'I32':
591
- sequenceTrigger = 0;
592
- break;
593
- case 'U64':
594
- case 'I64':
595
- case 'U128':
596
- case 'I128':
597
- case 'U256':
598
- case 'I256':
599
- sequenceTrigger = 0n;
600
- break;
601
- default:
602
- throw new TypeError('invalid sequence type');
603
- }
604
- return {
605
- colName: col.name!,
606
- sequenceTrigger,
607
- deserialize: AlgebraicType.makeDeserializer(colType, typespace),
608
- };
609
- });
610
- const hasAutoIncrement = sequences.length > 0;
611
-
612
- const iter = () =>
613
- tableIterator(sys.datastore_table_scan_bsatn(table_id), deserializeRow);
614
-
615
- const integrateGeneratedColumns = hasAutoIncrement
616
- ? (row: RowType<any>, ret_buf: DataView) => {
617
- BINARY_READER.reset(ret_buf);
618
- for (const { colName, deserialize, sequenceTrigger } of sequences) {
619
- if (row[colName] === sequenceTrigger) {
620
- row[colName] = deserialize(BINARY_READER);
621
- }
622
- }
623
- }
624
- : null;
625
-
626
- const tableMethods: TableMethods<any> = {
627
- count: () => sys.datastore_table_row_count(table_id),
628
- iter,
629
- [Symbol.iterator]: () => iter(),
630
- insert: row => {
631
- const buf = LEAF_BUF;
632
- BINARY_WRITER.reset(buf);
633
- serializeRow(BINARY_WRITER, row);
634
- sys.datastore_insert_bsatn(table_id, buf.buffer, BINARY_WRITER.offset);
635
- const ret = { ...row };
636
- integrateGeneratedColumns?.(ret, buf.view);
637
-
638
- return ret;
639
- },
640
- delete: (row: RowType<any>): boolean => {
641
- const buf = LEAF_BUF;
642
- BINARY_WRITER.reset(buf);
643
- BINARY_WRITER.writeU32(1);
644
- serializeRow(BINARY_WRITER, row);
645
- const count = sys.datastore_delete_all_by_eq_bsatn(
646
- table_id,
647
- buf.buffer,
648
- BINARY_WRITER.offset
649
- );
650
- return count > 0;
651
- },
652
- clear: () => sys.datastore_clear(table_id),
653
- };
654
-
655
- const tableView = Object.assign(
656
- Object.create(null),
657
- tableMethods
658
- ) as Table<any>;
659
-
660
- for (const indexDef of table.indexes) {
661
- const accessorName = indexDef.accessorName!;
662
- const index_id = sys.index_id_from_name(indexDef.sourceName!);
663
-
664
- let column_ids: number[];
665
- let isHashIndex = false;
666
- switch (indexDef.algorithm.tag) {
667
- case 'Hash':
668
- isHashIndex = true;
669
- column_ids = indexDef.algorithm.value;
670
- break;
671
- case 'BTree':
672
- column_ids = indexDef.algorithm.value;
673
- break;
674
- case 'Direct':
675
- column_ids = [indexDef.algorithm.value];
676
- break;
677
- }
678
- const numColumns = column_ids.length;
679
-
680
- const columnSet = new Set(column_ids);
681
- const isUnique = table.constraints
682
- .filter(x => x.data.tag === 'Unique')
683
- .some(x => columnSet.isSubsetOf(new Set(x.data.value.columns)));
684
-
685
- const isPrimaryKey =
686
- isUnique &&
687
- column_ids.length === table.primaryKey.length &&
688
- column_ids.every((id, i) => table.primaryKey[i] === id);
689
-
690
- const indexSerializers = column_ids.map(id =>
691
- AlgebraicType.makeSerializer(
692
- rowType.value.elements[id].algebraicType,
693
- typespace
694
- )
695
- );
696
-
697
- const serializePoint = (buffer: ResizableBuffer, colVal: any[]): number => {
698
- BINARY_WRITER.reset(buffer);
699
- for (let i = 0; i < numColumns; i++) {
700
- indexSerializers[i](BINARY_WRITER, colVal[i]);
701
- }
702
- return BINARY_WRITER.offset;
703
- };
704
-
705
- const serializeSingleElement =
706
- numColumns === 1 ? indexSerializers[0] : null;
707
-
708
- const serializeSinglePoint =
709
- serializeSingleElement &&
710
- ((buffer: ResizableBuffer, colVal: any): number => {
711
- BINARY_WRITER.reset(buffer);
712
- serializeSingleElement(BINARY_WRITER, colVal);
713
- return BINARY_WRITER.offset;
714
- });
715
-
716
- type IndexScanArgs = [
717
- prefix_len: u32,
718
- prefix_elems: u16,
719
- rstart_len: u32,
720
- rend_len: u32,
721
- ];
722
-
723
- let index: Index<any, any>;
724
- if (isUnique && serializeSinglePoint) {
725
- // numColumns == 1, unique index
726
- const base = {
727
- find: (colVal: IndexVal<any, any>): RowType<any> | null => {
728
- const buf = LEAF_BUF;
729
- const point_len = serializeSinglePoint(buf, colVal);
730
- const iter_id = sys.datastore_index_scan_point_bsatn(
731
- index_id,
732
- buf.buffer,
733
- point_len
734
- );
735
- return tableIterateOne(iter_id, deserializeRow);
736
- },
737
- delete: (colVal: IndexVal<any, any>): boolean => {
738
- const buf = LEAF_BUF;
739
- const point_len = serializeSinglePoint(buf, colVal);
740
- const num = sys.datastore_delete_by_index_scan_point_bsatn(
741
- index_id,
742
- buf.buffer,
743
- point_len
744
- );
745
- return num > 0;
746
- },
747
- };
748
- if (isPrimaryKey) {
749
- (base as any).update = (row: RowType<any>): RowType<any> => {
750
- const buf = LEAF_BUF;
751
- BINARY_WRITER.reset(buf);
752
- serializeRow(BINARY_WRITER, row);
753
- sys.datastore_update_bsatn(
754
- table_id,
755
- index_id,
756
- buf.buffer,
757
- BINARY_WRITER.offset
758
- );
759
- integrateGeneratedColumns?.(row, buf.view);
760
- return row;
761
- };
762
- }
763
- index = base as UniqueIndex<any, any>;
764
- } else if (isUnique) {
765
- // numColumns != 1, unique index
766
- const base = {
767
- find: (colVal: IndexVal<any, any>): RowType<any> | null => {
768
- if (colVal.length !== numColumns) {
769
- throw new TypeError('wrong number of elements');
770
- }
771
- const buf = LEAF_BUF;
772
- const point_len = serializePoint(buf, colVal);
773
- const iter_id = sys.datastore_index_scan_point_bsatn(
774
- index_id,
775
- buf.buffer,
776
- point_len
777
- );
778
- return tableIterateOne(iter_id, deserializeRow);
779
- },
780
- delete: (colVal: IndexVal<any, any>): boolean => {
781
- if (colVal.length !== numColumns)
782
- throw new TypeError('wrong number of elements');
783
-
784
- const buf = LEAF_BUF;
785
- const point_len = serializePoint(buf, colVal);
786
- const num = sys.datastore_delete_by_index_scan_point_bsatn(
787
- index_id,
788
- buf.buffer,
789
- point_len
790
- );
791
- return num > 0;
792
- },
793
- };
794
- if (isPrimaryKey) {
795
- (base as any).update = (row: RowType<any>): RowType<any> => {
796
- const buf = LEAF_BUF;
797
- BINARY_WRITER.reset(buf);
798
- serializeRow(BINARY_WRITER, row);
799
- sys.datastore_update_bsatn(
800
- table_id,
801
- index_id,
802
- buf.buffer,
803
- BINARY_WRITER.offset
804
- );
805
- integrateGeneratedColumns?.(row, buf.view);
806
- return row;
807
- };
808
- }
809
- index = base as UniqueIndex<any, any>;
810
- } else if (serializeSinglePoint) {
811
- // numColumns == 1
812
-
813
- const serializeSingleRange = !isHashIndex
814
- ? (buffer: ResizableBuffer, range: Range<any>): IndexScanArgs => {
815
- BINARY_WRITER.reset(buffer);
816
- const writer = BINARY_WRITER;
817
- const writeBound = (bound: Bound<any>) => {
818
- const tags = { included: 0, excluded: 1, unbounded: 2 };
819
- writer.writeU8(tags[bound.tag]);
820
- if (bound.tag !== 'unbounded')
821
- serializeSingleElement!(writer, bound.value);
822
- };
823
- writeBound(range.from);
824
- const rstartLen = writer.offset;
825
- writeBound(range.to);
826
- const rendLen = writer.offset - rstartLen;
827
- return [0, 0, rstartLen, rendLen];
828
- }
829
- : null;
830
-
831
- const rawIndex = {
832
- filter: (range: any): IteratorObject<RowType<any>> => {
833
- const buf = LEAF_BUF;
834
- if (serializeSingleRange && range instanceof Range) {
835
- const args = serializeSingleRange(buf, range);
836
- const iter_id = sys.datastore_index_scan_range_bsatn(
837
- index_id,
838
- buf.buffer,
839
- ...args
840
- );
841
- return tableIterator(iter_id, deserializeRow);
842
- }
843
- const point_len = serializeSinglePoint(buf, range);
844
- const iter_id = sys.datastore_index_scan_point_bsatn(
845
- index_id,
846
- buf.buffer,
847
- point_len
848
- );
849
- return tableIterator(iter_id, deserializeRow);
850
- },
851
- delete: (range: any): u32 => {
852
- const buf = LEAF_BUF;
853
- if (serializeSingleRange && range instanceof Range) {
854
- const args = serializeSingleRange(buf, range);
855
- return sys.datastore_delete_by_index_scan_range_bsatn(
856
- index_id,
857
- buf.buffer,
858
- ...args
859
- );
860
- }
861
- const point_len = serializeSinglePoint(buf, range);
862
- return sys.datastore_delete_by_index_scan_point_bsatn(
863
- index_id,
864
- buf.buffer,
865
- point_len
866
- );
867
- },
868
- };
869
- if (isHashIndex) {
870
- index = rawIndex as PointIndex<any, any>;
871
- } else {
872
- index = rawIndex as RangedIndex<any, any>;
873
- }
874
- } else if (isHashIndex) {
875
- // numColumns != 1
876
- index = {
877
- filter: (range: any[]): IteratorObject<RowType<any>> => {
878
- const buf = LEAF_BUF;
879
- const point_len = serializePoint(buf, range);
880
- const iter_id = sys.datastore_index_scan_point_bsatn(
881
- index_id,
882
- buf.buffer,
883
- point_len
884
- );
885
- return tableIterator(iter_id, deserializeRow);
886
- },
887
- delete: (range: any[]): u32 => {
888
- const buf = LEAF_BUF;
889
- const point_len = serializePoint(buf, range);
890
- return sys.datastore_delete_by_index_scan_point_bsatn(
891
- index_id,
892
- buf.buffer,
893
- point_len
894
- );
895
- },
896
- } as PointIndex<any, any>;
897
- } else {
898
- // numColumns != 1
899
- const serializeRange = (
900
- buffer: ResizableBuffer,
901
- range: any[]
902
- ): IndexScanArgs => {
903
- if (range.length > numColumns) throw new TypeError('too many elements');
904
-
905
- BINARY_WRITER.reset(buffer);
906
- const writer = BINARY_WRITER;
907
- const prefix_elems = range.length - 1;
908
- for (let i = 0; i < prefix_elems; i++) {
909
- indexSerializers[i](writer, range[i]);
910
- }
911
- const rstartOffset = writer.offset;
912
- const term = range[range.length - 1];
913
- const serializeTerm = indexSerializers[range.length - 1];
914
- if (term instanceof Range) {
915
- const writeBound = (bound: Bound<any>) => {
916
- const tags = { included: 0, excluded: 1, unbounded: 2 };
917
- writer.writeU8(tags[bound.tag]);
918
- if (bound.tag !== 'unbounded') serializeTerm(writer, bound.value);
919
- };
920
- writeBound(term.from);
921
- const rstartLen = writer.offset - rstartOffset;
922
- writeBound(term.to);
923
- const rendLen = writer.offset - rstartLen;
924
- return [rstartOffset, prefix_elems, rstartLen, rendLen];
925
- } else {
926
- writer.writeU8(0);
927
- serializeTerm(writer, term);
928
- const rstartLen = writer.offset;
929
- const rendLen = 0;
930
- return [rstartOffset, prefix_elems, rstartLen, rendLen];
931
- }
932
- };
933
- index = {
934
- filter: (range: any[]): IteratorObject<RowType<any>> => {
935
- if (range.length === numColumns) {
936
- const buf = LEAF_BUF;
937
- const point_len = serializePoint(buf, range);
938
- const iter_id = sys.datastore_index_scan_point_bsatn(
939
- index_id,
940
- buf.buffer,
941
- point_len
942
- );
943
- return tableIterator(iter_id, deserializeRow);
944
- } else {
945
- const buf = LEAF_BUF;
946
- const args = serializeRange(buf, range);
947
- const iter_id = sys.datastore_index_scan_range_bsatn(
948
- index_id,
949
- buf.buffer,
950
- ...args
951
- );
952
- return tableIterator(iter_id, deserializeRow);
953
- }
954
- },
955
- delete: (range: any[]): u32 => {
956
- if (range.length === numColumns) {
957
- const buf = LEAF_BUF;
958
- const point_len = serializePoint(buf, range);
959
- return sys.datastore_delete_by_index_scan_point_bsatn(
960
- index_id,
961
- buf.buffer,
962
- point_len
963
- );
964
- } else {
965
- const buf = LEAF_BUF;
966
- const args = serializeRange(buf, range);
967
- return sys.datastore_delete_by_index_scan_range_bsatn(
968
- index_id,
969
- buf.buffer,
970
- ...args
971
- );
972
- }
973
- },
974
- } as RangedIndex<any, any>;
975
- }
976
-
977
- // IMPORTANT: duplicate accessor handling.
978
- // When multiple raw indexes share the same accessor name, we merge index
979
- // methods onto a single accessor object instead of throwing.
980
- if (Object.hasOwn(tableView, accessorName)) {
981
- freeze(Object.assign((tableView as any)[accessorName], index));
982
- } else {
983
- (tableView as any)[accessorName] = freeze(index);
984
- }
985
- }
986
-
987
- return freeze(tableView);
988
- }
989
-
990
- function* tableIterator<T>(
991
- id: u32,
992
- deserialize: Deserializer<T>
993
- ): Generator<T, undefined> {
994
- using iter = new IteratorHandle(id);
995
-
996
- const iterBuf = takeBuf();
997
- try {
998
- let amt;
999
- while ((amt = iter.advance(iterBuf))) {
1000
- const reader = new BinaryReader(iterBuf.view);
1001
- while (reader.offset < amt) {
1002
- yield deserialize(reader);
1003
- }
1004
- }
1005
- } finally {
1006
- returnBuf(iterBuf);
1007
- }
1008
- }
1009
-
1010
- function tableIterateOne<T>(id: u32, deserialize: Deserializer<T>): T | null {
1011
- const buf = LEAF_BUF;
1012
- // we only need to check for the `<= 0` case, since this function is only used
1013
- // with iterators that should only have zero or one element.
1014
- const ret = advanceIterRaw(id, buf);
1015
- if (ret !== 0) {
1016
- BINARY_READER.reset(buf.view);
1017
- return deserialize(BINARY_READER);
1018
- }
1019
- return null;
1020
- }
1021
-
1022
- /**
1023
- * `ret < 0` means the iterator yielded elements but is now exhausted and has been destroyed.
1024
- * `ret === 0` means the iterator was empty and has been destroyed.
1025
- * `ret > 0` means the iterator yielded elements and has more to give.
1026
- */
1027
- function advanceIterRaw(id: u32, buf: ResizableBuffer): number {
1028
- while (true) {
1029
- try {
1030
- return 0 | sys.row_iter_bsatn_advance(id, buf.buffer);
1031
- } catch (e) {
1032
- if (e && typeof e === 'object' && hasOwn(e, '__buffer_too_small__')) {
1033
- buf.grow(e.__buffer_too_small__ as number);
1034
- continue;
1035
- }
1036
- throw e;
1037
- }
1038
- }
1039
- }
1040
-
1041
- // This should guarantee in most cases that we don't have to reallocate an iterator
1042
- // buffer, unless there's a single row that serializes to >1 MiB.
1043
- const DEFAULT_BUFFER_CAPACITY = 32 * 1024 * 2;
1044
-
1045
- const ITER_BUFS: ResizableBuffer[] = [
1046
- new ResizableBuffer(DEFAULT_BUFFER_CAPACITY),
1047
- ];
1048
- let ITER_BUF_COUNT = 1;
1049
-
1050
- function takeBuf(): ResizableBuffer {
1051
- return ITER_BUF_COUNT
1052
- ? ITER_BUFS[--ITER_BUF_COUNT]
1053
- : new ResizableBuffer(DEFAULT_BUFFER_CAPACITY);
1054
- }
1055
-
1056
- function returnBuf(buf: ResizableBuffer) {
1057
- ITER_BUFS[ITER_BUF_COUNT++] = buf;
1058
- }
1059
-
1060
- /**
1061
- * This should only be used from functions that don't need persistent ownership
1062
- * over the buffer. While using this value, one should not call a function that
1063
- * also uses this value.
1064
- */
1065
- const LEAF_BUF = new ResizableBuffer(DEFAULT_BUFFER_CAPACITY);
1066
-
1067
- /** A class to manage the lifecycle of an iterator handle. */
1068
- class IteratorHandle implements Disposable {
1069
- #id: u32 | -1;
1070
-
1071
- static #finalizationRegistry = new FinalizationRegistry<u32>(
1072
- sys.row_iter_bsatn_close
1073
- );
1074
-
1075
- constructor(id: u32) {
1076
- this.#id = id;
1077
- IteratorHandle.#finalizationRegistry.register(this, id, this);
1078
- }
1079
-
1080
- /** Unregister this object with the finalization registry and return the id */
1081
- #detach() {
1082
- const id = this.#id;
1083
- this.#id = -1;
1084
- IteratorHandle.#finalizationRegistry.unregister(this);
1085
- return id;
1086
- }
1087
-
1088
- /** Call `row_iter_bsatn_advance`, returning 0 if this iterator has been exhausted. */
1089
- advance(buf: ResizableBuffer): number {
1090
- if (this.#id === -1) return 0;
1091
- const ret = advanceIterRaw(this.#id, buf);
1092
- if (ret <= 0) this.#detach();
1093
- return ret < 0 ? -ret : ret;
1094
- }
1095
-
1096
- [Symbol.dispose]() {
1097
- if (this.#id >= 0) {
1098
- const id = this.#detach();
1099
- sys.row_iter_bsatn_close(id);
1100
- }
1101
- }
1102
- }
1
+ import * as _syscalls2_0 from 'spacetime:sys@2.0';
2
+ import * as _syscalls2_1 from 'spacetime:sys@2.1';
3
+
4
+ import type { ModuleHooks, u128, u16, u256, u32 } from 'spacetime:sys@2.0';
5
+ import {
6
+ AlgebraicType,
7
+ ProductType,
8
+ type Deserializer,
9
+ } from '../lib/algebraic_type';
10
+ import {
11
+ RawModuleDef,
12
+ ViewResultHeader,
13
+ type RawTableDefV10,
14
+ type Typespace,
15
+ } from '../lib/autogen/types';
16
+ import { ConnectionId } from '../lib/connection_id';
17
+ import { Identity } from '../lib/identity';
18
+ import { Timestamp } from '../lib/timestamp';
19
+ import { Uuid } from '../lib/uuid';
20
+ import BinaryReader from '../lib/binary_reader';
21
+ import BinaryWriter, { ResizableBuffer } from '../lib/binary_writer';
22
+ import {
23
+ type Index,
24
+ type IndexVal,
25
+ type PointIndex,
26
+ type RangedIndex,
27
+ type UniqueIndex,
28
+ } from '../lib/indexes';
29
+ import { callProcedure } from './procedures';
30
+ import {
31
+ type HandlerContext,
32
+ Request,
33
+ SyncResponse,
34
+ makeRequest,
35
+ } from './http_handlers';
36
+ import { httpClient } from './http_internal';
37
+ import {
38
+ deserializeHeaders,
39
+ deserializeMethod,
40
+ serializeHeaders,
41
+ } from './http_shared';
42
+ import {
43
+ type AuthCtx,
44
+ type JsonObject,
45
+ type JwtClaims,
46
+ type ReducerCtx as IReducerCtx,
47
+ } from '../lib/reducers';
48
+ import { type UntypedSchemaDef } from '../lib/schema';
49
+ import { type RowType, type Table, type TableMethods } from '../lib/table';
50
+ import { bsatnBaseSize, hasOwn } from '../lib/util';
51
+ import { type AnonymousViewCtx, type ViewCtx } from './views';
52
+ import { isRowTypedQuery, makeQueryBuilder, toSql } from './query';
53
+ import type { DbView } from './db_view';
54
+ import { getErrorConstructor, SenderError } from './errors';
55
+ import { Range, type Bound } from './range';
56
+ import { makeRandom, type Random } from './rng';
57
+ import type { SchemaInner } from './schema';
58
+ import { HttpRequest, HttpResponse } from '../lib/autogen/types';
59
+
60
+ const { freeze } = Object;
61
+
62
+ export const sys = { ..._syscalls2_0, ..._syscalls2_1 };
63
+
64
+ function requestFromWire(request: HttpRequest, body: Uint8Array): Request {
65
+ return Request[makeRequest](body, {
66
+ headers: deserializeHeaders(request.headers),
67
+ method: deserializeMethod(request.method),
68
+ uri: request.uri,
69
+ version: request.version,
70
+ });
71
+ }
72
+
73
+ function responseIntoWire(response: SyncResponse): [HttpResponse, Uint8Array] {
74
+ return [
75
+ {
76
+ headers: serializeHeaders(response.headers),
77
+ version: response.version,
78
+ code: response.status,
79
+ },
80
+ response.bytes(),
81
+ ];
82
+ }
83
+
84
+ export function parseJsonObject(json: string): JsonObject {
85
+ let value: unknown;
86
+
87
+ try {
88
+ value = JSON.parse(json);
89
+ } catch {
90
+ throw new Error('Invalid JSON: failed to parse string');
91
+ }
92
+
93
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
94
+ throw new Error('Expected a JSON object at the top level');
95
+ }
96
+
97
+ // The runtime check above guarantees this cast is safe
98
+ return value as JsonObject;
99
+ }
100
+
101
+ class JwtClaimsImpl implements JwtClaims {
102
+ readonly fullPayload: JsonObject;
103
+ private readonly _identity: Identity;
104
+ /**
105
+ * Creates a new JwtClaims instance.
106
+ * @param rawPayload The JWT payload as a raw JSON string.
107
+ * @param identity The identity for this JWT. We are only taking this because we don't have a blake3 implementation (which we need to compute it).
108
+ */
109
+ constructor(
110
+ public readonly rawPayload: string,
111
+ identity: Identity
112
+ ) {
113
+ this.fullPayload = parseJsonObject(rawPayload);
114
+ this._identity = identity;
115
+ }
116
+ readonly [claim: string]: unknown;
117
+ get identity(): Identity {
118
+ return this._identity;
119
+ }
120
+ get subject() {
121
+ return this.fullPayload['sub'] as string;
122
+ }
123
+ get issuer() {
124
+ return this.fullPayload['iss'] as string;
125
+ }
126
+ get audience() {
127
+ const aud = this.fullPayload['aud'];
128
+ if (aud == null) {
129
+ return [];
130
+ }
131
+ return typeof aud === 'string' ? [aud] : (aud as string[]);
132
+ }
133
+ }
134
+
135
+ class AuthCtxImpl implements AuthCtx {
136
+ public readonly isInternal: boolean;
137
+
138
+ // Source of the JWT payload string, if there is one.
139
+ private readonly _jwtSource: () => string | null;
140
+ // Whether we have initialized the JWT claims.
141
+ private _initializedJWT: boolean = false;
142
+ private _jwtClaims?: JwtClaims | null;
143
+ private _senderIdentity: Identity;
144
+
145
+ private constructor(opts: {
146
+ isInternal: boolean;
147
+ jwtSource: () => string | null;
148
+ senderIdentity: Identity;
149
+ }) {
150
+ this.isInternal = opts.isInternal;
151
+ this._jwtSource = opts.jwtSource;
152
+ this._senderIdentity = opts.senderIdentity;
153
+ }
154
+
155
+ private _initializeJWT() {
156
+ if (this._initializedJWT) return;
157
+ this._initializedJWT = true;
158
+
159
+ const token = this._jwtSource();
160
+ if (!token) {
161
+ this._jwtClaims = null;
162
+ } else {
163
+ this._jwtClaims = new JwtClaimsImpl(token, this._senderIdentity);
164
+ }
165
+ // At this point we can safely freeze the object.
166
+ Object.freeze(this);
167
+ }
168
+
169
+ /** Lazily compute whether a JWT exists and is parseable. */
170
+ get hasJWT(): boolean {
171
+ this._initializeJWT();
172
+ return this._jwtClaims !== null;
173
+ }
174
+
175
+ /** Lazily parse the JwtClaims only when accessed. */
176
+ get jwt(): JwtClaims | null {
177
+ this._initializeJWT();
178
+ return this._jwtClaims!;
179
+ }
180
+
181
+ /** Create a context representing internal (non-user) requests. */
182
+ static internal(): AuthCtx {
183
+ return new AuthCtxImpl({
184
+ isInternal: true,
185
+ jwtSource: () => null,
186
+ senderIdentity: Identity.zero(),
187
+ });
188
+ }
189
+
190
+ /** If there is a connection id, look up the JWT payload from the system tables. */
191
+ static fromSystemTables(
192
+ connectionId: ConnectionId | null,
193
+ sender: Identity
194
+ ): AuthCtx {
195
+ if (connectionId === null) {
196
+ return new AuthCtxImpl({
197
+ isInternal: false,
198
+ jwtSource: () => null,
199
+ senderIdentity: sender,
200
+ });
201
+ }
202
+ return new AuthCtxImpl({
203
+ isInternal: false,
204
+ jwtSource: () => {
205
+ const payloadBuf = sys.get_jwt_payload(connectionId.__connection_id__);
206
+ if (payloadBuf.length === 0) return null;
207
+ const payloadStr = new TextDecoder().decode(payloadBuf);
208
+ return payloadStr;
209
+ },
210
+ senderIdentity: sender,
211
+ });
212
+ }
213
+ }
214
+
215
+ // Using a class expression rather than declaration keeps the class out of the
216
+ // type namespace, so that `ReducerCtx` still refers to the interface.
217
+ export const ReducerCtxImpl = class ReducerCtx<
218
+ SchemaDef extends UntypedSchemaDef,
219
+ > implements IReducerCtx<SchemaDef>
220
+ {
221
+ #identity: Identity | undefined;
222
+ #senderAuth: AuthCtx | undefined;
223
+ #uuidCounter: { value: number } | undefined;
224
+ #random: Random | undefined;
225
+ sender: Identity;
226
+ timestamp: Timestamp;
227
+ connectionId: ConnectionId | null;
228
+ db: DbView<SchemaDef>;
229
+
230
+ constructor(
231
+ sender: Identity,
232
+ timestamp: Timestamp,
233
+ connectionId: ConnectionId | null,
234
+ dbView: DbView<any>
235
+ ) {
236
+ Object.seal(this);
237
+ this.sender = sender;
238
+ this.timestamp = timestamp;
239
+ this.connectionId = connectionId;
240
+ this.db = dbView;
241
+ }
242
+
243
+ /** Reset the `ReducerCtx` to be used for a new transaction */
244
+ static reset(
245
+ me: InstanceType<typeof this>,
246
+ sender: Identity,
247
+ timestamp: Timestamp,
248
+ connectionId: ConnectionId | null
249
+ ) {
250
+ me.sender = sender;
251
+ me.timestamp = timestamp;
252
+ me.connectionId = connectionId;
253
+ me.#uuidCounter = undefined;
254
+ me.#senderAuth = undefined;
255
+ }
256
+
257
+ get databaseIdentity() {
258
+ return (this.#identity ??= new Identity(sys.identity()));
259
+ }
260
+
261
+ get identity() {
262
+ return this.databaseIdentity;
263
+ }
264
+
265
+ get senderAuth() {
266
+ return (this.#senderAuth ??= AuthCtxImpl.fromSystemTables(
267
+ this.connectionId,
268
+ this.sender
269
+ ));
270
+ }
271
+
272
+ get random() {
273
+ return (this.#random ??= makeRandom(this.timestamp));
274
+ }
275
+
276
+ /**
277
+ * Create a new random {@link Uuid} `v4` using this `ReducerCtx`'s RNG.
278
+ */
279
+ newUuidV4(): Uuid {
280
+ const bytes = this.random.fill(new Uint8Array(16));
281
+ return Uuid.fromRandomBytesV4(bytes);
282
+ }
283
+
284
+ /**
285
+ * Create a new sortable {@link Uuid} `v7` using this `ReducerCtx`'s RNG, counter,
286
+ * and timestamp.
287
+ */
288
+ newUuidV7(): Uuid {
289
+ const bytes = this.random.fill(new Uint8Array(4));
290
+ const counter = (this.#uuidCounter ??= { value: 0 });
291
+ return Uuid.fromCounterV7(counter, this.timestamp, bytes);
292
+ }
293
+ };
294
+
295
+ /**
296
+ * Call into a user function `fn` - the backtrace from an exception thrown in
297
+ * `fn` or one of its descendants in the callgraph will be stripped by host
298
+ * code in `crates/core/src/host/v8/error.rs` such that `fn` will be shown to
299
+ * be the root of the call stack.
300
+ */
301
+ export const callUserFunction = function __spacetimedb_end_short_backtrace<
302
+ Args extends any[],
303
+ R,
304
+ >(fn: (...args: Args) => R, ...args: Args): R {
305
+ return fn(...args);
306
+ };
307
+
308
+ export function runWithTx<T, Ctx>(
309
+ makeCtx: (timestamp: Timestamp) => Ctx,
310
+ body: (ctx: Ctx) => T
311
+ ): T {
312
+ const run = () => {
313
+ const timestamp = sys.procedure_start_mut_tx();
314
+
315
+ try {
316
+ return body(makeCtx(new Timestamp(timestamp)));
317
+ } catch (e) {
318
+ sys.procedure_abort_mut_tx();
319
+ throw e;
320
+ }
321
+ };
322
+
323
+ let res = run();
324
+ try {
325
+ sys.procedure_commit_mut_tx();
326
+ return res;
327
+ } catch {
328
+ // ignore the commit error
329
+ }
330
+ console.warn('committing anonymous transaction failed');
331
+ res = run();
332
+ try {
333
+ sys.procedure_commit_mut_tx();
334
+ return res;
335
+ } catch (e) {
336
+ throw new Error('transaction retry failed again', { cause: e });
337
+ }
338
+ }
339
+
340
+ export const makeHooks = (schema: SchemaInner): ModuleHooks =>
341
+ new ModuleHooksImpl(schema);
342
+
343
+ class ModuleHooksImpl implements ModuleHooks {
344
+ #schema: SchemaInner;
345
+ #dbView_: DbView<any> | undefined;
346
+ #reducerArgsDeserializers;
347
+ /** Cache the `ReducerCtx` object to avoid allocating anew for ever reducer call. */
348
+ #reducerCtx_: InstanceType<typeof ReducerCtxImpl> | undefined;
349
+
350
+ constructor(schema: SchemaInner) {
351
+ this.#schema = schema;
352
+ this.#reducerArgsDeserializers = schema.moduleDef.reducers.map(
353
+ ({ params }) => ProductType.makeDeserializer(params, schema.typespace)
354
+ );
355
+ }
356
+
357
+ get #dbView() {
358
+ return (this.#dbView_ ??= freeze(
359
+ Object.fromEntries(
360
+ Object.values(this.#schema.schemaType.tables).map(table => [
361
+ table.accessorName,
362
+ makeTableView(this.#schema.typespace, table.tableDef),
363
+ ])
364
+ )
365
+ ));
366
+ }
367
+
368
+ get #reducerCtx() {
369
+ return (this.#reducerCtx_ ??= new ReducerCtxImpl(
370
+ Identity.zero(),
371
+ Timestamp.UNIX_EPOCH,
372
+ null,
373
+ this.#dbView
374
+ ));
375
+ }
376
+
377
+ __describe_module__() {
378
+ const writer = new BinaryWriter(128);
379
+ RawModuleDef.serialize(
380
+ writer,
381
+ RawModuleDef.V10(this.#schema.rawModuleDefV10())
382
+ );
383
+ return writer.getBuffer();
384
+ }
385
+
386
+ __get_error_constructor__(code: number): new (msg: string) => Error {
387
+ return getErrorConstructor(code);
388
+ }
389
+
390
+ get __sender_error_class__() {
391
+ return SenderError;
392
+ }
393
+
394
+ __call_reducer__(
395
+ reducerId: u32,
396
+ sender: u256,
397
+ connId: u128,
398
+ timestamp: bigint,
399
+ argsBuf: DataView
400
+ ): void {
401
+ const moduleCtx = this.#schema;
402
+ const deserializeArgs = this.#reducerArgsDeserializers[reducerId];
403
+ BINARY_READER.reset(argsBuf);
404
+ const args = deserializeArgs(BINARY_READER);
405
+ const senderIdentity = new Identity(sender);
406
+ const ctx = this.#reducerCtx;
407
+ ReducerCtxImpl.reset(
408
+ ctx,
409
+ senderIdentity,
410
+ new Timestamp(timestamp),
411
+ ConnectionId.nullIfZero(new ConnectionId(connId))
412
+ );
413
+ callUserFunction(moduleCtx.reducers[reducerId], ctx, args);
414
+ }
415
+
416
+ __call_view__(
417
+ id: u32,
418
+ sender: u256,
419
+ argsBuf: Uint8Array
420
+ ): { data: Uint8Array } {
421
+ const moduleCtx = this.#schema;
422
+ const { fn, deserializeParams, serializeReturn, returnTypeBaseSize } =
423
+ moduleCtx.views[id];
424
+ const ctx: ViewCtx<any> = freeze({
425
+ sender: new Identity(sender),
426
+ // this is the non-readonly DbView, but the typing for the user will be
427
+ // the readonly one, and if they do call mutating functions it will fail
428
+ // at runtime
429
+ db: this.#dbView,
430
+ from: makeQueryBuilder(moduleCtx.schemaType),
431
+ });
432
+ const args = deserializeParams(new BinaryReader(argsBuf));
433
+ const ret = callUserFunction(fn, ctx, args);
434
+ const retBuf = new BinaryWriter(returnTypeBaseSize);
435
+ if (isRowTypedQuery(ret)) {
436
+ const query = toSql(ret);
437
+ ViewResultHeader.serialize(retBuf, ViewResultHeader.RawSql(query));
438
+ } else {
439
+ ViewResultHeader.serialize(retBuf, ViewResultHeader.RowData);
440
+ serializeReturn(retBuf, ret);
441
+ }
442
+ return { data: retBuf.getBuffer() };
443
+ }
444
+
445
+ __call_view_anon__(id: u32, argsBuf: Uint8Array): { data: Uint8Array } {
446
+ const moduleCtx = this.#schema;
447
+ const { fn, deserializeParams, serializeReturn, returnTypeBaseSize } =
448
+ moduleCtx.anonViews[id];
449
+ const ctx: AnonymousViewCtx<any> = freeze({
450
+ // this is the non-readonly DbView, but the typing for the user will be
451
+ // the readonly one, and if they do call mutating functions it will fail
452
+ // at runtime
453
+ db: this.#dbView,
454
+ from: makeQueryBuilder(moduleCtx.schemaType),
455
+ });
456
+ const args = deserializeParams(new BinaryReader(argsBuf));
457
+ const ret = callUserFunction(fn, ctx, args);
458
+ const retBuf = new BinaryWriter(returnTypeBaseSize);
459
+ if (isRowTypedQuery(ret)) {
460
+ const query = toSql(ret);
461
+ ViewResultHeader.serialize(retBuf, ViewResultHeader.RawSql(query));
462
+ } else {
463
+ ViewResultHeader.serialize(retBuf, ViewResultHeader.RowData);
464
+ serializeReturn(retBuf, ret);
465
+ }
466
+ return { data: retBuf.getBuffer() };
467
+ }
468
+
469
+ __call_procedure__(
470
+ id: u32,
471
+ sender: u256,
472
+ connection_id: u128,
473
+ timestamp: bigint,
474
+ args: Uint8Array
475
+ ): Uint8Array {
476
+ return callProcedure(
477
+ this.#schema,
478
+ id,
479
+ new Identity(sender),
480
+ ConnectionId.nullIfZero(new ConnectionId(connection_id)),
481
+ new Timestamp(timestamp),
482
+ args,
483
+ () => this.#dbView
484
+ );
485
+ }
486
+
487
+ __call_http_handler__(
488
+ id: u32,
489
+ timestamp: bigint,
490
+ request: Uint8Array,
491
+ body: Uint8Array
492
+ ): [response: Uint8Array, body: Uint8Array] {
493
+ const moduleCtx = this.#schema;
494
+ const handler = moduleCtx.httpHandlers[id];
495
+ const ctx = new HandlerContextImpl(
496
+ new Timestamp(timestamp),
497
+ () => this.#dbView
498
+ );
499
+ const requestMetadata = HttpRequest.deserialize(new BinaryReader(request));
500
+ const response = callUserFunction(
501
+ handler,
502
+ ctx,
503
+ requestFromWire(requestMetadata, body)
504
+ );
505
+ const [responseMetadata, responseBody] = responseIntoWire(response);
506
+ const responseBuf = new BinaryWriter(
507
+ bsatnBaseSize(moduleCtx.typespace, HttpResponse.algebraicType)
508
+ );
509
+ HttpResponse.serialize(responseBuf, responseMetadata);
510
+ return [responseBuf.getBuffer(), responseBody];
511
+ }
512
+ }
513
+
514
+ const BINARY_WRITER = new BinaryWriter(0);
515
+ const BINARY_READER = new BinaryReader(new Uint8Array());
516
+
517
+ class HandlerContextImpl<S extends UntypedSchemaDef = UntypedSchemaDef>
518
+ implements HandlerContext<S>
519
+ {
520
+ #identity: Identity | undefined;
521
+ #uuidCounter: { value: number } | undefined;
522
+ #random: Random | undefined;
523
+ #dbView: () => DbView<any>;
524
+
525
+ readonly http = httpClient;
526
+
527
+ constructor(
528
+ readonly timestamp: Timestamp,
529
+ dbView: () => DbView<any>
530
+ ) {
531
+ this.#dbView = dbView;
532
+ }
533
+
534
+ get identity() {
535
+ return (this.#identity ??= new Identity(sys.identity()));
536
+ }
537
+
538
+ get random() {
539
+ return (this.#random ??= makeRandom(this.timestamp));
540
+ }
541
+
542
+ withTx<T>(body: (ctx: any) => T): T {
543
+ return runWithTx(
544
+ timestamp =>
545
+ new ReducerCtxImpl(Identity.zero(), timestamp, null, this.#dbView()),
546
+ body
547
+ );
548
+ }
549
+
550
+ newUuidV4(): Uuid {
551
+ const bytes = this.random.fill(new Uint8Array(16));
552
+ return Uuid.fromRandomBytesV4(bytes);
553
+ }
554
+
555
+ newUuidV7(): Uuid {
556
+ const bytes = this.random.fill(new Uint8Array(4));
557
+ const counter = (this.#uuidCounter ??= { value: 0 });
558
+ return Uuid.fromCounterV7(counter, this.timestamp, bytes);
559
+ }
560
+ }
561
+
562
+ function makeTableView(
563
+ typespace: Typespace,
564
+ table: RawTableDefV10
565
+ ): Table<any> {
566
+ const table_id = sys.table_id_from_name(table.sourceName);
567
+ const rowType = typespace.types[table.productTypeRef];
568
+ if (rowType.tag !== 'Product') {
569
+ throw 'impossible';
570
+ }
571
+
572
+ const serializeRow = AlgebraicType.makeSerializer(rowType, typespace);
573
+ const deserializeRow = AlgebraicType.makeDeserializer(rowType, typespace);
574
+
575
+ const sequences = table.sequences.map(seq => {
576
+ const col = rowType.value.elements[seq.column];
577
+ const colType = col.algebraicType;
578
+
579
+ // Determine the sentinel value which users will pass to as a placeholder
580
+ // to cause the sequence to advance.
581
+ // For small integer SATS types which fit in V8 `number`s, this is `0: number`,
582
+ // and for larger integer SATS types it's `0n: BigInt`.
583
+ let sequenceTrigger: bigint | number;
584
+ switch (colType.tag) {
585
+ case 'U8':
586
+ case 'I8':
587
+ case 'U16':
588
+ case 'I16':
589
+ case 'U32':
590
+ case 'I32':
591
+ sequenceTrigger = 0;
592
+ break;
593
+ case 'U64':
594
+ case 'I64':
595
+ case 'U128':
596
+ case 'I128':
597
+ case 'U256':
598
+ case 'I256':
599
+ sequenceTrigger = 0n;
600
+ break;
601
+ default:
602
+ throw new TypeError('invalid sequence type');
603
+ }
604
+ return {
605
+ colName: col.name!,
606
+ sequenceTrigger,
607
+ deserialize: AlgebraicType.makeDeserializer(colType, typespace),
608
+ };
609
+ });
610
+ const hasAutoIncrement = sequences.length > 0;
611
+
612
+ const iter = () =>
613
+ tableIterator(sys.datastore_table_scan_bsatn(table_id), deserializeRow);
614
+
615
+ const integrateGeneratedColumns = hasAutoIncrement
616
+ ? (row: RowType<any>, ret_buf: DataView) => {
617
+ BINARY_READER.reset(ret_buf);
618
+ for (const { colName, deserialize, sequenceTrigger } of sequences) {
619
+ if (row[colName] === sequenceTrigger) {
620
+ row[colName] = deserialize(BINARY_READER);
621
+ }
622
+ }
623
+ }
624
+ : null;
625
+
626
+ const tableMethods: TableMethods<any> = {
627
+ count: () => sys.datastore_table_row_count(table_id),
628
+ iter,
629
+ [Symbol.iterator]: () => iter(),
630
+ insert: row => {
631
+ const buf = LEAF_BUF;
632
+ BINARY_WRITER.reset(buf);
633
+ serializeRow(BINARY_WRITER, row);
634
+ sys.datastore_insert_bsatn(table_id, buf.buffer, BINARY_WRITER.offset);
635
+ const ret = { ...row };
636
+ integrateGeneratedColumns?.(ret, buf.view);
637
+
638
+ return ret;
639
+ },
640
+ delete: (row: RowType<any>): boolean => {
641
+ const buf = LEAF_BUF;
642
+ BINARY_WRITER.reset(buf);
643
+ BINARY_WRITER.writeU32(1);
644
+ serializeRow(BINARY_WRITER, row);
645
+ const count = sys.datastore_delete_all_by_eq_bsatn(
646
+ table_id,
647
+ buf.buffer,
648
+ BINARY_WRITER.offset
649
+ );
650
+ return count > 0;
651
+ },
652
+ clear: () => sys.datastore_clear(table_id),
653
+ };
654
+
655
+ const tableView = Object.assign(
656
+ Object.create(null),
657
+ tableMethods
658
+ ) as Table<any>;
659
+
660
+ for (const indexDef of table.indexes) {
661
+ const accessorName = indexDef.accessorName!;
662
+ const index_id = sys.index_id_from_name(indexDef.sourceName!);
663
+
664
+ let column_ids: number[];
665
+ let isHashIndex = false;
666
+ switch (indexDef.algorithm.tag) {
667
+ case 'Hash':
668
+ isHashIndex = true;
669
+ column_ids = indexDef.algorithm.value;
670
+ break;
671
+ case 'BTree':
672
+ column_ids = indexDef.algorithm.value;
673
+ break;
674
+ case 'Direct':
675
+ column_ids = [indexDef.algorithm.value];
676
+ break;
677
+ }
678
+ const numColumns = column_ids.length;
679
+
680
+ const columnSet = new Set(column_ids);
681
+ const isUnique = table.constraints
682
+ .filter(x => x.data.tag === 'Unique')
683
+ .some(x => columnSet.isSubsetOf(new Set(x.data.value.columns)));
684
+
685
+ const isPrimaryKey =
686
+ isUnique &&
687
+ column_ids.length === table.primaryKey.length &&
688
+ column_ids.every((id, i) => table.primaryKey[i] === id);
689
+
690
+ const indexSerializers = column_ids.map(id =>
691
+ AlgebraicType.makeSerializer(
692
+ rowType.value.elements[id].algebraicType,
693
+ typespace
694
+ )
695
+ );
696
+
697
+ const serializePoint = (buffer: ResizableBuffer, colVal: any[]): number => {
698
+ BINARY_WRITER.reset(buffer);
699
+ for (let i = 0; i < numColumns; i++) {
700
+ indexSerializers[i](BINARY_WRITER, colVal[i]);
701
+ }
702
+ return BINARY_WRITER.offset;
703
+ };
704
+
705
+ const serializeSingleElement =
706
+ numColumns === 1 ? indexSerializers[0] : null;
707
+
708
+ const serializeSinglePoint =
709
+ serializeSingleElement &&
710
+ ((buffer: ResizableBuffer, colVal: any): number => {
711
+ BINARY_WRITER.reset(buffer);
712
+ serializeSingleElement(BINARY_WRITER, colVal);
713
+ return BINARY_WRITER.offset;
714
+ });
715
+
716
+ type IndexScanArgs = [
717
+ prefix_len: u32,
718
+ prefix_elems: u16,
719
+ rstart_len: u32,
720
+ rend_len: u32,
721
+ ];
722
+
723
+ let index: Index<any, any>;
724
+ if (isUnique && serializeSinglePoint) {
725
+ // numColumns == 1, unique index
726
+ const base = {
727
+ find: (colVal: IndexVal<any, any>): RowType<any> | null => {
728
+ const buf = LEAF_BUF;
729
+ const point_len = serializeSinglePoint(buf, colVal);
730
+ const iter_id = sys.datastore_index_scan_point_bsatn(
731
+ index_id,
732
+ buf.buffer,
733
+ point_len
734
+ );
735
+ return tableIterateOne(iter_id, deserializeRow);
736
+ },
737
+ delete: (colVal: IndexVal<any, any>): boolean => {
738
+ const buf = LEAF_BUF;
739
+ const point_len = serializeSinglePoint(buf, colVal);
740
+ const num = sys.datastore_delete_by_index_scan_point_bsatn(
741
+ index_id,
742
+ buf.buffer,
743
+ point_len
744
+ );
745
+ return num > 0;
746
+ },
747
+ };
748
+ if (isPrimaryKey) {
749
+ (base as any).update = (row: RowType<any>): RowType<any> => {
750
+ const buf = LEAF_BUF;
751
+ BINARY_WRITER.reset(buf);
752
+ serializeRow(BINARY_WRITER, row);
753
+ sys.datastore_update_bsatn(
754
+ table_id,
755
+ index_id,
756
+ buf.buffer,
757
+ BINARY_WRITER.offset
758
+ );
759
+ integrateGeneratedColumns?.(row, buf.view);
760
+ return row;
761
+ };
762
+ }
763
+ index = base as UniqueIndex<any, any>;
764
+ } else if (isUnique) {
765
+ // numColumns != 1, unique index
766
+ const base = {
767
+ find: (colVal: IndexVal<any, any>): RowType<any> | null => {
768
+ if (colVal.length !== numColumns) {
769
+ throw new TypeError('wrong number of elements');
770
+ }
771
+ const buf = LEAF_BUF;
772
+ const point_len = serializePoint(buf, colVal);
773
+ const iter_id = sys.datastore_index_scan_point_bsatn(
774
+ index_id,
775
+ buf.buffer,
776
+ point_len
777
+ );
778
+ return tableIterateOne(iter_id, deserializeRow);
779
+ },
780
+ delete: (colVal: IndexVal<any, any>): boolean => {
781
+ if (colVal.length !== numColumns)
782
+ throw new TypeError('wrong number of elements');
783
+
784
+ const buf = LEAF_BUF;
785
+ const point_len = serializePoint(buf, colVal);
786
+ const num = sys.datastore_delete_by_index_scan_point_bsatn(
787
+ index_id,
788
+ buf.buffer,
789
+ point_len
790
+ );
791
+ return num > 0;
792
+ },
793
+ };
794
+ if (isPrimaryKey) {
795
+ (base as any).update = (row: RowType<any>): RowType<any> => {
796
+ const buf = LEAF_BUF;
797
+ BINARY_WRITER.reset(buf);
798
+ serializeRow(BINARY_WRITER, row);
799
+ sys.datastore_update_bsatn(
800
+ table_id,
801
+ index_id,
802
+ buf.buffer,
803
+ BINARY_WRITER.offset
804
+ );
805
+ integrateGeneratedColumns?.(row, buf.view);
806
+ return row;
807
+ };
808
+ }
809
+ index = base as UniqueIndex<any, any>;
810
+ } else if (serializeSinglePoint) {
811
+ // numColumns == 1
812
+
813
+ const serializeSingleRange = !isHashIndex
814
+ ? (buffer: ResizableBuffer, range: Range<any>): IndexScanArgs => {
815
+ BINARY_WRITER.reset(buffer);
816
+ const writer = BINARY_WRITER;
817
+ const writeBound = (bound: Bound<any>) => {
818
+ const tags = { included: 0, excluded: 1, unbounded: 2 };
819
+ writer.writeU8(tags[bound.tag]);
820
+ if (bound.tag !== 'unbounded')
821
+ serializeSingleElement!(writer, bound.value);
822
+ };
823
+ writeBound(range.from);
824
+ const rstartLen = writer.offset;
825
+ writeBound(range.to);
826
+ const rendLen = writer.offset - rstartLen;
827
+ return [0, 0, rstartLen, rendLen];
828
+ }
829
+ : null;
830
+
831
+ const rawIndex = {
832
+ filter: (range: any): IteratorObject<RowType<any>> => {
833
+ const buf = LEAF_BUF;
834
+ if (serializeSingleRange && range instanceof Range) {
835
+ const args = serializeSingleRange(buf, range);
836
+ const iter_id = sys.datastore_index_scan_range_bsatn(
837
+ index_id,
838
+ buf.buffer,
839
+ ...args
840
+ );
841
+ return tableIterator(iter_id, deserializeRow);
842
+ }
843
+ const point_len = serializeSinglePoint(buf, range);
844
+ const iter_id = sys.datastore_index_scan_point_bsatn(
845
+ index_id,
846
+ buf.buffer,
847
+ point_len
848
+ );
849
+ return tableIterator(iter_id, deserializeRow);
850
+ },
851
+ delete: (range: any): u32 => {
852
+ const buf = LEAF_BUF;
853
+ if (serializeSingleRange && range instanceof Range) {
854
+ const args = serializeSingleRange(buf, range);
855
+ return sys.datastore_delete_by_index_scan_range_bsatn(
856
+ index_id,
857
+ buf.buffer,
858
+ ...args
859
+ );
860
+ }
861
+ const point_len = serializeSinglePoint(buf, range);
862
+ return sys.datastore_delete_by_index_scan_point_bsatn(
863
+ index_id,
864
+ buf.buffer,
865
+ point_len
866
+ );
867
+ },
868
+ };
869
+ if (isHashIndex) {
870
+ index = rawIndex as PointIndex<any, any>;
871
+ } else {
872
+ index = rawIndex as RangedIndex<any, any>;
873
+ }
874
+ } else if (isHashIndex) {
875
+ // numColumns != 1
876
+ index = {
877
+ filter: (range: any[]): IteratorObject<RowType<any>> => {
878
+ const buf = LEAF_BUF;
879
+ const point_len = serializePoint(buf, range);
880
+ const iter_id = sys.datastore_index_scan_point_bsatn(
881
+ index_id,
882
+ buf.buffer,
883
+ point_len
884
+ );
885
+ return tableIterator(iter_id, deserializeRow);
886
+ },
887
+ delete: (range: any[]): u32 => {
888
+ const buf = LEAF_BUF;
889
+ const point_len = serializePoint(buf, range);
890
+ return sys.datastore_delete_by_index_scan_point_bsatn(
891
+ index_id,
892
+ buf.buffer,
893
+ point_len
894
+ );
895
+ },
896
+ } as PointIndex<any, any>;
897
+ } else {
898
+ // numColumns != 1
899
+ const serializeRange = (
900
+ buffer: ResizableBuffer,
901
+ range: any[]
902
+ ): IndexScanArgs => {
903
+ if (range.length > numColumns) throw new TypeError('too many elements');
904
+
905
+ BINARY_WRITER.reset(buffer);
906
+ const writer = BINARY_WRITER;
907
+ const prefix_elems = range.length - 1;
908
+ for (let i = 0; i < prefix_elems; i++) {
909
+ indexSerializers[i](writer, range[i]);
910
+ }
911
+ const rstartOffset = writer.offset;
912
+ const term = range[range.length - 1];
913
+ const serializeTerm = indexSerializers[range.length - 1];
914
+ if (term instanceof Range) {
915
+ const writeBound = (bound: Bound<any>) => {
916
+ const tags = { included: 0, excluded: 1, unbounded: 2 };
917
+ writer.writeU8(tags[bound.tag]);
918
+ if (bound.tag !== 'unbounded') serializeTerm(writer, bound.value);
919
+ };
920
+ writeBound(term.from);
921
+ const rstartLen = writer.offset - rstartOffset;
922
+ writeBound(term.to);
923
+ const rendLen = writer.offset - rstartLen;
924
+ return [rstartOffset, prefix_elems, rstartLen, rendLen];
925
+ } else {
926
+ writer.writeU8(0);
927
+ serializeTerm(writer, term);
928
+ const rstartLen = writer.offset;
929
+ const rendLen = 0;
930
+ return [rstartOffset, prefix_elems, rstartLen, rendLen];
931
+ }
932
+ };
933
+ index = {
934
+ filter: (range: any[]): IteratorObject<RowType<any>> => {
935
+ if (range.length === numColumns) {
936
+ const buf = LEAF_BUF;
937
+ const point_len = serializePoint(buf, range);
938
+ const iter_id = sys.datastore_index_scan_point_bsatn(
939
+ index_id,
940
+ buf.buffer,
941
+ point_len
942
+ );
943
+ return tableIterator(iter_id, deserializeRow);
944
+ } else {
945
+ const buf = LEAF_BUF;
946
+ const args = serializeRange(buf, range);
947
+ const iter_id = sys.datastore_index_scan_range_bsatn(
948
+ index_id,
949
+ buf.buffer,
950
+ ...args
951
+ );
952
+ return tableIterator(iter_id, deserializeRow);
953
+ }
954
+ },
955
+ delete: (range: any[]): u32 => {
956
+ if (range.length === numColumns) {
957
+ const buf = LEAF_BUF;
958
+ const point_len = serializePoint(buf, range);
959
+ return sys.datastore_delete_by_index_scan_point_bsatn(
960
+ index_id,
961
+ buf.buffer,
962
+ point_len
963
+ );
964
+ } else {
965
+ const buf = LEAF_BUF;
966
+ const args = serializeRange(buf, range);
967
+ return sys.datastore_delete_by_index_scan_range_bsatn(
968
+ index_id,
969
+ buf.buffer,
970
+ ...args
971
+ );
972
+ }
973
+ },
974
+ } as RangedIndex<any, any>;
975
+ }
976
+
977
+ // IMPORTANT: duplicate accessor handling.
978
+ // When multiple raw indexes share the same accessor name, we merge index
979
+ // methods onto a single accessor object instead of throwing.
980
+ if (Object.hasOwn(tableView, accessorName)) {
981
+ freeze(Object.assign((tableView as any)[accessorName], index));
982
+ } else {
983
+ (tableView as any)[accessorName] = freeze(index);
984
+ }
985
+ }
986
+
987
+ return freeze(tableView);
988
+ }
989
+
990
+ function* tableIterator<T>(
991
+ id: u32,
992
+ deserialize: Deserializer<T>
993
+ ): Generator<T, undefined> {
994
+ using iter = new IteratorHandle(id);
995
+
996
+ const iterBuf = takeBuf();
997
+ try {
998
+ let amt;
999
+ while ((amt = iter.advance(iterBuf))) {
1000
+ const reader = new BinaryReader(iterBuf.view);
1001
+ while (reader.offset < amt) {
1002
+ yield deserialize(reader);
1003
+ }
1004
+ }
1005
+ } finally {
1006
+ returnBuf(iterBuf);
1007
+ }
1008
+ }
1009
+
1010
+ function tableIterateOne<T>(id: u32, deserialize: Deserializer<T>): T | null {
1011
+ const buf = LEAF_BUF;
1012
+ // we only need to check for the `<= 0` case, since this function is only used
1013
+ // with iterators that should only have zero or one element.
1014
+ const ret = advanceIterRaw(id, buf);
1015
+ if (ret !== 0) {
1016
+ BINARY_READER.reset(buf.view);
1017
+ return deserialize(BINARY_READER);
1018
+ }
1019
+ return null;
1020
+ }
1021
+
1022
+ /**
1023
+ * `ret < 0` means the iterator yielded elements but is now exhausted and has been destroyed.
1024
+ * `ret === 0` means the iterator was empty and has been destroyed.
1025
+ * `ret > 0` means the iterator yielded elements and has more to give.
1026
+ */
1027
+ function advanceIterRaw(id: u32, buf: ResizableBuffer): number {
1028
+ while (true) {
1029
+ try {
1030
+ return 0 | sys.row_iter_bsatn_advance(id, buf.buffer);
1031
+ } catch (e) {
1032
+ if (e && typeof e === 'object' && hasOwn(e, '__buffer_too_small__')) {
1033
+ buf.grow(e.__buffer_too_small__ as number);
1034
+ continue;
1035
+ }
1036
+ throw e;
1037
+ }
1038
+ }
1039
+ }
1040
+
1041
+ // This should guarantee in most cases that we don't have to reallocate an iterator
1042
+ // buffer, unless there's a single row that serializes to >1 MiB.
1043
+ const DEFAULT_BUFFER_CAPACITY = 32 * 1024 * 2;
1044
+
1045
+ const ITER_BUFS: ResizableBuffer[] = [
1046
+ new ResizableBuffer(DEFAULT_BUFFER_CAPACITY),
1047
+ ];
1048
+ let ITER_BUF_COUNT = 1;
1049
+
1050
+ function takeBuf(): ResizableBuffer {
1051
+ return ITER_BUF_COUNT
1052
+ ? ITER_BUFS[--ITER_BUF_COUNT]
1053
+ : new ResizableBuffer(DEFAULT_BUFFER_CAPACITY);
1054
+ }
1055
+
1056
+ function returnBuf(buf: ResizableBuffer) {
1057
+ ITER_BUFS[ITER_BUF_COUNT++] = buf;
1058
+ }
1059
+
1060
+ /**
1061
+ * This should only be used from functions that don't need persistent ownership
1062
+ * over the buffer. While using this value, one should not call a function that
1063
+ * also uses this value.
1064
+ */
1065
+ const LEAF_BUF = new ResizableBuffer(DEFAULT_BUFFER_CAPACITY);
1066
+
1067
+ /** A class to manage the lifecycle of an iterator handle. */
1068
+ class IteratorHandle implements Disposable {
1069
+ #id: u32 | -1;
1070
+
1071
+ static #finalizationRegistry = new FinalizationRegistry<u32>(
1072
+ sys.row_iter_bsatn_close
1073
+ );
1074
+
1075
+ constructor(id: u32) {
1076
+ this.#id = id;
1077
+ IteratorHandle.#finalizationRegistry.register(this, id, this);
1078
+ }
1079
+
1080
+ /** Unregister this object with the finalization registry and return the id */
1081
+ #detach() {
1082
+ const id = this.#id;
1083
+ this.#id = -1;
1084
+ IteratorHandle.#finalizationRegistry.unregister(this);
1085
+ return id;
1086
+ }
1087
+
1088
+ /** Call `row_iter_bsatn_advance`, returning 0 if this iterator has been exhausted. */
1089
+ advance(buf: ResizableBuffer): number {
1090
+ if (this.#id === -1) return 0;
1091
+ const ret = advanceIterRaw(this.#id, buf);
1092
+ if (ret <= 0) this.#detach();
1093
+ return ret < 0 ? -ret : ret;
1094
+ }
1095
+
1096
+ [Symbol.dispose]() {
1097
+ if (this.#id >= 0) {
1098
+ const id = this.#detach();
1099
+ sys.row_iter_bsatn_close(id);
1100
+ }
1101
+ }
1102
+ }