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.
- package/LICENSE.txt +2 -2
- package/dist/browser/vue/index.mjs +36 -1
- package/dist/browser/vue/index.mjs.map +1 -1
- 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/reducers.d.ts +2 -0
- package/dist/lib/reducers.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 +590 -136
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/procedures.d.ts +2 -0
- package/dist/server/procedures.d.ts.map +1 -1
- package/dist/server/runtime.d.ts +2 -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/dist/tanstack/index.cjs +34 -0
- package/dist/tanstack/index.cjs.map +1 -1
- package/dist/tanstack/index.d.ts +1 -0
- package/dist/tanstack/index.d.ts.map +1 -1
- package/dist/tanstack/index.mjs +34 -1
- package/dist/tanstack/index.mjs.map +1 -1
- package/dist/vue/index.cjs +36 -0
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.d.ts.map +1 -1
- package/dist/vue/index.mjs +36 -1
- package/dist/vue/index.mjs.map +1 -1
- package/dist/vue/useProcedure.d.ts +4 -0
- package/dist/vue/useProcedure.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/lib/autogen/types.ts +29 -0
- package/src/lib/reducers.ts +2 -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 +15 -31
- package/src/server/runtime.ts +142 -2
- package/src/server/schema.ts +71 -2
- package/src/server/sys.d.ts +7 -0
- package/src/server/views.ts +1 -0
- package/src/tanstack/index.ts +1 -0
- package/src/vue/index.ts +1 -0
- package/src/vue/useProcedure.ts +62 -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
|
@@ -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
|
-
|
|
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 =
|
|
164
|
-
|
|
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
|
+
}
|
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,
|
|
@@ -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
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
const ctx: TransactionCtx<S> = new TransactionCtxImpl(
|
|
217
|
+
return runWithTx(
|
|
218
|
+
timestamp =>
|
|
219
|
+
new TransactionCtxImpl(
|
|
216
220
|
this.sender,
|
|
217
|
-
|
|
221
|
+
timestamp,
|
|
218
222
|
this.connectionId,
|
|
219
223
|
this.#dbView()
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
|
|
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 {
|
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
|
|
|
@@ -221,10 +254,14 @@ export const ReducerCtxImpl = class ReducerCtx<
|
|
|
221
254
|
me.#senderAuth = undefined;
|
|
222
255
|
}
|
|
223
256
|
|
|
224
|
-
get
|
|
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
|