spacetimedb 2.2.0 → 2.4.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 (83) hide show
  1. package/LICENSE.txt +2 -2
  2. package/dist/browser/vue/index.mjs +36 -1
  3. package/dist/browser/vue/index.mjs.map +1 -1
  4. package/dist/index.browser.mjs +50 -4
  5. package/dist/index.browser.mjs.map +1 -1
  6. package/dist/index.cjs +50 -4
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.mjs +50 -4
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/lib/autogen/types.d.ts +674 -18
  11. package/dist/lib/autogen/types.d.ts.map +1 -1
  12. package/dist/lib/reducers.d.ts +2 -0
  13. package/dist/lib/reducers.d.ts.map +1 -1
  14. package/dist/lib/schema.d.ts.map +1 -1
  15. package/dist/min/index.browser.mjs +1 -1
  16. package/dist/min/index.browser.mjs.map +1 -1
  17. package/dist/min/sdk/index.browser.mjs +1 -1
  18. package/dist/min/sdk/index.browser.mjs.map +1 -1
  19. package/dist/sdk/decompress.d.ts.map +1 -1
  20. package/dist/sdk/index.browser.mjs +50 -4
  21. package/dist/sdk/index.browser.mjs.map +1 -1
  22. package/dist/sdk/index.cjs +50 -4
  23. package/dist/sdk/index.cjs.map +1 -1
  24. package/dist/sdk/index.mjs +50 -4
  25. package/dist/sdk/index.mjs.map +1 -1
  26. package/dist/server/http.d.ts +1 -2
  27. package/dist/server/http.d.ts.map +1 -1
  28. package/dist/server/http.test-d.d.ts +2 -0
  29. package/dist/server/http.test-d.d.ts.map +1 -0
  30. package/dist/server/http_handlers.d.ts +82 -0
  31. package/dist/server/http_handlers.d.ts.map +1 -0
  32. package/dist/server/http_internal.d.ts +1 -32
  33. package/dist/server/http_internal.d.ts.map +1 -1
  34. package/dist/server/http_shared.d.ts +44 -0
  35. package/dist/server/http_shared.d.ts.map +1 -0
  36. package/dist/server/index.d.ts +2 -0
  37. package/dist/server/index.d.ts.map +1 -1
  38. package/dist/server/index.mjs +590 -136
  39. package/dist/server/index.mjs.map +1 -1
  40. package/dist/server/procedures.d.ts +2 -0
  41. package/dist/server/procedures.d.ts.map +1 -1
  42. package/dist/server/runtime.d.ts +2 -0
  43. package/dist/server/runtime.d.ts.map +1 -1
  44. package/dist/server/schema.d.ts +16 -1
  45. package/dist/server/schema.d.ts.map +1 -1
  46. package/dist/server/views.d.ts.map +1 -1
  47. package/dist/tanstack/index.cjs +34 -0
  48. package/dist/tanstack/index.cjs.map +1 -1
  49. package/dist/tanstack/index.d.ts +1 -0
  50. package/dist/tanstack/index.d.ts.map +1 -1
  51. package/dist/tanstack/index.mjs +34 -1
  52. package/dist/tanstack/index.mjs.map +1 -1
  53. package/dist/vue/index.cjs +36 -0
  54. package/dist/vue/index.cjs.map +1 -1
  55. package/dist/vue/index.d.ts +1 -0
  56. package/dist/vue/index.d.ts.map +1 -1
  57. package/dist/vue/index.mjs +36 -1
  58. package/dist/vue/index.mjs.map +1 -1
  59. package/dist/vue/useProcedure.d.ts +4 -0
  60. package/dist/vue/useProcedure.d.ts.map +1 -0
  61. package/package.json +1 -1
  62. package/src/lib/autogen/types.ts +29 -0
  63. package/src/lib/reducers.ts +2 -0
  64. package/src/lib/schema.ts +14 -0
  65. package/src/sdk/decompress.ts +19 -4
  66. package/src/sdk/logger.ts +1 -1
  67. package/src/server/http.test-d.ts +80 -0
  68. package/src/server/http.ts +14 -2
  69. package/src/server/http_handlers.ts +413 -0
  70. package/src/server/http_internal.ts +15 -142
  71. package/src/server/http_shared.ts +186 -0
  72. package/src/server/index.ts +11 -0
  73. package/src/server/procedures.ts +15 -31
  74. package/src/server/runtime.ts +142 -2
  75. package/src/server/schema.ts +71 -2
  76. package/src/server/sys.d.ts +7 -0
  77. package/src/server/views.ts +1 -0
  78. package/src/tanstack/index.ts +1 -0
  79. package/src/vue/index.ts +1 -0
  80. package/src/vue/useProcedure.ts +62 -0
  81. package/dist/lib/http_types.d.ts +0 -2
  82. package/dist/lib/http_types.d.ts.map +0 -1
  83. package/src/lib/http_types.ts +0 -8
