Skip to content

Commit 165bb60

Browse files
committed
Fix ModuleDef deserialization
1 parent 90ff06c commit 165bb60

File tree

8 files changed

+183
-56
lines changed

8 files changed

+183
-56
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/bindings-typescript/src/server/rt.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ declare global {
126126

127127
const { freeze } = Object;
128128

129-
const _syscalls = {
129+
const _syscalls = () => ({
130130
table_id_from_name,
131131
index_id_from_name,
132132
datastore_table_row_count,
@@ -143,15 +143,9 @@ const _syscalls = {
143143
console_timer_start,
144144
console_timer_end,
145145
identity,
146-
};
146+
});
147147

148-
const sys = freeze(
149-
Object.fromEntries(
150-
Object.entries(_syscalls).map(([name, syscall]) => {
151-
return [name, wrapSyscall(syscall)];
152-
})
153-
) as typeof _syscalls
154-
);
148+
const sys = {} as ReturnType<typeof _syscalls>;
155149

156150
globalThis.__call_reducer__ = function __call_reducer__(
157151
reducer_id,
@@ -177,6 +171,11 @@ globalThis.__call_reducer__ = function __call_reducer__(
177171
};
178172

179173
globalThis.__describe_module__ = function __describe_module__() {
174+
for (const [name, syscall] of Object.entries(_syscalls())) {
175+
(sys as any)[name] = wrapSyscall(syscall);
176+
}
177+
freeze(sys);
178+
180179
return RawModuleDef.V9(MODULE_DEF);
181180
};
182181

crates/bindings-typescript/src/server/schema.ts

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type ColList = ColId[];
2727
type TableSchema<
2828
TableName extends string,
2929
Row extends Record<string, ColumnBuilder<any, any, any>>,
30-
Idx extends readonly PendingIndex<keyof Row & string>[],
30+
Idx extends readonly IndexOpts<keyof Row & string>[],
3131
> = {
3232
/**
3333
* The TypeScript phantom type. This is not stored at runtime,
@@ -70,30 +70,27 @@ type CoerceColumn<
7070
type TableOpts<Row extends RowObj> = {
7171
name: string;
7272
public?: boolean;
73-
indexes?: PendingIndex<keyof Row & string>[]; // declarative multi‑column indexes
73+
indexes?: IndexOpts<keyof Row & string>[]; // declarative multi‑column indexes
7474
scheduled?: string; // reducer name for cron‑like tables
7575
};
7676

7777
/**
7878
* Index helper type used *inside* {@link table} to enforce that only
7979
* existing column names are referenced.
8080
*/
81-
type PendingIndex<AllowedCol extends string> = {
81+
type IndexOpts<AllowedCol extends string> = {
8282
name?: string;
83-
accessor_name?: string;
84-
is_unique?: boolean;
85-
algorithm:
86-
| { tag: 'BTree'; value: { columns: readonly AllowedCol[] } }
87-
// | { tag: 'Hash'; value: { columns: readonly AllowedCol[] } }
88-
| { tag: 'Direct'; value: { column: AllowedCol } };
89-
};
83+
} & (
84+
| { algorithm: 'btree'; columns: readonly AllowedCol[] }
85+
| { algorithm: 'direct'; column: AllowedCol }
86+
);
9087

9188
type OptsIndices<Opts extends TableOpts<any>> = Opts extends {
9289
indexes: infer Ixs extends NonNullable<any[]>;
9390
}
9491
? Ixs
9592
: CoerceArray<[]>;
96-
type CoerceArray<X extends PendingIndex<any>[]> = X;
93+
type CoerceArray<X extends IndexOpts<any>[]> = X;
9794

9895
/**
9996
* Defines a database table with schema and options
@@ -181,9 +178,9 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
181178
if (meta.isAutoIncrement) {
182179
sequences.push({
183180
name: undefined,
184-
start: 0n,
185-
minValue: 0n,
186-
maxValue: 0n,
181+
start: undefined,
182+
minValue: undefined,
183+
maxValue: undefined,
187184
column: colIds.get(name)!,
188185
increment: 1n,
189186
});
@@ -194,22 +191,20 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
194191
}
195192

196193
/** 3. convert explicit multi‑column indexes coming from options.indexes */
197-
for (const pending of userIndexes ?? []) {
198-
const converted: RawIndexDefV9 = {
199-
name: pending.name,
200-
accessorName: pending.accessor_name,
201-
algorithm:
202-
pending.algorithm.tag === 'Direct'
203-
? {
204-
tag: 'Direct',
205-
value: colIds.get(pending.algorithm.value.column)!,
206-
}
207-
: {
208-
tag: pending.algorithm.tag,
209-
value: pending.algorithm.value.columns.map(c => colIds.get(c)!),
210-
},
211-
};
212-
indexes.push(converted);
194+
for (const indexOpts of userIndexes ?? []) {
195+
let algorithm: RawIndexAlgorithm;
196+
switch (indexOpts.algorithm) {
197+
case 'btree':
198+
algorithm = {
199+
tag: 'BTree',
200+
value: indexOpts.columns.map(c => colIds.get(c)!),
201+
};
202+
break;
203+
case 'direct':
204+
algorithm = { tag: 'Direct', value: colIds.get(indexOpts.column)! };
205+
break;
206+
}
207+
indexes.push({ name: undefined, accessorName: indexOpts.name, algorithm });
213208
}
214209

215210
// Temporarily set the type ref to 0. We will set this later
@@ -493,7 +488,7 @@ export function reducer<
493488
export type UntypedTableDef = {
494489
name: string;
495490
columns: Record<string, ColumnBuilder<any, any, ColumnMetadata>>;
496-
indexes: PendingIndex<any>[];
491+
indexes: IndexOpts<any>[];
497492
};
498493

499494
export type RowType<TableDef extends UntypedTableDef> = InferTypeOfRow<
@@ -540,7 +535,7 @@ export type TableIndexes<TableDef extends UntypedTableDef> = {
540535
[I in TableDef['indexes'][number] as I['name'] & {}]: {
541536
name: I['name'];
542537
unique: AllUnique<TableDef, IndexColumns<I>>;
543-
algorithm: Lowercase<I['algorithm']['tag']>;
538+
algorithm: Lowercase<I['algorithm']>;
544539
columns: IndexColumns<I>;
545540
};
546541
};
@@ -556,12 +551,10 @@ type AllUnique<
556551
? true
557552
: false;
558553

559-
type IndexColumns<I extends PendingIndex<any>> = I['algorithm'] extends {
560-
value: { columns: infer C extends string[] };
561-
}
562-
? C
563-
: I['algorithm'] extends { value: { column: infer C extends string } }
564-
? [C]
554+
type IndexColumns<I extends IndexOpts<any>> = I extends { columns: string[] }
555+
? I['columns']
556+
: I extends { column: string }
557+
? [I['column']]
565558
: never;
566559

567560
type CollapseTuple<A extends any[]> = A extends [infer T] ? T : A;

crates/bindings-typescript/src/server/type_builders.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1526,7 +1526,7 @@ export class SumColumnBuilder<
15261526
*
15271527
* @see {@link TypeBuilder}
15281528
*/
1529-
const t = {
1529+
export const t = {
15301530
/**
15311531
* Creates a new `Bool` {@link AlgebraicType} to be used in table definitions
15321532
* Represented as `boolean` in TypeScript.

crates/bindings-typescript/tsup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export default defineConfig([
120120
platform: 'neutral', // flip to 'node' if you actually rely on Node builtins
121121
treeshake: 'smallest',
122122
external: ['undici'],
123+
noExternal: ['base64-js'],
123124
outExtension,
124125
esbuildOptions: commonEsbuildTweaks(),
125126
},

crates/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ bytemuck.workspace = true
4949
bytes.workspace = true
5050
bytestring.workspace = true
5151
chrono.workspace = true
52+
convert_case.workspace = true
5253
crossbeam-channel.workspace = true
5354
crossbeam-queue.workspace = true
5455
derive_more.workspace = true

crates/core/src/host/v8/de.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use super::error::{exception_already_thrown, ExcResult, ExceptionThrown, ExceptionValue, Throwable, TypeError};
44
use super::from_value::{cast, FromValue};
55
use super::string_const::{TAG, VALUE};
6+
use convert_case::{Case, Casing};
67
use core::fmt;
78
use core::iter::{repeat_n, RepeatN};
89
use core::marker::PhantomData;
@@ -132,6 +133,10 @@ impl<'de, 'this, 'scope: 'de> de::Deserializer<'de> for Deserializer<'this, 'sco
132133
deserialize_primitive!(deserialize_f32, f32);
133134

134135
fn deserialize_product<V: ProductVisitor<'de>>(self, visitor: V) -> Result<V::Output, Self::Error> {
136+
if visitor.product_len() == 0 && self.input.is_null_or_undefined() {
137+
return visitor.visit_seq_product(de::UnitAccess::new());
138+
}
139+
135140
let object = cast!(
136141
self.common.scope,
137142
self.input,
@@ -150,6 +155,15 @@ impl<'de, 'this, 'scope: 'de> de::Deserializer<'de> for Deserializer<'this, 'sco
150155

151156
fn deserialize_sum<V: SumVisitor<'de>>(self, visitor: V) -> Result<V::Output, Self::Error> {
152157
let scope = &*self.common.scope;
158+
159+
if visitor.is_option() {
160+
return if self.input.is_null_or_undefined() {
161+
visitor.visit_sum(de::NoneAccess::new())
162+
} else {
163+
visitor.visit_sum(de::SomeAccess::new(self))
164+
};
165+
}
166+
153167
let sum_name = visitor.sum_name().unwrap_or("<unknown>");
154168

155169
// We expect a canonical representation of a sum value in JS to be
@@ -230,8 +244,8 @@ pub(super) fn intern_field_name<'scope>(
230244
index: usize,
231245
) -> Local<'scope, Name> {
232246
let field = match field {
233-
Some(field) => Cow::Borrowed(field),
234-
None => Cow::Owned(format!("{index}")),
247+
Some(field) => field.to_case(Case::Camel),
248+
None => format!("{index}"),
235249
};
236250
v8_interned_string(scope, &field).into()
237251
}

crates/sats/src/de.rs

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -719,12 +719,7 @@ impl<'de, E: Error> SumAccess<'de> for NoneAccess<E> {
719719
impl<'de, E: Error> VariantAccess<'de> for NoneAccess<E> {
720720
type Error = E;
721721
fn deserialize_seed<T: DeserializeSeed<'de>>(self, seed: T) -> Result<T::Output, Self::Error> {
722-
use crate::algebraic_value::de::*;
723-
seed.deserialize(ValueDeserializer::new(crate::AlgebraicValue::unit()))
724-
.map_err(|err| match err {
725-
ValueDeserializeError::MismatchedType => E::custom("mismatched type"),
726-
ValueDeserializeError::Custom(err) => E::custom(err),
727-
})
722+
seed.deserialize(UnitAccess::new())
728723
}
729724
}
730725

@@ -753,3 +748,126 @@ impl<'de, D: Deserializer<'de>> VariantAccess<'de> for SomeAccess<D> {
753748
seed.deserialize(self.0)
754749
}
755750
}
751+
752+
pub struct UnitAccess<E>(PhantomData<E>);
753+
754+
impl<E: Error> UnitAccess<E> {
755+
/// Returns a new [`UnitAccess`].
756+
pub fn new() -> Self {
757+
Self(PhantomData)
758+
}
759+
}
760+
761+
impl<E: Error> Default for UnitAccess<E> {
762+
fn default() -> Self {
763+
Self::new()
764+
}
765+
}
766+
767+
impl<'de, E: Error> SeqProductAccess<'de> for UnitAccess<E> {
768+
type Error = E;
769+
770+
fn next_element_seed<T: DeserializeSeed<'de>>(&mut self, _seed: T) -> Result<Option<T::Output>, Self::Error> {
771+
Ok(None)
772+
}
773+
}
774+
775+
impl<'de, E: Error> NamedProductAccess<'de> for UnitAccess<E> {
776+
type Error = E;
777+
778+
fn get_field_ident<V: FieldNameVisitor<'de>>(&mut self, _visitor: V) -> Result<Option<V::Output>, Self::Error> {
779+
Ok(None)
780+
}
781+
782+
fn get_field_value_seed<T: DeserializeSeed<'de>>(&mut self, _seed: T) -> Result<T::Output, Self::Error> {
783+
unreachable!()
784+
}
785+
}
786+
787+
impl<'de, E: Error> Deserializer<'de> for UnitAccess<E> {
788+
type Error = E;
789+
790+
fn deserialize_product<V: ProductVisitor<'de>>(self, visitor: V) -> Result<V::Output, Self::Error> {
791+
visitor.visit_seq_product(self)
792+
}
793+
794+
fn deserialize_sum<V: SumVisitor<'de>>(self, _visitor: V) -> Result<V::Output, Self::Error> {
795+
Err(E::custom("invalid type"))
796+
}
797+
798+
fn deserialize_bool(self) -> Result<bool, Self::Error> {
799+
Err(E::custom("invalid type"))
800+
}
801+
802+
fn deserialize_u8(self) -> Result<u8, Self::Error> {
803+
Err(E::custom("invalid type"))
804+
}
805+
806+
fn deserialize_u16(self) -> Result<u16, Self::Error> {
807+
Err(E::custom("invalid type"))
808+
}
809+
810+
fn deserialize_u32(self) -> Result<u32, Self::Error> {
811+
Err(E::custom("invalid type"))
812+
}
813+
814+
fn deserialize_u64(self) -> Result<u64, Self::Error> {
815+
Err(E::custom("invalid type"))
816+
}
817+
818+
fn deserialize_u128(self) -> Result<u128, Self::Error> {
819+
Err(E::custom("invalid type"))
820+
}
821+
822+
fn deserialize_u256(self) -> Result<u256, Self::Error> {
823+
Err(E::custom("invalid type"))
824+
}
825+
826+
fn deserialize_i8(self) -> Result<i8, Self::Error> {
827+
Err(E::custom("invalid type"))
828+
}
829+
830+
fn deserialize_i16(self) -> Result<i16, Self::Error> {
831+
Err(E::custom("invalid type"))
832+
}
833+
834+
fn deserialize_i32(self) -> Result<i32, Self::Error> {
835+
Err(E::custom("invalid type"))
836+
}
837+
838+
fn deserialize_i64(self) -> Result<i64, Self::Error> {
839+
Err(E::custom("invalid type"))
840+
}
841+
842+
fn deserialize_i128(self) -> Result<i128, Self::Error> {
843+
Err(E::custom("invalid type"))
844+
}
845+
846+
fn deserialize_i256(self) -> Result<i256, Self::Error> {
847+
Err(E::custom("invalid type"))
848+
}
849+
850+
fn deserialize_f32(self) -> Result<f32, Self::Error> {
851+
Err(E::custom("invalid type"))
852+
}
853+
854+
fn deserialize_f64(self) -> Result<f64, Self::Error> {
855+
Err(E::custom("invalid type"))
856+
}
857+
858+
fn deserialize_str<V: SliceVisitor<'de, str>>(self, _visitor: V) -> Result<V::Output, Self::Error> {
859+
Err(E::custom("invalid type"))
860+
}
861+
862+
fn deserialize_bytes<V: SliceVisitor<'de, [u8]>>(self, _visitor: V) -> Result<V::Output, Self::Error> {
863+
Err(E::custom("invalid type"))
864+
}
865+
866+
fn deserialize_array_seed<V: ArrayVisitor<'de, T::Output>, T: DeserializeSeed<'de> + Clone>(
867+
self,
868+
_visitor: V,
869+
_seed: T,
870+
) -> Result<V::Output, Self::Error> {
871+
Err(E::custom("invalid type"))
872+
}
873+
}

0 commit comments

Comments
 (0)