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
@@ -0,0 +1,4 @@
1
+ import type { UntypedProcedureDef } from '../sdk/procedures';
2
+ import type { ProcedureParamsType, ProcedureReturnType } from '../sdk/type_utils';
3
+ export declare function useProcedure<ProcedureDef extends UntypedProcedureDef>(procedureDef: ProcedureDef): (...params: ProcedureParamsType<ProcedureDef>) => Promise<ProcedureReturnType<ProcedureDef>>;
4
+ //# sourceMappingURL=useProcedure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProcedure.d.ts","sourceRoot":"","sources":["../../src/vue/useProcedure.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,wBAAgB,YAAY,CAAC,YAAY,SAAS,mBAAmB,EACnE,YAAY,EAAE,YAAY,GACzB,CACD,GAAG,MAAM,EAAE,mBAAmB,CAAC,YAAY,CAAC,KACzC,OAAO,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAiD9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacetimedb",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "API and ABI bindings for the SpacetimeDB TypeScript module library",
5
5
  "homepage": "https://github.com/clockworklabs/SpacetimeDB#readme",
6
6
  "bugs": {
@@ -158,6 +158,15 @@ export const Lifecycle = __t.enum('Lifecycle', {
158
158
  });
159
159
  export type Lifecycle = __Infer<typeof Lifecycle>;
160
160
 
161
+ // The tagged union or sum type for the algebraic type `MethodOrAny`.
162
+ export const MethodOrAny = __t.enum('MethodOrAny', {
163
+ Any: __t.unit(),
164
+ get Method() {
165
+ return HttpMethod;
166
+ },
167
+ });
168
+ export type MethodOrAny = __Infer<typeof MethodOrAny>;
169
+
161
170
  // The tagged union or sum type for the algebraic type `MiscModuleExport`.
162
171
  export const MiscModuleExport = __t.enum('MiscModuleExport', {
163
172
  get TypeAlias() {
@@ -239,6 +248,20 @@ export const RawConstraintDefV9 = __t.object('RawConstraintDefV9', {
239
248
  });
240
249
  export type RawConstraintDefV9 = __Infer<typeof RawConstraintDefV9>;
241
250
 
251
+ export const RawHttpHandlerDefV10 = __t.object('RawHttpHandlerDefV10', {
252
+ sourceName: __t.string(),
253
+ });
254
+ export type RawHttpHandlerDefV10 = __Infer<typeof RawHttpHandlerDefV10>;
255
+
256
+ export const RawHttpRouteDefV10 = __t.object('RawHttpRouteDefV10', {
257
+ handlerFunction: __t.string(),
258
+ get method() {
259
+ return MethodOrAny;
260
+ },
261
+ path: __t.string(),
262
+ });
263
+ export type RawHttpRouteDefV10 = __Infer<typeof RawHttpRouteDefV10>;
264
+
242
265
  // The tagged union or sum type for the algebraic type `RawIndexAlgorithm`.
243
266
  export const RawIndexAlgorithm = __t.enum('RawIndexAlgorithm', {
244
267
  BTree: __t.array(__t.u16()),
@@ -358,6 +381,12 @@ export const RawModuleDefV10Section = __t.enum('RawModuleDefV10Section', {
358
381
  get ExplicitNames() {
359
382
  return ExplicitNames;
360
383
  },
384
+ get HttpHandlers() {
385
+ return __t.array(RawHttpHandlerDefV10);
386
+ },
387
+ get HttpRoutes() {
388
+ return __t.array(RawHttpRouteDefV10);
389
+ },
361
390
  });
362
391
  export type RawModuleDefV10Section = __Infer<typeof RawModuleDefV10Section>;
363
392
 
@@ -103,6 +103,8 @@ export interface JwtClaims {
103
103
  */
104
104
  export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
105
105
  sender: Identity;
106
+ databaseIdentity: Identity;
107
+ /** @deprecated Use `databaseIdentity` instead. */
106
108
  identity: Identity;
107
109
  timestamp: Timestamp;
108
110
  connectionId: ConnectionId | null;
package/src/lib/schema.ts CHANGED
@@ -195,6 +195,8 @@ export class ModuleContext {
195
195
  procedures: [],
196
196
  views: [],
197
197
  lifeCycleReducers: [],
198
+ httpHandlers: [],
199
+ httpRoutes: [],
198
200
  caseConversionPolicy: { tag: 'SnakeCase' },
199
201
  explicitNames: {
200
202
  entries: [],
@@ -227,6 +229,18 @@ export class ModuleContext {
227
229
  value: module.lifeCycleReducers,
228
230
  }
229
231
  );
232
+ push(
233
+ module.httpHandlers && {
234
+ tag: 'HttpHandlers',
235
+ value: module.httpHandlers,
236
+ }
237
+ );
238
+ push(
239
+ module.httpRoutes && {
240
+ tag: 'HttpRoutes',
241
+ value: module.httpRoutes,
242
+ }
243
+ );
230
244
  push(
231
245
  module.rowLevelSecurity && {
232
246
  tag: 'RowLevelSecurity',
@@ -28,9 +28,24 @@ export async function decompress(
28
28
  const decompressedStream = readableStream.pipeThrough(decompressionStream);
29
29
 
30
30
  // Collect the decompressed chunks efficiently
31
- const chunks = [];
32
- for await (const chunk of decompressedStream) {
33
- chunks.push(chunk);
31
+ const reader = decompressedStream.getReader();
32
+ const chunks: Uint8Array[] = [];
33
+ let totalLength = 0;
34
+ let result: any;
35
+
36
+ while (!(result = await reader.read()).done) {
37
+ chunks.push(result.value);
38
+ totalLength += result.value.length;
34
39
  }
35
- return new Blob(chunks).bytes();
40
+
41
+ // Allocate a single Uint8Array for the decompressed data
42
+ const decompressedArray = new Uint8Array(totalLength);
43
+ let chunkOffset = 0;
44
+
45
+ for (const chunk of chunks) {
46
+ decompressedArray.set(chunk, chunkOffset);
47
+ chunkOffset += chunk.length;
48
+ }
49
+
50
+ return decompressedArray;
36
51
  }
package/src/sdk/logger.ts CHANGED
@@ -73,7 +73,7 @@ const SENSITIVE_KEYS = new Set([
73
73
  ]);
74
74
 
75
75
  export const stringify = (value: unknown): string | undefined =>
76
- ssStringify(value, (key, current) => {
76
+ ssStringify(value, (key: string, current: unknown) => {
77
77
  if (SENSITIVE_KEYS.has(key)) {
78
78
  return '[REDACTED]';
79
79
  }
@@ -0,0 +1,80 @@
1
+ import { table } from '../lib/table';
2
+ import t from '../lib/type_builders';
3
+ import {
4
+ type HandlerContext,
5
+ Request,
6
+ SyncResponse,
7
+ Router,
8
+ schema,
9
+ } from './index';
10
+
11
+ const person = table(
12
+ {},
13
+ {
14
+ id: t.u32().primaryKey(),
15
+ name: t.string(),
16
+ }
17
+ );
18
+
19
+ const stdb = schema({ person });
20
+
21
+ const hello = stdb.httpHandler((ctx, req) => {
22
+ void ctx.identity;
23
+ void ctx.random;
24
+ req.text();
25
+ req.json();
26
+
27
+ ctx.withTx(tx => {
28
+ tx.db.person.insert({ id: 1, name: 'alice' });
29
+ });
30
+
31
+ return new SyncResponse('hello', {
32
+ headers: { 'content-type': 'text/plain' },
33
+ status: 200,
34
+ });
35
+ });
36
+
37
+ const _typedHello: (ctx: HandlerContext<any>, req: Request) => SyncResponse = (
38
+ ctx,
39
+ req
40
+ ) => {
41
+ void ctx.timestamp;
42
+ return new SyncResponse(req.text());
43
+ };
44
+
45
+ const named = stdb.httpHandler({ name: 'hello' }, (_ctx, _req) => {
46
+ return new SyncResponse('named');
47
+ });
48
+
49
+ const routes = stdb.httpRouter(
50
+ new Router()
51
+ .get('/hello', hello)
52
+ .get('/named', named)
53
+ .post('/hello-post', hello)
54
+ .nest('/api', new Router().any('/v1', hello))
55
+ .merge(new Router().get('', hello))
56
+ );
57
+
58
+ void routes;
59
+
60
+ // @ts-expect-error handlers must return SyncResponse
61
+ stdb.httpHandler((_ctx, _req) => 123);
62
+
63
+ // @ts-expect-error handlers must take HandlerContext as the first argument
64
+ stdb.httpHandler((_ctx: number, _req: Request) => new SyncResponse('bad'));
65
+
66
+ // @ts-expect-error handlers must take a Request as the second argument
67
+ stdb.httpHandler((_ctx, _req: number) => new SyncResponse('bad'));
68
+
69
+ stdb.httpHandler((ctx, req) => {
70
+ // @ts-expect-error HTTP handlers do not expose sender directly
71
+ void ctx.sender;
72
+ // @ts-expect-error HTTP handlers do not expose connectionId directly
73
+ void ctx.connectionId;
74
+ // @ts-expect-error HTTP handlers do not expose db directly
75
+ void ctx.db;
76
+ return new SyncResponse(req.text());
77
+ });
78
+
79
+ // @ts-expect-error routers must reference exported http handlers, not raw functions
80
+ new Router().get('/raw', (_ctx, _req) => new SyncResponse('bad'));
@@ -1,2 +1,14 @@
1
- export { Headers, SyncResponse } from './http_internal';
2
- export type { BodyInit, HeadersInit, ResponseInit } from './http_internal';
1
+ export {
2
+ type BodyInit,
3
+ type HeadersInit,
4
+ type RequestInit,
5
+ type ResponseInit,
6
+ Headers,
7
+ Request,
8
+ SyncResponse,
9
+ Router,
10
+ type HandlerContext,
11
+ type HandlerFn,
12
+ type HttpHandlerExport,
13
+ type HttpHandlerOpts,
14
+ } from './http_handlers';
@@ -0,0 +1,413 @@
1
+ import type { Identity } from '../lib/identity';
2
+ import type {
3
+ HttpMethod,
4
+ HttpVersion,
5
+ MethodOrAny,
6
+ } from '../lib/autogen/types';
7
+ import type { UntypedSchemaDef } from '../lib/schema';
8
+ import type { Timestamp } from '../lib/timestamp';
9
+ import type { Uuid } from '../lib/uuid';
10
+ import type { TransactionCtx } from './procedures';
11
+ import type { HttpClient } from './http_internal';
12
+ import type { Random } from './rng';
13
+ import {
14
+ exportContext,
15
+ registerExport,
16
+ type ModuleExport,
17
+ type SchemaInner,
18
+ } from './schema';
19
+ import {
20
+ Headers,
21
+ makeResponse,
22
+ SyncResponse,
23
+ textDecoder,
24
+ textEncoder,
25
+ type BodyInit,
26
+ type HeadersInit,
27
+ type ResponseInit,
28
+ } from './http_shared';
29
+
30
+ export { Headers };
31
+ export { SyncResponse };
32
+ export type { BodyInit, HeadersInit, ResponseInit };
33
+ export { makeResponse };
34
+ export const httpHandlerFn = Symbol('SpacetimeDB.httpHandlerFn');
35
+
36
+ export interface RequestInit {
37
+ body?: BodyInit | null;
38
+ headers?: HeadersInit;
39
+ method?: string;
40
+ version?: HttpVersion;
41
+ }
42
+
43
+ type RequestInner = {
44
+ headers: Headers;
45
+ method: string;
46
+ uri: string;
47
+ version: HttpVersion;
48
+ };
49
+
50
+ type RouteSpec = {
51
+ handler: HttpHandlerExport<any>;
52
+ method: MethodOrAny;
53
+ path: string;
54
+ };
55
+
56
+ const ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION =
57
+ 'ASCII lowercase letters, digits and `-_~/`';
58
+
59
+ export const makeRequest = Symbol('makeRequest');
60
+
61
+ function coerceRequestBody(body?: BodyInit | null): string | Uint8Array | null {
62
+ if (body == null) {
63
+ return null;
64
+ }
65
+ if (typeof body === 'string') {
66
+ return body;
67
+ }
68
+ return new Uint8Array(body as any);
69
+ }
70
+
71
+ function requestBodyToBytes(body: string | Uint8Array | null): Uint8Array {
72
+ if (body == null) {
73
+ return new Uint8Array();
74
+ }
75
+ if (typeof body === 'string') {
76
+ return textEncoder.encode(body);
77
+ }
78
+ return body;
79
+ }
80
+
81
+ function requestBodyToText(body: string | Uint8Array | null): string {
82
+ if (body == null) {
83
+ return '';
84
+ }
85
+ if (typeof body === 'string') {
86
+ return body;
87
+ }
88
+ return textDecoder.decode(body);
89
+ }
90
+
91
+ function characterIsAcceptableForRoutePath(c: string) {
92
+ return (
93
+ (c >= 'a' && c <= 'z') ||
94
+ (c >= '0' && c <= '9') ||
95
+ c === '-' ||
96
+ c === '_' ||
97
+ c === '~' ||
98
+ c === '/'
99
+ );
100
+ }
101
+
102
+ function assertValidPath(path: string) {
103
+ if (path !== '' && !path.startsWith('/')) {
104
+ throw new TypeError(`Route paths must start with \`/\`: ${path}`);
105
+ }
106
+ if (![...path].every(characterIsAcceptableForRoutePath)) {
107
+ throw new TypeError(
108
+ `Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION}: ${path}`
109
+ );
110
+ }
111
+ }
112
+
113
+ function routesOverlap(a: RouteSpec, b: RouteSpec) {
114
+ const methodsMatch = (left: HttpMethod, right: HttpMethod) => {
115
+ if (left.tag !== right.tag) {
116
+ return false;
117
+ }
118
+ if (left.tag === 'Extension' && right.tag === 'Extension') {
119
+ return left.value === right.value;
120
+ }
121
+ return true;
122
+ };
123
+
124
+ return (
125
+ a.path === b.path &&
126
+ (a.method.tag === 'Any' ||
127
+ b.method.tag === 'Any' ||
128
+ (a.method.tag === 'Method' &&
129
+ b.method.tag === 'Method' &&
130
+ methodsMatch(a.method.value, b.method.value)))
131
+ );
132
+ }
133
+
134
+ function joinPaths(prefix: string, suffix: string) {
135
+ if (prefix === '/') {
136
+ return suffix;
137
+ }
138
+ if (suffix === '/') {
139
+ return prefix;
140
+ }
141
+ let prefixEnd = prefix.length;
142
+ while (prefixEnd > 0 && prefix[prefixEnd - 1] === '/') {
143
+ prefixEnd--;
144
+ }
145
+
146
+ let suffixStart = 0;
147
+ while (suffixStart < suffix.length && suffix[suffixStart] === '/') {
148
+ suffixStart++;
149
+ }
150
+
151
+ const joinedPrefix = prefix.slice(0, prefixEnd);
152
+ const joinedSuffix = suffix.slice(suffixStart);
153
+ return `${joinedPrefix}/${joinedSuffix}`;
154
+ }
155
+
156
+ export class Request {
157
+ #body: string | Uint8Array | null;
158
+ #inner: RequestInner;
159
+
160
+ constructor(url: URL | string, init: RequestInit = {}) {
161
+ this.#body = coerceRequestBody(init.body);
162
+ this.#inner = {
163
+ headers: new Headers(init.headers as any),
164
+ method: init.method ?? 'GET',
165
+ uri: '' + url,
166
+ version: init.version ?? { tag: 'Http11' },
167
+ };
168
+ }
169
+
170
+ static [makeRequest](body: BodyInit | null, inner: RequestInner) {
171
+ const me = new Request(inner.uri);
172
+ me.#body = coerceRequestBody(body);
173
+ me.#inner = inner;
174
+ return me;
175
+ }
176
+
177
+ get headers(): Headers {
178
+ return this.#inner.headers;
179
+ }
180
+
181
+ get method(): string {
182
+ return this.#inner.method;
183
+ }
184
+
185
+ get uri(): string {
186
+ return this.#inner.uri;
187
+ }
188
+
189
+ get url(): string {
190
+ return this.#inner.uri;
191
+ }
192
+
193
+ get version(): HttpVersion {
194
+ return this.#inner.version;
195
+ }
196
+
197
+ arrayBuffer(): ArrayBuffer {
198
+ return this.bytes().buffer as ArrayBuffer;
199
+ }
200
+
201
+ bytes(): Uint8Array {
202
+ return requestBodyToBytes(this.#body);
203
+ }
204
+
205
+ json(): any {
206
+ return JSON.parse(this.text());
207
+ }
208
+
209
+ text(): string {
210
+ return requestBodyToText(this.#body);
211
+ }
212
+ }
213
+
214
+ export interface HandlerContext<S extends UntypedSchemaDef = UntypedSchemaDef> {
215
+ readonly timestamp: Timestamp;
216
+ readonly http: HttpClient;
217
+ readonly identity: Identity;
218
+ readonly random: Random;
219
+ withTx<T>(body: (ctx: TransactionCtx<S>) => T): T;
220
+ newUuidV4(): Uuid;
221
+ newUuidV7(): Uuid;
222
+ }
223
+
224
+ export type HandlerFn<S extends UntypedSchemaDef = UntypedSchemaDef> = (
225
+ ctx: HandlerContext<S>,
226
+ req: Request
227
+ ) => SyncResponse;
228
+
229
+ export interface HttpHandlerExport<
230
+ S extends UntypedSchemaDef = UntypedSchemaDef,
231
+ > extends ModuleExport {
232
+ [httpHandlerFn]: HandlerFn<S>;
233
+ }
234
+
235
+ const exportedHttpHandlerObjects = new WeakSet<object>();
236
+
237
+ export interface HttpHandlerOpts {
238
+ name: string;
239
+ }
240
+
241
+ export class Router {
242
+ #routes: RouteSpec[];
243
+
244
+ constructor(routes: RouteSpec[] = []) {
245
+ this.#routes = routes;
246
+ }
247
+
248
+ get(path: string, handler: HttpHandlerExport<any>) {
249
+ return this.addRoute(
250
+ { tag: 'Method', value: { tag: 'Get' } },
251
+ path,
252
+ handler
253
+ );
254
+ }
255
+
256
+ head(path: string, handler: HttpHandlerExport<any>) {
257
+ return this.addRoute(
258
+ { tag: 'Method', value: { tag: 'Head' } },
259
+ path,
260
+ handler
261
+ );
262
+ }
263
+
264
+ options(path: string, handler: HttpHandlerExport<any>) {
265
+ return this.addRoute(
266
+ { tag: 'Method', value: { tag: 'Options' } },
267
+ path,
268
+ handler
269
+ );
270
+ }
271
+
272
+ put(path: string, handler: HttpHandlerExport<any>) {
273
+ return this.addRoute(
274
+ { tag: 'Method', value: { tag: 'Put' } },
275
+ path,
276
+ handler
277
+ );
278
+ }
279
+
280
+ delete(path: string, handler: HttpHandlerExport<any>) {
281
+ return this.addRoute(
282
+ { tag: 'Method', value: { tag: 'Delete' } },
283
+ path,
284
+ handler
285
+ );
286
+ }
287
+
288
+ post(path: string, handler: HttpHandlerExport<any>) {
289
+ return this.addRoute(
290
+ { tag: 'Method', value: { tag: 'Post' } },
291
+ path,
292
+ handler
293
+ );
294
+ }
295
+
296
+ patch(path: string, handler: HttpHandlerExport<any>) {
297
+ return this.addRoute(
298
+ { tag: 'Method', value: { tag: 'Patch' } },
299
+ path,
300
+ handler
301
+ );
302
+ }
303
+
304
+ any(path: string, handler: HttpHandlerExport<any>) {
305
+ return this.addRoute({ tag: 'Any' }, path, handler);
306
+ }
307
+
308
+ nest(path: string, subRouter: Router) {
309
+ assertValidPath(path);
310
+ if (this.#routes.some(route => route.path.startsWith(path))) {
311
+ throw new TypeError(
312
+ `Cannot nest router at \`${path}\`; existing routes overlap with nested path`
313
+ );
314
+ }
315
+ let merged = new Router(this.#routes);
316
+ for (const route of subRouter.#routes) {
317
+ merged = merged.addRoute(
318
+ route.method,
319
+ joinPaths(path, route.path),
320
+ route.handler
321
+ );
322
+ }
323
+ return merged;
324
+ }
325
+
326
+ merge(otherRouter: Router) {
327
+ let merged = new Router(this.#routes);
328
+ for (const route of otherRouter.#routes) {
329
+ merged = merged.addRoute(route.method, route.path, route.handler);
330
+ }
331
+ return merged;
332
+ }
333
+
334
+ intoRoutes() {
335
+ return this.#routes.slice();
336
+ }
337
+
338
+ private addRoute(
339
+ method: MethodOrAny,
340
+ path: string,
341
+ handler: HttpHandlerExport<any>
342
+ ) {
343
+ assertValidPath(path);
344
+ const candidate = { method, path, handler };
345
+ if (this.#routes.some(route => routesOverlap(route, candidate))) {
346
+ throw new TypeError(`Route conflict for \`${path}\``);
347
+ }
348
+ return new Router([...this.#routes, candidate]);
349
+ }
350
+ }
351
+
352
+ export function makeHttpHandlerExport<S extends UntypedSchemaDef>(
353
+ ctx: SchemaInner,
354
+ opts: HttpHandlerOpts | undefined,
355
+ fn: HandlerFn<S>
356
+ ): HttpHandlerExport<S> {
357
+ const handlerExport = {
358
+ [httpHandlerFn]: fn,
359
+ [exportContext]: ctx,
360
+ [registerExport](ctx: SchemaInner, exportName: string) {
361
+ if (exportedHttpHandlerObjects.has(handlerExport)) {
362
+ throw new TypeError(
363
+ `HTTP handler '${exportName}' was exported more than once`
364
+ );
365
+ }
366
+ exportedHttpHandlerObjects.add(handlerExport);
367
+ registerHttpHandler(ctx, exportName, fn, opts);
368
+ ctx.httpHandlerExports.set(
369
+ handlerExport as HttpHandlerExport<UntypedSchemaDef>,
370
+ exportName
371
+ );
372
+ },
373
+ };
374
+ return handlerExport as HttpHandlerExport<S>;
375
+ }
376
+
377
+ export function makeHttpRouterExport(
378
+ ctx: SchemaInner,
379
+ router: Router
380
+ ): ModuleExport {
381
+ return {
382
+ [exportContext]: ctx,
383
+ [registerExport](ctx: SchemaInner) {
384
+ ctx.pendingHttpRoutes.push(...router.intoRoutes());
385
+ },
386
+ };
387
+ }
388
+
389
+ function registerHttpHandler<S extends UntypedSchemaDef>(
390
+ ctx: SchemaInner,
391
+ exportName: string,
392
+ fn: HandlerFn<S>,
393
+ opts?: HttpHandlerOpts
394
+ ) {
395
+ ctx.defineHttpHandler(exportName);
396
+ ctx.moduleDef.httpHandlers.push({ sourceName: exportName });
397
+
398
+ if (opts?.name != null) {
399
+ ctx.moduleDef.explicitNames.entries.push({
400
+ tag: 'Function',
401
+ value: {
402
+ sourceName: exportName,
403
+ canonicalName: opts.name,
404
+ },
405
+ });
406
+ }
407
+
408
+ if (!fn.name) {
409
+ Object.defineProperty(fn, 'name', { value: exportName, writable: false });
410
+ }
411
+
412
+ ctx.httpHandlers.push(fn as HandlerFn<UntypedSchemaDef>);
413
+ }