@@ -1,133 +1,25 @@
1
- import { Headers, headersToList } from 'headers-polyfill';
2
- import status from 'statuses';
3
1
  import BinaryReader from '../lib/binary_reader';
4
2
  import BinaryWriter from '../lib/binary_writer';
5
- import {
6
- HttpHeaders,
7
- HttpMethod,
8
- HttpRequest,
9
- HttpResponse,
10
- } from '../lib/http_types';
3
+ import status from 'statuses';
4
+ import { HttpRequest, HttpResponse } from '../lib/autogen/types';
11
5
  import type { TimeDuration } from '../lib/time_duration';
12
6
  import { bsatnBaseSize } from '../lib/util';
7
+ import {
8
+ type BodyInit,
9
+ type HeadersInit,
10
+ deserializeHeaders,
11
+ Headers,
12
+ makeResponse,
13
+ serializeHeaders,
14
+ serializeMethod,
15
+ SyncResponse,
16
+ } from './http_shared';
13
17
  import { sys } from './runtime';
14
18
 
15
19
  export { Headers };
16
20
 
17
21
  const { freeze } = Object;
18
22
 
19
- export type BodyInit = ArrayBuffer | ArrayBufferView | string;
20
- export type HeadersInit = [string, string][] | Record<string, string> | Headers;
21
- export interface ResponseInit {
22
- headers?: HeadersInit;
23
- status?: number;
24
- statusText?: string;
25
- }
26
-
27
- const textEncoder = new TextEncoder();
28
- const textDecoder = new TextDecoder('utf-8' /* { fatal: true } */);
29
-
30
- function deserializeHeaders(headers: HttpHeaders): Headers {
31
- return new Headers(
32
- headers.entries.map(({ name, value }): [string, string] => [
33
- name,
34
- textDecoder.decode(value),
35
- ])
36
- );
37
- }
38
-
39
- const makeResponse = Symbol('makeResponse');
40
-
41
- // based on deno's type of the same name
42
- interface InnerResponse {
43
- type: 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect';
44
- url: string | null;
45
- status: number;
46
- statusText: string;
47
- headers: Headers;
48
- aborted: boolean;
49
- }
50
-
51
- export class SyncResponse {
52
- #body: string | ArrayBuffer | null;
53
- #inner: InnerResponse;
54
-
55
- constructor(body?: BodyInit | null, init?: ResponseInit) {
56
- if (body == null) {
57
- this.#body = null;
58
- } else if (typeof body === 'string') {
59
- this.#body = body;
60
- } else {
61
- // this call is fine, the typings are just weird
62
- this.#body = new Uint8Array<ArrayBuffer>(body as any).buffer;
63
- }
64
-
65
- // there's a type mismatch - headers-polyfill's typing doesn't expect its
66
- // own `Headers` type, even though the actual code handles it correctly.
67
- this.#inner = {
68
- headers: new Headers(init?.headers as any),
69
- status: init?.status ?? 200,
70
- statusText: init?.statusText ?? '',
71
- type: 'default',
72
- url: null,
73
- aborted: false,
74
- };
75
- }
76
-
77
- static [makeResponse](body: BodyInit | null, inner: InnerResponse) {
78
- const me = new SyncResponse(body);
79
- me.#inner = inner;
80
- return me;
81
- }
82
-
83
- get headers(): Headers {
84
- return this.#inner.headers;
85
- }
86
- get status(): number {
87
- return this.#inner.status;
88
- }
89
- get statusText() {
90
- return this.#inner.statusText;
91
- }
92
- get ok(): boolean {
93
- return 200 <= this.#inner.status && this.#inner.status <= 299;
94
- }
95
- get url(): string {
96
- return this.#inner.url ?? '';
97
- }
98
- get type() {
99
- return this.#inner.type;
100
- }
101
-
102
- arrayBuffer(): ArrayBuffer {
103
- return this.bytes().buffer;
104
- }
105
-
106
- bytes(): Uint8Array<ArrayBuffer> {
107
- if (this.#body == null) {
108
- return new Uint8Array();
109
- } else if (typeof this.#body === 'string') {
110
- return textEncoder.encode(this.#body);
111
- } else {
112
- return new Uint8Array(this.#body);
113
- }
114
- }
115
-
116
- json(): any {
117
- return JSON.parse(this.text());
118
- }
119
-
120
- text(): string {
121
- if (this.#body == null) {
122
- return '';
123
- } else if (typeof this.#body === 'string') {
124
- return this.#body;
125
- } else {
126
- return textDecoder.decode(this.#body);
127
- }
128
- }
129
- }
130
-
131
23
  export interface RequestOptions {
132
24
  /** A BodyInit object or null to set request's body. */
133
25
  body?: BodyInit | null;
@@ -147,29 +39,9 @@ export interface HttpClient {
147
39
 
148
40
  const requestBaseSize = bsatnBaseSize({ types: [] }, HttpRequest.algebraicType);
149
41
 
150
- const methods = new Map<string, HttpMethod>([
151
- ['GET', { tag: 'Get' }],
152
- ['HEAD', { tag: 'Head' }],
153
- ['POST', { tag: 'Post' }],
154
- ['PUT', { tag: 'Put' }],
155
- ['DELETE', { tag: 'Delete' }],
156
- ['CONNECT', { tag: 'Connect' }],
157
- ['OPTIONS', { tag: 'Options' }],
158
- ['TRACE', { tag: 'Trace' }],
159
- ['PATCH', { tag: 'Patch' }],
160
- ]);
161
-
162
42
  function fetch(url: URL | string, init: RequestOptions = {}) {
163
- const method = methods.get(init.method?.toUpperCase() ?? 'GET') ?? {
164
- tag: 'Extension',
165
- value: init.method!,
166
- };
167
- const headers: HttpHeaders = {
168
- // anys because the typings are wonky - see comment in SyncResponse.constructor
169
- entries: headersToList(new Headers(init.headers as any) as any)
170
- .flatMap(([k, v]) => (Array.isArray(v) ? v.map(v => [k, v]) : [[k, v]]))
171
- .map(([name, value]) => ({ name, value: textEncoder.encode(value) })),
172
- };
43
+ const method = serializeMethod(init.method);
44
+ const headers = serializeHeaders(new Headers(init.headers as any));
173
45
  const uri = '' + url;
174
46
  const request: HttpRequest = freeze({
175
47
  method,
@@ -198,6 +70,7 @@ function fetch(url: URL | string, init: RequestOptions = {}) {
198
70
  statusText: status(response.code),
199
71
  headers: deserializeHeaders(response.headers),
200
72
  aborted: false,
73
+ version: response.version,
201
74
  });
202
75
  }
203
76
 
@@ -0,0 +1,186 @@
1
+ import { Headers, headersToList } from 'headers-polyfill';
2
+ import type {
3
+ HttpHeaders,
4
+ HttpMethod,
5
+ HttpVersion,
6
+ } from '../lib/autogen/types';
7
+
8
+ export { Headers };
9
+
10
+ export type BodyInit = ArrayBuffer | ArrayBufferView | string;
11
+ export type HeadersInit = [string, string][] | Record<string, string> | Headers;
12
+
13
+ export const textEncoder = new TextEncoder();
14
+ export const textDecoder = new TextDecoder('utf-8');
15
+
16
+ export function deserializeMethod(method: HttpMethod): string {
17
+ switch (method.tag) {
18
+ case 'Get':
19
+ return 'GET';
20
+ case 'Head':
21
+ return 'HEAD';
22
+ case 'Post':
23
+ return 'POST';
24
+ case 'Put':
25
+ return 'PUT';
26
+ case 'Delete':
27
+ return 'DELETE';
28
+ case 'Connect':
29
+ return 'CONNECT';
30
+ case 'Options':
31
+ return 'OPTIONS';
32
+ case 'Trace':
33
+ return 'TRACE';
34
+ case 'Patch':
35
+ return 'PATCH';
36
+ case 'Extension':
37
+ return method.value;
38
+ }
39
+ }
40
+
41
+ const methods = new Map<string, HttpMethod>([
42
+ ['GET', { tag: 'Get' }],
43
+ ['HEAD', { tag: 'Head' }],
44
+ ['POST', { tag: 'Post' }],
45
+ ['PUT', { tag: 'Put' }],
46
+ ['DELETE', { tag: 'Delete' }],
47
+ ['CONNECT', { tag: 'Connect' }],
48
+ ['OPTIONS', { tag: 'Options' }],
49
+ ['TRACE', { tag: 'Trace' }],
50
+ ['PATCH', { tag: 'Patch' }],
51
+ ]);
52
+
53
+ export function serializeMethod(method?: string): HttpMethod {
54
+ return (
55
+ methods.get(method?.toUpperCase() ?? 'GET') ?? {
56
+ tag: 'Extension',
57
+ value: method!,
58
+ }
59
+ );
60
+ }
61
+
62
+ export function serializeHeaders(headers: Headers): HttpHeaders {
63
+ return {
64
+ entries: headersToList(headers as any)
65
+ .flatMap(([k, v]) => (Array.isArray(v) ? v.map(v => [k, v]) : [[k, v]]))
66
+ .map(([name, value]) => ({ name, value: textEncoder.encode(value) })),
67
+ };
68
+ }
69
+
70
+ export function deserializeHeaders(headers: HttpHeaders): Headers {
71
+ return new Headers(
72
+ headers.entries.map(({ name, value }): [string, string] => [
73
+ name,
74
+ textDecoder.decode(value),
75
+ ])
76
+ );
77
+ }
78
+
79
+ export interface ResponseInit {
80
+ headers?: HeadersInit;
81
+ status?: number;
82
+ statusText?: string;
83
+ version?: HttpVersion;
84
+ }
85
+
86
+ export interface InnerResponse {
87
+ type: 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect';
88
+ url: string | null;
89
+ status: number;
90
+ statusText: string;
91
+ headers: Headers;
92
+ aborted: boolean;
93
+ version: HttpVersion;
94
+ }
95
+
96
+ export const makeResponse = Symbol('makeResponse');
97
+
98
+ export class SyncResponse {
99
+ #body: string | ArrayBuffer | null;
100
+ #inner: InnerResponse;
101
+
102
+ constructor(body?: BodyInit | null, init?: ResponseInit) {
103
+ if (body == null) {
104
+ this.#body = null;
105
+ } else if (typeof body === 'string') {
106
+ this.#body = body;
107
+ } else {
108
+ // this call is fine, the typings are just weird
109
+ this.#body = new Uint8Array<ArrayBuffer>(body as any).buffer;
110
+ }
111
+
112
+ // there's a type mismatch - headers-polyfill's typing doesn't expect its
113
+ // own `Headers` type, even though the actual code handles it correctly.
114
+ this.#inner = {
115
+ headers: new Headers(init?.headers as any),
116
+ status: init?.status ?? 200,
117
+ statusText: init?.statusText ?? '',
118
+ type: 'default',
119
+ url: null,
120
+ aborted: false,
121
+ version: init?.version ?? { tag: 'Http11' },
122
+ };
123
+ }
124
+
125
+ static [makeResponse](body: BodyInit | null, inner: InnerResponse) {
126
+ const me = new SyncResponse(body);
127
+ me.#inner = inner;
128
+ return me;
129
+ }
130
+
131
+ get headers(): Headers {
132
+ return this.#inner.headers;
133
+ }
134
+
135
+ get status(): number {
136
+ return this.#inner.status;
137
+ }
138
+
139
+ get statusText() {
140
+ return this.#inner.statusText;
141
+ }
142
+
143
+ get ok(): boolean {
144
+ return 200 <= this.#inner.status && this.#inner.status <= 299;
145
+ }
146
+
147
+ get url(): string {
148
+ return this.#inner.url ?? '';
149
+ }
150
+
151
+ get type() {
152
+ return this.#inner.type;
153
+ }
154
+
155
+ get version(): HttpVersion {
156
+ return this.#inner.version;
157
+ }
158
+
159
+ arrayBuffer(): ArrayBuffer {
160
+ return this.bytes().buffer;
161
+ }
162
+
163
+ bytes(): Uint8Array<ArrayBuffer> {
164
+ if (this.#body == null) {
165
+ return new Uint8Array();
166
+ } else if (typeof this.#body === 'string') {
167
+ return textEncoder.encode(this.#body);
168
+ } else {
169
+ return new Uint8Array(this.#body);
170
+ }
171
+ }
172
+
173
+ json(): any {
174
+ return JSON.parse(this.text());
175
+ }
176
+
177
+ text(): string {
178
+ if (this.#body == null) {
179
+ return '';
180
+ } else if (typeof this.#body === 'string') {
181
+ return this.#body;
182
+ } else {
183
+ return textDecoder.decode(this.#body);
184
+ }
185
+ }
186
+ }
@@ -22,5 +22,16 @@ export type { Uuid } from '../lib/uuid';
22
22
  export type { Random } from './rng';
23
23
  export type { ViewExport, ViewCtx, AnonymousViewCtx } from './views';
24
24
  export { Range, type Bound } from './range';
25
+ export {
26
+ Headers,
27
+ Request,
28
+ SyncResponse,
29
+ Router,
30
+ type BodyInit,
31
+ type HeadersInit,
32
+ type RequestInit,
33
+ type ResponseInit,
34
+ } from './http';
35
+ export type { HandlerContext, HttpHandlerExport } from './http';
25
36
 
26
37
  import './polyfills'; // Ensure polyfills are loaded
@@ -22,7 +22,7 @@ import { Uuid } from '../lib/uuid';
22
22
  import { httpClient, type HttpClient } from './http_internal';
23
23
  import type { DbView } from './db_view';
24
24
  import { makeRandom, type Random } from './rng';
25
- import { callUserFunction, ReducerCtxImpl, sys } from './runtime';
25
+ import { callUserFunction, ReducerCtxImpl, runWithTx, sys } from './runtime';
26
26
  import {
27
27
  exportContext,
28
28
  registerExport,
@@ -75,6 +75,8 @@ export interface ProcedureOpts {
75
75
 
76
76
  export interface ProcedureCtx<S extends UntypedSchemaDef> {
77
77
  readonly sender: Identity;
78
+ readonly databaseIdentity: Identity;
79
+ /** @deprecated Use `databaseIdentity` instead. */
78
80
  readonly identity: Identity;
79
81
  readonly timestamp: Timestamp;
80
82
  readonly connectionId: ConnectionId | null;
@@ -195,10 +197,14 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
195
197
  this.#dbView = dbView;
196
198
  }
197
199
 
198
- get identity() {
200
+ get databaseIdentity() {
199
201
  return (this.#identity ??= new Identity(sys.identity()));
200
202
  }
201
203
 
204
+ get identity() {
205
+ return this.databaseIdentity;
206
+ }
207
+
202
208
  get random() {
203
209
  return (this.#random ??= makeRandom(this.timestamp));
204
210
  }
@@ -208,38 +214,16 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
208
214
  }
209
215
 
210
216
  withTx<T>(body: (ctx: TransactionCtx<S>) => T): T {
211
- const run = () => {
212
- const timestamp = sys.procedure_start_mut_tx();
213
-
214
- try {
215
- const ctx: TransactionCtx<S> = new TransactionCtxImpl(
217
+ return runWithTx(
218
+ timestamp =>
219
+ new TransactionCtxImpl(
216
220
  this.sender,
217
- new Timestamp(timestamp),
221
+ timestamp,
218
222
  this.connectionId,
219
223
  this.#dbView()
220
- );
221
- return body(ctx);
222
- } catch (e) {
223
- sys.procedure_abort_mut_tx();
224
- throw e;
225
- }
226
- };
227
-
228
- let res = run();
229
- try {
230
- sys.procedure_commit_mut_tx();
231
- return res;
232
- } catch {
233
- // ignore the commit error
234
- }
235
- console.warn('committing anonymous transaction failed');
236
- res = run();
237
- try {
238
- sys.procedure_commit_mut_tx();
239
- return res;
240
- } catch (e) {
241
- throw new Error('transaction retry failed again', { cause: e });
242
- }
224
+ ) as TransactionCtx<S>,
225
+ body
226
+ );
243
227
  }
244
228
 
245
229
  newUuidV4(): Uuid {
@@ -27,6 +27,18 @@ import {
27
27
  type UniqueIndex,
28
28
  } from '../lib/indexes';
29
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';
30
42
  import {
31
43
  type AuthCtx,
32
44
  type JsonObject,
@@ -35,7 +47,7 @@ import {
35
47
  } from '../lib/reducers';
36
48
  import { type UntypedSchemaDef } from '../lib/schema';
37
49
  import { type RowType, type Table, type TableMethods } from '../lib/table';
38
- import { hasOwn } from '../lib/util';
50
+ import { bsatnBaseSize, hasOwn } from '../lib/util';
39
51
  import { type AnonymousViewCtx, type ViewCtx } from './views';
40
52
  import { isRowTypedQuery, makeQueryBuilder, toSql } from './query';
41
53
  import type { DbView } from './db_view';
@@ -43,11 +55,32 @@ import { getErrorConstructor, SenderError } from './errors';
43
55
  import { Range, type Bound } from './range';
44
56
  import { makeRandom, type Random } from './rng';
45
57
  import type { SchemaInner } from './schema';
58
+ import { HttpRequest, HttpResponse } from '../lib/autogen/types';
46
59
 
47
60
  const { freeze } = Object;
48
61
 
49
62
  export const sys = { ..._syscalls2_0, ..._syscalls2_1 };
50
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
+
51
84
  export function parseJsonObject(json: string): JsonObject {
52
85
  let value: unknown;
53
86
 
@@ -221,10 +254,14 @@ export const ReducerCtxImpl = class ReducerCtx<
221
254
  me.#senderAuth = undefined;
222
255
  }
223
256
 
224
- get identity() {
257
+ get databaseIdentity() {
225
258
  return (this.#identity ??= new Identity(sys.identity()));
226
259
  }
227
260
 
261
+ get identity() {
262
+ return this.databaseIdentity;
263
+ }
264
+
228
265
  get senderAuth() {
229
266
  return (this.#senderAuth ??= AuthCtxImpl.fromSystemTables(
230
267
  this.connectionId,
@@ -268,6 +305,38 @@ export const callUserFunction = function __spacetimedb_end_short_backtrace<
268
305
  return fn(...args);
269
306
  };
270
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
+
271
340
  export const makeHooks = (schema: SchemaInner): ModuleHooks =>
272
341
  new ModuleHooksImpl(schema);
273
342
 
@@ -414,11 +483,82 @@ class ModuleHooksImpl implements ModuleHooks {
414
483
  () => this.#dbView
415
484
  );
416
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
+ }
417
512
  }
418
513
 
419
514
  const BINARY_WRITER = new BinaryWriter(0);
420
515
  const BINARY_READER = new BinaryReader(new Uint8Array());
421
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
+
422
562
  function makeTableView(
423
563
  typespace: Typespace,
424
564
  table: RawTableDefV10