spacetimedb 2.3.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.
- package/LICENSE.txt +2 -2
- package/dist/index.browser.mjs +50 -4
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.cjs +50 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +50 -4
- package/dist/index.mjs.map +1 -1
- package/dist/lib/autogen/types.d.ts +674 -18
- package/dist/lib/autogen/types.d.ts.map +1 -1
- package/dist/lib/schema.d.ts.map +1 -1
- package/dist/min/index.browser.mjs +1 -1
- package/dist/min/index.browser.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/sdk/decompress.d.ts.map +1 -1
- package/dist/sdk/index.browser.mjs +50 -4
- package/dist/sdk/index.browser.mjs.map +1 -1
- package/dist/sdk/index.cjs +50 -4
- package/dist/sdk/index.cjs.map +1 -1
- package/dist/sdk/index.mjs +50 -4
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/server/http.d.ts +1 -2
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.test-d.d.ts +2 -0
- package/dist/server/http.test-d.d.ts.map +1 -0
- package/dist/server/http_handlers.d.ts +82 -0
- package/dist/server/http_handlers.d.ts.map +1 -0
- package/dist/server/http_internal.d.ts +1 -32
- package/dist/server/http_internal.d.ts.map +1 -1
- package/dist/server/http_shared.d.ts +44 -0
- package/dist/server/http_shared.d.ts.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.mjs +582 -134
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/runtime.d.ts +1 -0
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/schema.d.ts +16 -1
- package/dist/server/schema.d.ts.map +1 -1
- package/dist/server/views.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/autogen/types.ts +29 -0
- package/src/lib/schema.ts +14 -0
- package/src/sdk/decompress.ts +19 -4
- package/src/sdk/logger.ts +1 -1
- package/src/server/http.test-d.ts +80 -0
- package/src/server/http.ts +14 -2
- package/src/server/http_handlers.ts +413 -0
- package/src/server/http_internal.ts +15 -142
- package/src/server/http_shared.ts +186 -0
- package/src/server/index.ts +11 -0
- package/src/server/procedures.ts +8 -30
- package/src/server/runtime.ts +137 -1
- package/src/server/schema.ts +71 -2
- package/src/server/sys.d.ts +7 -0
- package/src/server/views.ts +1 -0
- package/dist/lib/http_types.d.ts +0 -2
- package/dist/lib/http_types.d.ts.map +0 -1
- package/src/lib/http_types.ts +0 -8
|
@@ -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
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -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
|
package/src/server/procedures.ts
CHANGED
|
@@ -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,
|
|
@@ -214,38 +214,16 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
withTx<T>(body: (ctx: TransactionCtx<S>) => T): T {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
const ctx: TransactionCtx<S> = new TransactionCtxImpl(
|
|
217
|
+
return runWithTx(
|
|
218
|
+
timestamp =>
|
|
219
|
+
new TransactionCtxImpl(
|
|
222
220
|
this.sender,
|
|
223
|
-
|
|
221
|
+
timestamp,
|
|
224
222
|
this.connectionId,
|
|
225
223
|
this.#dbView()
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
sys.procedure_abort_mut_tx();
|
|
230
|
-
throw e;
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
let res = run();
|
|
235
|
-
try {
|
|
236
|
-
sys.procedure_commit_mut_tx();
|
|
237
|
-
return res;
|
|
238
|
-
} catch {
|
|
239
|
-
// ignore the commit error
|
|
240
|
-
}
|
|
241
|
-
console.warn('committing anonymous transaction failed');
|
|
242
|
-
res = run();
|
|
243
|
-
try {
|
|
244
|
-
sys.procedure_commit_mut_tx();
|
|
245
|
-
return res;
|
|
246
|
-
} catch (e) {
|
|
247
|
-
throw new Error('transaction retry failed again', { cause: e });
|
|
248
|
-
}
|
|
224
|
+
) as TransactionCtx<S>,
|
|
225
|
+
body
|
|
226
|
+
);
|
|
249
227
|
}
|
|
250
228
|
|
|
251
229
|
newUuidV4(): Uuid {
|
package/src/server/runtime.ts
CHANGED
|
@@ -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
|
|
|
@@ -272,6 +305,38 @@ export const callUserFunction = function __spacetimedb_end_short_backtrace<
|
|
|
272
305
|
return fn(...args);
|
|
273
306
|
};
|
|
274
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
|
+
|
|
275
340
|
export const makeHooks = (schema: SchemaInner): ModuleHooks =>
|
|
276
341
|
new ModuleHooksImpl(schema);
|
|
277
342
|
|
|
@@ -418,11 +483,82 @@ class ModuleHooksImpl implements ModuleHooks {
|
|
|
418
483
|
() => this.#dbView
|
|
419
484
|
);
|
|
420
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
|
+
}
|
|
421
512
|
}
|
|
422
513
|
|
|
423
514
|
const BINARY_WRITER = new BinaryWriter(0);
|
|
424
515
|
const BINARY_READER = new BinaryReader(new Uint8Array());
|
|
425
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
|
+
|
|
426
562
|
function makeTableView(
|
|
427
563
|
typespace: Typespace,
|
|
428
564
|
table: RawTableDefV10
|
package/src/server/schema.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { moduleHooks, type ModuleDefaultExport } from 'spacetime:sys@2.0';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CaseConversionPolicy,
|
|
4
|
+
Lifecycle,
|
|
5
|
+
type MethodOrAny,
|
|
6
|
+
} from '../lib/autogen/types';
|
|
3
7
|
import {
|
|
4
8
|
type ParamsAsObject,
|
|
5
9
|
type ParamsObj,
|
|
@@ -14,6 +18,14 @@ import {
|
|
|
14
18
|
} from '../lib/schema';
|
|
15
19
|
import type { UntypedTableSchema } from '../lib/table_schema';
|
|
16
20
|
import { ColumnBuilder, TypeBuilder } from '../lib/type_builders';
|
|
21
|
+
import {
|
|
22
|
+
Router,
|
|
23
|
+
type HandlerFn,
|
|
24
|
+
type HttpHandlerExport,
|
|
25
|
+
type HttpHandlerOpts,
|
|
26
|
+
makeHttpHandlerExport,
|
|
27
|
+
makeHttpRouterExport,
|
|
28
|
+
} from './http_handlers';
|
|
17
29
|
import {
|
|
18
30
|
makeProcedureExport,
|
|
19
31
|
type ProcedureExport,
|
|
@@ -47,10 +59,12 @@ export class SchemaInner<
|
|
|
47
59
|
> extends ModuleContext {
|
|
48
60
|
schemaType: S;
|
|
49
61
|
existingFunctions = new Set<string>();
|
|
62
|
+
existingHttpHandlers = new Set<string>();
|
|
50
63
|
reducers: Reducers = [];
|
|
51
64
|
procedures: Procedures = [];
|
|
52
65
|
views: Views = [];
|
|
53
66
|
anonViews: AnonViews = [];
|
|
67
|
+
httpHandlers: HandlerFn[] = [];
|
|
54
68
|
/**
|
|
55
69
|
* Maps ReducerExport objects to the name of the reducer.
|
|
56
70
|
* Used for resolving the reducers of scheduled tables.
|
|
@@ -60,7 +74,10 @@ export class SchemaInner<
|
|
|
60
74
|
| ProcedureExport<UntypedSchemaDef, any, any>,
|
|
61
75
|
string
|
|
62
76
|
> = new Map();
|
|
77
|
+
httpHandlerExports: Map<HttpHandlerExport<UntypedSchemaDef>, string> =
|
|
78
|
+
new Map();
|
|
63
79
|
pendingSchedules: PendingSchedule[] = [];
|
|
80
|
+
pendingHttpRoutes: PendingHttpRoute[] = [];
|
|
64
81
|
|
|
65
82
|
constructor(getSchemaType: (ctx: SchemaInner<S>) => S) {
|
|
66
83
|
super();
|
|
@@ -70,12 +87,21 @@ export class SchemaInner<
|
|
|
70
87
|
defineFunction(name: string) {
|
|
71
88
|
if (this.existingFunctions.has(name)) {
|
|
72
89
|
throw new TypeError(
|
|
73
|
-
`There is already a reducer or
|
|
90
|
+
`There is already a reducer, procedure, or view with the name '${name}'`
|
|
74
91
|
);
|
|
75
92
|
}
|
|
76
93
|
this.existingFunctions.add(name);
|
|
77
94
|
}
|
|
78
95
|
|
|
96
|
+
defineHttpHandler(name: string) {
|
|
97
|
+
if (this.existingHttpHandlers.has(name)) {
|
|
98
|
+
throw new TypeError(
|
|
99
|
+
`There is already an HTTP handler with the name '${name}'`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
this.existingHttpHandlers.add(name);
|
|
103
|
+
}
|
|
104
|
+
|
|
79
105
|
resolveSchedules() {
|
|
80
106
|
for (const { reducer, scheduleAtCol, tableName } of this.pendingSchedules) {
|
|
81
107
|
const functionName = this.functionExports.get(reducer());
|
|
@@ -91,9 +117,30 @@ export class SchemaInner<
|
|
|
91
117
|
});
|
|
92
118
|
}
|
|
93
119
|
}
|
|
120
|
+
|
|
121
|
+
resolveHttpRoutes() {
|
|
122
|
+
for (const route of this.pendingHttpRoutes) {
|
|
123
|
+
const handlerFunction = this.httpHandlerExports.get(route.handler);
|
|
124
|
+
if (handlerFunction === undefined) {
|
|
125
|
+
throw new TypeError(
|
|
126
|
+
`HTTP route for path '${route.path}' refers to a handler that was not exported.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
this.moduleDef.httpRoutes.push({
|
|
130
|
+
handlerFunction,
|
|
131
|
+
method: route.method,
|
|
132
|
+
path: route.path,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
94
136
|
}
|
|
95
137
|
|
|
96
138
|
type PendingSchedule = UntypedTableSchema['schedule'] & { tableName: string };
|
|
139
|
+
type PendingHttpRoute = {
|
|
140
|
+
handler: HttpHandlerExport<UntypedSchemaDef>;
|
|
141
|
+
method: MethodOrAny;
|
|
142
|
+
path: string;
|
|
143
|
+
};
|
|
97
144
|
|
|
98
145
|
/**
|
|
99
146
|
* The Schema class represents the database schema for a SpacetimeDB application.
|
|
@@ -153,6 +200,7 @@ export class Schema<S extends UntypedSchemaDef> implements ModuleDefaultExport {
|
|
|
153
200
|
moduleExport[registerExport](registeredSchema, name);
|
|
154
201
|
}
|
|
155
202
|
registeredSchema.resolveSchedules();
|
|
203
|
+
registeredSchema.resolveHttpRoutes();
|
|
156
204
|
return makeHooks(registeredSchema);
|
|
157
205
|
}
|
|
158
206
|
|
|
@@ -458,6 +506,27 @@ export class Schema<S extends UntypedSchemaDef> implements ModuleDefaultExport {
|
|
|
458
506
|
return makeProcedureExport(this.#ctx, opts, params, ret, fn);
|
|
459
507
|
}
|
|
460
508
|
|
|
509
|
+
httpHandler(fn: HandlerFn<S>): HttpHandlerExport<S>;
|
|
510
|
+
httpHandler(opts: HttpHandlerOpts, fn: HandlerFn<S>): HttpHandlerExport<S>;
|
|
511
|
+
httpHandler(
|
|
512
|
+
...args: [HandlerFn<S>] | [HttpHandlerOpts, HandlerFn<S>]
|
|
513
|
+
): HttpHandlerExport<S> {
|
|
514
|
+
let opts: HttpHandlerOpts | undefined, fn: HandlerFn<S>;
|
|
515
|
+
switch (args.length) {
|
|
516
|
+
case 1:
|
|
517
|
+
[fn] = args;
|
|
518
|
+
break;
|
|
519
|
+
case 2:
|
|
520
|
+
[opts, fn] = args;
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
return makeHttpHandlerExport(this.#ctx, opts, fn);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
httpRouter(router: Router): ModuleExport {
|
|
527
|
+
return makeHttpRouterExport(this.#ctx, router);
|
|
528
|
+
}
|
|
529
|
+
|
|
461
530
|
/**
|
|
462
531
|
* Bundle multiple reducers, procedures, etc into one value to export.
|
|
463
532
|
* The name they will be exported with is their corresponding key in the `exports` argument.
|
package/src/server/sys.d.ts
CHANGED
|
@@ -37,6 +37,13 @@ declare module 'spacetime:sys@2.0' {
|
|
|
37
37
|
timestamp: bigint,
|
|
38
38
|
args: Uint8Array
|
|
39
39
|
): Uint8Array;
|
|
40
|
+
|
|
41
|
+
__call_http_handler__(
|
|
42
|
+
id: u32,
|
|
43
|
+
timestamp: bigint,
|
|
44
|
+
request: Uint8Array,
|
|
45
|
+
body: Uint8Array
|
|
46
|
+
): [response: Uint8Array, body: Uint8Array];
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
export function register_hooks(hooks: ModuleHooks);
|
package/src/server/views.ts
CHANGED
|
@@ -143,6 +143,7 @@ export function registerView<
|
|
|
143
143
|
? AnonymousViewFn<S, Params, Ret>
|
|
144
144
|
: ViewFn<S, Params, Ret>
|
|
145
145
|
) {
|
|
146
|
+
ctx.defineFunction(exportName);
|
|
146
147
|
const paramsBuilder = new RowBuilder(params, toPascalCase(exportName));
|
|
147
148
|
|
|
148
149
|
// Register return types if they are product types
|
package/dist/lib/http_types.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"http_types.d.ts","sourceRoot":"","sources":["../../src/lib/http_types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,WAAW,EACX,UAAU,EACV,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,MAAM,iBAAiB,CAAC"}
|