spacetimedb 2.4.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/LICENSE.txt +759 -759
  2. package/README.md +211 -120
  3. package/dist/angular/index.cjs.map +1 -1
  4. package/dist/angular/index.mjs.map +1 -1
  5. package/dist/browser/angular/index.mjs.map +1 -1
  6. package/dist/browser/react/index.mjs +129 -57
  7. package/dist/browser/react/index.mjs.map +1 -1
  8. package/dist/browser/solid/index.mjs +1933 -0
  9. package/dist/browser/solid/index.mjs.map +1 -0
  10. package/dist/browser/svelte/index.mjs.map +1 -1
  11. package/dist/browser/vue/index.mjs.map +1 -1
  12. package/dist/index.browser.mjs +10 -2
  13. package/dist/index.browser.mjs.map +1 -1
  14. package/dist/index.cjs +10 -2
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.mjs +10 -2
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/min/index.browser.mjs +1 -1
  19. package/dist/min/index.browser.mjs.map +1 -1
  20. package/dist/min/react/index.mjs +1 -1
  21. package/dist/min/react/index.mjs.map +1 -1
  22. package/dist/min/sdk/index.browser.mjs +1 -1
  23. package/dist/min/sdk/index.browser.mjs.map +1 -1
  24. package/dist/react/index.cjs +129 -57
  25. package/dist/react/index.cjs.map +1 -1
  26. package/dist/react/index.mjs +129 -57
  27. package/dist/react/index.mjs.map +1 -1
  28. package/dist/react/useTable.d.ts.map +1 -1
  29. package/dist/sdk/connection_manager.d.ts +8 -0
  30. package/dist/sdk/connection_manager.d.ts.map +1 -1
  31. package/dist/sdk/db_connection_impl.d.ts +7 -0
  32. package/dist/sdk/db_connection_impl.d.ts.map +1 -1
  33. package/dist/sdk/index.browser.mjs +10 -2
  34. package/dist/sdk/index.browser.mjs.map +1 -1
  35. package/dist/sdk/index.cjs +10 -2
  36. package/dist/sdk/index.cjs.map +1 -1
  37. package/dist/sdk/index.mjs +10 -2
  38. package/dist/sdk/index.mjs.map +1 -1
  39. package/dist/sdk/websocket_test_adapter.d.ts +2 -1
  40. package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
  41. package/dist/server/index.mjs.map +1 -1
  42. package/dist/server/runtime.d.ts.map +1 -1
  43. package/dist/solid/SpacetimeDBProvider.d.ts +7 -0
  44. package/dist/solid/SpacetimeDBProvider.d.ts.map +1 -0
  45. package/dist/solid/connection_state.d.ts +6 -0
  46. package/dist/solid/connection_state.d.ts.map +1 -0
  47. package/dist/solid/index.cjs +1939 -0
  48. package/dist/solid/index.cjs.map +1 -0
  49. package/dist/solid/index.d.ts +6 -0
  50. package/dist/solid/index.d.ts.map +1 -0
  51. package/dist/solid/index.mjs +1933 -0
  52. package/dist/solid/index.mjs.map +1 -0
  53. package/dist/solid/useProcedure.d.ts +4 -0
  54. package/dist/solid/useProcedure.d.ts.map +1 -0
  55. package/dist/solid/useReducer.d.ts +4 -0
  56. package/dist/solid/useReducer.d.ts.map +1 -0
  57. package/dist/solid/useSpacetimeDB.d.ts +4 -0
  58. package/dist/solid/useSpacetimeDB.d.ts.map +1 -0
  59. package/dist/solid/useTable.d.ts +32 -0
  60. package/dist/solid/useTable.d.ts.map +1 -0
  61. package/dist/svelte/index.cjs.map +1 -1
  62. package/dist/svelte/index.mjs.map +1 -1
  63. package/dist/tanstack/index.cjs +120 -50
  64. package/dist/tanstack/index.cjs.map +1 -1
  65. package/dist/tanstack/index.mjs +120 -50
  66. package/dist/tanstack/index.mjs.map +1 -1
  67. package/dist/vue/index.cjs.map +1 -1
  68. package/dist/vue/index.mjs.map +1 -1
  69. package/package.json +13 -3
  70. package/src/angular/connection_state.ts +19 -19
  71. package/src/angular/index.ts +3 -3
  72. package/src/angular/injectors/index.ts +4 -4
  73. package/src/angular/injectors/inject-reducer.ts +62 -62
  74. package/src/angular/injectors/inject-spacetimedb-connected.ts +13 -13
  75. package/src/angular/injectors/inject-spacetimedb.ts +10 -10
  76. package/src/angular/injectors/inject-table.ts +234 -234
  77. package/src/angular/providers/index.ts +1 -1
  78. package/src/angular/providers/provide-spacetimedb.ts +96 -96
  79. package/src/index.ts +16 -16
  80. package/src/lib/algebraic_type.ts +819 -819
  81. package/src/lib/algebraic_type_variants.ts +26 -26
  82. package/src/lib/algebraic_value.ts +10 -10
  83. package/src/lib/autogen/types.ts +746 -746
  84. package/src/lib/binary_reader.ts +188 -188
  85. package/src/lib/binary_writer.ts +213 -213
  86. package/src/lib/connection_id.ts +102 -102
  87. package/src/lib/constraints.ts +48 -48
  88. package/src/lib/errors.ts +26 -26
  89. package/src/lib/filter.ts +195 -195
  90. package/src/lib/identity.ts +83 -83
  91. package/src/lib/indexes.ts +251 -251
  92. package/src/lib/option.ts +34 -34
  93. package/src/lib/query.ts +1019 -1019
  94. package/src/lib/reducer_schema.ts +38 -38
  95. package/src/lib/reducers.ts +116 -116
  96. package/src/lib/result.ts +36 -36
  97. package/src/lib/schedule_at.ts +86 -86
  98. package/src/lib/schema.ts +420 -420
  99. package/src/lib/table.ts +548 -548
  100. package/src/lib/table_schema.ts +64 -64
  101. package/src/lib/time_duration.ts +77 -77
  102. package/src/lib/timestamp.ts +148 -148
  103. package/src/lib/type_builders.test-d.ts +128 -128
  104. package/src/lib/type_builders.ts +4014 -4014
  105. package/src/lib/type_util.ts +124 -124
  106. package/src/lib/util.ts +196 -196
  107. package/src/lib/uuid.ts +337 -337
  108. package/src/react/SpacetimeDBProvider.ts +84 -84
  109. package/src/react/connection_state.ts +6 -6
  110. package/src/react/index.ts +5 -5
  111. package/src/react/useProcedure.ts +60 -60
  112. package/src/react/useReducer.ts +53 -53
  113. package/src/react/useSpacetimeDB.ts +18 -18
  114. package/src/react/useTable.ts +256 -251
  115. package/src/sdk/client_api/index.ts +114 -114
  116. package/src/sdk/client_api/types/procedures.ts +8 -8
  117. package/src/sdk/client_api/types/reducers.ts +8 -8
  118. package/src/sdk/client_api/types.ts +288 -288
  119. package/src/sdk/client_cache.ts +129 -129
  120. package/src/sdk/client_table.ts +179 -179
  121. package/src/sdk/connection_manager.ts +352 -237
  122. package/src/sdk/db_connection_builder.ts +290 -290
  123. package/src/sdk/db_connection_impl.ts +1356 -1347
  124. package/src/sdk/db_context.ts +28 -28
  125. package/src/sdk/db_view.ts +12 -12
  126. package/src/sdk/decompress.ts +51 -51
  127. package/src/sdk/event.ts +18 -18
  128. package/src/sdk/event_context.ts +51 -51
  129. package/src/sdk/event_emitter.ts +32 -32
  130. package/src/sdk/index.ts +14 -14
  131. package/src/sdk/internal.ts +2 -2
  132. package/src/sdk/json_api.ts +46 -46
  133. package/src/sdk/logger.ts +134 -134
  134. package/src/sdk/message_types.ts +46 -46
  135. package/src/sdk/procedures.ts +83 -83
  136. package/src/sdk/reducer_event.ts +20 -20
  137. package/src/sdk/reducer_handle.ts +12 -12
  138. package/src/sdk/reducers.ts +159 -159
  139. package/src/sdk/schema.ts +45 -45
  140. package/src/sdk/spacetime_module.ts +28 -28
  141. package/src/sdk/subscription_builder_impl.ts +275 -275
  142. package/src/sdk/table_cache.ts +581 -581
  143. package/src/sdk/type_utils.ts +19 -19
  144. package/src/sdk/version.ts +133 -133
  145. package/src/sdk/websocket_decompress_adapter.ts +63 -63
  146. package/src/sdk/websocket_protocols.ts +25 -25
  147. package/src/sdk/websocket_test_adapter.ts +107 -100
  148. package/src/sdk/websocket_v3_frames.ts +126 -126
  149. package/src/sdk/ws.ts +105 -105
  150. package/src/server/console.ts +81 -81
  151. package/src/server/db_view.ts +21 -21
  152. package/src/server/errors.ts +138 -138
  153. package/src/server/http.test-d.ts +80 -80
  154. package/src/server/http.ts +14 -14
  155. package/src/server/http_handlers.ts +413 -413
  156. package/src/server/http_internal.ts +79 -79
  157. package/src/server/http_shared.ts +186 -186
  158. package/src/server/index.ts +37 -37
  159. package/src/server/polyfills.ts +4 -4
  160. package/src/server/procedures.ts +239 -239
  161. package/src/server/query.ts +1 -1
  162. package/src/server/range.ts +53 -53
  163. package/src/server/reducers.ts +113 -113
  164. package/src/server/rng.ts +113 -113
  165. package/src/server/runtime.ts +1102 -1102
  166. package/src/server/schema.test-d.ts +99 -99
  167. package/src/server/schema.ts +663 -663
  168. package/src/server/sys.d.ts +125 -125
  169. package/src/server/view.test-d.ts +194 -194
  170. package/src/server/views.ts +340 -340
  171. package/src/solid/SpacetimeDBProvider.ts +97 -0
  172. package/src/solid/connection_state.ts +6 -0
  173. package/src/solid/index.ts +5 -0
  174. package/src/solid/useProcedure.ts +57 -0
  175. package/src/solid/useReducer.ts +50 -0
  176. package/src/solid/useSpacetimeDB.ts +18 -0
  177. package/src/solid/useTable.ts +203 -0
  178. package/src/svelte/SpacetimeDBProvider.ts +101 -101
  179. package/src/svelte/connection_state.ts +16 -16
  180. package/src/svelte/index.ts +4 -4
  181. package/src/svelte/useReducer.ts +61 -61
  182. package/src/svelte/useSpacetimeDB.ts +22 -22
  183. package/src/svelte/useTable.ts +218 -218
  184. package/src/tanstack/SpacetimeDBQueryClient.ts +330 -330
  185. package/src/tanstack/hooks.ts +83 -83
  186. package/src/tanstack/index.ts +16 -16
  187. package/src/util-stub.ts +1 -1
  188. package/src/vue/SpacetimeDBProvider.ts +157 -157
  189. package/src/vue/connection_state.ts +19 -19
  190. package/src/vue/index.ts +5 -5
  191. package/src/vue/useProcedure.ts +62 -62
  192. package/src/vue/useReducer.ts +55 -55
  193. package/src/vue/useSpacetimeDB.ts +18 -18
  194. package/src/vue/useTable.ts +229 -229
