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.
- package/LICENSE.txt +759 -759
- package/README.md +211 -120
- package/dist/angular/index.cjs.map +1 -1
- package/dist/angular/index.mjs.map +1 -1
- package/dist/browser/angular/index.mjs.map +1 -1
- package/dist/browser/react/index.mjs +129 -57
- package/dist/browser/react/index.mjs.map +1 -1
- package/dist/browser/solid/index.mjs +1933 -0
- package/dist/browser/solid/index.mjs.map +1 -0
- package/dist/browser/svelte/index.mjs.map +1 -1
- package/dist/browser/vue/index.mjs.map +1 -1
- package/dist/index.browser.mjs +10 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.cjs +10 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/index.mjs.map +1 -1
- package/dist/min/index.browser.mjs +1 -1
- package/dist/min/index.browser.mjs.map +1 -1
- package/dist/min/react/index.mjs +1 -1
- package/dist/min/react/index.mjs.map +1 -1
- package/dist/min/sdk/index.browser.mjs +1 -1
- package/dist/min/sdk/index.browser.mjs.map +1 -1
- package/dist/react/index.cjs +129 -57
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.mjs +129 -57
- package/dist/react/index.mjs.map +1 -1
- package/dist/react/useTable.d.ts.map +1 -1
- package/dist/sdk/connection_manager.d.ts +8 -0
- package/dist/sdk/connection_manager.d.ts.map +1 -1
- package/dist/sdk/db_connection_impl.d.ts +7 -0
- package/dist/sdk/db_connection_impl.d.ts.map +1 -1
- package/dist/sdk/index.browser.mjs +10 -2
- package/dist/sdk/index.browser.mjs.map +1 -1
- package/dist/sdk/index.cjs +10 -2
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.mjs +10 -2
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/sdk/websocket_test_adapter.d.ts +2 -1
- package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/solid/SpacetimeDBProvider.d.ts +7 -0
- package/dist/solid/SpacetimeDBProvider.d.ts.map +1 -0
- package/dist/solid/connection_state.d.ts +6 -0
- package/dist/solid/connection_state.d.ts.map +1 -0
- package/dist/solid/index.cjs +1939 -0
- package/dist/solid/index.cjs.map +1 -0
- package/dist/solid/index.d.ts +6 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.mjs +1933 -0
- package/dist/solid/index.mjs.map +1 -0
- package/dist/solid/useProcedure.d.ts +4 -0
- package/dist/solid/useProcedure.d.ts.map +1 -0
- package/dist/solid/useReducer.d.ts +4 -0
- package/dist/solid/useReducer.d.ts.map +1 -0
- package/dist/solid/useSpacetimeDB.d.ts +4 -0
- package/dist/solid/useSpacetimeDB.d.ts.map +1 -0
- package/dist/solid/useTable.d.ts +32 -0
- package/dist/solid/useTable.d.ts.map +1 -0
- package/dist/svelte/index.cjs.map +1 -1
- package/dist/svelte/index.mjs.map +1 -1
- package/dist/tanstack/index.cjs +120 -50
- package/dist/tanstack/index.cjs.map +1 -1
- package/dist/tanstack/index.mjs +120 -50
- package/dist/tanstack/index.mjs.map +1 -1
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.mjs.map +1 -1
- package/package.json +13 -3
- package/src/angular/connection_state.ts +19 -19
- package/src/angular/index.ts +3 -3
- package/src/angular/injectors/index.ts +4 -4
- package/src/angular/injectors/inject-reducer.ts +62 -62
- package/src/angular/injectors/inject-spacetimedb-connected.ts +13 -13
- package/src/angular/injectors/inject-spacetimedb.ts +10 -10
- package/src/angular/injectors/inject-table.ts +234 -234
- package/src/angular/providers/index.ts +1 -1
- package/src/angular/providers/provide-spacetimedb.ts +96 -96
- package/src/index.ts +16 -16
- package/src/lib/algebraic_type.ts +819 -819
- package/src/lib/algebraic_type_variants.ts +26 -26
- package/src/lib/algebraic_value.ts +10 -10
- package/src/lib/autogen/types.ts +746 -746
- package/src/lib/binary_reader.ts +188 -188
- package/src/lib/binary_writer.ts +213 -213
- package/src/lib/connection_id.ts +102 -102
- package/src/lib/constraints.ts +48 -48
- package/src/lib/errors.ts +26 -26
- package/src/lib/filter.ts +195 -195
- package/src/lib/identity.ts +83 -83
- package/src/lib/indexes.ts +251 -251
- package/src/lib/option.ts +34 -34
- package/src/lib/query.ts +1019 -1019
- package/src/lib/reducer_schema.ts +38 -38
- package/src/lib/reducers.ts +116 -116
- package/src/lib/result.ts +36 -36
- package/src/lib/schedule_at.ts +86 -86
- package/src/lib/schema.ts +420 -420
- package/src/lib/table.ts +548 -548
- package/src/lib/table_schema.ts +64 -64
- package/src/lib/time_duration.ts +77 -77
- package/src/lib/timestamp.ts +148 -148
- package/src/lib/type_builders.test-d.ts +128 -128
- package/src/lib/type_builders.ts +4014 -4014
- package/src/lib/type_util.ts +124 -124
- package/src/lib/util.ts +196 -196
- package/src/lib/uuid.ts +337 -337
- package/src/react/SpacetimeDBProvider.ts +84 -84
- package/src/react/connection_state.ts +6 -6
- package/src/react/index.ts +5 -5
- package/src/react/useProcedure.ts +60 -60
- package/src/react/useReducer.ts +53 -53
- package/src/react/useSpacetimeDB.ts +18 -18
- package/src/react/useTable.ts +256 -251
- package/src/sdk/client_api/index.ts +114 -114
- package/src/sdk/client_api/types/procedures.ts +8 -8
- package/src/sdk/client_api/types/reducers.ts +8 -8
- package/src/sdk/client_api/types.ts +288 -288
- package/src/sdk/client_cache.ts +129 -129
- package/src/sdk/client_table.ts +179 -179
- package/src/sdk/connection_manager.ts +352 -237
- package/src/sdk/db_connection_builder.ts +290 -290
- package/src/sdk/db_connection_impl.ts +1356 -1347
- package/src/sdk/db_context.ts +28 -28
- package/src/sdk/db_view.ts +12 -12
- package/src/sdk/decompress.ts +51 -51
- package/src/sdk/event.ts +18 -18
- package/src/sdk/event_context.ts +51 -51
- package/src/sdk/event_emitter.ts +32 -32
- package/src/sdk/index.ts +14 -14
- package/src/sdk/internal.ts +2 -2
- package/src/sdk/json_api.ts +46 -46
- package/src/sdk/logger.ts +134 -134
- package/src/sdk/message_types.ts +46 -46
- package/src/sdk/procedures.ts +83 -83
- package/src/sdk/reducer_event.ts +20 -20
- package/src/sdk/reducer_handle.ts +12 -12
- package/src/sdk/reducers.ts +159 -159
- package/src/sdk/schema.ts +45 -45
- package/src/sdk/spacetime_module.ts +28 -28
- package/src/sdk/subscription_builder_impl.ts +275 -275
- package/src/sdk/table_cache.ts +581 -581
- package/src/sdk/type_utils.ts +19 -19
- package/src/sdk/version.ts +133 -133
- package/src/sdk/websocket_decompress_adapter.ts +63 -63
- package/src/sdk/websocket_protocols.ts +25 -25
- package/src/sdk/websocket_test_adapter.ts +107 -100
- package/src/sdk/websocket_v3_frames.ts +126 -126
- package/src/sdk/ws.ts +105 -105
- package/src/server/console.ts +81 -81
- package/src/server/db_view.ts +21 -21
- package/src/server/errors.ts +138 -138
- package/src/server/http.test-d.ts +80 -80
- package/src/server/http.ts +14 -14
- package/src/server/http_handlers.ts +413 -413
- package/src/server/http_internal.ts +79 -79
- package/src/server/http_shared.ts +186 -186
- package/src/server/index.ts +37 -37
- package/src/server/polyfills.ts +4 -4
- package/src/server/procedures.ts +239 -239
- package/src/server/query.ts +1 -1
- package/src/server/range.ts +53 -53
- package/src/server/reducers.ts +113 -113
- package/src/server/rng.ts +113 -113
- package/src/server/runtime.ts +1102 -1102
- package/src/server/schema.test-d.ts +99 -99
- package/src/server/schema.ts +663 -663
- package/src/server/sys.d.ts +125 -125
- package/src/server/view.test-d.ts +194 -194
- package/src/server/views.ts +340 -340
- package/src/solid/SpacetimeDBProvider.ts +97 -0
- package/src/solid/connection_state.ts +6 -0
- package/src/solid/index.ts +5 -0
- package/src/solid/useProcedure.ts +57 -0
- package/src/solid/useReducer.ts +50 -0
- package/src/solid/useSpacetimeDB.ts +18 -0
- package/src/solid/useTable.ts +203 -0
- package/src/svelte/SpacetimeDBProvider.ts +101 -101
- package/src/svelte/connection_state.ts +16 -16
- package/src/svelte/index.ts +4 -4
- package/src/svelte/useReducer.ts +61 -61
- package/src/svelte/useSpacetimeDB.ts +22 -22
- package/src/svelte/useTable.ts +218 -218
- package/src/tanstack/SpacetimeDBQueryClient.ts +330 -330
- package/src/tanstack/hooks.ts +83 -83
- package/src/tanstack/index.ts +16 -16
- package/src/util-stub.ts +1 -1
- package/src/vue/SpacetimeDBProvider.ts +157 -157
- package/src/vue/connection_state.ts +19 -19
- package/src/vue/index.ts +5 -5
- package/src/vue/useProcedure.ts +62 -62
- package/src/vue/useReducer.ts +55 -55
- package/src/vue/useSpacetimeDB.ts +18 -18
- package/src/vue/useTable.ts +229 -229
package/src/server/runtime.ts
CHANGED
|
@@ -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
|
+
}
|