@@ -1,819 +1,819 @@
1
- import { TimeDuration } from './time_duration';
2
- import { Timestamp } from './timestamp';
3
- import { Uuid } from './uuid';
4
- import { ConnectionId } from './connection_id';
5
- import BinaryReader from './binary_reader';
6
- import BinaryWriter from './binary_writer';
7
- import { Identity } from './identity';
8
- import * as AlgebraicTypeVariants from './algebraic_type_variants';
9
- import { hasOwn } from './util';
10
-
11
- type TypespaceType = {
12
- types: AlgebraicTypeType[];
13
- };
14
-
15
- export type ProductTypeType = {
16
- elements: ProductTypeElement[];
17
- };
18
-
19
- /**
20
- * A factor / element of a product type.
21
- *
22
- * An element consist of an optional name and a type.
23
- *
24
- * NOTE: Each element has an implicit element tag based on its order.
25
- * Uniquely identifies an element similarly to protobuf tags.
26
- */
27
- export type ProductTypeElement = {
28
- name: string | undefined;
29
- algebraicType: AlgebraicTypeType;
30
- };
31
-
32
- export type SumTypeType = {
33
- variants: SumTypeVariant[];
34
- };
35
-
36
- /**
37
- * A variant of a sum type.
38
- *
39
- * NOTE: Each element has an implicit element tag based on its order.
40
- * Uniquely identifies an element similarly to protobuf tags.
41
- */
42
- export type SumTypeVariant = {
43
- name: string | undefined;
44
- algebraicType: AlgebraicTypeType;
45
- };
46
-
47
- export type AlgebraicTypeType =
48
- | AlgebraicTypeVariants.Ref
49
- | AlgebraicTypeVariants.Sum
50
- | AlgebraicTypeVariants.Product
51
- | AlgebraicTypeVariants.Array
52
- | AlgebraicTypeVariants.String
53
- | AlgebraicTypeVariants.Bool
54
- | AlgebraicTypeVariants.I8
55
- | AlgebraicTypeVariants.U8
56
- | AlgebraicTypeVariants.I16
57
- | AlgebraicTypeVariants.U16
58
- | AlgebraicTypeVariants.I32
59
- | AlgebraicTypeVariants.U32
60
- | AlgebraicTypeVariants.I64
61
- | AlgebraicTypeVariants.U64
62
- | AlgebraicTypeVariants.I128
63
- | AlgebraicTypeVariants.U128
64
- | AlgebraicTypeVariants.I256
65
- | AlgebraicTypeVariants.U256
66
- | AlgebraicTypeVariants.F32
67
- | AlgebraicTypeVariants.F64;
68
-
69
- export type AlgebraicType = AlgebraicTypeType;
70
-
71
- /**
72
- * The variant types of the Algebraic Type tagged union.
73
- */
74
- export { AlgebraicTypeVariants };
75
-
76
- export type Serializer<T> = (writer: BinaryWriter, value: T) => void;
77
-
78
- export type Deserializer<T> = (reader: BinaryReader) => T;
79
-
80
- // Caches to prevent `makeSerializer`/`makeDeserializer` from recursing
81
- // infinitely when called on recursive types.
82
- //
83
- // We check for recursion in `{Product,Sum}Type.make{Deser,Ser}ializer` rather
84
- // than in `AlgebraciType.make{Deser,Ser}ializer` because we need to store the
85
- // [de]serializer in the cache before recursing into its fields/variants, and
86
- // we wouldn't be able to do that in the `AlgebraicType` functions.
87
- const SERIALIZERS = new Map<ProductType | SumType, Serializer<any>>();
88
- const DESERIALIZERS = new Map<ProductType | SumType, Deserializer<any>>();
89
-
90
- // A value with helper functions to construct the type.
91
- export const AlgebraicType = {
92
- Ref: (value: number): AlgebraicTypeVariants.Ref => ({ tag: 'Ref', value }),
93
- Sum: <T extends SumTypeType>(value: T): { tag: 'Sum'; value: T } => ({
94
- tag: 'Sum',
95
- value,
96
- }),
97
- Product: <T extends ProductTypeType>(
98
- value: T
99
- ): { tag: 'Product'; value: T } => ({
100
- tag: 'Product',
101
- value,
102
- }),
103
- Array: <T extends AlgebraicTypeType>(
104
- value: T
105
- ): { tag: 'Array'; value: T } => ({
106
- tag: 'Array',
107
- value,
108
- }),
109
- String: { tag: 'String' } as const,
110
- Bool: { tag: 'Bool' } as const,
111
- I8: { tag: 'I8' } as const,
112
- U8: { tag: 'U8' } as const,
113
- I16: { tag: 'I16' } as const,
114
- U16: { tag: 'U16' } as const,
115
- I32: { tag: 'I32' } as const,
116
- U32: { tag: 'U32' } as const,
117
- I64: { tag: 'I64' } as const,
118
- U64: { tag: 'U64' } as const,
119
- I128: { tag: 'I128' } as const,
120
- U128: { tag: 'U128' } as const,
121
- I256: { tag: 'I256' } as const,
122
- U256: { tag: 'U256' } as const,
123
- F32: { tag: 'F32' } as const,
124
- F64: { tag: 'F64' } as const,
125
- makeSerializer(
126
- ty: AlgebraicTypeType,
127
- typespace?: TypespaceType
128
- ): Serializer<any> {
129
- if (ty.tag === 'Ref') {
130
- if (!typespace)
131
- throw new Error('cannot serialize refs without a typespace');
132
- while (ty.tag === 'Ref') ty = typespace.types[ty.value];
133
- }
134
- switch (ty.tag) {
135
- case 'Product':
136
- return ProductType.makeSerializer(ty.value, typespace);
137
- case 'Sum':
138
- return SumType.makeSerializer(ty.value, typespace);
139
- case 'Array':
140
- if (ty.value.tag === 'U8') {
141
- return serializeUint8Array;
142
- } else {
143
- const serialize = AlgebraicType.makeSerializer(ty.value, typespace);
144
- return (writer, value) => {
145
- writer.writeU32(value.length);
146
- for (const elem of value) {
147
- serialize(writer, elem);
148
- }
149
- };
150
- }
151
- default:
152
- return primitiveSerializers[ty.tag];
153
- }
154
- },
155
- /** @deprecated Use `makeSerializer` instead. */
156
- serializeValue(
157
- writer: BinaryWriter,
158
- ty: AlgebraicTypeType,
159
- value: any,
160
- typespace?: TypespaceType
161
- ) {
162
- AlgebraicType.makeSerializer(ty, typespace)(writer, value);
163
- },
164
- makeDeserializer(
165
- ty: AlgebraicTypeType,
166
- typespace?: TypespaceType
167
- ): Deserializer<any> {
168
- if (ty.tag === 'Ref') {
169
- if (!typespace)
170
- throw new Error('cannot deserialize refs without a typespace');
171
- while (ty.tag === 'Ref') ty = typespace.types[ty.value];
172
- }
173
- switch (ty.tag) {
174
- case 'Product':
175
- return ProductType.makeDeserializer(ty.value, typespace);
176
- case 'Sum':
177
- return SumType.makeDeserializer(ty.value, typespace);
178
- case 'Array':
179
- if (ty.value.tag === 'U8') {
180
- return deserializeUint8Array;
181
- } else {
182
- const deserialize = AlgebraicType.makeDeserializer(
183
- ty.value,
184
- typespace
185
- );
186
- return reader => {
187
- const length = reader.readU32();
188
- const result: any[] = Array(length);
189
- for (let i = 0; i < length; i++) {
190
- result[i] = deserialize(reader);
191
- }
192
- return result;
193
- };
194
- }
195
- default:
196
- return primitiveDeserializers[ty.tag];
197
- }
198
- },
199
- /** @deprecated Use `makeDeserializer` instead. */
200
- deserializeValue(
201
- reader: BinaryReader,
202
- ty: AlgebraicTypeType,
203
- typespace?: TypespaceType
204
- ): any {
205
- return AlgebraicType.makeDeserializer(ty, typespace)(reader);
206
- },
207
- /**
208
- * Convert a value of the algebraic type into something that can be used as a key in a map.
209
- * There are no guarantees about being able to order it.
210
- * This is only guaranteed to be comparable to other values of the same type.
211
- * @param value A value of the algebraic type
212
- * @returns Something that can be used as a key in a map.
213
- */
214
- intoMapKey: function (
215
- ty: AlgebraicTypeType,
216
- value: any
217
- ): ComparablePrimitive {
218
- switch (ty.tag) {
219
- case 'U8':
220
- case 'U16':
221
- case 'U32':
222
- case 'U64':
223
- case 'U128':
224
- case 'U256':
225
- case 'I8':
226
- case 'I16':
227
- case 'I32':
228
- case 'I64':
229
- case 'I128':
230
- case 'I256':
231
- case 'F32':
232
- case 'F64':
233
- case 'String':
234
- case 'Bool':
235
- return value;
236
- case 'Product':
237
- return ProductType.intoMapKey(ty.value, value);
238
- default: {
239
- // The fallback is to serialize and base64 encode the bytes.
240
- const writer = new BinaryWriter(10);
241
- AlgebraicType.serializeValue(writer, ty, value);
242
- return writer.toBase64();
243
- }
244
- }
245
- },
246
- };
247
-
248
- function bindCall<F extends (this: any, ...args: any[]) => any>(
249
- f: F
250
- ): (recv: ThisParameterType<F>, ...args: Parameters<F>) => ReturnType<F> {
251
- return Function.prototype.call.bind(f);
252
- }
253
-
254
- type Primitives = Exclude<
255
- AlgebraicType['tag'],
256
- 'Ref' | 'Sum' | 'Product' | 'Array'
257
- >;
258
-
259
- const primitiveSerializers: Record<Primitives, Serializer<any>> = {
260
- Bool: bindCall(BinaryWriter.prototype.writeBool),
261
- I8: bindCall(BinaryWriter.prototype.writeI8),
262
- U8: bindCall(BinaryWriter.prototype.writeU8),
263
- I16: bindCall(BinaryWriter.prototype.writeI16),
264
- U16: bindCall(BinaryWriter.prototype.writeU16),
265
- I32: bindCall(BinaryWriter.prototype.writeI32),
266
- U32: bindCall(BinaryWriter.prototype.writeU32),
267
- I64: bindCall(BinaryWriter.prototype.writeI64),
268
- U64: bindCall(BinaryWriter.prototype.writeU64),
269
- I128: bindCall(BinaryWriter.prototype.writeI128),
270
- U128: bindCall(BinaryWriter.prototype.writeU128),
271
- I256: bindCall(BinaryWriter.prototype.writeI256),
272
- U256: bindCall(BinaryWriter.prototype.writeU256),
273
- F32: bindCall(BinaryWriter.prototype.writeF32),
274
- F64: bindCall(BinaryWriter.prototype.writeF64),
275
- String: bindCall(BinaryWriter.prototype.writeString),
276
- };
277
- Object.freeze(primitiveSerializers);
278
-
279
- const serializeUint8Array = bindCall(BinaryWriter.prototype.writeUInt8Array);
280
-
281
- const primitiveDeserializers: Record<Primitives, Deserializer<any>> = {
282
- Bool: bindCall(BinaryReader.prototype.readBool),
283
- I8: bindCall(BinaryReader.prototype.readI8),
284
- U8: bindCall(BinaryReader.prototype.readU8),
285
- I16: bindCall(BinaryReader.prototype.readI16),
286
- U16: bindCall(BinaryReader.prototype.readU16),
287
- I32: bindCall(BinaryReader.prototype.readI32),
288
- U32: bindCall(BinaryReader.prototype.readU32),
289
- I64: bindCall(BinaryReader.prototype.readI64),
290
- U64: bindCall(BinaryReader.prototype.readU64),
291
- I128: bindCall(BinaryReader.prototype.readI128),
292
- U128: bindCall(BinaryReader.prototype.readU128),
293
- I256: bindCall(BinaryReader.prototype.readI256),
294
- U256: bindCall(BinaryReader.prototype.readU256),
295
- F32: bindCall(BinaryReader.prototype.readF32),
296
- F64: bindCall(BinaryReader.prototype.readF64),
297
- String: bindCall(BinaryReader.prototype.readString),
298
- };
299
- Object.freeze(primitiveDeserializers);
300
-
301
- const deserializeUint8Array = bindCall(BinaryReader.prototype.readUInt8Array);
302
-
303
- type FixedSizePrimitives = Exclude<Primitives, 'String'>;
304
-
305
- const primitiveSizes: Record<FixedSizePrimitives, number> = {
306
- Bool: 1,
307
- I8: 1,
308
- U8: 1,
309
- I16: 2,
310
- U16: 2,
311
- I32: 4,
312
- U32: 4,
313
- I64: 8,
314
- U64: 8,
315
- I128: 16,
316
- U128: 16,
317
- I256: 32,
318
- U256: 32,
319
- F32: 4,
320
- F64: 8,
321
- };
322
-
323
- const fixedSizePrimitives = new Set(Object.keys(primitiveSizes));
324
-
325
- type FixedSizeProductType = {
326
- elements: { name: string; algebraicType: { tag: FixedSizePrimitives } }[];
327
- };
328
-
329
- const isFixedSizeProduct = (ty: ProductType): ty is FixedSizeProductType =>
330
- ty.elements.every(({ algebraicType }) =>
331
- fixedSizePrimitives.has(algebraicType.tag)
332
- );
333
-
334
- const productSize = (ty: FixedSizeProductType): number =>
335
- ty.elements.reduce(
336
- (acc, { algebraicType }) => acc + primitiveSizes[algebraicType.tag],
337
- 0
338
- );
339
-
340
- type JSPrimitives = Exclude<
341
- FixedSizePrimitives,
342
- 'I128' | 'U128' | 'I256' | 'U256'
343
- >;
344
-
345
- const primitiveJSName: Record<JSPrimitives, string> = {
346
- Bool: 'Uint8',
347
- I8: 'Int8',
348
- U8: 'Uint8',
349
- I16: 'Int16',
350
- U16: 'Uint16',
351
- I32: 'Int32',
352
- U32: 'Uint32',
353
- I64: 'BigInt64',
354
- U64: 'BigUint64',
355
- F32: 'Float32',
356
- F64: 'Float64',
357
- };
358
-
359
- type SpecialProducts = {
360
- __time_duration_micros__: TimeDuration;
361
- __timestamp_micros_since_unix_epoch__: Timestamp;
362
- __identity__: Identity;
363
- __connection_id__: ConnectionId;
364
- __uuid__: Uuid;
365
- };
366
-
367
- const specialProductDeserializers: {
368
- [k in keyof SpecialProducts]: Deserializer<SpecialProducts[k]>;
369
- } = {
370
- __time_duration_micros__: reader => new TimeDuration(reader.readI64()),
371
- __timestamp_micros_since_unix_epoch__: reader =>
372
- new Timestamp(reader.readI64()),
373
- __identity__: reader => new Identity(reader.readU256()),
374
- __connection_id__: reader => new ConnectionId(reader.readU128()),
375
- __uuid__: reader => new Uuid(reader.readU128()),
376
- };
377
- Object.freeze(specialProductDeserializers);
378
-
379
- const unitDeserializer: Deserializer<{}> = () => ({});
380
-
381
- const getElementInitializer = (element: ProductTypeElement) => {
382
- let init: string;
383
- switch (element.algebraicType.tag) {
384
- case 'String':
385
- init = "''";
386
- break;
387
- case 'Bool':
388
- init = 'false';
389
- break;
390
- case 'I8':
391
- case 'U8':
392
- case 'I16':
393
- case 'U16':
394
- case 'I32':
395
- case 'U32':
396
- init = '0';
397
- break;
398
- case 'I64':
399
- case 'U64':
400
- case 'I128':
401
- case 'U128':
402
- case 'I256':
403
- case 'U256':
404
- init = '0n';
405
- break;
406
- case 'F32':
407
- case 'F64':
408
- init = '0.0';
409
- break;
410
- default:
411
- init = 'undefined';
412
- }
413
- return `${element.name!}: ${init}`;
414
- };
415
-
416
- /**
417
- * A structural product type of the factors given by `elements`.
418
- *
419
- * This is also known as `struct` and `tuple` in many languages,
420
- * but note that unlike most languages, products in SATs are *[structural]* and not nominal.
421
- * When checking whether two nominal types are the same,
422
- * their names and/or declaration sites (e.g., module / namespace) are considered.
423
- * Meanwhile, a structural type system would only check the structure of the type itself,
424
- * e.g., the names of its fields and their types in the case of a record.
425
- * The name "product" comes from category theory.
426
- *
427
- * See also: https://ncatlab.org/nlab/show/product+type.
428
- *
429
- * These structures are known as product types because the number of possible values in product
430
- * ```ignore
431
- * { N_0: T_0, N_1: T_1, ..., N_n: T_n }
432
- * ```
433
- * is:
434
- * ```ignore
435
- * Π (i ∈ 0..n). values(T_i)
436
- * ```
437
- * so for example, `values({ A: U64, B: Bool }) = values(U64) * values(Bool)`.
438
- *
439
- * [structural]: https://en.wikipedia.org/wiki/Structural_type_system
440
- */
441
- export type ProductType = ProductTypeType;
442
-
443
- export const ProductType = {
444
- makeSerializer(
445
- ty: ProductTypeType,
446
- typespace?: TypespaceType
447
- ): Serializer<any> {
448
- let serializer = SERIALIZERS.get(ty);
449
- if (serializer != null) return serializer;
450
-
451
- if (isFixedSizeProduct(ty)) {
452
- const size = productSize(ty);
453
- const body = `\
454
- "use strict";
455
- writer.expandBuffer(${size});
456
- const view = writer.view;
457
- ${ty.elements
458
- .map(({ name, algebraicType: { tag } }) =>
459
- tag in primitiveJSName
460
- ? `\
461
- view.set${primitiveJSName[tag as JSPrimitives]}(writer.offset, value.${name!}, ${primitiveSizes[tag] > 1 ? 'true' : ''});
462
- writer.offset += ${primitiveSizes[tag]};`
463
- : `writer.write${tag}(value.${name});`
464
- )
465
- .join('\n')}`;
466
- serializer = Function('writer', 'value', body) as Serializer<any>;
467
- SERIALIZERS.set(ty, serializer);
468
- return serializer;
469
- }
470
-
471
- // Because V8 forces us to generate our code as a string, rather than a proper syntax tree,
472
- // we can't have our `body` close over values.
473
- // Instead, we construct an object with the values we'd otherwise "close over" in `serializers`,
474
- // and use `Function.prototype.bind` to pass it as the `this` argument.
475
- //
476
- // We populate `serializers` after constructing this type's `serializer`
477
- // so that it can close over itself, in the case that `ty` is recursive.
478
- const serializers: Record<string, Serializer<any>> = {};
479
- const body =
480
- '"use strict";\n' +
481
- ty.elements
482
- .map(
483
- element => `this.${element.name!}(writer, value.${element.name!});`
484
- )
485
- .join('\n');
486
- serializer = Function('writer', 'value', body).bind(
487
- serializers
488
- ) as Serializer<any>;
489
- // In case `ty` is recursive, we cache the function *before* before computing
490
- // `serializers`, so that a recursive `makeSerializer` with the same `ty` has
491
- // an exit condition.
492
- SERIALIZERS.set(ty, serializer);
493
- for (const { name, algebraicType } of ty.elements) {
494
- serializers[name!] = AlgebraicType.makeSerializer(
495
- algebraicType,
496
- typespace
497
- );
498
- }
499
- Object.freeze(serializers);
500
- return serializer;
501
- },
502
- /** @deprecated Use `makeSerializer` instead. */
503
- serializeValue(
504
- writer: BinaryWriter,
505
- ty: ProductTypeType,
506
- value: any,
507
- typespace?: TypespaceType
508
- ): void {
509
- ProductType.makeSerializer(ty, typespace)(writer, value);
510
- },
511
- makeDeserializer(
512
- ty: ProductTypeType,
513
- typespace?: TypespaceType
514
- ): Deserializer<any> {
515
- switch (ty.elements.length) {
516
- case 0:
517
- return unitDeserializer;
518
- case 1: {
519
- const fieldName = ty.elements[0].name!;
520
- if (hasOwn(specialProductDeserializers, fieldName))
521
- return specialProductDeserializers[
522
- fieldName as keyof SpecialProducts
523
- ];
524
- }
525
- }
526
-
527
- let deserializer = DESERIALIZERS.get(ty);
528
- if (deserializer != null) return deserializer;
529
-
530
- if (isFixedSizeProduct(ty)) {
531
- const body = `\
532
- "use strict";
533
- const result = { ${ty.elements.map(getElementInitializer).join(', ')} };
534
- const view = reader.view;
535
- ${ty.elements
536
- .map(({ name, algebraicType: { tag } }) =>
537
- tag in primitiveJSName
538
- ? tag === 'Bool'
539
- ? `\
540
- result.${name} = view.getUint8(reader.offset) !== 0;
541
- reader.offset += 1;`
542
- : `\
543
- result.${name} = view.get${primitiveJSName[tag as JSPrimitives]}(reader.offset, ${primitiveSizes[tag] > 1 ? 'true' : ''});
544
- reader.offset += ${primitiveSizes[tag]};`
545
- : `result.${name} = reader.read${tag}();`
546
- )
547
- .join('\n')}
548
- return result;`;
549
- deserializer = Function('reader', body) as Deserializer<any>;
550
- DESERIALIZERS.set(ty, deserializer);
551
- return deserializer;
552
- }
553
-
554
- // Because V8 forces us to generate our code as a string, rather than a proper syntax tree,
555
- // we can't have our `body` close over values.
556
- // Instead, we construct an object with the values we'd otherwise "close over" in `deserializers`,
557
- // and use `Function.prototype.bind` to pass it as the `this` argument.
558
- //
559
- // We populate `deserializers` after constructing this type's `deserializer`
560
- // so that it can close over itself, in the case that `ty` is recursive.
561
- const deserializers: Record<string, Deserializer<any>> = {};
562
- deserializer = Function(
563
- 'reader',
564
- `\
565
- "use strict";
566
- const result = { ${ty.elements.map(getElementInitializer).join(', ')} };
567
- ${ty.elements.map(({ name }) => `result.${name!} = this.${name!}(reader);`).join('\n')}
568
- return result;`
569
- ).bind(deserializers) as Deserializer<any>;
570
- // In case `ty` is recursive, we cache the function *before* before computing
571
- // `deserializers`, so that a recursive `makeDeserializer` with the same `ty` has
572
- // an exit condition.
573
- DESERIALIZERS.set(ty, deserializer);
574
- for (const { name, algebraicType } of ty.elements) {
575
- deserializers[name!] = AlgebraicType.makeDeserializer(
576
- algebraicType,
577
- typespace
578
- );
579
- }
580
- Object.freeze(deserializers);
581
- return deserializer;
582
- },
583
- /** @deprecated Use `makeDeserializer` instead. */
584
- deserializeValue(
585
- reader: BinaryReader,
586
- ty: ProductTypeType,
587
- typespace?: TypespaceType
588
- ): any {
589
- return ProductType.makeDeserializer(ty, typespace)(reader);
590
- },
591
- intoMapKey(ty: ProductTypeType, value: any): ComparablePrimitive {
592
- if (ty.elements.length === 1) {
593
- const fieldName = ty.elements[0].name!;
594
- if (hasOwn(specialProductDeserializers, fieldName)) {
595
- return value[fieldName];
596
- }
597
- }
598
- // The fallback is to serialize and base64 encode the bytes.
599
- const writer = new BinaryWriter(10);
600
- AlgebraicType.serializeValue(writer, AlgebraicType.Product(ty), value);
601
- return writer.toBase64();
602
- },
603
- };
604
-
605
- export type SumType = SumTypeType;
606
-
607
- /**
608
- * Unlike most languages, sums in SATS are *[structural]* and not nominal.
609
- * When checking whether two nominal types are the same,
610
- * their names and/or declaration sites (e.g., module / namespace) are considered.
611
- * Meanwhile, a structural type system would only check the structure of the type itself,
612
- * e.g., the names of its variants and their inner data types in the case of a sum.
613
- *
614
- * This is also known as a discriminated union (implementation) or disjoint union.
615
- * Another name is [coproduct (category theory)](https://ncatlab.org/nlab/show/coproduct).
616
- *
617
- * These structures are known as sum types because the number of possible values a sum
618
- * ```ignore
619
- * { N_0(T_0), N_1(T_1), ..., N_n(T_n) }
620
- * ```
621
- * is:
622
- * ```ignore
623
- * Σ (i ∈ 0..n). values(T_i)
624
- * ```
625
- * so for example, `values({ A(U64), B(Bool) }) = values(U64) + values(Bool)`.
626
- *
627
- * See also: https://ncatlab.org/nlab/show/sum+type.
628
- *
629
- * [structural]: https://en.wikipedia.org/wiki/Structural_type_system
630
- */
631
- export const SumType = {
632
- makeSerializer(ty: SumTypeType, typespace?: TypespaceType): Serializer<any> {
633
- if (
634
- ty.variants.length == 2 &&
635
- ty.variants[0].name === 'some' &&
636
- ty.variants[1].name === 'none'
637
- ) {
638
- const serialize = AlgebraicType.makeSerializer(
639
- ty.variants[0].algebraicType,
640
- typespace
641
- );
642
- return (writer, value) => {
643
- if (value !== null && value !== undefined) {
644
- writer.writeByte(0);
645
- serialize(writer, value);
646
- } else {
647
- writer.writeByte(1);
648
- }
649
- };
650
- } else if (
651
- ty.variants.length == 2 &&
652
- ty.variants[0].name === 'ok' &&
653
- ty.variants[1].name === 'err'
654
- ) {
655
- const serializeOk = AlgebraicType.makeSerializer(
656
- ty.variants[0].algebraicType,
657
- typespace
658
- );
659
- const serializeErr = AlgebraicType.makeSerializer(
660
- ty.variants[0].algebraicType,
661
- typespace
662
- );
663
-
664
- return (writer, value) => {
665
- if ('ok' in value) {
666
- writer.writeU8(0);
667
- serializeOk(writer, value.ok);
668
- } else if ('err' in value) {
669
- writer.writeU8(1);
670
- serializeErr(writer, value.err);
671
- } else {
672
- throw new TypeError(
673
- 'could not serialize result: object had neither a `ok` nor an `err` field'
674
- );
675
- }
676
- };
677
- } else {
678
- let serializer = SERIALIZERS.get(ty);
679
- if (serializer != null) return serializer;
680
-
681
- const serializers: Record<string, Serializer<any>> = {};
682
-
683
- const body = `\
684
- switch (value.tag) {
685
- ${ty.variants
686
- .map(
687
- ({ name }, i) => `\
688
- case ${JSON.stringify(name!)}:
689
- writer.writeByte(${i});
690
- return this.${name!}(writer, value.value);`
691
- )
692
- .join('\n')}
693
- default:
694
- throw new TypeError(
695
- \`Could not serialize sum type; unknown tag \${value.tag}\`
696
- )
697
- }
698
- `;
699
-
700
- serializer = Function('writer', 'value', body).bind(
701
- serializers
702
- ) as Serializer<any>;
703
-
704
- // In case `ty` is recursive, we cache the function *before* before computing
705
- // `variants`, so that a recursive `makeSerializer` with the same `ty` has
706
- // an exit condition.
707
- SERIALIZERS.set(ty, serializer);
708
-
709
- for (const { name, algebraicType } of ty.variants) {
710
- serializers[name!] = AlgebraicType.makeSerializer(
711
- algebraicType,
712
- typespace
713
- );
714
- }
715
- Object.freeze(serializers);
716
- return serializer;
717
- }
718
- },
719
- /** @deprecated Use `makeSerializer` instead. */
720
- serializeValue(
721
- writer: BinaryWriter,
722
- ty: SumTypeType,
723
- value: any,
724
- typespace?: TypespaceType
725
- ): void {
726
- SumType.makeSerializer(ty, typespace)(writer, value);
727
- },
728
- makeDeserializer(
729
- ty: SumTypeType,
730
- typespace?: TypespaceType
731
- ): Deserializer<any> {
732
- // In TypeScript we handle Option values as a special case
733
- // we don't represent the some and none variants, but instead
734
- // we represent the value directly.
735
- //
736
- // For these special cases, we don't do dynamic codegen, since that has the
737
- // most benefit in cases where the object has a different shape. Since
738
- // option/result always have the same number of variants, there's not as
739
- // much benefit for the amount of work.
740
- if (
741
- ty.variants.length == 2 &&
742
- ty.variants[0].name === 'some' &&
743
- ty.variants[1].name === 'none'
744
- ) {
745
- const deserialize = AlgebraicType.makeDeserializer(
746
- ty.variants[0].algebraicType,
747
- typespace
748
- );
749
- return reader => {
750
- const tag = reader.readU8();
751
- if (tag === 0) {
752
- return deserialize(reader);
753
- } else if (tag === 1) {
754
- return undefined;
755
- } else {
756
- throw `Can't deserialize an option type, couldn't find ${tag} tag`;
757
- }
758
- };
759
- } else if (
760
- ty.variants.length == 2 &&
761
- ty.variants[0].name === 'ok' &&
762
- ty.variants[1].name === 'err'
763
- ) {
764
- const deserializeOk = AlgebraicType.makeDeserializer(
765
- ty.variants[0].algebraicType,
766
- typespace
767
- );
768
- const deserializeErr = AlgebraicType.makeDeserializer(
769
- ty.variants[1].algebraicType,
770
- typespace
771
- );
772
- return reader => {
773
- const tag = reader.readByte();
774
- if (tag === 0) {
775
- return { ok: deserializeOk(reader) };
776
- } else if (tag === 1) {
777
- return { err: deserializeErr(reader) };
778
- } else {
779
- throw `Can't deserialize a result type, couldn't find ${tag} tag`;
780
- }
781
- };
782
- } else {
783
- let deserializer = DESERIALIZERS.get(ty);
784
- if (deserializer != null) return deserializer;
785
- const deserializers: Record<string, Deserializer<any>> = {};
786
- deserializer = Function(
787
- 'reader',
788
- `switch (reader.readU8()) {\n${ty.variants
789
- .map(
790
- ({ name }, i) =>
791
- `case ${i}: return { tag: ${JSON.stringify(name!)}, value: this.${name!}(reader) };`
792
- )
793
- .join('\n')} }`
794
- ).bind(deserializers) as Deserializer<any>;
795
- // In case `ty` is recursive, we cache the function *before* before computing
796
- // `deserializers`, so that a recursive `makeDeserializer` with the same `ty` has
797
- // an exit condition.
798
- DESERIALIZERS.set(ty, deserializer);
799
- for (const { name, algebraicType } of ty.variants) {
800
- deserializers[name!] = AlgebraicType.makeDeserializer(
801
- algebraicType,
802
- typespace
803
- );
804
- }
805
- Object.freeze(deserializers);
806
- return deserializer;
807
- }
808
- },
809
- /** @deprecated Use `makeDeserializer` instead. */
810
- deserializeValue(
811
- reader: BinaryReader,
812
- ty: SumTypeType,
813
- typespace?: TypespaceType
814
- ): any {
815
- return SumType.makeDeserializer(ty, typespace)(reader);
816
- },
817
- };
818
-
819
- export type ComparablePrimitive = number | string | boolean | bigint;
1
+ import { TimeDuration } from './time_duration';
2
+ import { Timestamp } from './timestamp';
3
+ import { Uuid } from './uuid';
4
+ import { ConnectionId } from './connection_id';
5
+ import BinaryReader from './binary_reader';
6
+ import BinaryWriter from './binary_writer';
7
+ import { Identity } from './identity';
8
+ import * as AlgebraicTypeVariants from './algebraic_type_variants';
9
+ import { hasOwn } from './util';
10
+
11
+ type TypespaceType = {
12
+ types: AlgebraicTypeType[];
13
+ };
14
+
15
+ export type ProductTypeType = {
16
+ elements: ProductTypeElement[];
17
+ };
18
+
19
+ /**
20
+ * A factor / element of a product type.
21
+ *
22
+ * An element consist of an optional name and a type.
23
+ *
24
+ * NOTE: Each element has an implicit element tag based on its order.
25
+ * Uniquely identifies an element similarly to protobuf tags.
26
+ */
27
+ export type ProductTypeElement = {
28
+ name: string | undefined;
29
+ algebraicType: AlgebraicTypeType;
30
+ };
31
+
32
+ export type SumTypeType = {
33
+ variants: SumTypeVariant[];
34
+ };
35
+
36
+ /**
37
+ * A variant of a sum type.
38
+ *
39
+ * NOTE: Each element has an implicit element tag based on its order.
40
+ * Uniquely identifies an element similarly to protobuf tags.
41
+ */
42
+ export type SumTypeVariant = {
43
+ name: string | undefined;
44
+ algebraicType: AlgebraicTypeType;
45
+ };
46
+
47
+ export type AlgebraicTypeType =
48
+ | AlgebraicTypeVariants.Ref
49
+ | AlgebraicTypeVariants.Sum
50
+ | AlgebraicTypeVariants.Product
51
+ | AlgebraicTypeVariants.Array
52
+ | AlgebraicTypeVariants.String
53
+ | AlgebraicTypeVariants.Bool
54
+ | AlgebraicTypeVariants.I8
55
+ | AlgebraicTypeVariants.U8
56
+ | AlgebraicTypeVariants.I16
57
+ | AlgebraicTypeVariants.U16
58
+ | AlgebraicTypeVariants.I32
59
+ | AlgebraicTypeVariants.U32
60
+ | AlgebraicTypeVariants.I64
61
+ | AlgebraicTypeVariants.U64
62
+ | AlgebraicTypeVariants.I128
63
+ | AlgebraicTypeVariants.U128
64
+ | AlgebraicTypeVariants.I256
65
+ | AlgebraicTypeVariants.U256
66
+ | AlgebraicTypeVariants.F32
67
+ | AlgebraicTypeVariants.F64;
68
+
69
+ export type AlgebraicType = AlgebraicTypeType;
70
+
71
+ /**
72
+ * The variant types of the Algebraic Type tagged union.
73
+ */
74
+ export { AlgebraicTypeVariants };
75
+
76
+ export type Serializer<T> = (writer: BinaryWriter, value: T) => void;
77
+
78
+ export type Deserializer<T> = (reader: BinaryReader) => T;
79
+
80
+ // Caches to prevent `makeSerializer`/`makeDeserializer` from recursing
81
+ // infinitely when called on recursive types.
82
+ //
83
+ // We check for recursion in `{Product,Sum}Type.make{Deser,Ser}ializer` rather
84
+ // than in `AlgebraciType.make{Deser,Ser}ializer` because we need to store the
85
+ // [de]serializer in the cache before recursing into its fields/variants, and
86
+ // we wouldn't be able to do that in the `AlgebraicType` functions.
87
+ const SERIALIZERS = new Map<ProductType | SumType, Serializer<any>>();
88
+ const DESERIALIZERS = new Map<ProductType | SumType, Deserializer<any>>();
89
+
90
+ // A value with helper functions to construct the type.
91
+ export const AlgebraicType = {
92
+ Ref: (value: number): AlgebraicTypeVariants.Ref => ({ tag: 'Ref', value }),
93
+ Sum: <T extends SumTypeType>(value: T): { tag: 'Sum'; value: T } => ({
94
+ tag: 'Sum',
95
+ value,
96
+ }),
97
+ Product: <T extends ProductTypeType>(
98
+ value: T
99
+ ): { tag: 'Product'; value: T } => ({
100
+ tag: 'Product',
101
+ value,
102
+ }),
103
+ Array: <T extends AlgebraicTypeType>(
104
+ value: T
105
+ ): { tag: 'Array'; value: T } => ({
106
+ tag: 'Array',
107
+ value,
108
+ }),
109
+ String: { tag: 'String' } as const,
110
+ Bool: { tag: 'Bool' } as const,
111
+ I8: { tag: 'I8' } as const,
112
+ U8: { tag: 'U8' } as const,
113
+ I16: { tag: 'I16' } as const,
114
+ U16: { tag: 'U16' } as const,
115
+ I32: { tag: 'I32' } as const,
116
+ U32: { tag: 'U32' } as const,
117
+ I64: { tag: 'I64' } as const,
118
+ U64: { tag: 'U64' } as const,
119
+ I128: { tag: 'I128' } as const,
120
+ U128: { tag: 'U128' } as const,
121
+ I256: { tag: 'I256' } as const,
122
+ U256: { tag: 'U256' } as const,
123
+ F32: { tag: 'F32' } as const,
124
+ F64: { tag: 'F64' } as const,
125
+ makeSerializer(
126
+ ty: AlgebraicTypeType,
127
+ typespace?: TypespaceType
128
+ ): Serializer<any> {
129
+ if (ty.tag === 'Ref') {
130
+ if (!typespace)
131
+ throw new Error('cannot serialize refs without a typespace');
132
+ while (ty.tag === 'Ref') ty = typespace.types[ty.value];
133
+ }
134
+ switch (ty.tag) {
135
+ case 'Product':
136
+ return ProductType.makeSerializer(ty.value, typespace);
137
+ case 'Sum':
138
+ return SumType.makeSerializer(ty.value, typespace);
139
+ case 'Array':
140
+ if (ty.value.tag === 'U8') {
141
+ return serializeUint8Array;
142
+ } else {
143
+ const serialize = AlgebraicType.makeSerializer(ty.value, typespace);
144
+ return (writer, value) => {
145
+ writer.writeU32(value.length);
146
+ for (const elem of value) {
147
+ serialize(writer, elem);
148
+ }
149
+ };
150
+ }
151
+ default:
152
+ return primitiveSerializers[ty.tag];
153
+ }
154
+ },
155
+ /** @deprecated Use `makeSerializer` instead. */
156
+ serializeValue(
157
+ writer: BinaryWriter,
158
+ ty: AlgebraicTypeType,
159
+ value: any,
160
+ typespace?: TypespaceType
161
+ ) {
162
+ AlgebraicType.makeSerializer(ty, typespace)(writer, value);
163
+ },
164
+ makeDeserializer(
165
+ ty: AlgebraicTypeType,
166
+ typespace?: TypespaceType
167
+ ): Deserializer<any> {
168
+ if (ty.tag === 'Ref') {
169
+ if (!typespace)
170
+ throw new Error('cannot deserialize refs without a typespace');
171
+ while (ty.tag === 'Ref') ty = typespace.types[ty.value];
172
+ }
173
+ switch (ty.tag) {
174
+ case 'Product':
175
+ return ProductType.makeDeserializer(ty.value, typespace);
176
+ case 'Sum':
177
+ return SumType.makeDeserializer(ty.value, typespace);
178
+ case 'Array':
179
+ if (ty.value.tag === 'U8') {
180
+ return deserializeUint8Array;
181
+ } else {
182
+ const deserialize = AlgebraicType.makeDeserializer(
183
+ ty.value,
184
+ typespace
185
+ );
186
+ return reader => {
187
+ const length = reader.readU32();
188
+ const result: any[] = Array(length);
189
+ for (let i = 0; i < length; i++) {
190
+ result[i] = deserialize(reader);
191
+ }
192
+ return result;
193
+ };
194
+ }
195
+ default:
196
+ return primitiveDeserializers[ty.tag];
197
+ }
198
+ },
199
+ /** @deprecated Use `makeDeserializer` instead. */
200
+ deserializeValue(
201
+ reader: BinaryReader,
202
+ ty: AlgebraicTypeType,
203
+ typespace?: TypespaceType
204
+ ): any {
205
+ return AlgebraicType.makeDeserializer(ty, typespace)(reader);
206
+ },
207
+ /**
208
+ * Convert a value of the algebraic type into something that can be used as a key in a map.
209
+ * There are no guarantees about being able to order it.
210
+ * This is only guaranteed to be comparable to other values of the same type.
211
+ * @param value A value of the algebraic type
212
+ * @returns Something that can be used as a key in a map.
213
+ */
214
+ intoMapKey: function (
215
+ ty: AlgebraicTypeType,
216
+ value: any
217
+ ): ComparablePrimitive {
218
+ switch (ty.tag) {
219
+ case 'U8':
220
+ case 'U16':
221
+ case 'U32':
222
+ case 'U64':
223
+ case 'U128':
224
+ case 'U256':
225
+ case 'I8':
226
+ case 'I16':
227
+ case 'I32':
228
+ case 'I64':
229
+ case 'I128':
230
+ case 'I256':
231
+ case 'F32':
232
+ case 'F64':
233
+ case 'String':
234
+ case 'Bool':
235
+ return value;
236
+ case 'Product':
237
+ return ProductType.intoMapKey(ty.value, value);
238
+ default: {
239
+ // The fallback is to serialize and base64 encode the bytes.
240
+ const writer = new BinaryWriter(10);
241
+ AlgebraicType.serializeValue(writer, ty, value);
242
+ return writer.toBase64();
243
+ }
244
+ }
245
+ },
246
+ };
247
+
248
+ function bindCall<F extends (this: any, ...args: any[]) => any>(
249
+ f: F
250
+ ): (recv: ThisParameterType<F>, ...args: Parameters<F>) => ReturnType<F> {
251
+ return Function.prototype.call.bind(f);
252
+ }
253
+
254
+ type Primitives = Exclude<
255
+ AlgebraicType['tag'],
256
+ 'Ref' | 'Sum' | 'Product' | 'Array'
257
+ >;
258
+
259
+ const primitiveSerializers: Record<Primitives, Serializer<any>> = {
260
+ Bool: bindCall(BinaryWriter.prototype.writeBool),
261
+ I8: bindCall(BinaryWriter.prototype.writeI8),
262
+ U8: bindCall(BinaryWriter.prototype.writeU8),
263
+ I16: bindCall(BinaryWriter.prototype.writeI16),
264
+ U16: bindCall(BinaryWriter.prototype.writeU16),
265
+ I32: bindCall(BinaryWriter.prototype.writeI32),
266
+ U32: bindCall(BinaryWriter.prototype.writeU32),
267
+ I64: bindCall(BinaryWriter.prototype.writeI64),
268
+ U64: bindCall(BinaryWriter.prototype.writeU64),
269
+ I128: bindCall(BinaryWriter.prototype.writeI128),
270
+ U128: bindCall(BinaryWriter.prototype.writeU128),
271
+ I256: bindCall(BinaryWriter.prototype.writeI256),
272
+ U256: bindCall(BinaryWriter.prototype.writeU256),
273
+ F32: bindCall(BinaryWriter.prototype.writeF32),
274
+ F64: bindCall(BinaryWriter.prototype.writeF64),
275
+ String: bindCall(BinaryWriter.prototype.writeString),
276
+ };
277
+ Object.freeze(primitiveSerializers);
278
+
279
+ const serializeUint8Array = bindCall(BinaryWriter.prototype.writeUInt8Array);
280
+
281
+ const primitiveDeserializers: Record<Primitives, Deserializer<any>> = {
282
+ Bool: bindCall(BinaryReader.prototype.readBool),
283
+ I8: bindCall(BinaryReader.prototype.readI8),
284
+ U8: bindCall(BinaryReader.prototype.readU8),
285
+ I16: bindCall(BinaryReader.prototype.readI16),
286
+ U16: bindCall(BinaryReader.prototype.readU16),
287
+ I32: bindCall(BinaryReader.prototype.readI32),
288
+ U32: bindCall(BinaryReader.prototype.readU32),
289
+ I64: bindCall(BinaryReader.prototype.readI64),
290
+ U64: bindCall(BinaryReader.prototype.readU64),
291
+ I128: bindCall(BinaryReader.prototype.readI128),
292
+ U128: bindCall(BinaryReader.prototype.readU128),
293
+ I256: bindCall(BinaryReader.prototype.readI256),
294
+ U256: bindCall(BinaryReader.prototype.readU256),
295
+ F32: bindCall(BinaryReader.prototype.readF32),
296
+ F64: bindCall(BinaryReader.prototype.readF64),
297
+ String: bindCall(BinaryReader.prototype.readString),
298
+ };
299
+ Object.freeze(primitiveDeserializers);
300
+
301
+ const deserializeUint8Array = bindCall(BinaryReader.prototype.readUInt8Array);
302
+
303
+ type FixedSizePrimitives = Exclude<Primitives, 'String'>;
304
+
305
+ const primitiveSizes: Record<FixedSizePrimitives, number> = {
306
+ Bool: 1,
307
+ I8: 1,
308
+ U8: 1,
309
+ I16: 2,
310
+ U16: 2,
311
+ I32: 4,
312
+ U32: 4,
313
+ I64: 8,
314
+ U64: 8,
315
+ I128: 16,
316
+ U128: 16,
317
+ I256: 32,
318
+ U256: 32,
319
+ F32: 4,
320
+ F64: 8,
321
+ };
322
+
323
+ const fixedSizePrimitives = new Set(Object.keys(primitiveSizes));
324
+
325
+ type FixedSizeProductType = {
326
+ elements: { name: string; algebraicType: { tag: FixedSizePrimitives } }[];
327
+ };
328
+
329
+ const isFixedSizeProduct = (ty: ProductType): ty is FixedSizeProductType =>
330
+ ty.elements.every(({ algebraicType }) =>
331
+ fixedSizePrimitives.has(algebraicType.tag)
332
+ );
333
+
334
+ const productSize = (ty: FixedSizeProductType): number =>
335
+ ty.elements.reduce(
336
+ (acc, { algebraicType }) => acc + primitiveSizes[algebraicType.tag],
337
+ 0
338
+ );
339
+
340
+ type JSPrimitives = Exclude<
341
+ FixedSizePrimitives,
342
+ 'I128' | 'U128' | 'I256' | 'U256'
343
+ >;
344
+
345
+ const primitiveJSName: Record<JSPrimitives, string> = {
346
+ Bool: 'Uint8',
347
+ I8: 'Int8',
348
+ U8: 'Uint8',
349
+ I16: 'Int16',
350
+ U16: 'Uint16',
351
+ I32: 'Int32',
352
+ U32: 'Uint32',
353
+ I64: 'BigInt64',
354
+ U64: 'BigUint64',
355
+ F32: 'Float32',
356
+ F64: 'Float64',
357
+ };
358
+
359
+ type SpecialProducts = {
360
+ __time_duration_micros__: TimeDuration;
361
+ __timestamp_micros_since_unix_epoch__: Timestamp;
362
+ __identity__: Identity;
363
+ __connection_id__: ConnectionId;
364
+ __uuid__: Uuid;
365
+ };
366
+
367
+ const specialProductDeserializers: {
368
+ [k in keyof SpecialProducts]: Deserializer<SpecialProducts[k]>;
369
+ } = {
370
+ __time_duration_micros__: reader => new TimeDuration(reader.readI64()),
371
+ __timestamp_micros_since_unix_epoch__: reader =>
372
+ new Timestamp(reader.readI64()),
373
+ __identity__: reader => new Identity(reader.readU256()),
374
+ __connection_id__: reader => new ConnectionId(reader.readU128()),
375
+ __uuid__: reader => new Uuid(reader.readU128()),
376
+ };
377
+ Object.freeze(specialProductDeserializers);
378
+
379
+ const unitDeserializer: Deserializer<{}> = () => ({});
380
+
381
+ const getElementInitializer = (element: ProductTypeElement) => {
382
+ let init: string;
383
+ switch (element.algebraicType.tag) {
384
+ case 'String':
385
+ init = "''";
386
+ break;
387
+ case 'Bool':
388
+ init = 'false';
389
+ break;
390
+ case 'I8':
391
+ case 'U8':
392
+ case 'I16':
393
+ case 'U16':
394
+ case 'I32':
395
+ case 'U32':
396
+ init = '0';
397
+ break;
398
+ case 'I64':
399
+ case 'U64':
400
+ case 'I128':
401
+ case 'U128':
402
+ case 'I256':
403
+ case 'U256':
404
+ init = '0n';
405
+ break;
406
+ case 'F32':
407
+ case 'F64':
408
+ init = '0.0';
409
+ break;
410
+ default:
411
+ init = 'undefined';
412
+ }
413
+ return `${element.name!}: ${init}`;
414
+ };
415
+
416
+ /**
417
+ * A structural product type of the factors given by `elements`.
418
+ *
419
+ * This is also known as `struct` and `tuple` in many languages,
420
+ * but note that unlike most languages, products in SATs are *[structural]* and not nominal.
421
+ * When checking whether two nominal types are the same,
422
+ * their names and/or declaration sites (e.g., module / namespace) are considered.
423
+ * Meanwhile, a structural type system would only check the structure of the type itself,
424
+ * e.g., the names of its fields and their types in the case of a record.
425
+ * The name "product" comes from category theory.
426
+ *
427
+ * See also: https://ncatlab.org/nlab/show/product+type.
428
+ *
429
+ * These structures are known as product types because the number of possible values in product
430
+ * ```ignore
431
+ * { N_0: T_0, N_1: T_1, ..., N_n: T_n }
432
+ * ```
433
+ * is:
434
+ * ```ignore
435
+ * Π (i ∈ 0..n). values(T_i)
436
+ * ```
437
+ * so for example, `values({ A: U64, B: Bool }) = values(U64) * values(Bool)`.
438
+ *
439
+ * [structural]: https://en.wikipedia.org/wiki/Structural_type_system
440
+ */
441
+ export type ProductType = ProductTypeType;
442
+
443
+ export const ProductType = {
444
+ makeSerializer(
445
+ ty: ProductTypeType,
446
+ typespace?: TypespaceType
447
+ ): Serializer<any> {
448
+ let serializer = SERIALIZERS.get(ty);
449
+ if (serializer != null) return serializer;
450
+
451
+ if (isFixedSizeProduct(ty)) {
452
+ const size = productSize(ty);
453
+ const body = `\
454
+ "use strict";
455
+ writer.expandBuffer(${size});
456
+ const view = writer.view;
457
+ ${ty.elements
458
+ .map(({ name, algebraicType: { tag } }) =>
459
+ tag in primitiveJSName
460
+ ? `\
461
+ view.set${primitiveJSName[tag as JSPrimitives]}(writer.offset, value.${name!}, ${primitiveSizes[tag] > 1 ? 'true' : ''});
462
+ writer.offset += ${primitiveSizes[tag]};`
463
+ : `writer.write${tag}(value.${name});`
464
+ )
465
+ .join('\n')}`;
466
+ serializer = Function('writer', 'value', body) as Serializer<any>;
467
+ SERIALIZERS.set(ty, serializer);
468
+ return serializer;
469
+ }
470
+
471
+ // Because V8 forces us to generate our code as a string, rather than a proper syntax tree,
472
+ // we can't have our `body` close over values.
473
+ // Instead, we construct an object with the values we'd otherwise "close over" in `serializers`,
474
+ // and use `Function.prototype.bind` to pass it as the `this` argument.
475
+ //
476
+ // We populate `serializers` after constructing this type's `serializer`
477
+ // so that it can close over itself, in the case that `ty` is recursive.
478
+ const serializers: Record<string, Serializer<any>> = {};
479
+ const body =
480
+ '"use strict";\n' +
481
+ ty.elements
482
+ .map(
483
+ element => `this.${element.name!}(writer, value.${element.name!});`
484
+ )
485
+ .join('\n');
486
+ serializer = Function('writer', 'value', body).bind(
487
+ serializers
488
+ ) as Serializer<any>;
489
+ // In case `ty` is recursive, we cache the function *before* before computing
490
+ // `serializers`, so that a recursive `makeSerializer` with the same `ty` has
491
+ // an exit condition.
492
+ SERIALIZERS.set(ty, serializer);
493
+ for (const { name, algebraicType } of ty.elements) {
494
+ serializers[name!] = AlgebraicType.makeSerializer(
495
+ algebraicType,
496
+ typespace
497
+ );
498
+ }
499
+ Object.freeze(serializers);
500
+ return serializer;
501
+ },
502
+ /** @deprecated Use `makeSerializer` instead. */
503
+ serializeValue(
504
+ writer: BinaryWriter,
505
+ ty: ProductTypeType,
506
+ value: any,
507
+ typespace?: TypespaceType
508
+ ): void {
509
+ ProductType.makeSerializer(ty, typespace)(writer, value);
510
+ },
511
+ makeDeserializer(
512
+ ty: ProductTypeType,
513
+ typespace?: TypespaceType
514
+ ): Deserializer<any> {
515
+ switch (ty.elements.length) {
516
+ case 0:
517
+ return unitDeserializer;
518
+ case 1: {
519
+ const fieldName = ty.elements[0].name!;
520
+ if (hasOwn(specialProductDeserializers, fieldName))
521
+ return specialProductDeserializers[
522
+ fieldName as keyof SpecialProducts
523
+ ];
524
+ }
525
+ }
526
+
527
+ let deserializer = DESERIALIZERS.get(ty);
528
+ if (deserializer != null) return deserializer;
529
+
530
+ if (isFixedSizeProduct(ty)) {
531
+ const body = `\
532
+ "use strict";
533
+ const result = { ${ty.elements.map(getElementInitializer).join(', ')} };
534
+ const view = reader.view;
535
+ ${ty.elements
536
+ .map(({ name, algebraicType: { tag } }) =>
537
+ tag in primitiveJSName
538
+ ? tag === 'Bool'
539
+ ? `\
540
+ result.${name} = view.getUint8(reader.offset) !== 0;
541
+ reader.offset += 1;`
542
+ : `\
543
+ result.${name} = view.get${primitiveJSName[tag as JSPrimitives]}(reader.offset, ${primitiveSizes[tag] > 1 ? 'true' : ''});
544
+ reader.offset += ${primitiveSizes[tag]};`
545
+ : `result.${name} = reader.read${tag}();`
546
+ )
547
+ .join('\n')}
548
+ return result;`;
549
+ deserializer = Function('reader', body) as Deserializer<any>;
550
+ DESERIALIZERS.set(ty, deserializer);
551
+ return deserializer;
552
+ }
553
+
554
+ // Because V8 forces us to generate our code as a string, rather than a proper syntax tree,
555
+ // we can't have our `body` close over values.
556
+ // Instead, we construct an object with the values we'd otherwise "close over" in `deserializers`,
557
+ // and use `Function.prototype.bind` to pass it as the `this` argument.
558
+ //
559
+ // We populate `deserializers` after constructing this type's `deserializer`
560
+ // so that it can close over itself, in the case that `ty` is recursive.
561
+ const deserializers: Record<string, Deserializer<any>> = {};
562
+ deserializer = Function(
563
+ 'reader',
564
+ `\
565
+ "use strict";
566
+ const result = { ${ty.elements.map(getElementInitializer).join(', ')} };
567
+ ${ty.elements.map(({ name }) => `result.${name!} = this.${name!}(reader);`).join('\n')}
568
+ return result;`
569
+ ).bind(deserializers) as Deserializer<any>;
570
+ // In case `ty` is recursive, we cache the function *before* before computing
571
+ // `deserializers`, so that a recursive `makeDeserializer` with the same `ty` has
572
+ // an exit condition.
573
+ DESERIALIZERS.set(ty, deserializer);
574
+ for (const { name, algebraicType } of ty.elements) {
575
+ deserializers[name!] = AlgebraicType.makeDeserializer(
576
+ algebraicType,
577
+ typespace
578
+ );
579
+ }
580
+ Object.freeze(deserializers);
581
+ return deserializer;
582
+ },
583
+ /** @deprecated Use `makeDeserializer` instead. */
584
+ deserializeValue(
585
+ reader: BinaryReader,
586
+ ty: ProductTypeType,
587
+ typespace?: TypespaceType
588
+ ): any {
589
+ return ProductType.makeDeserializer(ty, typespace)(reader);
590
+ },
591
+ intoMapKey(ty: ProductTypeType, value: any): ComparablePrimitive {
592
+ if (ty.elements.length === 1) {
593
+ const fieldName = ty.elements[0].name!;
594
+ if (hasOwn(specialProductDeserializers, fieldName)) {
595
+ return value[fieldName];
596
+ }
597
+ }
598
+ // The fallback is to serialize and base64 encode the bytes.
599
+ const writer = new BinaryWriter(10);
600
+ AlgebraicType.serializeValue(writer, AlgebraicType.Product(ty), value);
601
+ return writer.toBase64();
602
+ },
603
+ };
604
+
605
+ export type SumType = SumTypeType;
606
+
607
+ /**
608
+ * Unlike most languages, sums in SATS are *[structural]* and not nominal.
609
+ * When checking whether two nominal types are the same,
610
+ * their names and/or declaration sites (e.g., module / namespace) are considered.
611
+ * Meanwhile, a structural type system would only check the structure of the type itself,
612
+ * e.g., the names of its variants and their inner data types in the case of a sum.
613
+ *
614
+ * This is also known as a discriminated union (implementation) or disjoint union.
615
+ * Another name is [coproduct (category theory)](https://ncatlab.org/nlab/show/coproduct).
616
+ *
617
+ * These structures are known as sum types because the number of possible values a sum
618
+ * ```ignore
619
+ * { N_0(T_0), N_1(T_1), ..., N_n(T_n) }
620
+ * ```
621
+ * is:
622
+ * ```ignore
623
+ * Σ (i ∈ 0..n). values(T_i)
624
+ * ```
625
+ * so for example, `values({ A(U64), B(Bool) }) = values(U64) + values(Bool)`.
626
+ *
627
+ * See also: https://ncatlab.org/nlab/show/sum+type.
628
+ *
629
+ * [structural]: https://en.wikipedia.org/wiki/Structural_type_system
630
+ */
631
+ export const SumType = {
632
+ makeSerializer(ty: SumTypeType, typespace?: TypespaceType): Serializer<any> {
633
+ if (
634
+ ty.variants.length == 2 &&
635
+ ty.variants[0].name === 'some' &&
636
+ ty.variants[1].name === 'none'
637
+ ) {
638
+ const serialize = AlgebraicType.makeSerializer(
639
+ ty.variants[0].algebraicType,
640
+ typespace
641
+ );
642
+ return (writer, value) => {
643
+ if (value !== null && value !== undefined) {
644
+ writer.writeByte(0);
645
+ serialize(writer, value);
646
+ } else {
647
+ writer.writeByte(1);
648
+ }
649
+ };
650
+ } else if (
651
+ ty.variants.length == 2 &&
652
+ ty.variants[0].name === 'ok' &&
653
+ ty.variants[1].name === 'err'
654
+ ) {
655
+ const serializeOk = AlgebraicType.makeSerializer(
656
+ ty.variants[0].algebraicType,
657
+ typespace
658
+ );
659
+ const serializeErr = AlgebraicType.makeSerializer(
660
+ ty.variants[0].algebraicType,
661
+ typespace
662
+ );
663
+
664
+ return (writer, value) => {
665
+ if ('ok' in value) {
666
+ writer.writeU8(0);
667
+ serializeOk(writer, value.ok);
668
+ } else if ('err' in value) {
669
+ writer.writeU8(1);
670
+ serializeErr(writer, value.err);
671
+ } else {
672
+ throw new TypeError(
673
+ 'could not serialize result: object had neither a `ok` nor an `err` field'
674
+ );
675
+ }
676
+ };
677
+ } else {
678
+ let serializer = SERIALIZERS.get(ty);
679
+ if (serializer != null) return serializer;
680
+
681
+ const serializers: Record<string, Serializer<any>> = {};
682
+
683
+ const body = `\
684
+ switch (value.tag) {
685
+ ${ty.variants
686
+ .map(
687
+ ({ name }, i) => `\
688
+ case ${JSON.stringify(name!)}:
689
+ writer.writeByte(${i});
690
+ return this.${name!}(writer, value.value);`
691
+ )
692
+ .join('\n')}
693
+ default:
694
+ throw new TypeError(
695
+ \`Could not serialize sum type; unknown tag \${value.tag}\`
696
+ )
697
+ }
698
+ `;
699
+
700
+ serializer = Function('writer', 'value', body).bind(
701
+ serializers
702
+ ) as Serializer<any>;
703
+
704
+ // In case `ty` is recursive, we cache the function *before* before computing
705
+ // `variants`, so that a recursive `makeSerializer` with the same `ty` has
706
+ // an exit condition.
707
+ SERIALIZERS.set(ty, serializer);
708
+
709
+ for (const { name, algebraicType } of ty.variants) {
710
+ serializers[name!] = AlgebraicType.makeSerializer(
711
+ algebraicType,
712
+ typespace
713
+ );
714
+ }
715
+ Object.freeze(serializers);
716
+ return serializer;
717
+ }
718
+ },
719
+ /** @deprecated Use `makeSerializer` instead. */
720
+ serializeValue(
721
+ writer: BinaryWriter,
722
+ ty: SumTypeType,
723
+ value: any,
724
+ typespace?: TypespaceType
725
+ ): void {
726
+ SumType.makeSerializer(ty, typespace)(writer, value);
727
+ },
728
+ makeDeserializer(
729
+ ty: SumTypeType,
730
+ typespace?: TypespaceType
731
+ ): Deserializer<any> {
732
+ // In TypeScript we handle Option values as a special case
733
+ // we don't represent the some and none variants, but instead
734
+ // we represent the value directly.
735
+ //
736
+ // For these special cases, we don't do dynamic codegen, since that has the
737
+ // most benefit in cases where the object has a different shape. Since
738
+ // option/result always have the same number of variants, there's not as
739
+ // much benefit for the amount of work.
740
+ if (
741
+ ty.variants.length == 2 &&
742
+ ty.variants[0].name === 'some' &&
743
+ ty.variants[1].name === 'none'
744
+ ) {
745
+ const deserialize = AlgebraicType.makeDeserializer(
746
+ ty.variants[0].algebraicType,
747
+ typespace
748
+ );
749
+ return reader => {
750
+ const tag = reader.readU8();
751
+ if (tag === 0) {
752
+ return deserialize(reader);
753
+ } else if (tag === 1) {
754
+ return undefined;
755
+ } else {
756
+ throw `Can't deserialize an option type, couldn't find ${tag} tag`;
757
+ }
758
+ };
759
+ } else if (
760
+ ty.variants.length == 2 &&
761
+ ty.variants[0].name === 'ok' &&
762
+ ty.variants[1].name === 'err'
763
+ ) {
764
+ const deserializeOk = AlgebraicType.makeDeserializer(
765
+ ty.variants[0].algebraicType,
766
+ typespace
767
+ );
768
+ const deserializeErr = AlgebraicType.makeDeserializer(
769
+ ty.variants[1].algebraicType,
770
+ typespace
771
+ );
772
+ return reader => {
773
+ const tag = reader.readByte();
774
+ if (tag === 0) {
775
+ return { ok: deserializeOk(reader) };
776
+ } else if (tag === 1) {
777
+ return { err: deserializeErr(reader) };
778
+ } else {
779
+ throw `Can't deserialize a result type, couldn't find ${tag} tag`;
780
+ }
781
+ };
782
+ } else {
783
+ let deserializer = DESERIALIZERS.get(ty);
784
+ if (deserializer != null) return deserializer;
785
+ const deserializers: Record<string, Deserializer<any>> = {};
786
+ deserializer = Function(
787
+ 'reader',
788
+ `switch (reader.readU8()) {\n${ty.variants
789
+ .map(
790
+ ({ name }, i) =>
791
+ `case ${i}: return { tag: ${JSON.stringify(name!)}, value: this.${name!}(reader) };`
792
+ )
793
+ .join('\n')} }`
794
+ ).bind(deserializers) as Deserializer<any>;
795
+ // In case `ty` is recursive, we cache the function *before* before computing
796
+ // `deserializers`, so that a recursive `makeDeserializer` with the same `ty` has
797
+ // an exit condition.
798
+ DESERIALIZERS.set(ty, deserializer);
799
+ for (const { name, algebraicType } of ty.variants) {
800
+ deserializers[name!] = AlgebraicType.makeDeserializer(
801
+ algebraicType,
802
+ typespace
803
+ );
804
+ }
805
+ Object.freeze(deserializers);
806
+ return deserializer;
807
+ }
808
+ },
809
+ /** @deprecated Use `makeDeserializer` instead. */
810
+ deserializeValue(
811
+ reader: BinaryReader,
812
+ ty: SumTypeType,
813
+ typespace?: TypespaceType
814
+ ): any {
815
+ return SumType.makeDeserializer(ty, typespace)(reader);
816
+ },
817
+ };
818
+
819
+ export type ComparablePrimitive = number | string | boolean | bigint;