From 412df5a047dad5f1e3d55be8706033f6d842deb7 Mon Sep 17 00:00:00 2001 From: Mario Alejandro Montoya Cortes Date: Fri, 10 Oct 2025 11:40:55 -0500 Subject: [PATCH] Add UUID built-in convenience type to SpacetimeDB --- Cargo.lock | 2 + Cargo.toml | 2 +- .../BSATN.Runtime/BSATN/U128.cs | 16 + .../bindings-csharp/BSATN.Runtime/Builtins.cs | 225 +++++++++- crates/bindings-csharp/Codegen/Module.cs | 55 ++- crates/bindings-csharp/Runtime/Runtime.sln | 24 + crates/bindings-typescript/package.json | 3 +- crates/bindings-typescript/src/index.ts | 1 + .../src/lib/algebraic_type.ts | 13 + .../bindings-typescript/src/lib/timestamp.ts | 28 ++ crates/bindings-typescript/src/lib/uuid.ts | 107 +++++ .../src/server/reducers.ts | 8 +- .../bindings-typescript/src/server/runtime.ts | 16 +- .../src/server/type_builders.ts | 262 +++++++++++ .../bindings-typescript/tests/serde.test.ts | 25 ++ crates/bindings-typescript/tests/uuid.test.ts | 36 ++ crates/bindings/Cargo.toml | 1 + crates/bindings/src/lib.rs | 47 ++ ...ps__spacetimedb_bindings_dependencies.snap | 14 +- crates/cli/src/subcommands/sql.rs | 50 ++- crates/client-api/Cargo.toml | 2 +- crates/codegen/src/csharp.rs | 4 +- crates/codegen/src/rust.rs | 1 + crates/codegen/src/typescript.rs | 4 + crates/codegen/src/unrealcpp.rs | 10 +- .../codegen__codegen_typescript.snap | 46 ++ crates/expr/src/lib.rs | 8 + crates/lib/src/lib.rs | 3 +- crates/pg/src/encoder.rs | 8 +- crates/sats/Cargo.toml | 5 + crates/sats/src/algebraic_type.rs | 14 +- crates/sats/src/lib.rs | 1 + crates/sats/src/product_type.rs | 47 +- crates/sats/src/satn.rs | 13 + crates/sats/src/timestamp.rs | 37 ++ crates/sats/src/uuid.rs | 216 +++++++++ crates/schema/src/type_for_generate.rs | 5 + modules/benchmarks-cs/benchmarks-cs.sln | 24 + modules/sdk-test-cs/Lib.cs | 130 ++++++ modules/sdk-test-cs/sdk-test-cs.sln | 24 + modules/sdk-test-ts/src/index.ts | 415 +++++++++++------- modules/sdk-test/src/lib.rs | 58 ++- package.json | 8 +- pnpm-lock.yaml | 13 + .../dotnet/cs/SpacetimeDB.BSATN.Codegen.dll | Bin 0 -> 75264 bytes .../SpacetimeDB.BSATN.Runtime.dll | Bin 0 -> 68608 bytes sdks/rust/src/lib.rs | 4 +- sdks/rust/tests/test-client/src/main.rs | 140 +++++- .../module_bindings/delete_pk_uuid_reducer.rs | 101 +++++ .../delete_unique_uuid_reducer.rs | 101 +++++ .../module_bindings/enum_with_payload_type.rs | 2 + .../every_primitive_struct_type.rs | 1 + .../module_bindings/every_vec_struct_type.rs | 1 + .../insert_call_uuid_v_4_reducer.rs | 99 +++++ .../insert_call_uuid_v_7_reducer.rs | 99 +++++ .../insert_one_uuid_reducer.rs | 101 +++++ .../insert_option_uuid_reducer.rs | 101 +++++ .../module_bindings/insert_pk_uuid_reducer.rs | 105 +++++ .../insert_unique_uuid_reducer.rs | 106 +++++ .../insert_vec_uuid_reducer.rs | 101 +++++ .../test-client/src/module_bindings/mod.rs | 220 ++++++++++ .../src/module_bindings/one_uuid_table.rs | 95 ++++ .../src/module_bindings/one_uuid_type.rs | 15 + .../src/module_bindings/option_uuid_table.rs | 95 ++++ .../src/module_bindings/option_uuid_type.rs | 15 + .../src/module_bindings/pk_uuid_table.rs | 111 +++++ .../src/module_bindings/pk_uuid_type.rs | 16 + .../sorted_uuids_insert_reducer.rs | 99 +++++ .../src/module_bindings/unique_uuid_table.rs | 95 ++++ .../src/module_bindings/unique_uuid_type.rs | 16 + .../module_bindings/update_pk_uuid_reducer.rs | 105 +++++ .../update_unique_uuid_reducer.rs | 106 +++++ .../src/module_bindings/vec_uuid_table.rs | 95 ++++ .../src/module_bindings/vec_uuid_type.rs | 15 + .../tests/test-client/src/pk_test_table.rs | 13 +- .../test-client/src/simple_test_table.rs | 25 +- .../test-client/src/unique_test_table.rs | 12 +- sdks/rust/tests/test.rs | 29 +- sdks/unreal/tests/test.rs | 12 + smoketests/tests/pg_wire.py | 15 +- smoketests/tests/sql.py | 16 +- 81 files changed, 4065 insertions(+), 248 deletions(-) create mode 100644 crates/bindings-csharp/Runtime/Runtime.sln create mode 100644 crates/bindings-typescript/src/lib/uuid.ts create mode 100644 crates/bindings-typescript/tests/uuid.test.ts create mode 100644 crates/sats/src/uuid.rs create mode 100644 modules/benchmarks-cs/benchmarks-cs.sln create mode 100644 modules/sdk-test-cs/sdk-test-cs.sln create mode 100755 sdks/csharp/packages/spacetimedb.bsatn.runtime/1.8.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll create mode 100755 sdks/csharp/packages/spacetimedb.bsatn.runtime/1.8.0/lib/netstandard2.1/SpacetimeDB.BSATN.Runtime.dll create mode 100644 sdks/rust/tests/test-client/src/module_bindings/delete_pk_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/delete_unique_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_4_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_7_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_one_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_option_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_pk_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_unique_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/insert_vec_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/one_uuid_table.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/one_uuid_type.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/option_uuid_table.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/option_uuid_type.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/pk_uuid_table.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/pk_uuid_type.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/sorted_uuids_insert_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/unique_uuid_table.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/unique_uuid_type.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/update_pk_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/update_unique_uuid_reducer.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/vec_uuid_table.rs create mode 100644 sdks/rust/tests/test-client/src/module_bindings/vec_uuid_type.rs diff --git a/Cargo.lock b/Cargo.lock index d9b398e7807..58aa8a1ad9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7030,6 +7030,7 @@ dependencies = [ name = "spacetimedb" version = "1.8.0" dependencies = [ + "anyhow", "bytemuck", "derive_more 0.99.20", "getrandom 0.2.16", @@ -7828,6 +7829,7 @@ dependencies = [ "spacetimedb-metrics", "spacetimedb-primitives 1.8.0", "thiserror 1.0.69", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f85ee4d671a..552ab01967e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -299,7 +299,7 @@ unicode-ident = "1.0.12" unicode-normalization = "0.1.23" url = "2.3.1" urlencoding = "2.1.2" -uuid = { version = "1.18.1", features = ["v4"] } +uuid = { version = "1.18.1", default-features = false } v8 = "140.2" walkdir = "2.2.5" wasmbin = "0.6" diff --git a/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs b/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs index 7deee1a7531..9c4d69e99cd 100644 --- a/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs +++ b/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs @@ -90,4 +90,20 @@ private BigInteger AsBigInt() => /// public override string ToString() => AsBigInt().ToString(); + + public Guid ToGuid() + { + Span bytes = stackalloc byte[16]; + if (BitConverter.IsLittleEndian) + { + BitConverter.TryWriteBytes(bytes, _lower); + BitConverter.TryWriteBytes(bytes[8..], _upper); + } + else + { + BitConverter.TryWriteBytes(bytes, _upper); + BitConverter.TryWriteBytes(bytes[8..], _lower); + } + return new Guid(bytes); + } } diff --git a/crates/bindings-csharp/BSATN.Runtime/Builtins.cs b/crates/bindings-csharp/BSATN.Runtime/Builtins.cs index a3478d18023..157ef58d010 100644 --- a/crates/bindings-csharp/BSATN.Runtime/Builtins.cs +++ b/crates/bindings-csharp/BSATN.Runtime/Builtins.cs @@ -365,7 +365,7 @@ public readonly TimeDuration TimeDurationSince(Timestamp earlier) => public static Timestamp operator -(Timestamp point, TimeDuration interval) => new Timestamp(checked(point.MicrosecondsSinceUnixEpoch - interval.Microseconds)); - public int CompareTo(Timestamp that) + public readonly int CompareTo(Timestamp that) { return this.MicrosecondsSinceUnixEpoch.CompareTo(that.MicrosecondsSinceUnixEpoch); } @@ -605,3 +605,226 @@ public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) => // --- / customized --- } } + +/// +/// A generator for monotonically increasing s by millisecond increments. +/// +public sealed class ClockGenerator(Timestamp start) +{ + private long _microsSinceUnixEpoch = start.MicrosecondsSinceUnixEpoch; + + /// + /// Returns the next in the sequence, guaranteed to be + /// greater than the previous one returned by this method. + /// + /// UUIDv7 requires monotonic millisecond timestamps, so each tick + /// increases the timestamp by at least 1 millisecond (1_000 microseconds). + /// + /// # Exceptions + /// + /// If the internal timestamp overflows i64 microseconds. + /// + public Timestamp Tick() + { + checked + { + _microsSinceUnixEpoch += 1000; + } + return new Timestamp(_microsSinceUnixEpoch); + } + + public static implicit operator ClockGenerator(Timestamp t) => new(t); +} + +/// +/// A universally unique identifier (UUID). +/// +/// Wraps the native type and provides methods +/// to generate nil, random (v4), and time-ordered (v7) UUIDs. +/// +[StructLayout(LayoutKind.Sequential)] +public readonly record struct Uuid : IEquatable, IComparable, IComparable +{ + private readonly U128 value; + internal Uuid(U128 val) => value = val; + public static readonly Uuid NIL = new(FromGuid(Guid.Empty)); + + public static Uuid Nil() => NIL; + + public static U128 FromGuid(Guid guid) + { + Span bytes = stackalloc byte[16]; + guid.TryWriteBytes(bytes); + if (BitConverter.IsLittleEndian) + { + var lower = BitConverter.ToUInt64(bytes); + var upper = BitConverter.ToUInt64(bytes[8..]); + return new U128(upper, lower); + } + else + { + var upper = BitConverter.ToUInt64(bytes); + var lower = BitConverter.ToUInt64(bytes[8..]); + return new U128(upper, lower); + } + } + + private static Guid GuidV4(ReadOnlySpan randomBytes) + { + if (randomBytes.Length != 16) + { + throw new ArgumentException("Must be 16 bytes", nameof(randomBytes)); + } + + Span bytes = stackalloc byte[16]; + randomBytes.CopyTo(bytes); + bytes[6] = (byte)((bytes[6] & 0x0F) | 0x40); // version 4 + bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); // variant RFC 4122 + + return new Guid(randomBytes); + } + + /// + /// Create a UUIDv4 from explicit random bytes. + /// + /// + /// This method assumes the provided bytes are already sufficiently random; + /// it will only set the appropriate bits for the UUID version and variant. + /// + /// + /// + /// var randomBytes = new byte[16]; + /// var uuid = Uuid.FromRandomBytesV4(randomBytes); + /// Console.WriteLine(uuid); + /// // Output: 00000000-0000-4000-8000-000000000000 + /// + /// + public static Uuid FromRandomBytesV4(ReadOnlySpan randomBytes) + { + return new(FromGuid(GuidV4(randomBytes))); + } + + /// + /// Create a UUIDv7 from a UNIX timestamp (milliseconds) and 10 random bytes. + /// + /// + /// This method sets the variant field within the counter bytes without + /// shifting data around it. Callers using the counter as a monotonic + /// value should avoid storing significant data in the two least significant + /// bits of the third byte. + /// + /// + /// + /// ulong millis = 1686000000000UL; + /// var randomBytes = new byte[10]; + /// var uuid = Uuid.FromUnixMillisV7(millis, randomBytes); + /// Console.WriteLine(uuid); + /// // Output: 01888d6e-5c00-7000-8000-000000000000 + /// + /// + public static Uuid FromUnixMillisV7(long millisSinceUnixEpoch, ReadOnlySpan randomBytes) + { + // TODO: Convert to ` CreateVersion7` from .NET 9 when we can. + if (millisSinceUnixEpoch < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisSinceUnixEpoch), "Timestamp precedes Unix epoch"); + } + + // Generate random 16 bytes + var bytes = GuidV4(randomBytes).ToByteArray(); + + // Insert 48-bit timestamp (big endian) + bytes[0] = (byte)((millisSinceUnixEpoch >> 40) & 0xFF); + bytes[1] = (byte)((millisSinceUnixEpoch >> 32) & 0xFF); + bytes[2] = (byte)((millisSinceUnixEpoch >> 24) & 0xFF); + bytes[3] = (byte)((millisSinceUnixEpoch >> 16) & 0xFF); + bytes[4] = (byte)((millisSinceUnixEpoch >> 8) & 0xFF); + bytes[5] = (byte)(millisSinceUnixEpoch & 0xFF); + + // Set version (0111) and variant (10xx) + bytes[6] = (byte)((bytes[6] & 0x0F) | 0x70); + bytes[8] = (byte)((bytes[8] & 0x3F) | 0x80); + + return new Uuid(FromGuid(new Guid(bytes))); + } + + /// + /// Generate a UUIDv7 using a monotonic . + /// + /// + /// This method sets the variant field within the counter bytes without + /// shifting data around it. Callers using the counter as a monotonic + /// value should avoid storing significant data in the two least significant + /// bits of the third byte. + /// + /// + /// + /// var clock = new ClockGenerator(1686000000000UL); + /// var randomBytes = new byte[10]; + /// var uuid = Uuid.FromClockV7(clock, randomBytes); + /// Console.WriteLine(uuid); + /// // Output: 0000647e-5181-7000-8000-000000000000 + /// + /// + public static Uuid FromClockV7(ClockGenerator clock, ReadOnlySpan randomBytes) + + { + var millis = clock.Tick().MicrosecondsSinceUnixEpoch / 1000; + return FromUnixMillisV7(millis, randomBytes); + } + + /// + /// Parses a UUID from its string representation. + /// + /// + /// + /// var s = "01888d6e-5c00-7000-8000-000000000000"; + /// var uuid = Uuid.Parse(s); + /// Console.WriteLine(uuid.ToString() == s); // True + /// + /// + public static Uuid Parse(string s) => new(FromGuid(Guid.Parse(s))); + + /// + /// Converts this instance to a . + /// + public Guid ToGuid() => value.ToGuid(); + + public override readonly string ToString() => ToGuid().ToString(); + + public readonly int CompareTo(Uuid other) => ToGuid().CompareTo(other.ToGuid()); + /// + public int CompareTo(object? value) + { + if (value is Uuid other) + { + return CompareTo(other); + } + else if (value is null) + { + return 1; + } + else + { + throw new ArgumentException("Argument must be a Uuid", nameof(value)); + } + } + public static bool operator <(Uuid l, Uuid r) => l.CompareTo(r) < 0; + public static bool operator >(Uuid l, Uuid r) => l.CompareTo(r) > 0; + + + public readonly partial struct BSATN : IReadWrite + { + public Uuid Read(BinaryReader reader) => new(new SpacetimeDB.BSATN.U128Stdb().Read(reader)); + public void Write(BinaryWriter writer, Uuid value) => new SpacetimeDB.BSATN.U128Stdb().Write(writer, value.value); + // --- / auto-generated --- + + // --- customized --- + public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) => + // Return a Product directly, not a Ref, because this is a special type. + new AlgebraicType.Product([ + // Using this specific name here is important. + new("__uuid__", new AlgebraicType.U128(default)) + ]); + } +} \ No newline at end of file diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index d414d9ad106..bcbad9656f0 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -175,7 +175,8 @@ or SpecialType.System_Int64 SpecialType.System_String or SpecialType.System_Boolean => true, SpecialType.None => type.ToString() is "SpacetimeDB.ConnectionId" - or "SpacetimeDB.Identity", + or "SpacetimeDB.Identity" + or "SpacetimeDB.Uuid", _ => false, } ) @@ -1464,12 +1465,54 @@ internal ReducerContext(Identity identity, ConnectionId? connectionId, Random ra Timestamp = time; SenderAuth = AuthCtx.BuildFromSystemTables(connectionId, identity); } + + /// + /// Create a new UUIDv4 using the built-in RNG. + /// + /// + /// This method fills 16 random bytes using the context RNG, + /// sets version and variant bits for UUIDv4, and returns the result. + /// + /// + /// + /// var uuid = ctx.NewUuidV4(); + /// Console.WriteLine(uuid); + /// + /// + public Uuid NewUuidV4() + { + var bytes = new byte[16]; + Rng.NextBytes(bytes); + return Uuid.FromRandomBytesV4(bytes); + } + + /// + /// Create a new UUIDv7 using the provided . + /// + /// + /// To preserve monotonicity guarantees, do not call this from multiple + /// threads or contexts sharing the same . + /// Use a dedicated instance per logical context. + /// + /// + /// + /// var clock = new ClockGenerator(ctx.Timestamp); + /// var uuid = ctx.NewUuidV7(clock); + /// Console.WriteLine(uuid); + /// + /// + public Uuid NewUuidV7(ClockGenerator clock) + { + var bytes = new byte[10]; + Rng.NextBytes(bytes); + return Uuid.FromClockV7(clock, bytes); + } } - - public sealed record ViewContext : DbContext, Internal.IViewContext + + public sealed record ViewContext : DbContext, Internal.IViewContext { public Identity Sender { get; } - + internal ViewContext(Identity sender, Internal.LocalReadOnly db) : base(db) { @@ -1477,12 +1520,12 @@ internal ViewContext(Identity sender, Internal.LocalReadOnly db) } } - public sealed record AnonymousViewContext : DbContext, Internal.IAnonymousViewContext + public sealed record AnonymousViewContext : DbContext, Internal.IAnonymousViewContext { internal AnonymousViewContext(Internal.LocalReadOnly db) : base(db) { } } - + namespace Internal.TableHandles { {{string.Join("\n", tableAccessors.Select(v => v.tableAccessor))}} } diff --git a/crates/bindings-csharp/Runtime/Runtime.sln b/crates/bindings-csharp/Runtime/Runtime.sln new file mode 100644 index 00000000000..53b00cc9196 --- /dev/null +++ b/crates/bindings-csharp/Runtime/Runtime.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Runtime", "Runtime.csproj", "{63C91D0B-0AE3-D1C8-1700-0E3CE3A0F7C2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {63C91D0B-0AE3-D1C8-1700-0E3CE3A0F7C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63C91D0B-0AE3-D1C8-1700-0E3CE3A0F7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63C91D0B-0AE3-D1C8-1700-0E3CE3A0F7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63C91D0B-0AE3-D1C8-1700-0E3CE3A0F7C2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {503F8EC9-458A-466F-941E-DF180F6F3CE1} + EndGlobalSection +EndGlobal diff --git a/crates/bindings-typescript/package.json b/crates/bindings-typescript/package.json index 4219947dc76..0f91250e2fd 100644 --- a/crates/bindings-typescript/package.json +++ b/crates/bindings-typescript/package.json @@ -155,7 +155,8 @@ "dependencies": { "base64-js": "^1.5.1", "fast-text-encoding": "^1.0.0", - "prettier": "^3.3.3" + "prettier": "^3.3.3", + "uuid": "^13.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0", diff --git a/crates/bindings-typescript/src/index.ts b/crates/bindings-typescript/src/index.ts index 5204fe0b972..01ef1576400 100644 --- a/crates/bindings-typescript/src/index.ts +++ b/crates/bindings-typescript/src/index.ts @@ -6,6 +6,7 @@ export { default as BinaryWriter } from './lib/binary_writer'; export * from './lib/schedule_at'; export * from './lib/time_duration'; export * from './lib/timestamp'; +export * from './lib/uuid'; export * from './lib/utils'; export * from './lib/identity'; export * from './lib/option'; diff --git a/crates/bindings-typescript/src/lib/algebraic_type.ts b/crates/bindings-typescript/src/lib/algebraic_type.ts index d7af713abac..e322306f754 100644 --- a/crates/bindings-typescript/src/lib/algebraic_type.ts +++ b/crates/bindings-typescript/src/lib/algebraic_type.ts @@ -1,5 +1,6 @@ import { TimeDuration } from './time_duration'; import { Timestamp } from './timestamp'; +import { Uuid } from './uuid'; import { ConnectionId } from './connection_id'; import type BinaryReader from './binary_reader'; import BinaryWriter from './binary_writer'; @@ -66,6 +67,7 @@ export const AlgebraicType: { createScheduleAtType(): AlgebraicTypeType; createTimestampType(): AlgebraicTypeType; createTimeDurationType(): AlgebraicTypeType; + createUuidType(): AlgebraicTypeType; serializeValue( writer: BinaryWriter, ty: AlgebraicTypeType, @@ -117,6 +119,9 @@ export const AlgebraicType: { createTimeDurationType: function (): AlgebraicTypeType { return TimeDuration.getAlgebraicType(); }, + createUuidType: function (): AlgebraicTypeType { + return Uuid.getAlgebraicType(); + }, serializeValue: function ( writer: BinaryWriter, ty: AlgebraicTypeType, @@ -379,6 +384,10 @@ export const ProductType: { if (ty.elements[0].name === '__connection_id__') { return new ConnectionId(reader.readU128()); } + + if (ty.elements[0].name === '__uuid__') { + return new Uuid(reader.readU128()); + } } for (const element of ty.elements) { @@ -407,6 +416,10 @@ export const ProductType: { if (ty.elements[0].name === '__connection_id__') { return (value as ConnectionId).__connection_id__; } + + if (ty.elements[0].name === '__uuid__') { + return (value as Uuid).__uuid__; + } } // The fallback is to serialize and base64 encode the bytes. const writer = new BinaryWriter(10); diff --git a/crates/bindings-typescript/src/lib/timestamp.ts b/crates/bindings-typescript/src/lib/timestamp.ts index e97fca90de8..edbc4c7ac7a 100644 --- a/crates/bindings-typescript/src/lib/timestamp.ts +++ b/crates/bindings-typescript/src/lib/timestamp.ts @@ -56,6 +56,11 @@ export class Timestamp { return Timestamp.fromDate(new Date()); } + /** Convert to milliseconds since Unix epoch (truncates microseconds). */ + toMillis(): bigint { + return this.microsSinceUnixEpoch / 1000n; + } + /** * Get a `Timestamp` representing the same point in time as `date`. */ @@ -92,3 +97,26 @@ export class Timestamp { ); } } + +/** A generator for monotonically increasing Timestamps by millisecond increments. */ +export class ClockGenerator { + private microsSinceUnixEpoch: bigint; + + constructor(start: Timestamp) { + this.microsSinceUnixEpoch = start.microsSinceUnixEpoch; + } + + /** Returns the next Timestamp, guaranteed to be greater than the previous. + * + * UUIDv7 requires monotonic millisecond timestamps, so each tick + * increases the timestamp by at least 1 millisecond (1_000 microseconds). + * */ + tick(): Timestamp { + this.microsSinceUnixEpoch += 1000n; + return new Timestamp(this.microsSinceUnixEpoch); + } + + static from(t: Timestamp): ClockGenerator { + return new ClockGenerator(t); + } +} diff --git a/crates/bindings-typescript/src/lib/uuid.ts b/crates/bindings-typescript/src/lib/uuid.ts new file mode 100644 index 00000000000..72dfc48d3d1 --- /dev/null +++ b/crates/bindings-typescript/src/lib/uuid.ts @@ -0,0 +1,107 @@ +import { parse as uuidParse, stringify as uuidStringify } from 'uuid'; +import { ClockGenerator } from './timestamp'; +import { AlgebraicType } from './algebraic_type.ts'; + +export type UuidAlgebraicType = { + tag: 'Product'; + value: { + elements: [ + { + name: '__uuid__'; + algebraicType: { tag: 'U128' }; + }, + ]; + }; +}; + +/** 128-bit UUID abstraction */ +export class Uuid { + __uuid__: bigint; + + constructor(u: bigint) { + this.__uuid__ = u; + } + + /** The nil UUID (all zeros). */ + static readonly NIL = new Uuid(0n); + + /** Create a UUIDv4 from explicit random bytes. */ + static fromRandomBytesV4(bytes: Uint8Array): Uuid { + if (bytes.length !== 16) throw new Error('UUIDv4 requires 16 bytes'); + const arr = new Uint8Array(bytes); + arr[6] = (arr[6] & 0x0f) | 0x40; // version 4 + arr[8] = (arr[8] & 0x3f) | 0x80; // variant + return new Uuid(Uuid.bytesToBigInt(arr)); + } + + /** Create a UUIDv7 from a UNIX timestamp (milliseconds) and 10 random bytes. */ + static fromUnixMillisV7(millis: number | bigint, random: Uint8Array): Uuid { + if (random.length !== 10) + throw new Error('UUIDv7 requires 10 random bytes'); + + const buf = new Uint8Array(16); + const ts = BigInt(millis); + // encode 48-bit timestamp (6 bytes) + for (let i = 0; i < 6; i++) + buf[5 - i] = Number((ts >> BigInt(i * 8)) & 0xffn); + buf.set(random, 6); + buf[6] = (buf[6] & 0x0f) | 0x70; // version 7 + buf[8] = (buf[8] & 0x3f) | 0x80; // variant + + return new Uuid(Uuid.bytesToBigInt(buf)); + } + + /** Generate a UUIDv7 using a monotonic clock generator. */ + static fromClockV7(clock: ClockGenerator, random: Uint8Array): Uuid { + const ts = clock.tick(); + const millis = ts.toMillis(); + return Uuid.fromUnixMillisV7(millis, random); + } + + /** Parse a UUID from a string. */ + static parse(s: string): Uuid { + const bytes = uuidParse(s); + return new Uuid(Uuid.bytesToBigInt(bytes)); + } + + /** Convert to string (hyphenated form). */ + toString(): string { + return uuidStringify(Uuid.bigIntToBytes(this.__uuid__)); + } + + /** Convert to bigint (u128). */ + asBigInt(): bigint { + return this.__uuid__; + } + + /** Return a `Uint8Array` of 16 bytes. */ + toBytes(): Uint8Array { + return Uuid.bigIntToBytes(this.__uuid__); + } + + private static bytesToBigInt(bytes: Uint8Array): bigint { + let result = 0n; + for (const b of bytes) result = (result << 8n) | BigInt(b); + return result; + } + + private static bigIntToBytes(value: bigint): Uint8Array { + const bytes = new Uint8Array(16); + for (let i = 15; i >= 0; i--) { + bytes[i] = Number(value & 0xffn); + value >>= 8n; + } + return bytes; + } + + static getAlgebraicType(): UuidAlgebraicType { + return AlgebraicType.Product({ + elements: [ + { + name: '__uuid__', + algebraicType: AlgebraicType.U128, + }, + ], + }); + } +} diff --git a/crates/bindings-typescript/src/server/reducers.ts b/crates/bindings-typescript/src/server/reducers.ts index 0735cc004c1..888aef7dd47 100644 --- a/crates/bindings-typescript/src/server/reducers.ts +++ b/crates/bindings-typescript/src/server/reducers.ts @@ -3,7 +3,8 @@ import Lifecycle from '../lib/autogen/lifecycle_type'; import type RawReducerDefV9 from '../lib/autogen/raw_reducer_def_v_9_type'; import type { ConnectionId } from '../lib/connection_id'; import type { Identity } from '../lib/identity'; -import type { Timestamp } from '../lib/timestamp'; +import type { ClockGenerator, Timestamp } from '../lib/timestamp'; +import type { Uuid } from '../lib/uuid'; import { MODULE_DEF, type UntypedSchemaDef } from './schema'; import type { Table } from './table'; import type { @@ -117,6 +118,11 @@ export type ReducerCtx = Readonly<{ connectionId: ConnectionId | null; db: DbView; senderAuth: AuthCtx; + /** Create a new UUIDv4 using built-in RNG. */ + newUuidV4(): Uuid; + + /** Create a new UUIDv7 using the provided ClockGenerator. */ + newUuidV7(clock: ClockGenerator): Uuid; }>; /** diff --git a/crates/bindings-typescript/src/server/runtime.ts b/crates/bindings-typescript/src/server/runtime.ts index f39e6bdb034..7f4c5c37a06 100644 --- a/crates/bindings-typescript/src/server/runtime.ts +++ b/crates/bindings-typescript/src/server/runtime.ts @@ -5,7 +5,8 @@ import type RawTableDefV9 from '../lib/autogen/raw_table_def_v_9_type'; import type Typespace from '../lib/autogen/typespace_type'; import { ConnectionId } from '../lib/connection_id'; import { Identity } from '../lib/identity'; -import { Timestamp } from '../lib/timestamp'; +import { Timestamp, ClockGenerator } from '../lib/timestamp'; +import { Uuid } from '../lib/uuid'; import BinaryReader from '../lib/binary_reader'; import BinaryWriter from '../lib/binary_writer'; import { SenderError, SpacetimeHostError } from './errors'; @@ -207,6 +208,19 @@ export const hooks: ModuleHooks = { ConnectionId.nullIfZero(new ConnectionId(connId)), senderIdentity ), + /** Create a new UUIDv4 using built-in RNG. */ + newUuidV4(): Uuid { + // TODO: Use a spacetime RNG when available + const bytes = crypto.getRandomValues(new Uint8Array(16)); + return Uuid.fromRandomBytesV4(bytes); + }, + + /** Create a new UUIDv7 using the provided ClockGenerator. */ + newUuidV7(clock: ClockGenerator): Uuid { + // TODO: Use a spacetime RNG when available + const bytes = crypto.getRandomValues(new Uint8Array(10)); + return Uuid.fromUnixMillisV7(clock.tick().toMillis(), bytes); + }, }); try { return REDUCERS[reducerId](ctx, args) ?? { tag: 'ok' }; diff --git a/crates/bindings-typescript/src/server/type_builders.ts b/crates/bindings-typescript/src/server/type_builders.ts index 4fa2b5484db..cc6385e580b 100644 --- a/crates/bindings-typescript/src/server/type_builders.ts +++ b/crates/bindings-typescript/src/server/type_builders.ts @@ -5,12 +5,14 @@ import { ScheduleAt, TimeDuration, Timestamp, + Uuid, Option, type AlgebraicTypeVariants, type ConnectionIdAlgebraicType, type IdentityAlgebraicType, type TimeDurationAlgebraicType, type TimestampAlgebraicType, + type UuidAlgebraicType, } from '..'; import type { OptionAlgebraicType } from '../lib/option'; import { addType, MODULE_DEF } from './schema'; @@ -419,21 +421,25 @@ export class U16Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): U16ColumnBuilder> { return new U16ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): U16ColumnBuilder> { return new U16ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): U16ColumnBuilder> { return new U16ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: number ): U16ColumnBuilder> { @@ -456,6 +462,7 @@ export class U32Builder constructor() { super(AlgebraicType.U32); } + index(): U32ColumnBuilder>; index>( algorithm: N @@ -468,21 +475,25 @@ export class U32Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): U32ColumnBuilder> { return new U32ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): U32ColumnBuilder> { return new U32ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): U32ColumnBuilder> { return new U32ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: number ): U32ColumnBuilder> { @@ -505,6 +516,7 @@ export class U64Builder constructor() { super(AlgebraicType.U64); } + index(): U64ColumnBuilder>; index>( algorithm: N @@ -517,21 +529,25 @@ export class U64Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): U64ColumnBuilder> { return new U64ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): U64ColumnBuilder> { return new U64ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): U64ColumnBuilder> { return new U64ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: bigint ): U64ColumnBuilder> { @@ -554,6 +570,7 @@ export class U128Builder constructor() { super(AlgebraicType.U128); } + index(): U128ColumnBuilder>; index>( algorithm: N @@ -566,24 +583,28 @@ export class U128Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): U128ColumnBuilder> { return new U128ColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): U128ColumnBuilder> { return new U128ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): U128ColumnBuilder> { return new U128ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: bigint ): U128ColumnBuilder> { @@ -606,6 +627,7 @@ export class U256Builder constructor() { super(AlgebraicType.U256); } + index(): U256ColumnBuilder>; index>( algorithm: N @@ -618,24 +640,28 @@ export class U256Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): U256ColumnBuilder> { return new U256ColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): U256ColumnBuilder> { return new U256ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): U256ColumnBuilder> { return new U256ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: bigint ): U256ColumnBuilder> { @@ -658,6 +684,7 @@ export class I8Builder constructor() { super(AlgebraicType.I8); } + index(): I8ColumnBuilder>; index>( algorithm: N @@ -670,21 +697,25 @@ export class I8Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): I8ColumnBuilder> { return new I8ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): I8ColumnBuilder> { return new I8ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): I8ColumnBuilder> { return new I8ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: number ): I8ColumnBuilder> { @@ -707,6 +738,7 @@ export class I16Builder constructor() { super(AlgebraicType.I16); } + index(): I16ColumnBuilder>; index>( algorithm: N @@ -719,21 +751,25 @@ export class I16Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): I16ColumnBuilder> { return new I16ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): I16ColumnBuilder> { return new I16ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): I16ColumnBuilder> { return new I16ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: number ): I16ColumnBuilder> { @@ -757,6 +793,7 @@ export class I32Builder constructor() { super(AlgebraicType.I32); } + index(): I32ColumnBuilder>; index>( algorithm: N @@ -769,21 +806,25 @@ export class I32Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): I32ColumnBuilder> { return new I32ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): I32ColumnBuilder> { return new I32ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): I32ColumnBuilder> { return new I32ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: number ): I32ColumnBuilder> { @@ -806,6 +847,7 @@ export class I64Builder constructor() { super(AlgebraicType.I64); } + index(): I64ColumnBuilder>; index>( algorithm: N @@ -818,21 +860,25 @@ export class I64Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): I64ColumnBuilder> { return new I64ColumnBuilder(this, set(defaultMetadata, { isUnique: true })); } + primaryKey(): I64ColumnBuilder> { return new I64ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): I64ColumnBuilder> { return new I64ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: bigint ): I64ColumnBuilder> { @@ -855,6 +901,7 @@ export class I128Builder constructor() { super(AlgebraicType.I128); } + index(): I128ColumnBuilder>; index>( algorithm: N @@ -867,24 +914,28 @@ export class I128Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): I128ColumnBuilder> { return new I128ColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): I128ColumnBuilder> { return new I128ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): I128ColumnBuilder> { return new I128ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: bigint ): I128ColumnBuilder> { @@ -907,6 +958,7 @@ export class I256Builder constructor() { super(AlgebraicType.I256); } + index(): I256ColumnBuilder>; index>( algorithm: N @@ -919,24 +971,28 @@ export class I256Builder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): I256ColumnBuilder> { return new I256ColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): I256ColumnBuilder> { return new I256ColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): I256ColumnBuilder> { return new I256ColumnBuilder( this, set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: bigint ): I256ColumnBuilder> { @@ -954,6 +1010,7 @@ export class F32Builder constructor() { super(AlgebraicType.F32); } + default( value: number ): F32ColumnBuilder> { @@ -971,6 +1028,7 @@ export class F64Builder constructor() { super(AlgebraicType.F64); } + default( value: number ): F64ColumnBuilder> { @@ -992,6 +1050,7 @@ export class BoolBuilder constructor() { super(AlgebraicType.Bool); } + index(): BoolColumnBuilder>; index>( algorithm: N @@ -1004,18 +1063,21 @@ export class BoolBuilder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): BoolColumnBuilder> { return new BoolColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): BoolColumnBuilder> { return new BoolColumnBuilder( this, set(defaultMetadata, { isPrimaryKey: true }) ); } + default( value: boolean ): BoolColumnBuilder> { @@ -1037,6 +1099,7 @@ export class StringBuilder constructor() { super(AlgebraicType.String); } + index(): StringColumnBuilder>; index>( algorithm: N @@ -1049,12 +1112,14 @@ export class StringBuilder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): StringColumnBuilder> { return new StringColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): StringColumnBuilder< Set > { @@ -1063,6 +1128,7 @@ export class StringBuilder set(defaultMetadata, { isPrimaryKey: true }) ); } + default( value: string ): StringColumnBuilder> { @@ -1088,6 +1154,7 @@ export class ArrayBuilder> constructor(element: Element) { super(AlgebraicType.Array(element.algebraicType)); } + default( value: Array> ): ArrayColumnBuilder> { @@ -1123,6 +1190,7 @@ export class OptionBuilder> } super(Option.getAlgebraicType(innerType)); } + default( value: InferTypeOfTypeBuilder | undefined ): OptionColumnBuilder< @@ -1157,6 +1225,7 @@ export class ProductBuilder algebraicType, })); } + super( addType( name, @@ -1166,6 +1235,7 @@ export class ProductBuilder ) ); } + default( value: ObjectType ): ProductColumnBuilder> { @@ -1185,6 +1255,7 @@ export class RowBuilder extends TypeBuilder< > { row: CoerceRow; nameProvided: boolean; + constructor(row: Row, name?: string) { const mappedRow = Object.fromEntries( Object.entries(row).map(([name, builder]) => [ @@ -1220,6 +1291,7 @@ export class SumBuilder extends TypeBuilder< algebraicType, })); } + super( addType( name, @@ -1229,6 +1301,7 @@ export class SumBuilder extends TypeBuilder< ) ); } + default( value: EnumType ): SumColumnBuilder> { @@ -1275,6 +1348,7 @@ export class SimpleSumBuilder set(defaultMetadata, { indexType: algorithm }) ); } + primaryKey(): SimpleSumColumnBuilder< Variants, Set @@ -1297,6 +1371,7 @@ export class IdentityBuilder constructor() { super(Identity.getAlgebraicType()); } + index(): IdentityColumnBuilder>; index>( algorithm: N @@ -1309,12 +1384,14 @@ export class IdentityBuilder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): IdentityColumnBuilder> { return new IdentityColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): IdentityColumnBuilder< Set > { @@ -1323,6 +1400,7 @@ export class IdentityBuilder set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): IdentityColumnBuilder< Set > { @@ -1331,6 +1409,7 @@ export class IdentityBuilder set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: Identity ): IdentityColumnBuilder> { @@ -1352,6 +1431,7 @@ export class ConnectionIdBuilder constructor() { super(ConnectionId.getAlgebraicType()); } + index(): ConnectionIdColumnBuilder< Set >; @@ -1366,12 +1446,14 @@ export class ConnectionIdBuilder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): ConnectionIdColumnBuilder> { return new ConnectionIdColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): ConnectionIdColumnBuilder< Set > { @@ -1380,6 +1462,7 @@ export class ConnectionIdBuilder set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): ConnectionIdColumnBuilder< Set > { @@ -1388,6 +1471,7 @@ export class ConnectionIdBuilder set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: ConnectionId ): ConnectionIdColumnBuilder< @@ -1411,6 +1495,7 @@ export class TimestampBuilder constructor() { super(Timestamp.getAlgebraicType()); } + index(): TimestampColumnBuilder>; index>( algorithm: N @@ -1423,12 +1508,14 @@ export class TimestampBuilder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): TimestampColumnBuilder> { return new TimestampColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): TimestampColumnBuilder< Set > { @@ -1437,6 +1524,7 @@ export class TimestampBuilder set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): TimestampColumnBuilder< Set > { @@ -1445,6 +1533,7 @@ export class TimestampBuilder set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: Timestamp ): TimestampColumnBuilder> { @@ -1466,6 +1555,7 @@ export class TimeDurationBuilder constructor() { super(TimeDuration.getAlgebraicType()); } + index(): TimeDurationColumnBuilder< Set >; @@ -1480,12 +1570,14 @@ export class TimeDurationBuilder set(defaultMetadata, { indexType: algorithm }) ); } + unique(): TimeDurationColumnBuilder> { return new TimeDurationColumnBuilder( this, set(defaultMetadata, { isUnique: true }) ); } + primaryKey(): TimeDurationColumnBuilder< Set > { @@ -1494,6 +1586,7 @@ export class TimeDurationBuilder set(defaultMetadata, { isPrimaryKey: true }) ); } + autoInc(): TimeDurationColumnBuilder< Set > { @@ -1502,6 +1595,7 @@ export class TimeDurationBuilder set(defaultMetadata, { isAutoIncrement: true }) ); } + default( value: TimeDuration ): TimeDurationColumnBuilder< @@ -1514,6 +1608,55 @@ export class TimeDurationBuilder } } +export class UuidBuilder + extends TypeBuilder + implements + Indexable, + Uniqueable, + PrimaryKeyable, + Defaultable +{ + constructor() { + super(Uuid.getAlgebraicType()); + } + + index(): UuidColumnBuilder>; + index>( + algorithm: N + ): UuidColumnBuilder>; + index( + algorithm: IndexTypes = 'btree' + ): UuidColumnBuilder> { + return new UuidColumnBuilder( + this, + set(defaultMetadata, { indexType: algorithm }) + ); + } + + unique(): UuidColumnBuilder> { + return new UuidColumnBuilder( + this, + set(defaultMetadata, { isUnique: true }) + ); + } + + primaryKey(): UuidColumnBuilder> { + return new UuidColumnBuilder( + this, + set(defaultMetadata, { isPrimaryKey: true }) + ); + } + + default( + value: Uuid + ): UuidColumnBuilder> { + return new UuidColumnBuilder( + this, + set(defaultMetadata, { defaultValue: value }) + ); + } +} + /** * The type of index types that can be applied to a column. * `undefined` is the default @@ -1584,24 +1727,28 @@ export class U8ColumnBuilder = DefaultMetadata> set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): U8ColumnBuilder> { return new U8ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): U8ColumnBuilder> { return new U8ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): U8ColumnBuilder> { return new U8ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: number): U8ColumnBuilder> { return new U8ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1633,24 +1780,28 @@ export class U16ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): U16ColumnBuilder> { return new U16ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): U16ColumnBuilder> { return new U16ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): U16ColumnBuilder> { return new U16ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: number): U16ColumnBuilder> { return new U16ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1682,24 +1833,28 @@ export class U32ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): U32ColumnBuilder> { return new U32ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): U32ColumnBuilder> { return new U32ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): U32ColumnBuilder> { return new U32ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: number): U32ColumnBuilder> { return new U32ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1731,24 +1886,28 @@ export class U64ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): U64ColumnBuilder> { return new U64ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): U64ColumnBuilder> { return new U64ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): U64ColumnBuilder> { return new U64ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: bigint): U64ColumnBuilder> { return new U64ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1780,24 +1939,28 @@ export class U128ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): U128ColumnBuilder> { return new U128ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): U128ColumnBuilder> { return new U128ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): U128ColumnBuilder> { return new U128ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: bigint): U128ColumnBuilder> { return new U128ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1829,24 +1992,28 @@ export class U256ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): U256ColumnBuilder> { return new U256ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): U256ColumnBuilder> { return new U256ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): U256ColumnBuilder> { return new U256ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: bigint): U256ColumnBuilder> { return new U256ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1876,24 +2043,28 @@ export class I8ColumnBuilder = DefaultMetadata> set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): I8ColumnBuilder> { return new I8ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): I8ColumnBuilder> { return new I8ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): I8ColumnBuilder> { return new I8ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: number): I8ColumnBuilder> { return new I8ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1925,24 +2096,28 @@ export class I16ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): I16ColumnBuilder> { return new I16ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): I16ColumnBuilder> { return new I16ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): I16ColumnBuilder> { return new I16ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: number): I16ColumnBuilder> { return new I16ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -1974,24 +2149,28 @@ export class I32ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): I32ColumnBuilder> { return new I32ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): I32ColumnBuilder> { return new I32ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): I32ColumnBuilder> { return new I32ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: number): I32ColumnBuilder> { return new I32ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -2023,24 +2202,28 @@ export class I64ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): I64ColumnBuilder> { return new I64ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): I64ColumnBuilder> { return new I64ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): I64ColumnBuilder> { return new I64ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: bigint): I64ColumnBuilder> { return new I64ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -2072,24 +2255,28 @@ export class I128ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): I128ColumnBuilder> { return new I128ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): I128ColumnBuilder> { return new I128ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): I128ColumnBuilder> { return new I128ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: bigint): I128ColumnBuilder> { return new I128ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -2121,24 +2308,28 @@ export class I256ColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): I256ColumnBuilder> { return new I256ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): I256ColumnBuilder> { return new I256ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + autoInc(): I256ColumnBuilder> { return new I256ColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isAutoIncrement: true }) ); } + default(value: bigint): I256ColumnBuilder> { return new I256ColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -2197,18 +2388,21 @@ export class BoolColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): BoolColumnBuilder> { return new BoolColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): BoolColumnBuilder> { return new BoolColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + default(value: boolean): BoolColumnBuilder> { return new BoolColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -2239,18 +2433,21 @@ export class StringColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): StringColumnBuilder> { return new StringColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): StringColumnBuilder> { return new StringColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + default(value: string): StringColumnBuilder> { return new StringColumnBuilder(this.typeBuilder, { ...this.columnMetadata, @@ -2391,6 +2588,7 @@ export class SimpleSumColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + primaryKey(): SimpleSumColumnBuilder< Variants, Set @@ -2424,18 +2622,21 @@ export class IdentityColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): IdentityColumnBuilder> { return new IdentityColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): IdentityColumnBuilder> { return new IdentityColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + default( value: Identity ): IdentityColumnBuilder> { @@ -2468,18 +2669,21 @@ export class ConnectionIdColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): ConnectionIdColumnBuilder> { return new ConnectionIdColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): ConnectionIdColumnBuilder> { return new ConnectionIdColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + default( value: ConnectionId ): ConnectionIdColumnBuilder> { @@ -2512,18 +2716,21 @@ export class TimestampColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): TimestampColumnBuilder> { return new TimestampColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): TimestampColumnBuilder> { return new TimestampColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + default( value: Timestamp ): TimestampColumnBuilder> { @@ -2556,18 +2763,21 @@ export class TimeDurationColumnBuilder< set(this.columnMetadata, { indexType: algorithm }) ); } + unique(): TimeDurationColumnBuilder> { return new TimeDurationColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isUnique: true }) ); } + primaryKey(): TimeDurationColumnBuilder> { return new TimeDurationColumnBuilder( this.typeBuilder, set(this.columnMetadata, { isPrimaryKey: true }) ); } + default( value: TimeDuration ): TimeDurationColumnBuilder> { @@ -2578,6 +2788,49 @@ export class TimeDurationColumnBuilder< } } +export class UuidColumnBuilder = DefaultMetadata> + extends ColumnBuilder + implements + Indexable, + Uniqueable, + PrimaryKeyable, + Defaultable +{ + index(): UuidColumnBuilder>; + index>( + algorithm: N + ): UuidColumnBuilder>; + index( + algorithm: IndexTypes = 'btree' + ): UuidColumnBuilder> { + return new UuidColumnBuilder( + this.typeBuilder, + set(this.columnMetadata, { indexType: algorithm }) + ); + } + + unique(): UuidColumnBuilder> { + return new UuidColumnBuilder( + this.typeBuilder, + set(this.columnMetadata, { isUnique: true }) + ); + } + + primaryKey(): UuidColumnBuilder> { + return new UuidColumnBuilder( + this.typeBuilder, + set(this.columnMetadata, { isPrimaryKey: true }) + ); + } + + default(value: Uuid): UuidColumnBuilder> { + return new UuidColumnBuilder(this.typeBuilder, { + ...this.columnMetadata, + defaultValue: value, + }); + } +} + /** * A collection of factory functions for creating various SpacetimeDB algebraic types * to be used in table definitions. Each function returns a corresponding builder @@ -2897,5 +3150,14 @@ export const t = { timeDuration: (): TimeDurationBuilder => { return new TimeDurationBuilder(); }, + + /** + * This is a convenience method for creating a column with the {@link Uuid} type. + * You can create a column of the same type by constructing an `object` with a single `__uuid__` element. + * @returns A new {@link TypeBuilder} instance with the {@link Uuid} type. + */ + uuid: (): UuidBuilder => { + return new UuidBuilder(); + }, } as const; export default t; diff --git a/crates/bindings-typescript/tests/serde.test.ts b/crates/bindings-typescript/tests/serde.test.ts index cb9759c1b69..6d20add98ce 100644 --- a/crates/bindings-typescript/tests/serde.test.ts +++ b/crates/bindings-typescript/tests/serde.test.ts @@ -151,4 +151,29 @@ describe('it correctly serializes and deserializes algebraic values', () => { expect(deserializedValue).toEqual(value); }); + + test('when it serializes and deserializes an Uuid ', () => { + const value = { + __uuid__: BigInt('0x1234567890abcdef1234567890abcdef'), + }; + + const algebraic_type = AlgebraicType.createUuidType(); + const binaryWriter = new BinaryWriter(1024); + AlgebraicType.serializeValue(binaryWriter, algebraic_type, value); + + const buffer = binaryWriter.getBuffer(); + expect(buffer).toEqual( + new Uint8Array([ + 239, 205, 171, 144, 120, 86, 52, 18, 239, 205, 171, 144, 120, 86, 52, + 18, + ]) + ); + + const deserializedValue = AlgebraicType.deserializeValue( + new BinaryReader(buffer), + algebraic_type + ); + + expect(deserializedValue).toEqual(value); + }); }); diff --git a/crates/bindings-typescript/tests/uuid.test.ts b/crates/bindings-typescript/tests/uuid.test.ts new file mode 100644 index 00000000000..9a50e7804fa --- /dev/null +++ b/crates/bindings-typescript/tests/uuid.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, test } from 'vitest'; +import { ClockGenerator, Timestamp, Uuid } from '../src'; + +function newUuidV4(): Uuid { + const bytes = crypto.getRandomValues(new Uint8Array(16)); + return Uuid.fromRandomBytesV4(bytes); +} + +function newUuidV7(clock: ClockGenerator): Uuid { + const random = crypto.getRandomValues(new Uint8Array(10)); + return Uuid.fromClockV7(clock, random); +} + +describe('Uuid', () => { + test('toString UUid', () => { + const uuid = Uuid.NIL; + expect(uuid.toString()).toBe('00000000-0000-0000-0000-000000000000'); + const parsed = Uuid.parse('00000000-0000-0000-0000-000000000000'); + expect(parsed.asBigInt()).toBe(0n); + }); + + test('sorted uuid', () => { + const clock = new ClockGenerator(new Timestamp(0n)); + const uuids: Uuid[] = []; + for (let i = 0; i < 1000; i++) { + const uuid = newUuidV7(clock); + uuids.push(uuid); + } + const sortedUuids = [...uuids].sort((a, b) => { + if (a.asBigInt() < b.asBigInt()) return -1; + if (a.asBigInt() > b.asBigInt()) return 1; + return 0; + }); + expect(uuids).toEqual(sortedUuids); + }); +}); diff --git a/crates/bindings/Cargo.toml b/crates/bindings/Cargo.toml index d852597e42b..3f09b58222d 100644 --- a/crates/bindings/Cargo.toml +++ b/crates/bindings/Cargo.toml @@ -25,6 +25,7 @@ spacetimedb-lib.workspace = true spacetimedb-bindings-macro.workspace = true spacetimedb-primitives.workspace = true +anyhow.workspace = true bytemuck.workspace = true derive_more.workspace = true log.workspace = true diff --git a/crates/bindings/src/lib.rs b/crates/bindings/src/lib.rs index 580b9ce0b6e..f7465e716b7 100644 --- a/crates/bindings/src/lib.rs +++ b/crates/bindings/src/lib.rs @@ -21,6 +21,7 @@ pub use client_visibility_filter::Filter; pub use log; #[cfg(feature = "rand")] pub use rand08 as rand; +use rand08::RngCore; #[cfg(feature = "rand08")] pub use rng::StdbRng; pub use sats::SpacetimeType; @@ -39,6 +40,7 @@ pub use spacetimedb_lib::Identity; pub use spacetimedb_lib::ScheduleAt; pub use spacetimedb_lib::TimeDuration; pub use spacetimedb_lib::Timestamp; +pub use spacetimedb_lib::Uuid; pub use spacetimedb_primitives::TableId; pub use sys::Errno; pub use table::{ @@ -419,6 +421,7 @@ pub use spacetimedb_bindings_macro::client_visibility_filter; #[doc(inline)] pub use spacetimedb_bindings_macro::table; +pub use crate::sats::timestamp::ClockGenerator; /// Marks a function as a spacetimedb reducer. /// /// A reducer is a function with read/write access to the database @@ -1009,6 +1012,50 @@ impl ReducerContext { db: LocalReadOnly {}, } } + + /// Create a new UUIDv4 using the built-in RNG. + //// # Example + /// ```no_run + /// # #[cfg(target_arch = "wasm32")] mod demo { + /// use spacetimedb::{reducer, ReducerContext, Uuid}; + /// + /// #[reducer] + /// fn generate_uuid_v4(ctx: &ReducerContext) -> Uuid { + /// ctx.new_uuid_v4() + /// } + /// # } + /// ``` + pub fn new_uuid_v4(&self) -> anyhow::Result { + let mut bytes = [0u8; 16]; + self.rng().try_fill_bytes(&mut bytes)?; + Ok(Uuid::from_random_bytes_v4(bytes)) + } + + /// Create a new UUIDv7 using the provided [`ClockGenerator`]. + /// + /// **NOTE**: To maintain UUIDv7 monotonicity guarantees, do not use this method + /// from multiple threads or contexts sharing the same [`ClockGenerator`]. + /// + /// Prefer to create a single [`ClockGenerator`] per context and reuse it for all UUIDv7 generation for the same `table` or operation. + /// + /// # Example + /// ```no_run + /// # #[cfg(target_arch = "wasm32")] mod demo { + /// use spacetimedb::{reducer, ReducerContext, ClockGenerator, Uuid}; + /// + /// #[reducer] + /// fn generate_uuid_v7(ctx: &ReducerContext) -> Result> { + /// let mut clock = ClockGenerator::new(ctx.timestamp); + /// let uuid = ctx.new_uuid_v7(&mut clock)?; + /// Ok(uuid) + /// } + /// # } + /// ``` + pub fn new_uuid_v7(&self, clock: &mut ClockGenerator) -> anyhow::Result { + let mut bytes = [0u8; 10]; + self.rng().try_fill_bytes(&mut bytes)?; + Uuid::from_clock_v7(clock, &bytes) + } } /// The context that any procedure is provided with. diff --git a/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap b/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap index a0b8fc583e8..950cfcf3582 100644 --- a/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap +++ b/crates/bindings/tests/snapshots/deps__spacetimedb_bindings_dependencies.snap @@ -2,8 +2,9 @@ source: crates/bindings/tests/deps.rs expression: "cargo tree -p spacetimedb -e no-dev --color never --target wasm32-unknown-unknown -f {lib}" --- -total crates: 66 +total crates: 67 spacetimedb +├── anyhow ├── bytemuck ├── derive_more │ ├── convert_case @@ -114,10 +115,11 @@ spacetimedb │ │ ├── smallvec │ │ ├── spacetimedb_bindings_macro (*) │ │ ├── spacetimedb_primitives (*) -│ │ └── thiserror -│ │ └── thiserror_impl -│ │ ├── proc_macro2 (*) -│ │ ├── quote (*) -│ │ └── syn (*) +│ │ ├── thiserror +│ │ │ └── thiserror_impl +│ │ │ ├── proc_macro2 (*) +│ │ │ ├── quote (*) +│ │ │ └── syn (*) +│ │ └── uuid │ └── thiserror (*) └── spacetimedb_primitives (*) diff --git a/crates/cli/src/subcommands/sql.rs b/crates/cli/src/subcommands/sql.rs index 00a8d645e42..ff8d551931f 100644 --- a/crates/cli/src/subcommands/sql.rs +++ b/crates/cli/src/subcommands/sql.rs @@ -240,7 +240,7 @@ mod tests { use spacetimedb_lib::sats::time_duration::TimeDuration; use spacetimedb_lib::sats::timestamp::Timestamp; use spacetimedb_lib::sats::{product, GroundSpacetimeType, ProductType}; - use spacetimedb_lib::{AlgebraicType, AlgebraicValue, ConnectionId, Identity}; + use spacetimedb_lib::{AlgebraicType, AlgebraicValue, ConnectionId, Identity, Uuid}; fn make_row(row: &[AlgebraicValue]) -> Result, serde_json::Error> { let json = serde_json::json!(row); @@ -470,6 +470,7 @@ Roundtrip time: 1.00ms"#, ConnectionId::get_type(), Timestamp::get_type(), TimeDuration::get_type(), + Uuid::get_type(), ] .into(); let value = product![ @@ -478,7 +479,8 @@ Roundtrip time: 1.00ms"#, Identity::ZERO, ConnectionId::ZERO, Timestamp::UNIX_EPOCH, - TimeDuration::ZERO + TimeDuration::ZERO, + Uuid::NIL ]; expect_psql_table( @@ -486,9 +488,9 @@ Roundtrip time: 1.00ms"#, &kind, vec![value.clone()], r#" - column 0 | column 1 | column 2 | column 3 | column 4 | column 5 -----------+----------+--------------------------------------------------------------------+------------------------------------+---------------------------+----------- - "a" | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000"#, + column 0 | column 1 | column 2 | column 3 | column 4 | column 5 | column 6 +----------+----------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------+---------------------------------------- + "a" | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000 | "00000000-0000-0000-0000-000000000000""#, ); expect_psql_table( @@ -496,9 +498,9 @@ Roundtrip time: 1.00ms"#, &kind, vec![value], r#" - column 0 | column 1 | column 2 | column 3 | column 4 | column 5 -----------+----------+----------------------------------------------------------------------+--------------------------------------+-----------------------------+---------- - "a" | 0 | "0x0000000000000000000000000000000000000000000000000000000000000000" | "0x00000000000000000000000000000000" | "1970-01-01T00:00:00+00:00" | "P0D""#, + column 0 | column 1 | column 2 | column 3 | column 4 | column 5 | column 6 +----------+----------+----------------------------------------------------------------------+--------------------------------------+-----------------------------+----------+---------------------------------------- + "a" | 0 | "0x0000000000000000000000000000000000000000000000000000000000000000" | "0x00000000000000000000000000000000" | "1970-01-01T00:00:00+00:00" | "P0D" | "00000000-0000-0000-0000-000000000000""#, ); // Check struct @@ -510,6 +512,7 @@ Roundtrip time: 1.00ms"#, ("connection_id", ConnectionId::get_type()), ("timestamp", Timestamp::get_type()), ("duration", TimeDuration::get_type()), + ("uuid", Uuid::get_type()), ] .into(); @@ -520,7 +523,8 @@ Roundtrip time: 1.00ms"#, Identity::ZERO, ConnectionId::ZERO, Timestamp::UNIX_EPOCH, - TimeDuration::ZERO + TimeDuration::ZERO, + Uuid::NIL ]; expect_psql_table( @@ -528,9 +532,9 @@ Roundtrip time: 1.00ms"#, &kind, vec![value.clone()], r#" - bool | str | bytes | identity | connection_id | timestamp | duration -------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+----------- - true | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000"#, + bool | str | bytes | identity | connection_id | timestamp | duration | uuid +------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------+---------------------------------------- + true | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000000 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000 | "00000000-0000-0000-0000-000000000000""#, ); expect_psql_table( @@ -538,9 +542,9 @@ Roundtrip time: 1.00ms"#, &kind, vec![value.clone()], r#" - bool | str | bytes | identity | connection_id | timestamp | duration -------+-----------------------+--------------------+----------------------------------------------------------------------+--------------------------------------+-----------------------------+---------- - true | "This is spacetimedb" | "0x01020304050607" | "0x0000000000000000000000000000000000000000000000000000000000000000" | "0x00000000000000000000000000000000" | "1970-01-01T00:00:00+00:00" | "P0D""#, + bool | str | bytes | identity | connection_id | timestamp | duration | uuid +------+-----------------------+--------------------+----------------------------------------------------------------------+--------------------------------------+-----------------------------+----------+---------------------------------------- + true | "This is spacetimedb" | "0x01020304050607" | "0x0000000000000000000000000000000000000000000000000000000000000000" | "0x00000000000000000000000000000000" | "1970-01-01T00:00:00+00:00" | "P0D" | "00000000-0000-0000-0000-000000000000""#, ); // Check nested struct, tuple... @@ -554,8 +558,8 @@ Roundtrip time: 1.00ms"#, vec![value.clone()], r#" column 0 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000)"#, +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000, uuid = "00000000-0000-0000-0000-000000000000")"#, ); expect_psql_table( @@ -564,8 +568,8 @@ Roundtrip time: 1.00ms"#, vec![value.clone()], r#" column 0 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"bool": true, "str": "This is spacetimedb", "bytes": "0x01020304050607", "identity": "0x0000000000000000000000000000000000000000000000000000000000000000", "connection_id": "0x00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00+00:00", "duration": "P0D"}"#, +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"bool": true, "str": "This is spacetimedb", "bytes": "0x01020304050607", "identity": "0x0000000000000000000000000000000000000000000000000000000000000000", "connection_id": "0x00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00+00:00", "duration": "P0D", "uuid": "00000000-0000-0000-0000-000000000000"}"#, ); let kind: ProductType = [("tuple", AlgebraicType::product(kind))].into(); @@ -578,8 +582,8 @@ Roundtrip time: 1.00ms"#, vec![value.clone()], r#" tuple --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - (col_0 = (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000))"#, +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (col_0 = (bool = true, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000000, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000, uuid = "00000000-0000-0000-0000-000000000000"))"#, ); expect_psql_table( @@ -588,8 +592,8 @@ Roundtrip time: 1.00ms"#, vec![value], r#" tuple --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"col_0": {"bool": true, "str": "This is spacetimedb", "bytes": "0x01020304050607", "identity": "0x0000000000000000000000000000000000000000000000000000000000000000", "connection_id": "0x00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00+00:00", "duration": "P0D"}}"#, +-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"col_0": {"bool": true, "str": "This is spacetimedb", "bytes": "0x01020304050607", "identity": "0x0000000000000000000000000000000000000000000000000000000000000000", "connection_id": "0x00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00+00:00", "duration": "P0D", "uuid": "00000000-0000-0000-0000-000000000000"}}"#, ); Ok(()) diff --git a/crates/client-api/Cargo.toml b/crates/client-api/Cargo.toml index 59c9e636d94..1b5643dc59c 100644 --- a/crates/client-api/Cargo.toml +++ b/crates/client-api/Cargo.toml @@ -47,7 +47,7 @@ bytestring = "1" tokio-tungstenite.workspace = true itoa.workspace = true derive_more = "0.99.17" -uuid.workspace = true +uuid = { workspace = true, features = ["v4"] } jsonwebtoken.workspace = true scopeguard.workspace = true serde_with.workspace = true diff --git a/crates/codegen/src/csharp.rs b/crates/codegen/src/csharp.rs index e81035aa594..fefd775789c 100644 --- a/crates/codegen/src/csharp.rs +++ b/crates/codegen/src/csharp.rs @@ -888,6 +888,7 @@ fn ty_fmt<'a>(module: &'a ModuleDef, ty: &'a AlgebraicTypeUse) -> impl fmt::Disp AlgebraicTypeUse::ScheduleAt => f.write_str("SpacetimeDB.ScheduleAt"), AlgebraicTypeUse::Timestamp => f.write_str("SpacetimeDB.Timestamp"), AlgebraicTypeUse::TimeDuration => f.write_str("SpacetimeDB.TimeDuration"), + AlgebraicTypeUse::Uuid => f.write_str("SpacetimeDB.Uuid"), AlgebraicTypeUse::Unit => f.write_str("SpacetimeDB.Unit"), AlgebraicTypeUse::Option(inner_ty) => write!(f, "{}?", ty_fmt(module, inner_ty)), AlgebraicTypeUse::Array(elem_ty) => write!(f, "System.Collections.Generic.List<{}>", ty_fmt(module, elem_ty)), @@ -937,7 +938,8 @@ fn default_init(ctx: &TypespaceForGenerate, ty: &AlgebraicTypeUse) -> Option<&'s | AlgebraicTypeUse::Identity | AlgebraicTypeUse::ConnectionId | AlgebraicTypeUse::Timestamp - | AlgebraicTypeUse::TimeDuration => None, + | AlgebraicTypeUse::TimeDuration + | AlgebraicTypeUse::Uuid => None, AlgebraicTypeUse::Never => unimplemented!("never types are not yet supported in C# output"), } } diff --git a/crates/codegen/src/rust.rs b/crates/codegen/src/rust.rs index a968ec7826a..f908cac65c1 100644 --- a/crates/codegen/src/rust.rs +++ b/crates/codegen/src/rust.rs @@ -615,6 +615,7 @@ pub fn write_type(module: &ModuleDef, out: &mut W, ty: &AlgebraicTypeU AlgebraicTypeUse::ConnectionId => write!(out, "__sdk::ConnectionId")?, AlgebraicTypeUse::Timestamp => write!(out, "__sdk::Timestamp")?, AlgebraicTypeUse::TimeDuration => write!(out, "__sdk::TimeDuration")?, + AlgebraicTypeUse::Uuid => write!(out, "__sdk::Uuid")?, AlgebraicTypeUse::ScheduleAt => write!(out, "__sdk::ScheduleAt")?, AlgebraicTypeUse::Option(inner_ty) => { write!(out, "Option::<")?; diff --git a/crates/codegen/src/typescript.rs b/crates/codegen/src/typescript.rs index 135c5852c67..fbb129f99e7 100644 --- a/crates/codegen/src/typescript.rs +++ b/crates/codegen/src/typescript.rs @@ -694,6 +694,7 @@ fn print_spacetimedb_imports(out: &mut Indenter) { "ConnectionId as __ConnectionId", "Timestamp as __Timestamp", "TimeDuration as __TimeDuration", + "Uuid as __Uuid", "DbConnectionBuilder as __DbConnectionBuilder", "TableCache as __TableCache", "BinaryWriter as __BinaryWriter", @@ -1080,6 +1081,7 @@ fn needs_parens_within_array(ty: &AlgebraicTypeUse) -> bool { | AlgebraicTypeUse::ConnectionId | AlgebraicTypeUse::Timestamp | AlgebraicTypeUse::TimeDuration + | AlgebraicTypeUse::Uuid | AlgebraicTypeUse::Primitive(_) | AlgebraicTypeUse::Array(_) | AlgebraicTypeUse::Ref(_) // We use the type name for these. @@ -1106,6 +1108,7 @@ pub fn write_type( AlgebraicTypeUse::ConnectionId => write!(out, "__ConnectionId")?, AlgebraicTypeUse::Timestamp => write!(out, "__Timestamp")?, AlgebraicTypeUse::TimeDuration => write!(out, "__TimeDuration")?, + AlgebraicTypeUse::Uuid => write!(out, "__Uuid")?, AlgebraicTypeUse::ScheduleAt => write!( out, "{{ tag: \"Interval\", value: __TimeDuration }} | {{ tag: \"Time\", value: __Timestamp }}" @@ -1172,6 +1175,7 @@ fn convert_algebraic_type<'a>( AlgebraicTypeUse::ConnectionId => write!(out, "__AlgebraicTypeValue.createConnectionIdType()"), AlgebraicTypeUse::Timestamp => write!(out, "__AlgebraicTypeValue.createTimestampType()"), AlgebraicTypeUse::TimeDuration => write!(out, "__AlgebraicTypeValue.createTimeDurationType()"), + AlgebraicTypeUse::Uuid => write!(out, "__AlgebraicTypeValue.createUuidType()"), AlgebraicTypeUse::Option(inner_ty) => { write!(out, "__AlgebraicTypeValue.createOptionType("); convert_algebraic_type(module, out, inner_ty, ref_prefix); diff --git a/crates/codegen/src/unrealcpp.rs b/crates/codegen/src/unrealcpp.rs index 280b1be4475..3a5fa6a3f31 100644 --- a/crates/codegen/src/unrealcpp.rs +++ b/crates/codegen/src/unrealcpp.rs @@ -3154,6 +3154,7 @@ fn get_cpp_type_for_array_element(elem_type_str: &str, _: &ModuleDef, module_nam "ConnectionId" => "FSpacetimeDBConnectionId".to_string(), "Timestamp" => "FSpacetimeDBTimestamp".to_string(), "TimeDuration" => "FSpacetimeDBTimeDuration".to_string(), + "Uuid" => "FGuid".to_string(), "ScheduleAt" => "FSpacetimeDBScheduleAt".to_string(), _ if elem_type_str.starts_with("Int32") => { // Handle nested optionals like Int32 from OptionalInt32 @@ -3191,6 +3192,7 @@ fn get_array_element_type_name(module: &ModuleDef, elem: &AlgebraicTypeUse) -> S AlgebraicTypeUse::ConnectionId => "ConnectionId".to_string(), AlgebraicTypeUse::Timestamp => "Timestamp".to_string(), AlgebraicTypeUse::TimeDuration => "TimeDuration".to_string(), + AlgebraicTypeUse::Uuid => "Uuid".to_string(), AlgebraicTypeUse::ScheduleAt => "ScheduleAt".to_string(), AlgebraicTypeUse::Ref(r) => type_ref_name(module, *r), AlgebraicTypeUse::Option(nested_inner) => { @@ -3226,6 +3228,7 @@ fn get_optional_type_name(module: &ModuleDef, inner: &AlgebraicTypeUse) -> Strin AlgebraicTypeUse::ConnectionId => "OptionalConnectionId".to_string(), AlgebraicTypeUse::Timestamp => "OptionalTimestamp".to_string(), AlgebraicTypeUse::TimeDuration => "OptionalTimeDuration".to_string(), + AlgebraicTypeUse::Uuid => "OptionalUuid".to_string(), AlgebraicTypeUse::ScheduleAt => "OptionalScheduleAt".to_string(), AlgebraicTypeUse::Array(elem) => { // Generate specific optional array types based on element type @@ -3653,6 +3656,7 @@ fn should_pass_by_value_in_delegate(_module: &ModuleDef, ty: &AlgebraicTypeUse) AlgebraicTypeUse::ConnectionId => false, // FSpacetimeDBConnectionId is a USTRUCT AlgebraicTypeUse::Timestamp => false, // FSpacetimeDBTimestamp is a USTRUCT AlgebraicTypeUse::TimeDuration => false, // FSpacetimeDBTimeDuration is a USTRUCT + AlgebraicTypeUse::Uuid => false, // FGuid is a USTRUCT // Custom structs/enums use const references AlgebraicTypeUse::Ref(_) => false, AlgebraicTypeUse::Array(_) => false, // Arrays use const references @@ -3692,6 +3696,7 @@ fn is_blueprintable(module: &ModuleDef, ty: &AlgebraicTypeUse) -> bool { AlgebraicTypeUse::ConnectionId => true, AlgebraicTypeUse::Timestamp => true, AlgebraicTypeUse::TimeDuration => true, + AlgebraicTypeUse::Uuid => true, AlgebraicTypeUse::ScheduleAt => true, // ScheduleAt is blueprintable as a property (TObjectPtr) AlgebraicTypeUse::Unit => true, AlgebraicTypeUse::Ref(r) => { @@ -3727,6 +3732,7 @@ fn is_type_blueprintable_for_delegates(module: &ModuleDef, ty: &AlgebraicTypeUse AlgebraicTypeUse::ConnectionId => true, AlgebraicTypeUse::Timestamp => true, AlgebraicTypeUse::TimeDuration => true, + AlgebraicTypeUse::Uuid => true, AlgebraicTypeUse::ScheduleAt => true, AlgebraicTypeUse::Unit => true, AlgebraicTypeUse::Ref(r) => { @@ -4152,6 +4158,7 @@ fn cpp_ty_fmt_impl<'a>( AlgebraicTypeUse::Timestamp => f.write_str("FSpacetimeDBTimestamp"), AlgebraicTypeUse::TimeDuration => f.write_str("FSpacetimeDBTimeDuration"), AlgebraicTypeUse::ScheduleAt => f.write_str("FSpacetimeDBScheduleAt"), + AlgebraicTypeUse::Uuid => f.write_str("FGuid"), AlgebraicTypeUse::Unit => f.write_str("FSpacetimeDBUnit"), // --------- references to user-defined types --------- @@ -4207,6 +4214,7 @@ fn cpp_ty_init_fmt_impl<'a>(ty: &'a AlgebraicTypeUse) -> impl fmt::Display + 'a AlgebraicTypeUse::Timestamp => f.write_str(""), AlgebraicTypeUse::TimeDuration => f.write_str(""), AlgebraicTypeUse::ScheduleAt => f.write_str(""), + AlgebraicTypeUse::Uuid => f.write_str(""), AlgebraicTypeUse::Unit => f.write_str(""), // --------- references to user-defined types --------- AlgebraicTypeUse::Ref(_r) => f.write_str(""), @@ -4243,7 +4251,7 @@ fn collect_includes_for_type( collect_includes_for_type(module, inner, out, module_name); } // Builtin types that require Builtins.h (also includes LargeIntegers.h) - Identity | ConnectionId | Timestamp | TimeDuration | ScheduleAt => { + Identity | ConnectionId | Timestamp | TimeDuration | ScheduleAt | Uuid => { out.insert("Types/Builtins.h".to_string()); } // Large integer primitives also need Builtins.h (for LargeIntegers.h) diff --git a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap index 799e85147f9..d8d641cbab6 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -21,6 +21,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -87,6 +88,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -153,6 +155,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -221,6 +224,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -284,6 +288,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -351,6 +356,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -414,6 +420,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -480,6 +487,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -546,6 +554,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -626,6 +635,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -666,6 +676,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -744,6 +755,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -815,6 +827,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -1469,6 +1482,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -1535,6 +1549,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -1598,6 +1613,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -1828,6 +1844,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -1901,6 +1918,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -1936,6 +1954,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2012,6 +2031,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2048,6 +2068,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2156,6 +2177,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2227,6 +2249,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2357,6 +2380,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2426,6 +2450,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2578,6 +2603,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2649,6 +2675,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2718,6 +2745,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2796,6 +2824,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2874,6 +2903,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -2941,6 +2971,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3004,6 +3035,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3112,6 +3144,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3183,6 +3216,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3254,6 +3288,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3318,6 +3353,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3396,6 +3432,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3467,6 +3504,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3534,6 +3572,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3597,6 +3636,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3679,6 +3719,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3750,6 +3791,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3858,6 +3900,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -3927,6 +3970,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -4009,6 +4053,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, @@ -4080,6 +4125,7 @@ import { TableCache as __TableCache, TimeDuration as __TimeDuration, Timestamp as __Timestamp, + Uuid as __Uuid, deepEqual as __deepEqual, type AlgebraicType as __AlgebraicTypeType, type AlgebraicTypeVariants as __AlgebraicTypeVariants, diff --git a/crates/expr/src/lib.rs b/crates/expr/src/lib.rs index 2d9b3cdc5ab..6bc5416887b 100644 --- a/crates/expr/src/lib.rs +++ b/crates/expr/src/lib.rs @@ -17,6 +17,7 @@ use spacetimedb_lib::Timestamp; use spacetimedb_lib::{from_hex_pad, AlgebraicType, AlgebraicValue, ConnectionId, Identity}; use spacetimedb_sats::algebraic_type::fmt::fmt_algebraic_type; use spacetimedb_sats::algebraic_value::ser::ValueSerializer; +use spacetimedb_sats::uuid::Uuid; use spacetimedb_schema::schema::ColumnSchema; use spacetimedb_sql_parser::ast::{self, BinOp, ProjectElem, SqlExpr, SqlIdent, SqlLiteral}; use spacetimedb_sql_parser::parser::recursion; @@ -166,6 +167,7 @@ fn op_supports_type(_op: BinOp, t: &AlgebraicType) -> bool { || t.is_identity() || t.is_connection_id() || t.is_timestamp() + || t.is_uuid() } /// Parse an integer literal into an [AlgebraicValue] @@ -234,6 +236,11 @@ pub(crate) fn parse(value: &str, ty: &AlgebraicType) -> anyhow::Result anyhow::Result to_bytes(), t if t.is_identity() => to_identity(), t if t.is_connection_id() => to_connection_id(), + t if t.is_uuid() => to_uuid(), t => bail!("Literal values for type {} are not supported", fmt_algebraic_type(t)), } } diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index f533f076986..27e3fd64321 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -35,7 +35,8 @@ pub use identity::Identity; pub use scheduler::ScheduleAt; pub use spacetimedb_sats::hash::{self, hash_bytes, Hash}; pub use spacetimedb_sats::time_duration::TimeDuration; -pub use spacetimedb_sats::timestamp::Timestamp; +pub use spacetimedb_sats::timestamp::{ClockGenerator, Timestamp}; +pub use spacetimedb_sats::uuid::Uuid; pub use spacetimedb_sats::SpacetimeType; pub use spacetimedb_sats::__make_register_reftype; pub use spacetimedb_sats::{self as sats, bsatn, buffer, de, ser}; diff --git a/crates/pg/src/encoder.rs b/crates/pg/src/encoder.rs index f5a6ed990ed..5a55f110d9f 100644 --- a/crates/pg/src/encoder.rs +++ b/crates/pg/src/encoder.rs @@ -5,7 +5,7 @@ use pgwire::api::Type; use spacetimedb_lib::sats::satn::{PsqlChars, PsqlPrintFmt, PsqlType, TypedWriter}; use spacetimedb_lib::sats::{satn, ValueWithType}; use spacetimedb_lib::{ - ser, AlgebraicType, AlgebraicValue, ProductType, ProductTypeElement, ProductValue, TimeDuration, Timestamp, + ser, AlgebraicType, AlgebraicValue, ProductType, ProductTypeElement, ProductValue, TimeDuration, Timestamp, Uuid, }; use std::borrow::Cow; use std::sync::Arc; @@ -56,6 +56,7 @@ pub(crate) fn type_of(schema: &ProductType, ty: &ProductTypeElement) -> Type { PsqlPrintFmt::Hex => Type::BYTEA_ARRAY, PsqlPrintFmt::Timestamp => Type::TIMESTAMP, PsqlPrintFmt::Duration => Type::INTERVAL, + PsqlPrintFmt::Uuid => Type::UUID, _ => Type::JSON, }, AlgebraicType::Sum(sum) if sum.is_simple_enum() => Type::ANYENUM, @@ -112,6 +113,11 @@ impl TypedWriter for PsqlFormatter<'_> { Ok(()) } + fn write_uuid(&mut self, value: Uuid) -> Result<(), Self::Error> { + self.encoder.encode_field(&value.to_string())?; + Ok(()) + } + fn write_alt_record( &mut self, ty: &PsqlType, diff --git a/crates/sats/Cargo.toml b/crates/sats/Cargo.toml index 5cac053184f..396fd6f327d 100644 --- a/crates/sats/Cargo.toml +++ b/crates/sats/Cargo.toml @@ -58,6 +58,11 @@ second-stack.workspace = true serde = { workspace = true, optional = true } smallvec.workspace = true thiserror.workspace = true +uuid.workspace = true + +[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] +uuid = { workspace = true, features = ["v4", "v7"] } +rand = { workspace = true, features = ["std"] } # For the `blake3` feature. blake3 = { workspace = true, optional = true } diff --git a/crates/sats/src/algebraic_type.rs b/crates/sats/src/algebraic_type.rs index e42ba015639..370c18a5623 100644 --- a/crates/sats/src/algebraic_type.rs +++ b/crates/sats/src/algebraic_type.rs @@ -5,7 +5,7 @@ use crate::algebraic_value::de::{ValueDeserializeError, ValueDeserializer}; use crate::algebraic_value::ser::value_serialize; use crate::de::Deserialize; use crate::meta_type::MetaType; -use crate::product_type::{CONNECTION_ID_TAG, IDENTITY_TAG, TIMESTAMP_TAG, TIME_DURATION_TAG}; +use crate::product_type::{CONNECTION_ID_TAG, IDENTITY_TAG, TIMESTAMP_TAG, TIME_DURATION_TAG, UUID_TAG}; use crate::sum_type::{OPTION_NONE_TAG, OPTION_SOME_TAG}; use crate::typespace::Typespace; use crate::{i256, u256}; @@ -187,6 +187,11 @@ impl AlgebraicType { matches!(self, Self::Product(p) if p.is_time_duration()) } + /// Returns whether this type is the conventional `UUID` type. + pub fn is_uuid(&self) -> bool { + matches!(self, Self::Product(p) if p.is_uuid()) + } + /// Returns whether this type is the conventional `ScheduleAt` type. pub fn is_schedule_at(&self) -> bool { matches!(self, Self::Sum(p) if p.is_schedule_at()) @@ -331,6 +336,11 @@ impl AlgebraicType { AlgebraicType::product([(TIME_DURATION_TAG, AlgebraicType::I64)]) } + /// Construct a copy of the `UUID` type. + pub fn uuid() -> Self { + AlgebraicType::product([(UUID_TAG, AlgebraicType::U128)]) + } + /// Returns a sum type of unit variants with names taken from `var_names`. pub fn simple_enum<'a>(var_names: impl Iterator) -> Self { Self::sum(var_names.into_iter().map(SumTypeVariant::unit).collect::>()) @@ -740,6 +750,8 @@ mod tests { assert!(AlgebraicType::timestamp().is_special()); assert!(AlgebraicType::time_duration().is_special()); assert!(AlgebraicType::time_duration().is_time_duration()); + assert!(AlgebraicType::uuid().is_uuid()); + assert!(AlgebraicType::uuid().is_special()); } #[test] diff --git a/crates/sats/src/lib.rs b/crates/sats/src/lib.rs index 04dd262dfbc..c4b6d9b9add 100644 --- a/crates/sats/src/lib.rs +++ b/crates/sats/src/lib.rs @@ -30,6 +30,7 @@ pub mod sum_value; pub mod time_duration; pub mod timestamp; pub mod typespace; +pub mod uuid; #[cfg(any(test, feature = "proptest"))] pub mod proptest; diff --git a/crates/sats/src/product_type.rs b/crates/sats/src/product_type.rs index 4ee813a32b6..4f920ea94f3 100644 --- a/crates/sats/src/product_type.rs +++ b/crates/sats/src/product_type.rs @@ -19,6 +19,9 @@ pub const TIMESTAMP_TAG: &str = "__timestamp_micros_since_unix_epoch__"; /// The tag used inside the special `TimeDuration` product type. pub const TIME_DURATION_TAG: &str = "__time_duration_micros__"; +/// The tag used inside the special `UUID` product type. +pub const UUID_TAG: &str = "__uuid__"; + /// A structural product type of the factors given by `elements`. /// /// This is also known as `struct` and `tuple` in many languages, @@ -110,16 +113,20 @@ impl ProductType { self.is_newtype(CONNECTION_ID_TAG, |i| i.is_u128()) } - fn is_i64_newtype(&self, expected_tag: &str) -> bool { + fn is_newtype_of(&self, expected_tag: &str, of: AlgebraicType) -> bool { match &*self.elements { [ProductTypeElement { name: Some(name), - algebraic_type: AlgebraicType::I64, - }] => &**name == expected_tag, + algebraic_type, + }] => &**name == expected_tag && *algebraic_type == of, _ => false, } } + fn is_i64_newtype(&self, expected_tag: &str) -> bool { + self.is_newtype_of(expected_tag, AlgebraicType::I64) + } + /// Returns whether this is the special case of `spacetimedb_lib::Timestamp`. /// Does not follow `Ref`s. pub fn is_timestamp(&self) -> bool { @@ -152,16 +159,38 @@ impl ProductType { tag_name == TIME_DURATION_TAG } - /// Returns whether this is a special known `tag`, - /// currently `Address`, `Identity`, `Timestamp` or `TimeDuration`. - pub fn is_special_tag(tag_name: &str) -> bool { - [IDENTITY_TAG, CONNECTION_ID_TAG, TIMESTAMP_TAG, TIME_DURATION_TAG].contains(&tag_name) + /// Returns whether this is the special tag of [`crate::uuid::Uuid`]. + pub fn is_uuid_tag(tag_name: &str) -> bool { + tag_name == UUID_TAG + } + + /// Returns whether this is the special case of [`crate::uuid::Uuid`]. + pub fn is_uuid(&self) -> bool { + self.is_newtype_of(UUID_TAG, AlgebraicType::U128) } - /// Returns whether this is a special known type, currently `ConnectionId` or `Identity`. + /// Returns whether this is a special known `tag`, + /// currently `Address`, `Identity`, `Timestamp`, `TimeDuration`, `ConnectionId` or `UUID`. + pub fn is_special_tag(tag_name: &str) -> bool { + [ + IDENTITY_TAG, + CONNECTION_ID_TAG, + TIMESTAMP_TAG, + TIME_DURATION_TAG, + UUID_TAG, + ] + .contains(&tag_name) + } + + /// Returns whether this is a special known type, + /// currently `Identity`, `Timestamp`, `TimeDuration`, `ConnectionId` or `UUID`. /// Does not follow `Ref`s. pub fn is_special(&self) -> bool { - self.is_identity() || self.is_connection_id() || self.is_timestamp() || self.is_time_duration() + self.is_identity() + || self.is_connection_id() + || self.is_timestamp() + || self.is_time_duration() + || self.is_uuid() } /// Returns whether this is a unit type, that is, has no elements. diff --git a/crates/sats/src/satn.rs b/crates/sats/src/satn.rs index 2a5f128262e..575edf230e5 100644 --- a/crates/sats/src/satn.rs +++ b/crates/sats/src/satn.rs @@ -1,5 +1,6 @@ use crate::time_duration::TimeDuration; use crate::timestamp::Timestamp; +use crate::uuid::Uuid; use crate::{i256, u256, AlgebraicType, AlgebraicValue, ProductValue, Serialize, SumValue, ValueWithType}; use crate::{ser, ProductType, ProductTypeElement}; use core::fmt; @@ -485,6 +486,8 @@ pub enum PsqlPrintFmt { Timestamp, /// Print as [`TimeDuration`] format Duration, + /// Print as `UUID` format + Uuid, /// Print as `Satn` format Satn, } @@ -521,6 +524,10 @@ impl PsqlPrintFmt { return PsqlPrintFmt::Duration; }; + if tuple.is_uuid() || field.algebraic_type.is_uuid() || name.map(ProductType::is_uuid_tag).unwrap_or_default() { + return PsqlPrintFmt::Uuid; + }; + PsqlPrintFmt::Satn } } @@ -571,6 +578,7 @@ pub trait TypedWriter { fn write_hex(&mut self, value: &[u8]) -> Result<(), Self::Error>; fn write_timestamp(&mut self, value: Timestamp) -> Result<(), Self::Error>; fn write_duration(&mut self, value: TimeDuration) -> Result<(), Self::Error>; + fn write_uuid(&mut self, value: Uuid) -> Result<(), Self::Error>; /// Writes a value as an alternative record format, e.g., for use `JSON` inside `SQL`. fn write_alt_record( &mut self, @@ -692,6 +700,7 @@ impl<'a, 'f, F: TypedWriter> ser::Serializer for TypedSerializer<'a, 'f, F> { fn serialize_u128(self, v: u128) -> Result { match self.ty.use_fmt() { PsqlPrintFmt::Hex => self.f.write_hex(&v.to_be_bytes()), + PsqlPrintFmt::Uuid => self.f.write_uuid(Uuid::from_u128(v)), _ => self.f.write(v), } } @@ -875,6 +884,10 @@ impl TypedWriter for SqlFormatter<'_, '_> { } } + fn write_uuid(&mut self, value: Uuid) -> Result<(), Self::Error> { + write!(self.fmt, "\"{value}\"") + } + fn write_record( &mut self, fields: Vec<(Cow, PsqlType<'_>, ValueWithType)>, diff --git a/crates/sats/src/timestamp.rs b/crates/sats/src/timestamp.rs index 50affcf6094..4193e17b4eb 100644 --- a/crates/sats/src/timestamp.rs +++ b/crates/sats/src/timestamp.rs @@ -266,6 +266,43 @@ impl From for AlgebraicValue { } } +/// A generator for monotonically increasing [`Timestamp`]s by millisecond increments. +#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct ClockGenerator { + t: Timestamp, +} + +impl ClockGenerator { + /// Create a new `ClockGenerator` initialized to the given `start` time. + pub fn new(start: Timestamp) -> Self { + Self { t: start } + } + + /// Returns the next [`Timestamp`] in the sequence, guaranteed to be + /// greater than the previous one returned by this method. + /// + /// UUIDv7 requires monotonic millisecond timestamps, so each tick + /// increases the timestamp by at least 1 millisecond (1_000 microseconds). + /// + /// # Panics + /// + /// If the internal timestamp overflows i64 microseconds. + pub fn tick(&mut self) -> Timestamp { + self.t.__timestamp_micros_since_unix_epoch__ = self + .t + .__timestamp_micros_since_unix_epoch__ + .checked_add(1_000) + .expect("ClockGenerator overflowed i64 microseconds"); + self.t + } +} + +impl From for ClockGenerator { + fn from(t: Timestamp) -> Self { + Self::new(t) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/sats/src/uuid.rs b/crates/sats/src/uuid.rs new file mode 100644 index 00000000000..e75ccc37f35 --- /dev/null +++ b/crates/sats/src/uuid.rs @@ -0,0 +1,216 @@ +use crate::timestamp::ClockGenerator; +use crate::{de::Deserialize, impl_st, ser::Serialize, AlgebraicType, AlgebraicValue}; +use std::fmt; +use uuid::{Builder, Uuid as UUID}; + +#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +#[sats(crate = crate)] +pub struct Uuid { + __uuid__: u128, +} + +impl Uuid { + /// The nil UUID (all zeros). + /// + /// Example: + /// + /// ``` + /// # use spacetimedb_sats::uuid::Uuid; + /// let uuid = Uuid::NIL; + /// + /// assert_eq!( + /// "00000000-0000-0000-0000-000000000000", + /// uuid.to_string(), + /// ); + /// ``` + pub const NIL: Self = Self { + __uuid__: UUID::nil().as_u128(), + }; + + /// Create a UUIDv4 from explicit random bytes. + /// + /// This method assumes the bytes are already sufficiently random, it will only + /// set the appropriate bits for the UUID version and variant. + /// + /// # Example + /// ``` + /// # use spacetimedb_sats::uuid::Uuid; + /// // Use the `ReducerContext::rng()` method to generate random bytes in reducers, + /// // or call `ReducerContext::new_uuid_v4` + /// let random_bytes = [0u8; 16]; + /// let uuid = Uuid::from_random_bytes_v4(random_bytes); + /// + /// assert_eq!( + /// "00000000-0000-4000-8000-000000000000", + /// uuid.to_string(), + /// ); + /// ``` + pub fn from_random_bytes_v4(counter_random_bytes: [u8; 16]) -> Self { + Self { + __uuid__: Builder::from_random_bytes(counter_random_bytes).into_uuid().as_u128(), + } + } + + /// Create a UUIDv7 from a UNIX timestamp (milliseconds) and 10 random bytes. + /// + /// This method will set the variant field within the counter bytes without attempting to shift + /// the data around it. Callers using the counter as a monotonic value should be careful not to + /// store significant data in the 2 least significant bits of the 3rd byte. + /// + /// # Example + /// + /// ``` + /// # use spacetimedb_sats::uuid::Uuid; + /// let millis = 1_686_000_000_000u64; + /// // Use the `ReducerContext::rng()` method to generate random bytes in reducers, + /// // or call `ReducerContext::new_uuid_v7` + /// let random_bytes = [0u8; 10]; + /// let uuid = Uuid::from_unix_millis_v7(millis, &random_bytes); + /// + /// assert_eq!( + /// "01888d6e-5c00-7000-8000-000000000000", + /// uuid.to_string(), + /// ); + /// ``` + pub fn from_unix_millis_v7(millis: u64, counter_random_bytes: &[u8; 10]) -> Self { + Self { + __uuid__: Builder::from_unix_timestamp_millis(millis, counter_random_bytes) + .into_uuid() + .as_u128(), + } + } + + /// Generate a UUIDv7 using a monotonic clock generator. + /// + /// This method will set the variant field within the counter bytes without attempting to shift + /// the data around it. Callers using the counter as a monotonic value should be careful not to + /// store significant data in the 2 least significant bits of the 3rd byte. + /// + /// # Example + /// ``` + /// # use spacetimedb_sats::uuid::Uuid; + /// # use spacetimedb_sats::timestamp::{Timestamp, ClockGenerator}; + /// let mut clock = ClockGenerator::new(Timestamp::from_micros_since_unix_epoch(1_686_000_000_000)); + /// // Use the `ReducerContext::rng()` method to generate random bytes in reducers, + /// // or call `ReducerContext::new_uuid_v7` + /// let random_bytes = [0u8; 10]; + /// let uuid = Uuid::from_clock_v7(&mut clock, &random_bytes).unwrap(); + /// + /// assert_eq!( + /// "0000647e-5181-7000-8000-000000000000", + /// uuid.to_string(), + /// ); + /// ``` + pub fn from_clock_v7(clock: &mut ClockGenerator, counter_random_bytes: &[u8; 10]) -> anyhow::Result { + let timestamp = clock.tick(); + let millis = timestamp + .to_duration_since_unix_epoch() + .map_err(|err| anyhow::anyhow!("cannot create v7 UUID from timestamp before Unix epoch: {err:?}"))? + .as_millis() + .try_into()?; + Ok(Uuid::from_unix_millis_v7(millis, counter_random_bytes)) + } + + /// Parse a UUID from a string representation. + /// + /// Any of the formats generated by this module (simple, hyphenated, urn, + /// Microsoft GUID) are supported by this parsing function. + /// + /// # Example + /// ``` + /// # use spacetimedb_sats::uuid::Uuid; + /// let s = "01888d6e-5c00-7000-8000-000000000000"; + /// let uuid = Uuid::parse_str(s).unwrap(); + /// + /// assert_eq!( + /// s, + /// uuid.to_string(), + /// ); + /// ``` + pub fn parse_str(s: &str) -> Result { + Ok(Self { + __uuid__: UUID::parse_str(s)?.as_u128(), + }) + } + + /// Convert to the `uuid` crate's `Uuid` type. + pub fn to_uuid(self) -> UUID { + UUID::from_u128(self.__uuid__) + } + + pub fn from_u128(u: u128) -> Self { + Self { __uuid__: u } + } + + pub fn as_u128(&self) -> u128 { + self.__uuid__ + } +} + +impl_st!([] Uuid, AlgebraicType::uuid()); + +impl fmt::Display for Uuid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_uuid()) + } +} + +impl From for AlgebraicValue { + fn from(value: Uuid) -> Self { + AlgebraicValue::product([value.as_u128().into()]) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::timestamp::Timestamp; + use crate::GroundSpacetimeType; + use rand::RngCore; + + #[test] + fn uuid_type_matches() { + assert_eq!(AlgebraicType::uuid(), Uuid::get_type()); + assert!(Uuid::get_type().is_uuid()); + assert!(Uuid::get_type().is_special()); + } + + #[test] + fn round_trip_uuid() { + let u1 = Uuid::NIL; + let s = u1.to_string(); + let u2 = Uuid::parse_str(&s).unwrap(); + assert_eq!(u1, u2); + assert_eq!(u1.as_u128(), u2.as_u128()); + assert_eq!(u1.to_uuid(), u2.to_uuid()); + assert_eq!(s, u2.to_string()); + } + + #[test] + fn ordered_uuids() { + let u1 = Uuid::from_u128(1); + let u2 = Uuid::from_u128(2); + assert!(u1 < u2); + assert!(u2 > u1); + assert_eq!(u1, u1); + assert_ne!(u1, u2); + + let mut clock = ClockGenerator::new(Timestamp::now()); + let uuids = (0..1000) + .map(|_| { + let mut bytes = [0u8; 10]; + rand::rng().fill_bytes(&mut bytes); + Uuid::from_clock_v7(&mut clock, &bytes).unwrap() + }) + .collect::>(); + + for (pos, pair) in uuids.windows(2).enumerate() { + assert!( + pair[0] < pair[1], + "UUIDs are not ordered at {pos}: {} !< {}", + pair[0], + pair[1] + ); + } + } +} diff --git a/crates/schema/src/type_for_generate.rs b/crates/schema/src/type_for_generate.rs index df2eff0446c..3a9dfb6feba 100644 --- a/crates/schema/src/type_for_generate.rs +++ b/crates/schema/src/type_for_generate.rs @@ -284,6 +284,9 @@ pub enum AlgebraicTypeUse { /// The special `TimeDuration` type. TimeDuration, + /// The special `Uuid` type. + Uuid, + /// The unit type (empty product). /// This is *distinct* from a use of a definition of a product type with no elements. Unit, @@ -378,6 +381,8 @@ impl TypespaceForGenerateBuilder<'_> { Ok(AlgebraicTypeUse::Timestamp) } else if ty.is_time_duration() { Ok(AlgebraicTypeUse::TimeDuration) + } else if ty.is_uuid() { + Ok(AlgebraicTypeUse::Uuid) } else if ty.is_unit() { Ok(AlgebraicTypeUse::Unit) } else if ty.is_never() { diff --git a/modules/benchmarks-cs/benchmarks-cs.sln b/modules/benchmarks-cs/benchmarks-cs.sln new file mode 100644 index 00000000000..6f817cf0276 --- /dev/null +++ b/modules/benchmarks-cs/benchmarks-cs.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "benchmarks-cs", "benchmarks-cs.csproj", "{83530328-431F-4033-44C6-36E2E8C48C0A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {83530328-431F-4033-44C6-36E2E8C48C0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83530328-431F-4033-44C6-36E2E8C48C0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83530328-431F-4033-44C6-36E2E8C48C0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83530328-431F-4033-44C6-36E2E8C48C0A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D918C41F-7885-45C9-A47D-87FA1B7EC25A} + EndGlobalSection +EndGlobal diff --git a/modules/sdk-test-cs/Lib.cs b/modules/sdk-test-cs/Lib.cs index 7ece58f476a..0a38827c414 100644 --- a/modules/sdk-test-cs/Lib.cs +++ b/modules/sdk-test-cs/Lib.cs @@ -36,6 +36,7 @@ public partial record EnumWithPayload Identity Identity, ConnectionId ConnectionId, Timestamp Timestamp, + Uuid Uuid, List Bytes, List Ints, List Strings, @@ -74,6 +75,7 @@ public partial struct EveryPrimitiveStruct public ConnectionId r; public Timestamp s; public TimeDuration t; + public Uuid u; } [SpacetimeDB.Type] @@ -99,6 +101,7 @@ public partial struct EveryVecStruct public List r; public List s; public List t; + public List u; } [SpacetimeDB.Table(Name = "one_u8", Public = true)] @@ -317,6 +320,18 @@ public static void insert_one_connection_id(ReducerContext ctx, ConnectionId a) ctx.Db.one_connection_id.Insert(new OneConnectionId { a = a }); } + [SpacetimeDB.Table(Name = "one_uuid", Public = true)] + public partial struct OneUuid + { + public Uuid u; + } + + [SpacetimeDB.Reducer] + public static void insert_one_uuid(ReducerContext ctx, Uuid u) + { + ctx.Db.one_uuid.Insert(new OneUuid { u = u }); + } + [SpacetimeDB.Table(Name = "one_timestamp", Public = true)] public partial struct OneTimestamp { @@ -617,6 +632,18 @@ public static void insert_vec_connection_id(ReducerContext ctx, List u; + } + + [SpacetimeDB.Reducer] + public static void insert_vec_uuid(ReducerContext ctx, List u) + { + ctx.Db.vec_uuid.Insert(new VecUuid { u = u }); + } + [SpacetimeDB.Table(Name = "vec_timestamp", Public = true)] public partial struct VecTimestamp { @@ -740,6 +767,18 @@ public static void insert_option_identity(ReducerContext ctx, Identity? i) ctx.Db.option_identity.Insert(new OptionIdentity { i = i }); } + [SpacetimeDB.Table(Name = "option_uuid", Public = true)] + public partial struct OptionUuid + { + public Uuid? u; + } + + [SpacetimeDB.Reducer] + public static void insert_option_uuid(ReducerContext ctx, Uuid? u) + { + ctx.Db.option_uuid.Insert(new OptionUuid { u = u }); + } + [SpacetimeDB.Table(Name = "option_simple_enum", Public = true)] public partial struct OptionSimpleEnum { @@ -1218,6 +1257,33 @@ public static void delete_unique_connection_id(ReducerContext ctx, ConnectionId ctx.Db.unique_connection_id.a.Delete(a); } + [SpacetimeDB.Table(Name = "unique_uuid", Public = true)] + public partial struct UniqueUuid + { + [SpacetimeDB.Unique] + public Uuid u; + public int data; + } + + [SpacetimeDB.Reducer] + public static void insert_unique_uuid(ReducerContext ctx, Uuid u, int data) + { + ctx.Db.unique_uuid.Insert(new UniqueUuid { u = u, data = data }); + } + + [SpacetimeDB.Reducer] + public static void update_unique_uuid(ReducerContext ctx, Uuid u, int data) + { + var key = u; + ctx.Db.unique_uuid.u.Update(new UniqueUuid { u = u, data = data }); + } + + [SpacetimeDB.Reducer] + public static void delete_unique_uuid(ReducerContext ctx, Uuid u) + { + ctx.Db.unique_uuid.u.Delete(u); + } + [SpacetimeDB.Table(Name = "pk_u8", Public = true)] public partial struct PkU8 { @@ -1684,6 +1750,33 @@ public static void delete_pk_connection_id(ReducerContext ctx, ConnectionId a) ctx.Db.pk_connection_id.a.Delete(a); } + [SpacetimeDB.Table(Name = "pk_uuid", Public = true)] + public partial struct PkUuid + { + [SpacetimeDB.PrimaryKey] + public Uuid u; + public int data; + } + + [SpacetimeDB.Reducer] + public static void insert_pk_uuid(ReducerContext ctx, Uuid u, int data) + { + ctx.Db.pk_uuid.Insert(new PkUuid { u = u, data = data }); + } + + [SpacetimeDB.Reducer] + public static void update_pk_uuid(ReducerContext ctx, Uuid u, int data) + { + var key = u; + ctx.Db.pk_uuid.u.Update(new PkUuid { u = u, data = data }); + } + + [SpacetimeDB.Reducer] + public static void delete_pk_uuid(ReducerContext ctx, Uuid u) + { + ctx.Db.pk_uuid.u.Delete(u); + } + [SpacetimeDB.Table(Name = "pk_simple_enum", Public = true)] public partial struct PkSimpleEnum { @@ -1963,6 +2056,19 @@ public static void insert_call_timestamp(ReducerContext ctx) ctx.Db.one_timestamp.Insert(new OneTimestamp { t = ctx.Timestamp }); } + [SpacetimeDB.Reducer] + public static void insert_call_uuid_v4(ReducerContext ctx) + { + ctx.Db.one_uuid.Insert(new OneUuid { u = ctx.NewUuidV4() }); + } + + [SpacetimeDB.Reducer] + public static void insert_call_uuid_v7(ReducerContext ctx) + { + var clock = new ClockGenerator(ctx.Timestamp); + ctx.Db.one_uuid.Insert(new OneUuid { u = ctx.NewUuidV7(clock) }); + } + [SpacetimeDB.Table(Name = "table_holds_table", Public = true)] public partial struct TableHoldsTable { @@ -2069,4 +2175,28 @@ public static void update_indexed_simple_enum(ReducerContext ctx, SimpleEnum a, ctx.Db.indexed_simple_enum.Insert(new IndexedSimpleEnum { n = b }); } } + + [SpacetimeDB.Reducer] + public static void sorted_uuids_insert(ReducerContext ctx) + { + var clock = new ClockGenerator(ctx.Timestamp); + + for (int i = 0; i < 1000; i++) + { + var uuid = ctx.NewUuidV7(clock); + + ctx.Db.pk_uuid.Insert(new PkUuid { u = uuid, data = 0 }); + } + + // Verify UUIDs are sorted + Uuid? lastUuid = null; + foreach (var row in ctx.Db.pk_uuid.Iter()) + { + if (lastUuid.HasValue && lastUuid.Value.CompareTo(row.u) >= 0) + { + throw new Exception("UUIDs are not sorted correctly"); + } + lastUuid = row.u; + }; + } } diff --git a/modules/sdk-test-cs/sdk-test-cs.sln b/modules/sdk-test-cs/sdk-test-cs.sln new file mode 100644 index 00000000000..66673ac1f36 --- /dev/null +++ b/modules/sdk-test-cs/sdk-test-cs.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sdk-test-cs", "sdk-test-cs.csproj", "{A8BDE7A4-453D-5B7A-BB1B-A86E6E17160F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A8BDE7A4-453D-5B7A-BB1B-A86E6E17160F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8BDE7A4-453D-5B7A-BB1B-A86E6E17160F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8BDE7A4-453D-5B7A-BB1B-A86E6E17160F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8BDE7A4-453D-5B7A-BB1B-A86E6E17160F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3936D96B-738F-45B8-8180-6A027AE35A07} + EndGlobalSection +EndGlobal diff --git a/modules/sdk-test-ts/src/index.ts b/modules/sdk-test-ts/src/index.ts index 2dcb9de8c94..9d912dee62e 100644 --- a/modules/sdk-test-ts/src/index.ts +++ b/modules/sdk-test-ts/src/index.ts @@ -1,7 +1,8 @@ // ───────────────────────────────────────────────────────────────────────────── // IMPORTS // ───────────────────────────────────────────────────────────────────────────── -import { type RowObj, schema, t, table } from 'spacetimedb/server'; +import {ClockGenerator, Uuid} from 'spacetimedb'; +import {type RowObj, schema, t, table} from 'spacetimedb/server'; const SimpleEnum = t.enum('SimpleEnum', { Zero: t.unit(), @@ -29,6 +30,7 @@ const EnumWithPayload = t.enum('EnumWithPayload', { Identity: t.identity(), ConnectionId: t.connectionId(), Timestamp: t.timestamp(), + Uuid: t.uuid(), Bytes: t.array(t.u8()), Ints: t.array(t.i32()), Strings: t.array(t.string()), @@ -64,6 +66,7 @@ const EveryPrimitiveStruct = t.object('EveryPrimitiveStruct', { r: t.connectionId(), s: t.timestamp(), t: t.timeDuration(), + u: t.uuid(), }); const EveryVecStruct = t.object('EveryVecStruct', { @@ -87,6 +90,7 @@ const EveryVecStruct = t.object('EveryVecStruct', { r: t.array(t.connectionId()), s: t.array(t.timestamp()), t: t.array(t.timeDuration()), + u: t.array(t.uuid()), }); type TableSchema = ReturnType>; @@ -108,34 +112,34 @@ function tbl( }, row: Row ): TableWithReducers>> { - const t = table({ name, public: true }, row); + const t = table({name, public: true}, row); return { table: t, reducers(spacetimedb) { if (ops.insert) { spacetimedb.reducer(ops.insert, row, (ctx, args) => { - (ctx.db[name] as any).insert({ ...args }); + (ctx.db[name] as any).insert({...args}); }); } if (ops.delete) { spacetimedb.reducer(ops.delete, row, (ctx, args) => { - (ctx.db[name] as any).delete({ ...args }); + (ctx.db[name] as any).delete({...args}); }); } if (ops.insert_or_panic) { spacetimedb.reducer(ops.insert_or_panic, row, (ctx, args) => { - (ctx.db[name] as any).insert({ ...args }); + (ctx.db[name] as any).insert({...args}); }); } if (ops.update_by) { const [reducer, col] = ops.update_by; spacetimedb.reducer(reducer, row, (ctx, args) => { - (ctx.db[name] as any)[col].update({ ...args }); + (ctx.db[name] as any)[col].update({...args}); }); } if (ops.delete_by) { const [reducer, col] = ops.delete_by; - spacetimedb.reducer(reducer, { [col]: row[col] }, (ctx, args) => { + spacetimedb.reducer(reducer, {[col]: row[col]}, (ctx, args) => { (ctx.db[name] as any)[col].delete(args[col as any]); }); } @@ -145,177 +149,194 @@ function tbl( // Tables holding a single value. const singleValTables = [ - tbl('one_u8', { insert: 'insert_one_u8' }, { n: t.u8() }), - tbl('one_u16', { insert: 'insert_one_u16' }, { n: t.u16() }), - tbl('one_u32', { insert: 'insert_one_u32' }, { n: t.u32() }), - tbl('one_u64', { insert: 'insert_one_u64' }, { n: t.u64() }), - tbl('one_u128', { insert: 'insert_one_u128' }, { n: t.u128() }), - tbl('one_u256', { insert: 'insert_one_u256' }, { n: t.u256() }), + tbl('one_u8', {insert: 'insert_one_u8'}, {n: t.u8()}), + tbl('one_u16', {insert: 'insert_one_u16'}, {n: t.u16()}), + tbl('one_u32', {insert: 'insert_one_u32'}, {n: t.u32()}), + tbl('one_u64', {insert: 'insert_one_u64'}, {n: t.u64()}), + tbl('one_u128', {insert: 'insert_one_u128'}, {n: t.u128()}), + tbl('one_u256', {insert: 'insert_one_u256'}, {n: t.u256()}), - tbl('one_i8', { insert: 'insert_one_i8' }, { n: t.i8() }), - tbl('one_i16', { insert: 'insert_one_i16' }, { n: t.i16() }), - tbl('one_i32', { insert: 'insert_one_i32' }, { n: t.i32() }), - tbl('one_i64', { insert: 'insert_one_i64' }, { n: t.i64() }), - tbl('one_i128', { insert: 'insert_one_i128' }, { n: t.i128() }), - tbl('one_i256', { insert: 'insert_one_i256' }, { n: t.i256() }), + tbl('one_i8', {insert: 'insert_one_i8'}, {n: t.i8()}), + tbl('one_i16', {insert: 'insert_one_i16'}, {n: t.i16()}), + tbl('one_i32', {insert: 'insert_one_i32'}, {n: t.i32()}), + tbl('one_i64', {insert: 'insert_one_i64'}, {n: t.i64()}), + tbl('one_i128', {insert: 'insert_one_i128'}, {n: t.i128()}), + tbl('one_i256', {insert: 'insert_one_i256'}, {n: t.i256()}), - tbl('one_bool', { insert: 'insert_one_bool' }, { b: t.bool() }), + tbl('one_bool', {insert: 'insert_one_bool'}, {b: t.bool()}), - tbl('one_f32', { insert: 'insert_one_f32' }, { f: t.f32() }), - tbl('one_f64', { insert: 'insert_one_f64' }, { f: t.f64() }), + tbl('one_f32', {insert: 'insert_one_f32'}, {f: t.f32()}), + tbl('one_f64', {insert: 'insert_one_f64'}, {f: t.f64()}), - tbl('one_string', { insert: 'insert_one_string' }, { s: t.string() }), + tbl('one_string', {insert: 'insert_one_string'}, {s: t.string()}), - tbl('one_identity', { insert: 'insert_one_identity' }, { i: t.identity() }), + tbl('one_identity', {insert: 'insert_one_identity'}, {i: t.identity()}), tbl( 'one_connection_id', - { insert: 'insert_one_connection_id' }, - { a: t.connectionId() } + {insert: 'insert_one_connection_id'}, + {a: t.connectionId()} + ), + + tbl( + 'one_uuid', + {insert: 'insert_one_uuid'}, + {u: t.uuid()} ), tbl( 'one_timestamp', - { insert: 'insert_one_timestamp' }, - { t: t.timestamp() } + {insert: 'insert_one_timestamp'}, + {t: t.timestamp()} ), tbl( 'one_simple_enum', - { insert: 'insert_one_simple_enum' }, - { e: SimpleEnum } + {insert: 'insert_one_simple_enum'}, + {e: SimpleEnum} ), tbl( 'one_enum_with_payload', - { insert: 'insert_one_enum_with_payload' }, - { e: EnumWithPayload } + {insert: 'insert_one_enum_with_payload'}, + {e: EnumWithPayload} ), tbl( 'one_unit_struct', - { insert: 'insert_one_unit_struct' }, - { s: UnitStruct } + {insert: 'insert_one_unit_struct'}, + {s: UnitStruct} ), tbl( 'one_byte_struct', - { insert: 'insert_one_byte_struct' }, - { s: ByteStruct } + {insert: 'insert_one_byte_struct'}, + {s: ByteStruct} ), tbl( 'one_every_primitive_struct', - { insert: 'insert_one_every_primitive_struct' }, - { s: EveryPrimitiveStruct } + {insert: 'insert_one_every_primitive_struct'}, + {s: EveryPrimitiveStruct} ), tbl( 'one_every_vec_struct', - { insert: 'insert_one_every_vec_struct' }, - { s: EveryVecStruct } + {insert: 'insert_one_every_vec_struct'}, + {s: EveryVecStruct} ), ] as const; // Tables holding a Vec of various types. const vecTables = [ - tbl('vec_u8', { insert: 'insert_vec_u8' }, { n: t.array(t.u8()) }), - tbl('vec_u16', { insert: 'insert_vec_u16' }, { n: t.array(t.u16()) }), - tbl('vec_u32', { insert: 'insert_vec_u32' }, { n: t.array(t.u32()) }), - tbl('vec_u64', { insert: 'insert_vec_u64' }, { n: t.array(t.u64()) }), - tbl('vec_u128', { insert: 'insert_vec_u128' }, { n: t.array(t.u128()) }), - tbl('vec_u256', { insert: 'insert_vec_u256' }, { n: t.array(t.u256()) }), + tbl('vec_u8', {insert: 'insert_vec_u8'}, {n: t.array(t.u8())}), + tbl('vec_u16', {insert: 'insert_vec_u16'}, {n: t.array(t.u16())}), + tbl('vec_u32', {insert: 'insert_vec_u32'}, {n: t.array(t.u32())}), + tbl('vec_u64', {insert: 'insert_vec_u64'}, {n: t.array(t.u64())}), + tbl('vec_u128', {insert: 'insert_vec_u128'}, {n: t.array(t.u128())}), + tbl('vec_u256', {insert: 'insert_vec_u256'}, {n: t.array(t.u256())}), - tbl('vec_i8', { insert: 'insert_vec_i8' }, { n: t.array(t.i8()) }), - tbl('vec_i16', { insert: 'insert_vec_i16' }, { n: t.array(t.i16()) }), - tbl('vec_i32', { insert: 'insert_vec_i32' }, { n: t.array(t.i32()) }), - tbl('vec_i64', { insert: 'insert_vec_i64' }, { n: t.array(t.i64()) }), - tbl('vec_i128', { insert: 'insert_vec_i128' }, { n: t.array(t.i128()) }), - tbl('vec_i256', { insert: 'insert_vec_i256' }, { n: t.array(t.i256()) }), + tbl('vec_i8', {insert: 'insert_vec_i8'}, {n: t.array(t.i8())}), + tbl('vec_i16', {insert: 'insert_vec_i16'}, {n: t.array(t.i16())}), + tbl('vec_i32', {insert: 'insert_vec_i32'}, {n: t.array(t.i32())}), + tbl('vec_i64', {insert: 'insert_vec_i64'}, {n: t.array(t.i64())}), + tbl('vec_i128', {insert: 'insert_vec_i128'}, {n: t.array(t.i128())}), + tbl('vec_i256', {insert: 'insert_vec_i256'}, {n: t.array(t.i256())}), - tbl('vec_bool', { insert: 'insert_vec_bool' }, { b: t.array(t.bool()) }), + tbl('vec_bool', {insert: 'insert_vec_bool'}, {b: t.array(t.bool())}), - tbl('vec_f32', { insert: 'insert_vec_f32' }, { f: t.array(t.f32()) }), - tbl('vec_f64', { insert: 'insert_vec_f64' }, { f: t.array(t.f64()) }), + tbl('vec_f32', {insert: 'insert_vec_f32'}, {f: t.array(t.f32())}), + tbl('vec_f64', {insert: 'insert_vec_f64'}, {f: t.array(t.f64())}), tbl( 'vec_string', - { insert: 'insert_vec_string' }, - { s: t.array(t.string()) } + {insert: 'insert_vec_string'}, + {s: t.array(t.string())} ), tbl( 'vec_identity', - { insert: 'insert_vec_identity' }, - { i: t.array(t.identity()) } + {insert: 'insert_vec_identity'}, + {i: t.array(t.identity())} ), tbl( 'vec_connection_id', - { insert: 'insert_vec_connection_id' }, - { a: t.array(t.connectionId()) } + {insert: 'insert_vec_connection_id'}, + {a: t.array(t.connectionId())} ), tbl( 'vec_timestamp', - { insert: 'insert_vec_timestamp' }, - { t: t.array(t.timestamp()) } + {insert: 'insert_vec_timestamp'}, + {t: t.array(t.timestamp())} + ), + + tbl( + 'vec_uuid', + {insert: 'insert_vec_uuid'}, + {u: t.array(t.uuid())} ), tbl( 'vec_simple_enum', - { insert: 'insert_vec_simple_enum' }, - { e: t.array(SimpleEnum) } + {insert: 'insert_vec_simple_enum'}, + {e: t.array(SimpleEnum)} ), tbl( 'vec_enum_with_payload', - { insert: 'insert_vec_enum_with_payload' }, - { e: t.array(EnumWithPayload) } + {insert: 'insert_vec_enum_with_payload'}, + {e: t.array(EnumWithPayload)} ), tbl( 'vec_unit_struct', - { insert: 'insert_vec_unit_struct' }, - { s: t.array(UnitStruct) } + {insert: 'insert_vec_unit_struct'}, + {s: t.array(UnitStruct)} ), tbl( 'vec_byte_struct', - { insert: 'insert_vec_byte_struct' }, - { s: t.array(ByteStruct) } + {insert: 'insert_vec_byte_struct'}, + {s: t.array(ByteStruct)} ), tbl( 'vec_every_primitive_struct', - { insert: 'insert_vec_every_primitive_struct' }, - { s: t.array(EveryPrimitiveStruct) } + {insert: 'insert_vec_every_primitive_struct'}, + {s: t.array(EveryPrimitiveStruct)} ), tbl( 'vec_every_vec_struct', - { insert: 'insert_vec_every_vec_struct' }, - { s: t.array(EveryVecStruct) } + {insert: 'insert_vec_every_vec_struct'}, + {s: t.array(EveryVecStruct)} ), ] as const; // Tables holding an Option of various types. const optionTables = [ - tbl('option_i32', { insert: 'insert_option_i32' }, { n: t.option(t.i32()) }), + tbl('option_i32', {insert: 'insert_option_i32'}, {n: t.option(t.i32())}), tbl( 'option_string', - { insert: 'insert_option_string' }, - { s: t.option(t.string()) } + {insert: 'insert_option_string'}, + {s: t.option(t.string())} ), tbl( 'option_identity', - { insert: 'insert_option_identity' }, - { i: t.option(t.identity()) } + {insert: 'insert_option_identity'}, + {i: t.option(t.identity())} + ), + tbl( + 'option_uuid', + {insert: 'insert_option_uuid'}, + {u: t.option(t.uuid())} ), tbl( 'option_simple_enum', - { insert: 'insert_option_simple_enum' }, - { e: t.option(SimpleEnum) } + {insert: 'insert_option_simple_enum'}, + {e: t.option(SimpleEnum)} ), tbl( 'option_every_primitive_struct', - { insert: 'insert_option_every_primitive_struct' }, - { s: t.option(EveryPrimitiveStruct) } + {insert: 'insert_option_every_primitive_struct'}, + {s: t.option(EveryPrimitiveStruct)} ), tbl( 'option_vec_option_i32', - { insert: 'insert_option_vec_option_i32' }, - { v: t.option(t.array(t.option(t.i32()))) } + {insert: 'insert_option_vec_option_i32'}, + {v: t.option(t.array(t.option(t.i32())))} ), ] as const; @@ -329,7 +350,7 @@ const uniqueTables = [ update_by: ['update_unique_u8', 'n'], delete_by: ['delete_unique_u8', 'n'], }, - { n: t.u8().unique(), data: t.i32() } + {n: t.u8().unique(), data: t.i32()} ), tbl( @@ -339,7 +360,7 @@ const uniqueTables = [ update_by: ['update_unique_u16', 'n'], delete_by: ['delete_unique_u16', 'n'], }, - { n: t.u16().unique(), data: t.i32() } + {n: t.u16().unique(), data: t.i32()} ), tbl( @@ -349,7 +370,7 @@ const uniqueTables = [ update_by: ['update_unique_u32', 'n'], delete_by: ['delete_unique_u32', 'n'], }, - { n: t.u32().unique(), data: t.i32() } + {n: t.u32().unique(), data: t.i32()} ), tbl( @@ -359,7 +380,7 @@ const uniqueTables = [ update_by: ['update_unique_u64', 'n'], delete_by: ['delete_unique_u64', 'n'], }, - { n: t.u64().unique(), data: t.i32() } + {n: t.u64().unique(), data: t.i32()} ), tbl( @@ -369,7 +390,7 @@ const uniqueTables = [ update_by: ['update_unique_u128', 'n'], delete_by: ['delete_unique_u128', 'n'], }, - { n: t.u128().unique(), data: t.i32() } + {n: t.u128().unique(), data: t.i32()} ), tbl( @@ -379,7 +400,7 @@ const uniqueTables = [ update_by: ['update_unique_u256', 'n'], delete_by: ['delete_unique_u256', 'n'], }, - { n: t.u256().unique(), data: t.i32() } + {n: t.u256().unique(), data: t.i32()} ), tbl( @@ -389,7 +410,7 @@ const uniqueTables = [ update_by: ['update_unique_i8', 'n'], delete_by: ['delete_unique_i8', 'n'], }, - { n: t.i8().unique(), data: t.i32() } + {n: t.i8().unique(), data: t.i32()} ), tbl( @@ -399,7 +420,7 @@ const uniqueTables = [ update_by: ['update_unique_i16', 'n'], delete_by: ['delete_unique_i16', 'n'], }, - { n: t.i16().unique(), data: t.i32() } + {n: t.i16().unique(), data: t.i32()} ), tbl( @@ -409,7 +430,7 @@ const uniqueTables = [ update_by: ['update_unique_i32', 'n'], delete_by: ['delete_unique_i32', 'n'], }, - { n: t.i32().unique(), data: t.i32() } + {n: t.i32().unique(), data: t.i32()} ), tbl( @@ -419,7 +440,7 @@ const uniqueTables = [ update_by: ['update_unique_i64', 'n'], delete_by: ['delete_unique_i64', 'n'], }, - { n: t.i64().unique(), data: t.i32() } + {n: t.i64().unique(), data: t.i32()} ), tbl( @@ -429,7 +450,7 @@ const uniqueTables = [ update_by: ['update_unique_i128', 'n'], delete_by: ['delete_unique_i128', 'n'], }, - { n: t.i128().unique(), data: t.i32() } + {n: t.i128().unique(), data: t.i32()} ), tbl( @@ -439,7 +460,7 @@ const uniqueTables = [ update_by: ['update_unique_i256', 'n'], delete_by: ['delete_unique_i256', 'n'], }, - { n: t.i256().unique(), data: t.i32() } + {n: t.i256().unique(), data: t.i32()} ), tbl( @@ -449,7 +470,7 @@ const uniqueTables = [ update_by: ['update_unique_bool', 'b'], delete_by: ['delete_unique_bool', 'b'], }, - { b: t.bool().unique(), data: t.i32() } + {b: t.bool().unique(), data: t.i32()} ), tbl( @@ -459,7 +480,7 @@ const uniqueTables = [ update_by: ['update_unique_string', 's'], delete_by: ['delete_unique_string', 's'], }, - { s: t.string().unique(), data: t.i32() } + {s: t.string().unique(), data: t.i32()} ), tbl( @@ -469,7 +490,7 @@ const uniqueTables = [ update_by: ['update_unique_identity', 'i'], delete_by: ['delete_unique_identity', 'i'], }, - { i: t.identity().unique(), data: t.i32() } + {i: t.identity().unique(), data: t.i32()} ), tbl( @@ -479,7 +500,17 @@ const uniqueTables = [ update_by: ['update_unique_connection_id', 'a'], delete_by: ['delete_unique_connection_id', 'a'], }, - { a: t.connectionId().unique(), data: t.i32() } + {a: t.connectionId().unique(), data: t.i32()} + ), + + tbl( + 'unique_uuid', + { + insert_or_panic: 'insert_unique_uuid', + update_by: ['update_unique_uuid', 'u'], + delete_by: ['delete_unique_uuid', 'u'], + }, + {u: t.uuid().unique(), data: t.i32()} ), ] as const; @@ -493,7 +524,7 @@ const pkTables = [ update_by: ['update_pk_u8', 'n'], delete_by: ['delete_pk_u8', 'n'], }, - { n: t.u8().primaryKey(), data: t.i32() } + {n: t.u8().primaryKey(), data: t.i32()} ), tbl( @@ -503,7 +534,7 @@ const pkTables = [ update_by: ['update_pk_u16', 'n'], delete_by: ['delete_pk_u16', 'n'], }, - { n: t.u16().primaryKey(), data: t.i32() } + {n: t.u16().primaryKey(), data: t.i32()} ), tbl( @@ -513,7 +544,7 @@ const pkTables = [ update_by: ['update_pk_u32', 'n'], delete_by: ['delete_pk_u32', 'n'], }, - { n: t.u32().primaryKey(), data: t.i32() } + {n: t.u32().primaryKey(), data: t.i32()} ), tbl( @@ -523,7 +554,7 @@ const pkTables = [ update_by: ['update_pk_u32_two', 'n'], delete_by: ['delete_pk_u32_two', 'n'], }, - { n: t.u32().primaryKey(), data: t.i32() } + {n: t.u32().primaryKey(), data: t.i32()} ), tbl( @@ -533,7 +564,7 @@ const pkTables = [ update_by: ['update_pk_u64', 'n'], delete_by: ['delete_pk_u64', 'n'], }, - { n: t.u64().primaryKey(), data: t.i32() } + {n: t.u64().primaryKey(), data: t.i32()} ), tbl( @@ -543,7 +574,7 @@ const pkTables = [ update_by: ['update_pk_u128', 'n'], delete_by: ['delete_pk_u128', 'n'], }, - { n: t.u128().primaryKey(), data: t.i32() } + {n: t.u128().primaryKey(), data: t.i32()} ), tbl( @@ -553,7 +584,7 @@ const pkTables = [ update_by: ['update_pk_u256', 'n'], delete_by: ['delete_pk_u256', 'n'], }, - { n: t.u256().primaryKey(), data: t.i32() } + {n: t.u256().primaryKey(), data: t.i32()} ), tbl( @@ -563,7 +594,7 @@ const pkTables = [ update_by: ['update_pk_i8', 'n'], delete_by: ['delete_pk_i8', 'n'], }, - { n: t.i8().primaryKey(), data: t.i32() } + {n: t.i8().primaryKey(), data: t.i32()} ), tbl( @@ -573,7 +604,7 @@ const pkTables = [ update_by: ['update_pk_i16', 'n'], delete_by: ['delete_pk_i16', 'n'], }, - { n: t.i16().primaryKey(), data: t.i32() } + {n: t.i16().primaryKey(), data: t.i32()} ), tbl( @@ -583,7 +614,7 @@ const pkTables = [ update_by: ['update_pk_i32', 'n'], delete_by: ['delete_pk_i32', 'n'], }, - { n: t.i32().primaryKey(), data: t.i32() } + {n: t.i32().primaryKey(), data: t.i32()} ), tbl( @@ -593,7 +624,7 @@ const pkTables = [ update_by: ['update_pk_i64', 'n'], delete_by: ['delete_pk_i64', 'n'], }, - { n: t.i64().primaryKey(), data: t.i32() } + {n: t.i64().primaryKey(), data: t.i32()} ), tbl( @@ -603,7 +634,7 @@ const pkTables = [ update_by: ['update_pk_i128', 'n'], delete_by: ['delete_pk_i128', 'n'], }, - { n: t.i128().primaryKey(), data: t.i32() } + {n: t.i128().primaryKey(), data: t.i32()} ), tbl( @@ -613,7 +644,7 @@ const pkTables = [ update_by: ['update_pk_i256', 'n'], delete_by: ['delete_pk_i256', 'n'], }, - { n: t.i256().primaryKey(), data: t.i32() } + {n: t.i256().primaryKey(), data: t.i32()} ), tbl( @@ -623,7 +654,7 @@ const pkTables = [ update_by: ['update_pk_bool', 'b'], delete_by: ['delete_pk_bool', 'b'], }, - { b: t.bool().primaryKey(), data: t.i32() } + {b: t.bool().primaryKey(), data: t.i32()} ), tbl( @@ -633,7 +664,7 @@ const pkTables = [ update_by: ['update_pk_string', 's'], delete_by: ['delete_pk_string', 's'], }, - { s: t.string().primaryKey(), data: t.i32() } + {s: t.string().primaryKey(), data: t.i32()} ), tbl( @@ -643,7 +674,7 @@ const pkTables = [ update_by: ['update_pk_identity', 'i'], delete_by: ['delete_pk_identity', 'i'], }, - { i: t.identity().primaryKey(), data: t.i32() } + {i: t.identity().primaryKey(), data: t.i32()} ), tbl( @@ -653,7 +684,17 @@ const pkTables = [ update_by: ['update_pk_connection_id', 'a'], delete_by: ['delete_pk_connection_id', 'a'], }, - { a: t.connectionId().primaryKey(), data: t.i32() } + {a: t.connectionId().primaryKey(), data: t.i32()} + ), + + tbl( + 'pk_uuid', + { + insert_or_panic: 'insert_pk_uuid', + update_by: ['update_pk_uuid', 'u'], + delete_by: ['delete_pk_uuid', 'u'], + }, + {u: t.uuid().primaryKey(), data: t.i32()} ), tbl( @@ -661,7 +702,7 @@ const pkTables = [ { insert_or_panic: 'insert_pk_simple_enum', }, - { a: SimpleEnum.primaryKey(), data: t.i32() } + {a: SimpleEnum.primaryKey(), data: t.i32()} ), ] as const; @@ -746,8 +787,8 @@ const ScheduledTable = table( ); const IndexedTable = table( - { name: 'indexed_table' }, - { player_id: t.u32().index('btree') } + {name: 'indexed_table'}, + {player_id: t.u32().index('btree')} ); const IndexedTable2 = table( @@ -768,7 +809,7 @@ const IndexedTable2 = table( ); const BTreeU32 = table( - { name: 'btree_u32', public: true }, + {name: 'btree_u32', public: true}, t.row('BTreeU32', { n: t.u32().index('btree'), data: t.i32(), @@ -776,7 +817,7 @@ const BTreeU32 = table( ); const Users = table( - { name: 'users', public: true }, + {name: 'users', public: true}, { identity: t.identity().primaryKey(), name: t.string(), @@ -784,8 +825,8 @@ const Users = table( ); const IndexedSimpleEnum = table( - { name: 'indexed_simple_enum', public: true }, - { n: SimpleEnum.index('btree') } + {name: 'indexed_simple_enum', public: true}, + {n: SimpleEnum.index('btree')} ); const spacetimedb = schema( @@ -798,7 +839,7 @@ const spacetimedb = schema( IndexedSimpleEnum ); -for (const { reducers } of allTables) { +for (const {reducers} of allTables) { reducers(spacetimedb as any); } @@ -808,8 +849,8 @@ spacetimedb.clientVisibilityFilter.sql( spacetimedb.reducer( 'update_pk_simple_enum', - { a: SimpleEnum, data: t.i32() }, - (ctx, { a, data }) => { + {a: SimpleEnum, data: t.i32()}, + (ctx, {a, data}) => { const o = ctx.db.pk_simple_enum.a.find(a); if (o == null) throw new Error('row not found'); o.data = data; @@ -819,8 +860,8 @@ spacetimedb.reducer( spacetimedb.reducer( 'insert_into_btree_u32', - { rows: t.array(BTreeU32.rowType) }, - (ctx, { rows }) => { + {rows: t.array(BTreeU32.rowType)}, + (ctx, {rows}) => { for (const row of rows) { ctx.db.btree_u32.insert(row); } @@ -829,8 +870,8 @@ spacetimedb.reducer( spacetimedb.reducer( 'delete_from_btree_u32', - { rows: t.array(BTreeU32.rowType) }, - (ctx, { rows }) => { + {rows: t.array(BTreeU32.rowType)}, + (ctx, {rows}) => { for (const row of rows) { ctx.db.btree_u32.delete(row); } @@ -839,8 +880,8 @@ spacetimedb.reducer( spacetimedb.reducer( 'insert_into_pk_btree_u32', - { pk_u32: t.array(PkU32), bt_u32: t.array(BTreeU32.rowType) }, - (ctx, { pk_u32, bt_u32 }) => { + {pk_u32: t.array(PkU32), bt_u32: t.array(BTreeU32.rowType)}, + (ctx, {pk_u32, bt_u32}) => { for (const row of pk_u32) { ctx.db.pk_u32.insert(row); } @@ -855,10 +896,10 @@ spacetimedb.reducer( /// for the purposes of behavior testing row-deduplication. spacetimedb.reducer( 'insert_unique_u32_update_pk_u32', - { n: t.u32(), d_unique: t.i32(), d_pk: t.i32() }, - (ctx, { n, d_unique, d_pk }) => { - ctx.db.unique_u32.insert({ n, data: d_unique }); - ctx.db.pk_u32.n.update({ n, data: d_pk }); + {n: t.u32(), d_unique: t.i32(), d_pk: t.i32()}, + (ctx, {n, d_unique, d_pk}) => { + ctx.db.unique_u32.insert({n, data: d_unique}); + ctx.db.pk_u32.n.update({n, data: d_pk}); } ); @@ -869,34 +910,34 @@ spacetimedb.reducer( /// for the purposes of behavior testing row-deduplication. spacetimedb.reducer( 'delete_pk_u32_insert_pk_u32_two', - { n: t.u32(), data: t.i32() }, - (ctx, { n, data }) => { - ctx.db.pk_u32_two.insert({ n, data }); - ctx.db.pk_u32.delete({ n, data }); + {n: t.u32(), data: t.i32()}, + (ctx, {n, data}) => { + ctx.db.pk_u32_two.insert({n, data}); + ctx.db.pk_u32.delete({n, data}); } ); spacetimedb.reducer('insert_caller_one_identity', ctx => { - ctx.db.one_identity.insert({ i: ctx.sender }); + ctx.db.one_identity.insert({i: ctx.sender}); }); spacetimedb.reducer('insert_caller_vec_identity', ctx => { - ctx.db.vec_identity.insert({ i: [ctx.sender] }); + ctx.db.vec_identity.insert({i: [ctx.sender]}); }); spacetimedb.reducer( 'insert_caller_unique_identity', - { data: t.i32() }, - (ctx, { data }) => { - ctx.db.unique_identity.insert({ i: ctx.sender, data }); + {data: t.i32()}, + (ctx, {data}) => { + ctx.db.unique_identity.insert({i: ctx.sender, data}); } ); spacetimedb.reducer( 'insert_caller_pk_identity', - { data: t.i32() }, - (ctx, { data }) => { - ctx.db.pk_identity.insert({ i: ctx.sender, data }); + {data: t.i32()}, + (ctx, {data}) => { + ctx.db.pk_identity.insert({i: ctx.sender, data}); } ); @@ -916,8 +957,8 @@ spacetimedb.reducer('insert_caller_vec_connection_id', ctx => { spacetimedb.reducer( 'insert_caller_unique_connection_id', - { data: t.i32() }, - (ctx, { data }) => { + {data: t.i32()}, + (ctx, {data}) => { if (!ctx.connectionId) throw new Error('No connection id in reducer context'); ctx.db.unique_connection_id.insert({ @@ -929,8 +970,8 @@ spacetimedb.reducer( spacetimedb.reducer( 'insert_caller_pk_connection_id', - { data: t.i32() }, - (ctx, { data }) => { + {data: t.i32()}, + (ctx, {data}) => { if (!ctx.connectionId) throw new Error('No connection id in reducer context'); ctx.db.pk_connection_id.insert({ @@ -941,13 +982,22 @@ spacetimedb.reducer( ); spacetimedb.reducer('insert_call_timestamp', ctx => { - ctx.db.one_timestamp.insert({ t: ctx.timestamp }); + ctx.db.one_timestamp.insert({t: ctx.timestamp}); +}); + +spacetimedb.reducer('insert_call_uuid_v4', ctx => { + ctx.db.one_uuid.insert({u: ctx.newUuidV4()}); +}); + +spacetimedb.reducer('insert_call_uuid_v7', ctx => { + var clock = ClockGenerator.from(ctx.timestamp); + ctx.db.one_uuid.insert({u: ctx.newUuidV7(clock)}); }); spacetimedb.reducer( 'insert_primitives_as_strings', - { s: EveryPrimitiveStruct }, - (ctx, { s }) => { + {s: EveryPrimitiveStruct}, + (ctx, {s}) => { ctx.db.vec_string.insert({ s: [ s.a.toString(), @@ -972,46 +1022,71 @@ spacetimedb.reducer( // s.s.toDate().toISOString(), '1970-01-01T02:44:36.543210+00:00', s.t.toString(), + s.u.toString(), ], }); } ); -spacetimedb.reducer('no_op_succeeds', _ctx => {}); +spacetimedb.reducer('no_op_succeeds', _ctx => { +}); spacetimedb.clientVisibilityFilter.sql('SELECT * FROM one_u8'); spacetimedb.reducer( 'send_scheduled_message', - { arg: ScheduledTable.rowType }, - (_ctx, { arg }) => { + {arg: ScheduledTable.rowType}, + (_ctx, {arg}) => { const _ = [arg.text, arg.scheduled_at, arg.scheduled_id]; } ); spacetimedb.reducer( 'insert_user', - { name: t.string(), identity: t.identity() }, - (ctx, { name, identity }) => { - ctx.db.users.insert({ name, identity }); + {name: t.string(), identity: t.identity()}, + (ctx, {name, identity}) => { + ctx.db.users.insert({name, identity}); } ); spacetimedb.reducer( 'insert_into_indexed_simple_enum', - { n: SimpleEnum }, - (ctx, { n }) => { - ctx.db.indexed_simple_enum.insert({ n }); + {n: SimpleEnum}, + (ctx, {n}) => { + ctx.db.indexed_simple_enum.insert({n}); } ); spacetimedb.reducer( 'update_indexed_simple_enum', - { a: SimpleEnum, b: SimpleEnum }, - (ctx, { a, b }) => { + {a: SimpleEnum, b: SimpleEnum}, + (ctx, {a, b}) => { if (!ctx.db.indexed_simple_enum.n.filter(a).next().done) { ctx.db.indexed_simple_enum.n.delete(a); - ctx.db.indexed_simple_enum.insert({ n: b }); + ctx.db.indexed_simple_enum.insert({n: b}); } } ); + +spacetimedb.reducer('sorted_uuids_insert', ctx => { + const clock = ClockGenerator.from(ctx.timestamp); + const PkTable = pkTables.find(t => t.table.tableName === 'pk_uuid')?.table.rowType; + if (!PkTable) { + throw new Error("Table 'pk_uuid' not found"); + } + + for (let i = 0; i < 1000; i++) { + const uuid = ctx.newUuidV7(clock); + ctx.db.pk_uuid.insert(new PkTable({u: uuid, data: 0})); + } + + // Verify UUIDs are sorted + let lastUuid: Uuid | null = null; + + for (const row of ctx.db.pk_uuid.iter()) { + if (lastUuid !== null && lastUuid >= row.u) { + throw new Error("UUIDs are not sorted correctly"); + } + lastUuid = row.u; + } +}); diff --git a/modules/sdk-test/src/lib.rs b/modules/sdk-test/src/lib.rs index c4dda5aeae3..684f1403c8b 100644 --- a/modules/sdk-test/src/lib.rs +++ b/modules/sdk-test/src/lib.rs @@ -9,7 +9,7 @@ use anyhow::{anyhow, Context, Result}; use spacetimedb::{ sats::{i256, u256}, - ConnectionId, Identity, ReducerContext, SpacetimeType, Table, TimeDuration, Timestamp, + ClockGenerator, ConnectionId, Identity, ReducerContext, SpacetimeType, Table, TimeDuration, Timestamp, Uuid, }; #[derive(PartialEq, Eq, Hash, SpacetimeType)] @@ -40,6 +40,7 @@ pub enum EnumWithPayload { Identity(Identity), ConnectionId(ConnectionId), Timestamp(Timestamp), + Uuid(Uuid), Bytes(Vec), Ints(Vec), Strings(Vec), @@ -78,6 +79,7 @@ pub struct EveryPrimitiveStruct { r: ConnectionId, s: Timestamp, t: TimeDuration, + u: Uuid, } #[derive(SpacetimeType)] @@ -102,6 +104,7 @@ pub struct EveryVecStruct { r: Vec, s: Vec, t: Vec, + u: Vec, } /// Defines one or more tables, and optionally reducers alongside them. @@ -276,6 +279,7 @@ define_tables! { OneIdentity { insert insert_one_identity } i Identity; OneConnectionId { insert insert_one_connection_id} a ConnectionId; + OneUuid { insert insert_one_uuid } u Uuid; OneTimestamp { insert insert_one_timestamp } t Timestamp; @@ -313,6 +317,7 @@ define_tables! { VecIdentity { insert insert_vec_identity } i Vec; VecConnectionId { insert insert_vec_connection_id} a Vec; + VecUuid { insert insert_vec_uuid } u Vec; VecTimestamp { insert insert_vec_timestamp } t Vec; @@ -330,6 +335,7 @@ define_tables! { OptionI32 { insert insert_option_i32 } n Option; OptionString { insert insert_option_string } s Option; OptionIdentity { insert insert_option_identity } i Option; + OptionUuid { insert insert_option_uuid } u Option; OptionSimpleEnum { insert insert_option_simple_enum } e Option; OptionEveryPrimitiveStruct { insert insert_option_every_primitive_struct } s Option; OptionVecOptionI32 { insert insert_option_vec_option_i32 } v Option>>; @@ -436,6 +442,12 @@ define_tables! { update_by update_unique_connection_id = update_by_a(a), delete_by delete_unique_connection_id = delete_by_a(a: ConnectionId), } #[unique] a ConnectionId, data i32; + + UniqueUuid { + insert_or_panic insert_unique_uuid, + update_by update_unique_uuid = update_by_u(u), + delete_by delete_unique_uuid = delete_by_u(u: Uuid), + } #[unique] u Uuid, data i32; } // Tables mapping a primary key to a boring i32 payload. @@ -543,6 +555,12 @@ define_tables! { delete_by delete_pk_connection_id = delete_by_a(a: ConnectionId), } #[primary_key] a ConnectionId, data i32; + PkUuid { + insert_or_panic insert_pk_uuid, + update_by update_pk_uuid = update_by_u(u), + delete_by delete_pk_uuid = delete_by_u(u: Uuid), + } #[primary_key] u Uuid, data i32; + PkSimpleEnum { insert_or_panic insert_pk_simple_enum, } #[primary_key] a SimpleEnum, data i32; @@ -670,6 +688,21 @@ fn insert_call_timestamp(ctx: &ReducerContext) { ctx.db.one_timestamp().insert(OneTimestamp { t: ctx.timestamp }); } +#[spacetimedb::reducer] +fn insert_call_uuid_v4(ctx: &ReducerContext) { + ctx.db.one_uuid().insert(OneUuid { + u: ctx.new_uuid_v4().unwrap(), + }); +} + +#[spacetimedb::reducer] +fn insert_call_uuid_v7(ctx: &ReducerContext) { + let mut clock = ClockGenerator::new(ctx.timestamp); + ctx.db.one_uuid().insert(OneUuid { + u: ctx.new_uuid_v7(&mut clock).unwrap(), + }); +} + #[spacetimedb::reducer] fn insert_primitives_as_strings(ctx: &ReducerContext, s: EveryPrimitiveStruct) { ctx.db.vec_string().insert(VecString { @@ -694,6 +727,7 @@ fn insert_primitives_as_strings(ctx: &ReducerContext, s: EveryPrimitiveStruct) { s.r.to_string(), s.s.to_string(), s.t.to_string(), + s.u.to_string(), ], }); } @@ -816,3 +850,25 @@ fn update_indexed_simple_enum(ctx: &ReducerContext, a: SimpleEnum, b: SimpleEnum } Ok(()) } + +#[spacetimedb::reducer] +fn sorted_uuids_insert(ctx: &ReducerContext) -> anyhow::Result<()> { + let mut clock = ClockGenerator::new(ctx.timestamp); + + for _ in 0..1000 { + let uuid = ctx.new_uuid_v7(&mut clock)?; + ctx.db.pk_uuid().insert(PkUuid { u: uuid, data: 0 }); + } + + // Verify UUIDs are sorted + let mut last_uuid = None; + for row in ctx.db.pk_uuid().iter() { + if let Some(last) = last_uuid { + if last >= row.u { + return Err(anyhow!("UUIDs are not sorted correctly")); + } + } + last_uuid = Some(row.u); + } + Ok(()) +} diff --git a/package.json b/package.json index e9d13f72335..034cc6bfede 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "private": true, "packageManager": "pnpm@9.7.0", - "engines": { "node": ">=18.0.0", "pnpm": ">=9.0.0" }, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=9.0.0" + }, "type": "module", "scripts": { "format": "pnpm --filter ./crates/bindings-typescript run format && pnpm --filter ./docs run format && pnpm --filter ./crates/bindings-typescript/examples/quickstart-chat run format && pnpm --filter ./crates/bindings-typescript/test-app run format", @@ -24,5 +27,8 @@ "rimraf": "^6.0.1", "typescript": "~5.6.2", "vitest": "^3.2.4" + }, + "dependencies": { + "uuid": "^13.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd66da9016c..984e86e62b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + uuid: + specifier: ^13.0.0 + version: 13.0.0 devDependencies: '@eslint/js': specifier: ^9.17.0 @@ -62,6 +66,9 @@ importers: undici: specifier: ^6.19.2 version: 6.21.3 + uuid: + specifier: ^13.0.0 + version: 13.0.0 devDependencies: '@eslint/js': specifier: ^9.17.0 @@ -8532,6 +8539,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -19984,6 +19995,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@13.0.0: {} + uuid@8.3.2: {} uvu@0.5.6: diff --git a/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.8.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll b/sdks/csharp/packages/spacetimedb.bsatn.runtime/1.8.0/analyzers/dotnet/cs/SpacetimeDB.BSATN.Codegen.dll new file mode 100755 index 0000000000000000000000000000000000000000..541cebb762e8249347c6194c0e6cf6913ce0dd0f GIT binary patch literal 75264 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℞)%Q;*WCq{@YmfTJ@5FhFtajxqsIic`dtjyo@_j`GnY0!U7&Q^@ru zAf9lxF@fKB?|!^6Cxf>n-qJ;UAQ*#>aeTm@+D|t|;*vcHc*3XxLSm*&wvRSf0=b0A zeNpQq<>ylT3oll)o47nVxExt-^{vHFPeiJDI%f7xoj$&V`v>LgfxcvKy?*k-!6}%l zOUb)}FOjgf7jG5kp zGY4@d?4QQW_R@MGgS#c&z2+IY*F0ly@Xi!*xubk}RV{;?i8E&_wbFJuQ}lPYX4do? z-Ac8VS*=uU^BV4b!!>c=k*f%&aW8K89nCYx?#KQgp_-jKJT)_gzdn2fU+*k04;^~+ ziPgi8<_^yse)O@~M;=2Lt_G3h@#2ISsO8t=lK+Z%uS zr=1$dTF2P60|3E&M3pgs^eH^r2YU_WJD4UX)Y2GT{4xeaV}w%2Xo*iTC?7UKqh z;_xB#wJu-`)f)RdLD`0qjP30X6hHv)G-txLgGHpU;n#Kwe~@JQ^%B{B`)v@8n^@!+ z%O~SNzNLKSAFS6fii4l8egD(HG_h09vH9EhJ^4h;oV-_;l$Dqo!|xaI`w7ZE36&ws zjPfJ&b9Qll@fZJL|LgzdhktYO-537qFNUAH*kB*i>y?edbaktYJHw|bSi^z=7d2nN zeY>5qHn&!AY4>yv7pE6$)A$N3e#=Jf;arV3&`(FcLt+|VfiZ1`^mOQJ;vmw)Jx68Q z)MkFgES{V@c<3=>;a>l}g&Vk8fBkoV@;6`n#lJgMdVBsqJ^N*kmsO@Sp^1l*-w4$+ z=G-xSn`^0b3U`&84Sc^5z9CM&?Sc(HeGq-LqA=9cACiAR7>LQD++$25I=%`!DBHGt z);o#cZ#)$<->0InKi}_#nP6T(l;RvdkK-)TB98Z-!QY>p!}By8_A^rR=e>XRzsPOa z%a?ce#NT+z54$9fhJWVq5<{<9`1!ohfCnlU5Lj465P&}qh{jt&DQ_aIpXK8O{b zvGiy8ctljAFKow)Iy`=+aa5EiuDX@$=!E}%o|H3R#ka9LT<|@!D0N)$S+};M14{cLt(tvi1_id`Rp)E0h_#s&R_&YIOL2Vj|<~#fq<1w z;EFM@HLP+Sbq)fOgZz^V{Nx3eGq#)*%2sYJd?V%kUGU;zI&5|UrspsQZ)VgpgnqW& z`ZnfOoULnZ*NR<_+lR~{%;^cx;bA;;_?(f{W5E9-xOsqQ6ld|diu9xAF_h&|gMa?= zILfp5J&4vh;K5riS-19qI z)j3tS?(MrY$4|LZIZ7!HKKu78^)R;bS5A6pQU`TL`$sd>meiLzJgmpO)M3Kev#X2d zSH;e%ntpE4jOp{{#TFEuF{`L*;k=^R^NNO^Fuv&A*vwgF>FI$^Zs-$-DK$nrs^ZDg z%bQzkQXPxZbPuJBR?2Usy*hv|0$+};Qd#7U?Y9V||NN_i9&-6}ROM>_D z_S$qYNca;Xth==ZY0jm!ZNfdnZN7P%+1xytgEWuno!mSjn&);&JogZ+FdVkip4)4~ z;*}^3)gCK_vp^{gt(yBhCmt*!!E!IlM+IuLTLz-aW2FcnPy*4aIZ*Sq>=o>LZKEAz z#wZo(VN^M0874}ox+7!(UlsWIv10IDd{udI-uPmjAnTUuYhM)WOl|vGmx*-&E(zq8 zlo=;_IQZOsx@4?z%F<0Yzj0zwQRkNAl?;z|1#4nxIS?xqX&>V(lnz;&^Wl@{bPHPx zhj!6!&E81i2;rNz$6$V-pEAABb*9|B6)_rNe(;xFGvq8R0MjkgxkI)A;!;La==a`$A*xZ6Y#Y^c80IIKTMlv$8hj6JGx{lCpuCTw?~ged|Q^-QDBR@PMmnh z6jv&Rq;zu0Y3=}~8#{v5J-6hUqyVqo+f3#NjPOvm{8^)JSI?Qd+r_NMfMt9D|<`zw&3r7b|yCG5+vOb)j@JIu!~| zEwtBiZb`y+ZV9(sNM3bcj7SuF;m?{|Cn5F$b1PsMmy@N{=IL{a^zHU&KTaR1NenZs zdHPtXbd^$0AL&XrnLffq=`PbJ5x^ETHo#7wLzGU*tZbY<&07&SkAch| zALmb7X&3WHwlaTClaAZ@bLmg!j~F=|SG(?5EaT7(3sr8(6Q)L5aY$u#Ng!EWq$duk zySlWnm(`)6w~ZkEhJ&_;{WzWocdxB#)mi z2h#V%av)vdcnTAxdty0A6r$yFkVrpyIly4ac;cAE@oe6TIC(sA`hxYKP};?K%2vkn zT8JayWRci+r7Ik3 zVWMASt`eHNA*M<{213v6~*wD26)`-=JQ1%EDftD z4*7v-IY`wPGPr*OiQ(ucheY@rbr`m(@HbJ=(ttz%vLHMq9HY4FvRC1dssdsgs{j-5 z<)6xv^|ahzX?}`7|5W48kKr63&^{|V44d`-{K-MTuh-rbo($svjoBz|O514?CohU{ zr2$`9@5{kCj7f>EL1m9f@l_9pe@WQv%bASRD?c1s_~rgwzu_iYgNV`N5H7k@Mor>i zhm-F;q3RJ3<1Bz}BV+WbG&T}4Ur8@hBmIrStdu?&4Ei_=rK7U{$X52Bl@iwOKYwqz z|BNyhN)X3};=1V`2_((<%5kv-8163Q;=p+$XAEpgmV9BNw973yEy>b`w$nqtaP*KG z!3(9&i{gt#DVSW#v?$7D(m1je&nQlQFs)c?$ro-|FC>cOD_hBTop{)M zd+HYFoyb?hI5rs96*u2&;1kdHdI>N*Itqoh?Z!rfm3-Nh=H6KEo8v{`42%-wK6n* z(kNEhfJ_O9ny188Nn@0ixY67xf&IbGiLg5-u#93(2t9C4Xp=N2q@$Rit;~sMC5`5j z|}wl3kxm`NV;z_Y(OUGpZEO~}E_Ts56YK%SI13^c<97Gb z&e;&~(vJHzem1bpCN>A`WL8TujnVZj6sfz@t;EE{(IMp(AAEgJxH>FH>>QY|v9Y#h z#>!d6l0I&)mrJipmN=f?_`2kDOY?N|JD1P)Tt2(ZX?K1k{c+^ zFurw;Z=G29@+Jp-=JMISzRW|RrOrjL!!aV%J``}|L@hJLzb@U^zWtQc$pL>J&d*X{ z{e`c@$Lk7xv3KB$L_gU{E;q|?*u-)$7agyqW*Lr_5WHlR&$Y28rbg8AO(I}TOpSzy z{t1Tchut6Y&<{C=;J}OZLa3}49?pGqQ?+$3Hdal-VBX$2&kcJfcFWs4=T}`uX@MKt zO50RDBvSc?BKwiU>qJr)>x-3wU5mw_FV(r$H#_P@$RnVWUN-9jzUl0%qp)_U{6vh1 zjp5ix^CeQM1=;&_i-@q4h>nfxM_Rv0b7XV^uimF_E^RwotVV`(j^+YM51x7gb@X5URp(O6St9G zM0Hr`FsKd-`(vq?wf#!13RA&E+J)Ld)h?VG%NJEUtHM#;MB0biL)AVUh#fAfkX20* zRZ*x2s-kdOtdFSNlqZX-L#P8(9m2sFI_K;6olP0X0u$*N>IhZGaC&Tls6MwUtmr0E z94dyYIGhoiA*%PS3X8ajbP9EXs#7>KRwb%etO`@cL^_8$L)AH)69#zeY= zx9Ex2js;yRq<=sRM4;>EG;o)%XHc@S|Dy-%v5)DP6iiRVxdquV0sxVVcq-&@v zR9(Z_vByPqg;h-#RY|C%|ILC+!a1=QMY{|rY@$^%X>$Xq-O9tI`*OPDD3}}RR@x~w z)a?qHZtm=l!76e@xHNP`6lbEPp>BDTBi%yXQn7fw87_@3fEwT0a??VdZbQOl&2hSy zb`E($-G36ON4PB1!v^Y(K;1*#Ls(*ay&f)Oq_mWhkk?%#{2?zF2&~*;-@e>-=#s_Z zlorU}0;RSkBD*(B1=z{f#V*pZA5XRT8dLJOeW05wy|^=%3{?P$BTimX^UxDy#$d0zUmrm z(lS+wE=s`;m8o!<-;Ww%i-dLE=1q)Z8^WQTpkhLr@D5xd|MoY z2ei_L+eC(R=YP>eyp6n0i8i8T&22d9XtaTDgq+w?^e?bUA|tPVEOmBq<|1$>j+6 zv=<-SQyjr#SHR4oZix^cH^!9`13uCoj%oBgu$^_ViQ??tS5g+moGY!9ez<*<>nKHu zKDJqU6?6ymvHEJUmyY5z5*?L(9_6;>WSZzTP-E9&M_t&hZ9EVp*TUdX{XL+~pY&Js zCGBh(OFB9pwj}kk=WQL(e>rcH#m`s$r6dYtnP)WS=pWoRfL%dsuWPowUYLX1;sy(C zZ)mo?v31+wuI)|Dwl}wKJ5y{qewak6BgaDgaK0`ZU-c9@Tbd_@J{4R{LJ7dp(}Gs| zyCNPJTv0ZNQ$l%be}h(iUyafjZg?F7N=vR|N}O8-sc` z|Mil#4A?Eg9K5dIyW@3hv+ZrI+ZMQS>zi#iv~G)UY#guKn{69fx1AWTRV!nFW!oKt z0pQ|efCcN2V^AHB`!mL1l(ZrH*1^XBS5%V60M{4~JqGLC{5Lk|e@E;2-{;!i*=+mU z)@`2<+j;HxRo?|0+~daFXj{8Uc&B!=xbe6(9P_BN%wXw9pr8 z114?h-Jrp+ad4`_1yL3=U#tN1h+Q89634I-yY3qmQPF|+n9^dslQ>ChmaHGuf0AkC zu6LqG4EZkC7yatOeN5(1z;~Ifz4FDgeTskSJrJdbo#gd-qv-iX)Yt3%l#)Kn?u8AK zx=%v(vM74LfGGs~a-<%1nU!$(=Iq3zsn`R8hC4WsV==H(JSY3dxc-EjCN5 zmf`T6bXa{1Hr3le>cpDb|G9IJoXrHt(NWUMhA*vLA>nfTxoM5VUxSQbcaKea5kAC@-dt9lh(wpW?^{h5M4Cj`$>@P0yo z!Gz16gaErS1fL}YS5xq}ga9W=u=rP8P`bcZ^?d@JiHDCo%{?fc?5j#ipi!yqi+W|I zwqM#K;7hgDDD9IPeG*k@pV7OMuPQxm>r0Jz!k(%K1kpz5ZAPX3W}gotif} z-BtB^&YI-*On#PYnysl@0@>dK$iKz@jw@VciSG%R?}pC<*<*3%eL1~x;I@bD>I*T> zb{}`-+L?R2Znp8*+j(-dhEb%4SL)2R@|rK1^hCOe6DB!PNV-xYUsLS)NDlnEVT{5G zv5#c?M?zVB$pP)RwDH*6YeyZT{fRBwmy?A9+i&MYaduydCz+7+=6HJt$tBhzmK?|r zj3s9QQ5;mE;4j^#xY zZjN<+8qN%ltM$K;rcQ!!6O3`GS)#M9ywof`UoO3t>-@E7Dp78n1|`ZZ8kU>P!D?OL z{bjYrak;fx7so@R)|aBktTlG(pVs z*7=V~wZ2Z8N~*O)XHi*e>G^W$HP@UGm|sUA=VfRU=Niu;3=XHhl)SQ>(k{!!PgI-+D>j|!w%22+PhGJ(zL3hP?+lH;km?~&rU(iGQ{*OM9U`V?-uZfRqP_TlxBMt zN^44hTHiCEp+x$v5jd|7=7=KzRPln^Cf_iPbP>{#6JL4M!HM1~{q}Kwp9oVEO%p-cD`U|}d^r&f=Qo)Z zRkxwjCfWeYwb)@a4~kWrAdJ3<;3xyv8(AZ;cFVkVV0z#haS!b6LV$TIu{duf-cQzu zyP!Q_-rfyS^SssI-8JGRv~Oypo3THEwQB@(p~Fw+tz1dN8j)~wtnA!@Ze(u<_>u&Z=9ba}+h zsiE_0N#%J_no25<7}N@3d8DtlJUD+(ln3QYlt(ly51DyY+9!M8EAafe<;jFcd{-04 zjVL z#*%9&v0AU8!Lj{-8u}n=wT9Boq#808Ivl!&;^x%QIk|>@X)39PVo)oDHI$wwYB&kv zcnwkKL=8p58j@L2Rm&O%5&IX^aCLGGC06S-#C_=lYnUU6x#!18=NWHtUzTnr)sVT+ z;m|b{H>ZZq$u-QArjlwX2DL(1L+N>ZZq z$u&Gmno6pn7}N@34W;La8sa-uqK2q*qK2Yj4auyidX_cp5AR=4!&j4QD6v|v;W5x2 zP{Sb*wOT{zW>O8A3mpz!LveFz=$u@`q0&@R4aJ~V2x}-kPt@>Kh~x7Rbxzb!G^`<+ z6;-=g!x8ZQ1vUI4xrP#}^%@=z?Ey6$2T`jvlx`-~kh##|&@~h{r-shSH5@NZCDl+2 zYK5?d((^m337(}hsP`a5^L*_zjVL4ot40#A>~UOQ1cVhD#x8wT9Boq#808Ivl!& z;^x%QIk|>)(o|9n#h_LQYbZTW)bMnO<26K`6EzeKYe;5A)$y$1DtP~b8lI9|Ly6UT z4KIfFfEway+tzC+-At+>bD_hbYbb7f>e&-ZuHhBZR8kGaOdgB2HI$wwYFG(zyoRWA zqK2Yj4auyiI)^p98s5L4hKrJGD6v|v;SJCpP{Z{QwK@-_n@KffE_66_4aLo=p>uK# zZ1O(oS(3~Gh2hSKvy4bOl$UPIJ5QA5$NhGbS$ z{gyR+0N%f#h7TmyP-3-S!>!OBP{T(dYPE*a&7>MK7djlehT`Vb&^ft=+oY+a8j3-! z5Y|w7o~Yprh~qUxof9<_4QohdMb&oJuo2$BpoT9b*HB`$Uc;xMJ)nlqLDXsurJG4L zWG-|#bPdJLsiAXn4PTI^l4>XhwL(}!>A9_;T<;4TK0)C=_nDS5EL-u1ANZ1MPw>a# zgY{We<8v`Sx8d`9d~owe?Z)RjeDFZ5D#oV|J|pp&icbum75H3>Pe0rXP|IdYZ=*-! zl1KFhxnk4r29eI9^c{Rv<&B*{KXF=iWUqS+W@FvjjWRadt>1>Jn zK5ffIIy)i#lF|~9&Phn+xwb&RY>}Rmkjhg%fqwgt)Uvq=sXW&f==Z5e&rL|9l)fg? zc?oG>O1FtLmXHpo^iGk^Pe>fhGqx3Hchv7u3it0(pr8nq0iQ!M89zrbe!g$KUF3>`NH9DvM}x z;Wz$LhsVByfy?O7$mPuVI~`v83dS4M$VIyND;+-j2FAIlP%LMWFIa2NG7md_R7ESZ(90B|G#Wrf>D_#6;jx_u7#lzId7nS(S z96|f;#TIJh^gI48N4kCW;(lu6q%!_0M}~d#0{7|AYdKYlzsZqlU%bFwKxpIyApRmp zmVNJHBQO0SvLJk(j+6%0=!q;#Sh88b^S@68zeM zK)%etXEG|#&!gh~Y`?E}zNwKkm{9LDbc^`~N>g(AP%d7vtl0#8?Pnn1Hw!Nv#gkAb z`Z<_*x%u1#R$l(apPP6OXCi!a((nO>=ofHl@$du=k@NW`rE4GqnlRv-E{P`C#@#p% zkiYiX%@3h{ERD>>Ofj4((POb&CSu?TjlYV)tc1arEqjq)^)E4yz2A*+fI&f(*AD|( z3S5Jh-R#_^DkKK7F1QBgwCqFMs`g?aYsA3~3ah$`fvgt?H#n@SyBNsYad3lnRY!?| ztRn|EXrEejv{=ZR(uzf(YM5Ba`qGL;TGjDlA!|)57Qw2CVj=5JD;DWh)5Jp7pjIq0 zs%D9WtVgX_WLCw*Le{2MEV8N=i-oLHtyqMrR*HqJS*=)vt1c4@S-)Dbh*VuG7P6Kd z)WR+%L&lF9q7{xv_zU85{*h%p%6j+c-Lt<)yb6P+)?!?Wmn!vdYP_=jCYPrB{)wA6Nh|JA+a z;ZA8CXCDgFTwy^~SWOkaODmiYD9m?0|JmKc_eS_mP6=dV@WG^JpOGSCNStDP+Terb zQl25i-=z_ooljkpxi`m=$=!4|5 zNqdsMN_j=PXlDe4-XD}`FJ`?R@u`{upY$vMaPRJ&ZEn<&}An5Sfm*JNgT zp%(sc5urZa$LmvPQxY{IF{Ej1<*A6oc}j`RlyvD&8`_Vic6IJI$mPg@(A}t;PerNy zFWc%~DDp3u<9#8CdTco!>d=W@O$!wb}SV7A^Y|wd-ZHsBtOt*T!HlA zWXjjk@{jD{SvgYkEXF)4aAA&5O$tfHE~4b7!VS>=Ia~VEjgpeKZ*vmho(TRp&+&3y zZScYT!Euz2d|j+)S+AK zBFt_eFJorNS4^NS=~p4640-zy=y{?nRYde3qLAzvVwowH5qbXxXnK}KQKB`p?11N? zHFOuG4>5yP2Rtdc9C>`MhM4ILR~=J{x;&*U0q0Yn>QKlEELp3T|!+|j-a0E2BM|ef_h`$(CFD?1w+gftP3x( zpWV^kbfDd|4CeNZoQvNh_*!j%*K0uovA>A!VxmE+9nn1z(H*Ti5_L8fbtd|dmPcbx z62A%;l@dMWqB5ecUhCJB=toy~BvD5a z=%SHCkGg1#_M@LM7o9-eW};)%c%s#X($8`=iRiwNpuy@?q6S(HR;Ll&%nDYhGxT!A ztV5NiV7WJ>=tNl3lwXCs*=z!b-wmux&H=x{U$YBEzt%sCj+fjBVF`E zK+q_RRieGo>V_mVR;{Ps&=8Jo>O{4H=nF)tSL4+EM3-PUtXC(gCyCZ!$E(L%GJhhv zkA4%>`$YH9Z=(8=s3Y>LS0}6Qi8>Qa!s)d16OXGwH(3=B$v63W90Q|7ICBJ=qWTh5 z5S^-q6X9Y3bW_ztqNj+ashLEXY~eIjO_WV^x>`Z>Z(3HWbwqfM22rM~dZK|uXQ=y# z9wnNgo-m1;%~UTLr$d(Nn==s_EQ#+_1Qx@Uz;II9Q(mJRUNay*NfX^l03skE&{Es;&*Uc|jly-7m zH_#4hQ0h|959vE8RV2lfTnfu(zKZfesVm_1U;3WVMk0Jcz6<##@{@B!{u=q}LgCA) z?MeFQut+vV7Nw1^J2oVJ9+|raEqXx{l8Y(1I7!mF&B=AYO}z{;*MVYf4T*1A=t^XI zm;bV~K5DGF2D}&AK5C)45!x40rg{6Q_fl_7^QnIX7J=TI)&TAPHVtV#Rc`1(Ur&|p z3`l9FUCEaR-iAbY@x2R{2}w^iHYeuqjkjg)N$agDJolved;>iT(5uOj2SI-bJcxG3 zrAX^(Dc8VeMCcEolOx+fe+WDS`kHwG^d0jSY&xb$WmcxV2mVIVhf*5A_e;h)Uelj2j4l3tYmHu#mG8W(v*`yi-KtEYy2Bv=~DrVkWm)Smy^{R=YE&G(fOD%;T+3?<1_uLbdR&Lr)a(}MycTG23$(li zx;T!q5akAo{NucRoHSMMqCgu#ceyCPO%~1oA4u{Gs^{Y91)LAQ?OGP0pBbttt{V=t zKaR!(vs6w;TeJUWJ%M*H4s((C;q5ewI48pDh&al^+Z!X|XnZiDPILXH7xr;-)R`{2 zwqQyyN6m{{`tYsl{1#{_(azMf+MMa+sA}NPf9h5tztP@y zYH%F+@M{kviDZ8`JJ?>GLbTFsm@( zx~l)Ux~+wagC)w_*|xA5sGG_pdPYfI%2YoWNnOg+iA0~EE>{P;s})3&?Tx`6>MA11 zc0;h2y2(Y~=Lp*9`boCE)UG(XGuWFifJ=Va*>?wzRFz$tv%NdmM?G#4vfUEwr(Pkl z+4fi2_+lUBPb3*V6CA0sU9=f!v}#9WN9=fYoQou*J=iJ(IoYES0`gMNqyt`Z7qB^h&wH%Hs)raDY&XD$YwNE^>&eDG*yiz zl8inIPE+zT*652GlK*M&G_}@6qq6@MJYBu$qDQHGPKa(@p$VV(w<9 z&s60^*Qrk;Iq9?11uhztos)j1y4OW_g*vC7r5auIVWda;+3GJuHovpgH$--yK3jD= zLRt`XRQhanERh|}Icf-zjOM`fbJSQDZ3a45O(wFr$J87bN$xSVf=F_&NT086B9a_N zrk|%8h-7R}NUu^45J^U-q*tp)T{I~Bl=KB^hl}nC%}QUW-XyX)oUis0*&NPSg=Nh- z%uQdUx)a$PE>OLQB!}ws#p)OrZ3e1QBZzDcwQ7osB!^m6MI<@YrY}{O5J|5Kz01_q z7WsRGzxULsn_YB5xX@dtHWFRu-`i$Y`f{}`jxI}Ip+2#e>eS~ds=KRe4E+vhkgIzyv?YDD8tJ0FsgDAkW-a}DvUjAfQ43tO z73dM<92fi6>jbS($uHKkvn-gD8gyuYMhslF!KtxwE- z*{oAoQ1NGLy-p1#l3Kr&ew8}jMVo=HQIm*lt*=wFT_m->PAw;rT7Q*(y}FTTjk>DM zztV3|4MgkmgL!kkH>wA$j^l8ndekC+P2TsOo77V-T8lBdNxeXHotl>SefrJn0~d`0 zTCe`@qQd;|({E8uPh0uQg74FBRXHxY2k16+xJ4LElTokw$5C3w1~uA6d$O}LZdcP? zv=wNhT12!4BfTa4PPK~2j`W@CJ)%#vRQFExm5Zdhzf~UG*2eW(R`)JdL}aUbmzqc< z)h)`{q^7%QGtlqUIYhR)_o!MINpdYWjbU(Q(W zSFaMuOzxR+zj`N*24p;-cE?dg#)ImsI2x1jkov(QwKZ>I#uk-_L#NE^Tk@u6{9bi( z(Y)}CjIHWe7hM{jlko>NhG;p)7T1*31R`6rhgCh%P98TOR-3FJDsf)MBkK1q+6?rV zdd~Hee`G(wVz1No_?FLUHNgt z4_DuT8az*_JR*Buw?h>Zt?`Gw&zl{p)I|l}doy;ZBQ0V^JJnDko6$~nCDBfv0q#^= ziDYaSd!JTMxd>-E-e=WYuAgM|oZ9aq$>=$ij$3><0wIS>z0V7aKRE;3n(>0FpiX+- znDL^TL?q+>LdHvK7Lm+}*E0U3RteJU)KlJfGhR`5yE=d1qm0+o+eCJTyw3R2!hg~} z&3IkCOP#EW|IK(qeeU|nynah%^bzAvw9M;2t0Rf*ynb7qNMtMVwp#BZ$?t8|PvtEhW0nzqv3q^If&sMO%S(sa-^N?tY*?CAwPuEjyC=fhy`}qeu%M zs-}S!$r;&)s@otzc4mF3jv|tAD9mh9gI%;4=p!|e$o6Hgnn5IUsY~X^YOd9xp0hJQ zQ8lh5zFlViMXe-~adc(-`}7Dv;9+q_@L(eKg@*Z)x0 z(C-s1mj6^=yGSg*QO6!5#-C`h{8pVwWPAOcy2#au-*;-8i^T6g>fcFz->Y`VN)+q& zgX%^k?Y)utgPQ50X~Er@KdQT2B<<~2e@gOG`rWwS-!hf{DDL-drq*93`8hhJT-uWs z_GNl>ri-Qpy;)wJ?;>d-MfY~mBi{5ZzaAL3%+5;HLz67i^yzWS!mOY^D{fhwm9FO} zS!U`>vc3L8-13~PBK>6CvMQ^C{$rA5vECE6 zT$0sEe-XD_p4C}@lVo|g&K%O*&x^C7I?qMZf>&mB)kQ9newOM%amyRBy6LHL%lfP% z^aV+l-Sstb%S~B5bYtA|fvleTnq*ui)bNxr_Pm?T<)fq#jbvw(;bs>?ASdY|leY%UL z1vjP+*5|rN`ck2nB>4^1>*9VNXARZ2#r^)8HB4_w@;gqyk>oc*zZLiUHfx0bYuxY0 ztdZJ3%+^KP8?8IJ=n=0sG)AB4qG`c!=y<&#$?^ofoXBQ$qP`*Sx5ay+-W>Pa<{hUW zNb(!6A5QX{px-9CPAv%)g(m3y;nJ6@)d)NUHc@Xp&Z0{~C83k`#u2ukk9d2ACh13A zG%eUaG+A$Rk>oH%|1oYkBy_6&GHy94G*xGfv{A(JG(Eyak9fz2PSRm~eXX?l(X~EXzES*CnBfU6umOjiy(}F8PXKVTIKAP9h zIr{V@zjO3-A~_8qYvLE zAuZJR5XsrvJ)!gUQ$%uhu_d%fzhV(|+d>!U4~c9>i}e>)=b!4?5n8PO>7p||&jI~F zWHVZ#M~smi?2&nizKTdPdM#9=Z*|dTpj!PPki(K7uZk3byxLA)N5Upico=ECBJM%hSZxQO*D}23v&b5^Do*VS5L{iWG;T!cQL{iW4 z@J;$_i=Z1GzFF@lvKg({X(vk0*7%?Ij18~X5f{DgnFv%!WHY)&_am|y-J)j`Nk*rK zZ`JZ+>yqDQpn83=>nCS`x9huHBpKbVw-VWm8uXJE$(dYuqyE&jl#K4sUlU12^TT&) z?>P1o87&I`R%cqojPBCyh-^libcxmZKSD;EbT1eE4H<3H1Bq-#oAo5?$Gv#7zKBRN zstf;4U*n?9K=W9}Cn6c6Md2;F%p&L>%>2DRn#g9fRgbhf{|}xU!dvw?7o~V_1)4%+Gx~!*-}*75 zKj_~QNk+d7Kdc{e(Pp4W^|P*@WVB6x;v>oBoE#X7srJ(IRB@K==vWWqcx|Cv_Q- zWb|;jQCAR2Mo)&f>*Fng?%D8DdMc64Xoo)A>ijD_FNJsL`7XNL^BT}%BAd}pz25pU zqn-K_BFX68@YDJqF2eP}@Uz-C!S+ltdQNwCk!19o?oDJfdR`B*2pR1Szo2KimNG^! z>T`)Cqc6fQ=@mqh(Lcg}(pOr|;Gy1cxvj`bEk+=0G*HSWi zM?XL$83iKm>Su^-M(^o2EMi8x^d2Ic(fj&uR_AYvjNaGZxu_T!;T?;UZLQ^7-3Pi8 zkzE-+(DR5Sqfq2Sy~IVEfp+W5T|XJ4J^CIONk)70V?;Kiy?Uob$f#}PWBpgxQZo8P z|BFa6>KOTp4os51*o;2a`4%yw&-CF$HlxpVZ>#f{BBRgsKo|8!MxX1UM0Sk6&}Ume zj?owTK_bbhYvfD)go`!{UoEW_1|428GWty6WNUZuKl>6EoUXW@_R@Ap?eb9 zmEoVdKapfKAo7hKOJpQBaeB^>imx&BahkPqNk9N$Gk{nGxD0R ztsgV;ngLU64qFRPiTKQL7i|Xen<+##qkviLBFQLVenVt4N;6klgp6iHg60v|QZh<6 z+leHjIgt$W7Lm;;)9kf~8D*J&64{JGX1~?>k4Hu!lX9x`bB+IGWE3)4L^h+aIg-fk zM`5#+NXF>ANW@&?B3yTi$f%!r-9@(|qkd)=kcp7IkSn^A@N#v*1k)Hs#w8OLat$s}6i|0gmUX7XJ0BQhFhiim7R!_814yB`fV zmlH`wuSSkDH@Rpt&`5Ky>nF2rw0X%zlF?}M9+AyxjM-}uGI}p^yh)#~@Ml-=u_lK| zGWsZTf+-=g8J%eQS;UOSnd6A;7@cIsTb(}z$D@kgSLPr0LOflnJOBth6%_&5Z z(SIXT%{(HT(KK_RMa<|lb19L{=yY?v)%jn*7@clzbJ3d^qtne@L^h*J^P=@*MwKRa zhV)Z1(%I8ZM;C1dnqm47*;zNsOmvZCG|S8&vKgIe=30b|(z4Go*SeOH(b?t}BFQM6 zJ=;7;WHXv$8ZBZ*=a`p?Y({g}s7JY=-Soa3S;$Y_mOKx8xejk&@4F{9s@kBB6rk=YlS zuUxbl=n`YjvOSZG)|w73l8n}x?nE}D%S=CukkR<;%guDxQpV^CGn+^rErkO8g@K=f3OBeitoLFQ(pLg7eq{EVa7PbRpWU(}F&Alac=mPwtDB7d~Nb zGFvQ4y|wN0K-*m1t!>0|hpT(H?aR=;;_BArzGrSW?PE5|%efz$_2zIQdne`=(}QTG znuMO+VlGM2-D<8$(#d-?S``I^mW70VM*gp9csW(wPyU4@ZJ)Mc(@-Gv3~$2g&K%)K zQXW7YqpZa8Rz9~-Y=jy{klu^r4f?**M&uuWYL(2t7Z%B1U}OKZ!b5D3_;SU56linL zZU2*flXEYZ{(xW?sqlv14lKE|1v!lz*OHKevYm%QU$~%b&(Mmb^wKY>c}yujh<9xV*Uz z2iM9I=90C{#-9`I|JrLjX)hJ~KecXlUl_o3?x|7+n2ye{$q-v0kU9icu# zv`_GE{^zQP`U-E}{{vCKRlU`J@oPf+@P56~eejzSeN{mBQyIFy3Tt^IUfzS3x8LQR z_hVF3Yk5F3-ZsaO7$naZTuRdzlHy=utd*P?_k>n+c&X&6>kvAm75T#)Ef78ag|5j zPwYwBpA^6E0Le(waiph^PA8pBdLC#FYAtcrfwomQlHWx72xtd9IV;-1+L!kNetkfm zyt$jtrmlnJVY5NE!&ReAyjnEI{0CQwHtEN4b!e0R53UMLFkj(n&;$A_Tm^ar_CCD7 zAasLT5Xf_Oli#f_N$uoZXyhFr;d?mS`3Bd8M&8Q1(8wEitBj}ZRPge&{3_!Qoa1az zxEBUXiS{2v&QWWPFK>;b&5-=doNLV9Hk+MPQAM~=-*Yz7{%*?e zru=Tow@|*#jdo6U2T#a!2_ECQ##~W2){|kgquMK&e|k@v6rn>SOZgO?ueWz7eh55sdIk*DgV{?*{+Y52Wp>rzmu`8VvtH|)bV z?86VpeT(-8BhMc1*3T5Y;Kj51xLUd!S3=(ekM~QwBVm7mXR&_5JI}XR|CoA#=Lh!i z2lna*_DZNvjWcf*F7_?w4Cw%Rjc*ICg>LgM*H;wY>?_bsS$F!f9eG21y14;w-b&Vg z@XbaR_$>_7{3%}t*gxwlbmT4OLPy?fF2q}npZhvFzP$auae79+H|0E2QkV{UMPb{N z0{tJnv)j#)_szHAnXxTii7*_zw7ZQnd=Rxm5uw2|)aej!(Rh;+@{7U0l~qrEBlxfMCi3@yKS^KU5gV-v^sD-N@`c_4y(aJ=ZMMQ@ zQs6Q2Po|7P>spO*7!^-pkQ4=vC=@;mvZ zl@kAAQ($_4-WuuyIx2sVeq{C zF7Yon*B4&lABp+z^{zAdg%@~U)h|bG^E>cl8uyXq+}x{cT;jVC3jP@1@U{NZb9UYsfEZ6GfGGD z9|bS(wih~b)q5M{`+et`m2KWn9plJ*?i)i`6%vrD{-48IFM`f(}&E zKr7T)prh4upySnBpr_)$i7!(#R9nz<)PUUHj9h`AeqEAVfg}17+^7?NhRfHeCv&S| zvn+Q3s17ctcBMft zDcMb%J+RTiJ(TQ$&8WORwD|_-6DdKZrB|BN(PHUnsfnX&)OmT1mS{!Pb|5XLO)+hX zVe^~3V%l`lm%_3)}yY$ZMc{BU^unzKzswC*1*?Z}N7~=5_iuQNEj&yUFi?P22oE zY-JBEKhtg0mfU~RMjNS*V`@~-e8)&NQYk4S?Lb;=q;AD{cRvg&qfkuCPKGss95tck zVA2ZOSJ1u!_Q&N{(0)86m9&{oI)j!oD4#*g8MIt(q=(CCvl8-o`70@3#THjl+d$t2 z`ZmDk+WZFEY^1i4zB?e_mcN7Y*I~IVcMl|c^7q1KQ^7y!>o}Yhj%4R?L>?hu?A(lM zbfTm)CA~qHo}>Pkvh(Q-9yE~dPmbR*;`!8<5#L?4#r zHd4NWUOTCMn$ezvjSfCXZ4)h<$nT-$XViX1iQ|zzdp&Jbaj>gLyt;a%C*`EWAPEO& zcp6loV1`GsoR_`hw~qWu+OMSjO4_fceLb}sNE>L=K$`~I+`)D? zG3F-PG?MP1@s}0Tinc zwZ)VaQ`?z*S8BVaNaY8o$evX}%L-~MXgQ4h49aJ?JZBF1*`Sy?j8F%gWx2~KSxNhq zv{}hGtI0P|-r({Q=MM6lKy`2v<7`Jy%7Qy+xr5prjPNw%`N2Ju@1cATpNq5k42emtB`84?^YWGmPhmt+i zeoX!|P|OY5s8sG*sjNvVYm&;EkT0UP18FgA!okjzcLhZ?C?8B(0WDT?YKM_dC!In0 z4Dz!`SGrVMSxvs4HVxz(*!mshx2MXO?jZj(=^j|(%!K^Mq&mR<2iSl77Xjg*m$U=r z#grFQ(wTHHNu4zIoV19N4y47DbS524Nd@`ob5Vx0Se_x0O42&gdeTPHCep!~vY%FFiv9LX>3I|R z-Q;zaNQ$yV+kt#Jc&yJ^5_vf#_2f5@ZzSJDir?&pO%Z8hNFp?ms<1>T53_Hib)@yA zjigPaMG>h_QA8}uN$V+Tbg9Ht*pT1i@jpL0U)Ih2#HBwtCsj(lB?#H=T6ByFO+ ziM+}cUxn`nXq|i!`Ev5*#3E^7Z-B`bPL}Drh9XlYA5Tz2sE^`&l5Co&vEfqNIqDa`NTmhf-TfNhKvS zAxR0YBCV&Ssg1-@ZJBjj&LZ;VYp}l5Zm4L|*k^TcqWr zLwiWfp*nY!OBL|8O zT25L?T1Q$>+DO_&TGWS`k`C=7k%#t?$d#1TlQ#B|`OrwdiF^}z)t7Dc6>Smua`NTm zE6G=quOqD|Z6w{>SK8X!movE^nYjTU#i_iUJYOz(n`|00TRECd_DPk@{OcTlr)i71KE>-j6lAK ze5ZlZVyA)9VmT#42Xa1>uOn@Qy$&{#Zz5HL7-taYfJ;SENqHSj z6D4~GNh|7T#vv^ytt72KS|ZnzZzOFZRmX_D$fe?2ehk|soq3G3GV>T|rH+zS$4D#n z+DO_& zsw%|3yh2(mCqJ}8S{Yg)tyEGnvqD;_BVSL;Mwg0j6Xj|s+a)b0tt71@ttV|HZ6e(} zR9e|PR9f*2llVo$qz^^I*dNkL(mKlPT`Jl}%9}{laJEZYPFhJ?N4jdbw6bcrv{FyW z#^KUdBYAZk$L=^rAT1}Yq`c0hqOGUAk+g|aji484rxDUhrxDUhIVD3!NGp}(>qzTK zn?^{BP2|-`Mj$O8De_8}ioA~UdeTPHog<}{og<}{CQ9~>lvdOz#vv^ytt71@Z5$<$ z8_743s?oF`E%I`gilmbAnWLqZnWLqZI!acJmR9P?H{i#Pj}nP-S&33N4ndi+-)Cs z+t=OpbGQB7?ErT>(A^Glw@16%W8Cer?zY_B4tBRg-0e_zJIvhK1(q1ro(o#&lboITEuPNwH5Po?KP&qbccJ+FDb^z8R!dOLWJ^bYWrdvEpL?tR+( zs`p**H{OeV^}Z*3T~h|9oR~5%wm}pssAhg zVX6I7?@jdu(gQy9AVoRKkNd1hp&P5;XI)>f~ALf$=#k?;*vR@hB#mA*%ash?71h-|1*hC z^KOaDukt(^=XWOYhbQrUllU!8!v2{geo7KQH;KP8iN8IG*GVyRlK6L$Z2p?Wk4WN| zl0UYs)MQcHC$kOam!k}3m=`lI0BSJHQb02xGb$7F4(~zW7p?I>UIJ;%IlQKhCz3*- zhXEPPv>eFWgBr}iJkZXVjRsFAwFNDKmBEZ_4_b!VXz-L$G3Y>88O)@^LC0WL8qB#; z(5aZ222TZb2R#R~)8NUP-iUt_sKI}rc@+4q@+=bm?~lHqA7RECwFmLE+6!v%zj+)D zT7&11jJi+{hP)Qks7rJO=%soXBx^y9x?LX!+Mq{5vJup%2lQyr2lergJOpY~qdoz2 zyB-J0Q=mq@tjB}Cq9;P~Dk$D>(vv{(zk5K^1Zvb@^{Jp=;e~9i{swB)5BhY_AN6!d z_JJCeW@dl}%`8aLL5*r-&H`;~W9@MBO&DEfd=2}R$gBtuF z0sl{X-yb8_ao;!Z?a#X-SKOuOpLgetB{yeCx?iNIQ%uu3-W|!cc_hWVqgYqSa&PzD z?wb3H@4cn&WV+ck=zwi4!~tSQLaBmAUBGry*DkC!YLUW9lfnq(!e|m8O7@S@&5=9vo$q(%J2T(;&Ud~uJMZnUBK%MGOBnyl)+D~0 zyp8Z@)%Osts*|xt)TX+dcnp^1Yl%68Ur!uG_|FrMBm8FK355S5@g&0ENPGz4ze;=< zS~HJve@gC?Z9r$r&=?o@v3lw@zW;wweG$I|@(tV%`z_r0`V-v5`XB1YxV@IK-eFBz zvsTC2wt9FE(&wxft-IEjt*=|ZVSUT`w)I`>53N78-n71F{m4>w+CF5z%l;YrgZ7+# z(q6I8*=zR4?UG%$uiM-9FWA3mzhHmfe%bz#{cHAb*}rH1SNjLJ;}DA-imk`~ZtNe& zen0kR?1wQs{$Ttc#=jE(!}yWJMxvAWLgK55KTG^#@~g?$liyFyrcS1wO>L!qJN55V z2htbQSJKt=E9rle{%-nzrtOgfBS%Ia9{IhIc#kVn8df*>v+%Tv4P5Z|?$LjZXNDB+ z+D*bzzYllq@{rBX;I7^KVX+^=TJR9&+C!LW58=Mu&tgrO!kRFJHDL;C!W7noDXa-o zSQDnOCQM;Xn8KPcg*9OcYr+)PgelC=DM)$>Yr+)Pgej~EQ&%XG zFX_)?4QBb9CVkVSKW);VHtAc2|G$%1sc#$lw}t*L=z*^P`zHRO$@do)@rQ8Vj4^&u z%wC95x(}K32N0LkKZ=#q_wX5wm(+OtlWG?6Z2Wupd`f*Z{u#tSP+vg$3-P~1o*$^! z5s$}TQ`N+ut8Vi9s+W8XdET_Di6i*D6Q9@A$5PKA&f8x}=k0%%e$(nD-?Y9t@|yLd zk#_>N@Eb~&YQh`%3O-GE1aH7PH=zq}z*@fnTm1$s^&4pUSMX`VM!x}@+=RrNu*XeU z<1ay~e+19qOYGK(rAE8uoP1cFI9VKzBmi)lf~ju0%sh5u~Bt4 z-BP{0zSD6|ZW_Yga>wqKd%$h(Ufe*qi&-A z?6`u{eEl|4=jxu1XnAv~-SVAn!l#eUs{%e7_>kX5q2(J;Lnq4ZZp%OUc(FLIPIp`7 ztMe+-)_HZg)aW|v-3~fs5*Iptz1>3UlCw2{rRsUL=(?pHkWSRe@u)g|{Fo}>vw=@g zy#Y}@iF3!*20j7NfFJ@Y%s&n)gGeiYo||7lAp}*&FK(=#UQh)f7NWTps1)kNYP-^H zIQZ44PO0qp^`^6YN^P7kTq&+xys&gOik?2Twzz&?t?hWe)0{3`P#<5pbOG}o0=)DL zMTihWM2J2SBO&@lcg{2N=l5@PTYhCV?c^07uE0d=Os1jF9FQ;HUs5Uyv)v zW6~RlZ%}=UMO7?v^oy0QTcY+BoAt8W_CO%hb&^Cv8X^uup-}=5qlZN!1DJT|s^g*_ z)6_wVG>)LgYe@KXn}!JizgG8jE6jju+91IaGME}~Kw_6SrOAk>Wov$AQznG~u#aoI*i54-3Loe-6t;UbWMa6f`yYgcGW zhiRvslHYZm{fQT`E;k*_+WpC=y7flIam9Za($7l{__ky{8m@I!tcrsZMXk4Co3O^gc`G|=2`0sN z3N4b6q+Tx@>nx&EUc2j(&^}x8YD?{kqs}<~0H%>LnxUFnL!v$xyrqH1rJN8f(0Oco z^u5IASZ;R(gb#!UaLCW1wIHG{rcS%EM&p9)!?6@~$1$c_$i`KJfJF+C8K*YG*@qFT!ll(yQTfar_-ElTPHT|Ec>@!` zQ7e@?)|gXn`#BlrH6QK6!ZlYcLe&5kU=`swLDw1sQK?1G!vt#VU=&Lg_~wf~el~iu zi|i|ySCxuW2{H7)>kY>><9f-t-mSa)Wsv0-RYuCLY?m9|il`M;dZ~>z!?=aa2hawM z-XNuxoy~5wO1%wn!FD6W?CE<*2>Q2P_oEyN5N-+55+SXY%C&kc!k-f$WPz5tAxYNjPP;*6(u=w_N^Y1%Z*>aVH-m>2Az{SU zN?VSNPLKiftA@ zCd3ucDZMKXabcse<8SSR1Y@LSzwXyJ>)gCYs9aA@?ZCv4r2y+~iMVKphatu>;&CW9 z^gei2u-*={VP({b8{G1!f_I*lxqbsS6WV|}wy+7}N{DjmbOXUktK6<&K?{tkY3{U{ zD5RJT16OW0lICtdg7Xbk7E5Q_q}IBdo-Rc^*D)Ss>~@Q=y5EBkl-cDI4BRPG?jA(S zFXDvkam8fU^fu)6_S+DbF$3A+3tL;*QL~Xc^PX-s#Sz?tTrK%E8PRi2s|s+#t*iFt zCom2kT-CMi)>ge-hrwLgcFJ9D*kF~**A&b|+ud0$xn8Z*P}n_H>#%0RDD)wvQ;}qJ z>6aP8BEgWI!(kIvKO89_hPvQP!2o7?rN-jG4f{W=Q4Mn-vEkPNh6gFer3kd9(rBnF z&4wIWNaNsLN~;*_K0q;?Qd74arz}SA)FpUa?WUqIdDbE4PvW!biGeM&Q);Ppr+5a% z(a`mpPM(9g7P#P{M_AWZTG+?76sB32AlFm%sv*IdbhaZ&qm;@I0PU4hQI(knio+c! znq(X#D#q>y6X;B%y@?aL=fxJX!I%QRTawLpkO%Y7Y{%)Y23vDx#y%PQ>UK-5maaK^ zZ=kSJt_A0da2*_3NpQ-hUHS`M|H2leS9L;Kp@jjhS1w@nm1cyLpj3$YsE69bI=Rkz zn}dWc1lLUIGrXl{CusJ$dh5Dcs+C;$zVHgPW9A|W{}0B1P*aMlQ_xO#hbGN&;dUJ@ z7BOmswOKGrTj1s)dzETfMTWLtL+F&;Mjf%7cWFh!ro&Z5$N>=zq}XKBZeXNB@pxVJ z91l{7^I_y~I5?Meg~z$}bL7#GXjqB&^9ku4ufd1mh@xz;p@6p(2qhf9!O_ZekPVL7 ziKtg>wj=i_9Vx|XBbgVWJvZp0sc9aaAe3lv(+gU0o+r!o@*o`^?07PPC^`XkxD^9WtJxDx5E&Ei`;~+tHx4fw>Ud)9ygPH|k|)(D;DN z(WQ?)plcTqYYQBTvIUxbz2@j%y$LVL^KjixkZm&`*k4d|0G zV<1EaPK?Y>GwBxA?gSB$a;Sv0z4@1*} z7mWi?1V(4|>4Q4ctyc^ftgj|13z3H@ZPFCr;M5{mT?~zr#(;rO5lQlk6q95fkz`<` zrAIOx@*PV{EVsI_`3am^sx=&~BQg#vc4K7>lE^5~g~&(^i~uI=^d$$5f(rVj{RY(* z_dzwBEnkVB%LSm&>@?s3`0(AR2riU~t}eb~LcpFcJ6ol0!^dV-TTQjNXa9|*^G4lm zx47*Z49R{CG=5#Qp`o=hXTSTE3+xm-_BPqxmOXp&Hf5t#M6;eaIbST6^|7?-w4v1^ zlO=wAMctyYS4$-i;-h;CF91Qk)#>_b6}xOs_@GDYgm2(`vdh@oDEB#Gq`iOT$-x(< zQPtU2HGK54r45NJJTNLcKI8Zp8u-x85;Up`Cyk>RXpoF(3$qdHhrgo=*x336>H)CQ z>>#O*tmB*LD*ie67J3Emx#{BjPnF86PvX09HM5BO0ad{{p!vWxk@5uEM43EF=kZ~E*KSkMj+)Kf2w)?D$cdw+b`_6Cp#~xFbr9N_hJz7$2^tXk6l+jl> z%7A%?OFeOa<(XGYtd8=K^M`g2^50|lo_`e~zV*P3f^~e(;@{6fu1$m<{!QZ^K=|)t z%KFeMT2RJFv_WNy*qRF9HQ-xFJGgz&0=$h8?0{Ov+aL0{Q-Be~EfMuzeCMt{g%O=Y zIE{E7H7!HtpHk|5_u_5JA6XM_)Q&Dj*1^p-U#Y{(xT&CJeyQ*fsc zmq5iqmXvM+(t1SwIu9B#rql->1+<8K)W%Kpwg9`VpmmQ^Eg&cPGV zd+%LVozZ$uy{0Vk;Orr6qgT}GHYA>x8e39(fMIJnBUp~oQ>&K;8h-!0w!q&7k0EYo zRnWKH?P8zUE)TVLWK3O=Vg*WV!Y;yXythgv{toI;366*wn?@beO5lg^Wv0=<-Ls?v zk1D*IAyd&AAe4_WSh|5O3j@kcxd!Dg2}tQ-gMltch-d`hbQ8 zl1zmVgdyD@{D2WihJw;kp>9Hs_2z#CqIj7XJvU=hOfAlKL_)YnG4ks}+x z18S;()d7@)j6>{(!7?6TN4T0CXYL0y6jEsWHAO=qrGR^m^QH`;kg_qLXegu2z5Qht!P-HGl`yk<)`-XedevdU0A> z9Z~}8s#02SR7z`!dSJIMsIhR=)xqfKs*zwMRe~d@5*#Uo(d0O&lpY19DRZWEtR>4d z+oohhWz8N5q(Yoomw_`-O7~46mU0T_cK#py=`S6A<-!+#?Jxi3?|$d`FRFOnveL1< zvJwEYStc{M2WDju0LDu{RBtG4l3$h{3kkOY7VN#4VHIRzL_4w*$m&wsa!%09$z^+xsVWws$+* z)7f&p&r11Rf6By<>S7>_v1ZivIrI(0-aoY_5~i{{0q;BMyp`+S&GlaVh++RqIz5r? z-Qfp>>^A?Q)>qOR3__q@O$G_Zy87N-(~JcZpD^)LCSEr2SreZ#@kJA_oA`=}pEdDS z6IV=JGjYSj90kY9^?ehY_Vl&2g^%fWuF1KpfXmQb%bWjhZ}cJo8eW5FgOdK~rhDpO2QhZJdl4~-MzVPz-nv3yJ;6SkcI!(Km@vPRjjA6sLoylo$b zOjr~2Kx(RyBb5RfvJFx&FhXgNA{gk1U^>KfGKbJDrYF^*NtFf;Vahaf3=P)cq>7DN z2?!eUO(-if0ocxDGN@uwF&nPuv-OE&Dwdu|(Bh<-Bb`ntJCR8rOvSP{vp13F^A=vj zh?=32I>Di)*^%OSS(pHaSrOZR6LL4LNuX4(Cvu=3vx}X0GTVD8nFryeQK7tyshsV- zj5L~{Q7^&hp`{oeN%zOK07q#Hfr({CN8`#G9ZjitCNn-hPCos-aJwzxE#$Pvr6FP> zSvk!~ED2#8vTzw19YPY6gKg4LX*f*fS_%vpJbj86-%v2Qwy;?A?K^@%F);)LeQW^A$By zyJ4EX{z9&QBG-Q^*I&-{&*u8)a{Y_B{(7!|CD(s8*T0(US91MYuHVS@JFqXg{&uc^ z69y&Mzm@I%W;!iFau)g`s*+AZE)$bV#_WzgsYX+=T<2lJA1yZ;y_G_)vXCuCZ~ zCTDxUj*iEZ82O}%aVXH)1coLt&Y_~!?td0J#9(~GW|Mn&X^7mP%pedOh0^&3YZAI7x8;Ndid#2M8PhaFD>k z5%lnLQV(1A1A$-!0x+J0u3&zlBeauj4<{teA$vMP{W`4Dhb@-=k(55DHA`}c!cqZI z6~;rUT>ry>WrhQhIf(MeNaG0}RDFWr)sG7=3P?c!{WM~><togbAqr&74!d(AXM0qk9J_YF3n7KO{c1R=MbBIe6 zM>$YyAx+{f$;nYEVkgtEN@*J9!&Z6>CL>U#^y{BI{ML6K`!7#FsGVKy#&)rx^0DQb zB=S0TOLgpIc$Tq>u!(&Z_ktkvD1#eO91Xu(nTf@dS!YCNcI-zEjbHFT*2 zo>3;r4dI-;f>t$D{1BXMSS|<^Z`+o?^i!uE5WrFvtj0vbPU3$imW2P3guzZujN8ev zSTftNlcR`a&AP>f>o$X5;heck=zlTzV+8$&;awR$XEM)VT&BEUA(+0MNK&c6a2v_q ze}+}$^ww(342Q`0#ROWV@G5OQ$HS+4uq9y-AR;to7_+#DAs_P4XURQ{Wm8SahQZDd zV^2twnSe=F6XR+6V05TxXvb*e(&I25X}CUEin5h#C4n^&VP3&`0i3PS&;SGP1^x&; z5>Ogaq$Wjb*_sroNj8RQ_%LLK%!1RoJA&(EJA&&RplPQYlYke4wa3a#D&Vm201LOo z22&U_WJaxczz|qEBAPflNN3{MiQbe-Z%PI5S)>Ana5s!I0)}JgORuNP)2j7)5DePd z>y5Cny&nDpK`2e~OW3OjfaUTf5ywkG^0p*z2g#Qusr?x4aK;pvpcMY!)>YinRS*XR zjKvE&`ej|=Ew=R#AM!bI<|D*R*4#+6jtg#OA(ps~dz)Dhi>wU9aUBtDgiv_1C*u1fihp^P- zZF?etJsc`x%)y#SWM}D9EQtNnjsX=63(#HNuRFSC_)GFfd|5cAKipk1zF05O$7V``wpayMkSEP zB9ppBNeDzIg6N4U?l} zRzW)84ts!&O9W9vfdgTI14teWlLsjSy$uv3B+!;tX7%bxOFou0|{spP!w_iEYVJ&#u>;eB`Q9UY=0Q zo4XtNI&KTdbAMib@nXTtZ?#>M!!wtb=sWD-crzvHO0YQXj$V0`Txh8`nATRXJ{PJX-Vpj`wpjGu66Z>uye$+s&DBARatN z>&-;C!AUmGCdOpiQXh=aOikw26mDK!oqr_qjBenYS}6IqEOqM|pFv-@4=y%v9c&dB zD(a9V5F>>aE}-GE34eJ-oqO=UvQa~R5;r_5{+9e?=H5E+JoNn(J`ks;y|?gNzp!6n zYj3s#_t8LTx;XI)Eqx04hb#Lm==af0c4$vwu>5byAuI&^Ez_`w*!K>Kg=~3~?I#Qu z?8#4TAS=BOi6a{y`e%3FrfupS+B~G?CL1nY#(C=quP3o^qX1tQ;u2L1w^>u>%1}b? zj3o7Kk~kl-vkMFM#zG8_Aja^(VGIvG#_*V94396y@UUW>4-m%rkZYWe6vz3PVGNH< z#_-VNTf=|&u8wcu;U7-S7?~X!_0y2>;ss4IX>KniD~2EMWeEQk{{FRJQr7B4mVE`E zcpi4imaLMA5PoP@`uQqouSRIhz%6a`@6y`x+TE{R_<)-&eC*Y)J~;C8ZywB&$4m{6 z1I@TyKG8fwn;4z&xVVOUkIsIm4xZVnmuJej;p%uZn{{0KZB@O;%D5oyc4ov(1`kNh z;DYsxv9B|chtXh-qnvl!s_71%rCmF_2$3t(?oaNRc0)D~%*4L-Z%>StYp)*pqhH03 zt_-^Re!}@dau@JmB%e~nrMA1=Xsnj%E!D(hHLl}K^8z*h-ueJ|>=ofIUjOeV_)ZMI zFGKZq_d(kqEJQCI2yhH1!CZV#ab94w*d`rcMUw4ei7 zv8PpxzmGo7qb2kFULSuIY0Cver_|Y&pf5~uy#Fe43EQ-Q_qABil<19?=-BPwU!^{c zw_)uqw^zEMY#&jNqfbvl6BiJd5zY#99Csa#;+@9JC5@@E*hypqKFB zyOqkoJuY~-sQ6ouv0a1~QCoT9%=6 exec_insert_timestamp(), "insert-call-timestamp" => exec_insert_call_timestamp(), + "insert-uuid" => exec_insert_uuid(), + "insert-call-uuid-v4" => exec_insert_caller_uuid_v4(), + "insert-call-uuid-v7" => exec_insert_caller_uuid_v7(), + "delete-uuid" => exec_delete_uuid(), + "update-uuid" => exec_update_uuid(), + "on-reducer" => exec_on_reducer(), "fail-reducer" => exec_fail_reducer(), @@ -134,6 +140,8 @@ fn main() { "overlapping-subscriptions" => exec_overlapping_subscriptions(), + "sorted-uuids-insert" => exec_sorted_uuids_insert(), + _ => panic!("Unknown test: {test}"), } } @@ -178,6 +186,7 @@ fn assert_all_tables_empty(ctx: &impl RemoteDbContext) -> anyhow::Result<()> { assert_table_empty(ctx.db().one_connection_id())?; assert_table_empty(ctx.db().one_timestamp())?; + assert_table_empty(ctx.db().one_uuid())?; assert_table_empty(ctx.db().one_simple_enum())?; assert_table_empty(ctx.db().one_enum_with_payload())?; @@ -211,6 +220,7 @@ fn assert_all_tables_empty(ctx: &impl RemoteDbContext) -> anyhow::Result<()> { assert_table_empty(ctx.db().vec_connection_id())?; assert_table_empty(ctx.db().vec_timestamp())?; + assert_table_empty(ctx.db().vec_uuid())?; assert_table_empty(ctx.db().vec_simple_enum())?; assert_table_empty(ctx.db().vec_enum_with_payload())?; @@ -223,6 +233,7 @@ fn assert_all_tables_empty(ctx: &impl RemoteDbContext) -> anyhow::Result<()> { assert_table_empty(ctx.db().option_i_32())?; assert_table_empty(ctx.db().option_string())?; assert_table_empty(ctx.db().option_identity())?; + assert_table_empty(ctx.db().option_uuid())?; assert_table_empty(ctx.db().option_simple_enum())?; assert_table_empty(ctx.db().option_every_primitive_struct())?; assert_table_empty(ctx.db().option_vec_option_i_32())?; @@ -246,6 +257,7 @@ fn assert_all_tables_empty(ctx: &impl RemoteDbContext) -> anyhow::Result<()> { assert_table_empty(ctx.db().unique_string())?; assert_table_empty(ctx.db().unique_identity())?; assert_table_empty(ctx.db().unique_connection_id())?; + assert_table_empty(ctx.db().unique_uuid())?; assert_table_empty(ctx.db().pk_u_8())?; assert_table_empty(ctx.db().pk_u_16())?; @@ -266,6 +278,7 @@ fn assert_all_tables_empty(ctx: &impl RemoteDbContext) -> anyhow::Result<()> { assert_table_empty(ctx.db().pk_string())?; assert_table_empty(ctx.db().pk_identity())?; assert_table_empty(ctx.db().pk_connection_id())?; + assert_table_empty(ctx.db().pk_uuid())?; assert_table_empty(ctx.db().large_table())?; @@ -295,6 +308,7 @@ const SUBSCRIBE_ALL: &[&str] = &[ "SELECT * FROM one_identity;", "SELECT * FROM one_connection_id;", "SELECT * FROM one_timestamp;", + "SELECT * FROM one_uuid;", "SELECT * FROM one_simple_enum;", "SELECT * FROM one_enum_with_payload;", "SELECT * FROM one_unit_struct;", @@ -320,6 +334,7 @@ const SUBSCRIBE_ALL: &[&str] = &[ "SELECT * FROM vec_identity;", "SELECT * FROM vec_connection_id;", "SELECT * FROM vec_timestamp;", + "SELECT * FROM vec_uuid;", "SELECT * FROM vec_simple_enum;", "SELECT * FROM vec_enum_with_payload;", "SELECT * FROM vec_unit_struct;", @@ -329,6 +344,7 @@ const SUBSCRIBE_ALL: &[&str] = &[ "SELECT * FROM option_i32;", "SELECT * FROM option_string;", "SELECT * FROM option_identity;", + "SELECT * FROM option_uuid;", "SELECT * FROM option_simple_enum;", "SELECT * FROM option_every_primitive_struct;", "SELECT * FROM option_vec_option_i32;", @@ -348,6 +364,7 @@ const SUBSCRIBE_ALL: &[&str] = &[ "SELECT * FROM unique_string;", "SELECT * FROM unique_identity;", "SELECT * FROM unique_connection_id;", + "SELECT * FROM unique_uuid;", "SELECT * FROM pk_u8;", "SELECT * FROM pk_u16;", "SELECT * FROM pk_u32;", @@ -364,6 +381,7 @@ const SUBSCRIBE_ALL: &[&str] = &[ "SELECT * FROM pk_string;", "SELECT * FROM pk_identity;", "SELECT * FROM pk_connection_id;", + "SELECT * FROM pk_uuid;", "SELECT * FROM large_table;", "SELECT * FROM table_holds_table;", ]; @@ -865,6 +883,93 @@ fn exec_insert_call_timestamp() { test_counter.wait_for_all(); } +/// This tests that we can serialize and deserialize `Uuid` in various contexts. +fn exec_insert_uuid() { + let test_counter = TestCounter::new(); + let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + subscribe_all_then(ctx, move |ctx| { + insert_one::(ctx, &test_counter, Uuid::NIL); + + sub_applied_nothing_result(assert_all_tables_empty(ctx)); + }); + } + }); + + test_counter.wait_for_all(); +} + +/// This tests that we can serialize and deserialize `Uuid` in various contexts. +fn exec_insert_caller_uuid_v4() { + /* let test_counter = TestCounter::new(); + let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); + + connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + subscribe_all_then(ctx, move |ctx| { + on_insert_one::(ctx, &test_counter, ctx.uuid(), |event| { + matches!(event, Reducer::InsertCallerOneUuid) + }); + ctx.reducers.insert_caller_one_uuid().unwrap(); + sub_applied_nothing_result(assert_all_tables_empty(ctx)); + }); + } + }); + + test_counter.wait_for_all();*/ +} + +/// This tests that we can serialize and deserialize `Uuid` in various contexts. +fn exec_insert_caller_uuid_v7() {} + +/// This test doesn't add much alongside `exec_insert_uuid` and `exec_delete_primitive`, +/// but it's here for symmetry. +fn exec_delete_uuid() { + let test_counter = TestCounter::new(); + let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); + + let connection = connect_then(&test_counter, { + let test_counter = test_counter.clone(); + move |ctx| { + subscribe_all_then(ctx, move |ctx| { + insert_then_delete_one::(ctx, &test_counter, Uuid::NIL, 0xbeef); + + sub_applied_nothing_result(assert_all_tables_empty(ctx)); + }); + } + }); + + test_counter.wait_for_all(); + + assert_all_tables_empty(&connection).unwrap(); +} + +/// This tests that we can distinguish between `on_delete` and `on_update` events +/// for tables with `Uuid` primary keys. +fn exec_update_uuid() { + let test_counter = TestCounter::new(); + let sub_applied_nothing_result = test_counter.add_test("on_subscription_applied_nothing"); + + let connection = connect(&test_counter); + + subscribe_all_then(&connection, { + let test_counter = test_counter.clone(); + move |ctx| { + insert_update_delete_one::(ctx, &test_counter, Uuid::NIL, 0xbeef, 0xbabe); + + sub_applied_nothing_result(assert_all_tables_empty(ctx)); + } + }); + + test_counter.wait_for_all(); + + assert_all_tables_empty(&connection).unwrap(); +} + /// This tests that we can observe reducer callbacks for successful reducer runs. fn exec_on_reducer() { let test_counter = TestCounter::new(); @@ -1138,6 +1243,7 @@ fn every_primitive_struct() -> EveryPrimitiveStruct { r: ConnectionId::ZERO, s: Timestamp::from_micros_since_unix_epoch(9876543210), t: TimeDuration::from_micros(-67_419_000_000_003), + u: Uuid::NIL, } } @@ -1163,6 +1269,7 @@ fn every_vec_struct() -> EveryVecStruct { r: vec![ConnectionId::ZERO], s: vec![Timestamp::from_micros_since_unix_epoch(9876543210)], t: vec![TimeDuration::from_micros(-67_419_000_000_003)], + u: vec![Uuid::NIL], } } @@ -1502,6 +1609,7 @@ fn exec_insert_primitives_as_strings() { s.r.to_string(), s.s.to_string(), s.t.to_string(), + s.u.to_string(), ]; ctx.db.vec_string().on_insert(move |ctx, row| { @@ -1826,6 +1934,36 @@ fn exec_subscribe_all_select_star() { test_counter.wait_for_all(); } +fn exec_sorted_uuids_insert() { + let test_counter = TestCounter::new(); + let sub_applied_nothing_result = test_counter.add_test("sorted-uuids-insert"); + + let connection = connect(&test_counter); + + subscribe_all_then(&connection, { + let test_counter = test_counter.clone(); + move |ctx| { + ctx.reducers.on_sorted_uuids_insert(move |ctx| { + let run_checks = || { + if !matches!(ctx.event.reducer, Reducer::SortedUuidsInsert) { + anyhow::bail!( + "Unexpected Event: expected reducer SortedUuidsInsert but found {:?}", + ctx.event, + ); + } + Ok(()) + }; + test_counter.add_test("sorted-uuids-insert-callback")(run_checks()); + }); + ctx.reducers.sorted_uuids_insert().unwrap(); + + sub_applied_nothing_result(assert_all_tables_empty(ctx)) + } + }); + + test_counter.wait_for_all(); +} + fn exec_caller_alice_receives_reducer_callback_but_not_bob() { fn check_val(val: T, eq: T) -> anyhow::Result<()> { (val == eq) diff --git a/sdks/rust/tests/test-client/src/module_bindings/delete_pk_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/delete_pk_uuid_reducer.rs new file mode 100644 index 00000000000..c44b12ddb3a --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/delete_pk_uuid_reducer.rs @@ -0,0 +1,101 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct DeletePkUuidArgs { + pub u: __sdk::Uuid, +} + +impl From for super::Reducer { + fn from(args: DeletePkUuidArgs) -> Self { + Self::DeletePkUuid { u: args.u } + } +} + +impl __sdk::InModule for DeletePkUuidArgs { + type Module = super::RemoteModule; +} + +pub struct DeletePkUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `delete_pk_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait delete_pk_uuid { + /// Request that the remote module invoke the reducer `delete_pk_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_delete_pk_uuid`] callbacks. + fn delete_pk_uuid(&self, u: __sdk::Uuid) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_pk_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`DeletePkUuidCallbackId`] can be passed to [`Self::remove_on_delete_pk_uuid`] + /// to cancel the callback. + fn on_delete_pk_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid) + Send + 'static, + ) -> DeletePkUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_delete_pk_uuid`], + /// causing it not to run in the future. + fn remove_on_delete_pk_uuid(&self, callback: DeletePkUuidCallbackId); +} + +impl delete_pk_uuid for super::RemoteReducers { + fn delete_pk_uuid(&self, u: __sdk::Uuid) -> __sdk::Result<()> { + self.imp.call_reducer("delete_pk_uuid", DeletePkUuidArgs { u }) + } + fn on_delete_pk_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid) + Send + 'static, + ) -> DeletePkUuidCallbackId { + DeletePkUuidCallbackId(self.imp.on_reducer( + "delete_pk_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::DeletePkUuid { u }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u) + }), + )) + } + fn remove_on_delete_pk_uuid(&self, callback: DeletePkUuidCallbackId) { + self.imp.remove_on_reducer("delete_pk_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `delete_pk_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_delete_pk_uuid { + /// Set the call-reducer flags for the reducer `delete_pk_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn delete_pk_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_delete_pk_uuid for super::SetReducerFlags { + fn delete_pk_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("delete_pk_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/delete_unique_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/delete_unique_uuid_reducer.rs new file mode 100644 index 00000000000..079f2f6b756 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/delete_unique_uuid_reducer.rs @@ -0,0 +1,101 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct DeleteUniqueUuidArgs { + pub u: __sdk::Uuid, +} + +impl From for super::Reducer { + fn from(args: DeleteUniqueUuidArgs) -> Self { + Self::DeleteUniqueUuid { u: args.u } + } +} + +impl __sdk::InModule for DeleteUniqueUuidArgs { + type Module = super::RemoteModule; +} + +pub struct DeleteUniqueUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `delete_unique_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait delete_unique_uuid { + /// Request that the remote module invoke the reducer `delete_unique_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_delete_unique_uuid`] callbacks. + fn delete_unique_uuid(&self, u: __sdk::Uuid) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `delete_unique_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`DeleteUniqueUuidCallbackId`] can be passed to [`Self::remove_on_delete_unique_uuid`] + /// to cancel the callback. + fn on_delete_unique_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid) + Send + 'static, + ) -> DeleteUniqueUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_delete_unique_uuid`], + /// causing it not to run in the future. + fn remove_on_delete_unique_uuid(&self, callback: DeleteUniqueUuidCallbackId); +} + +impl delete_unique_uuid for super::RemoteReducers { + fn delete_unique_uuid(&self, u: __sdk::Uuid) -> __sdk::Result<()> { + self.imp.call_reducer("delete_unique_uuid", DeleteUniqueUuidArgs { u }) + } + fn on_delete_unique_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid) + Send + 'static, + ) -> DeleteUniqueUuidCallbackId { + DeleteUniqueUuidCallbackId(self.imp.on_reducer( + "delete_unique_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::DeleteUniqueUuid { u }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u) + }), + )) + } + fn remove_on_delete_unique_uuid(&self, callback: DeleteUniqueUuidCallbackId) { + self.imp.remove_on_reducer("delete_unique_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `delete_unique_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_delete_unique_uuid { + /// Set the call-reducer flags for the reducer `delete_unique_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn delete_unique_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_delete_unique_uuid for super::SetReducerFlags { + fn delete_unique_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("delete_unique_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/enum_with_payload_type.rs b/sdks/rust/tests/test-client/src/module_bindings/enum_with_payload_type.rs index f79014a57cb..9ea415cfc21 100644 --- a/sdks/rust/tests/test-client/src/module_bindings/enum_with_payload_type.rs +++ b/sdks/rust/tests/test-client/src/module_bindings/enum_with_payload_type.rs @@ -47,6 +47,8 @@ pub enum EnumWithPayload { Timestamp(__sdk::Timestamp), + Uuid(__sdk::Uuid), + Bytes(Vec), Ints(Vec), diff --git a/sdks/rust/tests/test-client/src/module_bindings/every_primitive_struct_type.rs b/sdks/rust/tests/test-client/src/module_bindings/every_primitive_struct_type.rs index 4a3e85173c4..00545a3095b 100644 --- a/sdks/rust/tests/test-client/src/module_bindings/every_primitive_struct_type.rs +++ b/sdks/rust/tests/test-client/src/module_bindings/every_primitive_struct_type.rs @@ -27,6 +27,7 @@ pub struct EveryPrimitiveStruct { pub r: __sdk::ConnectionId, pub s: __sdk::Timestamp, pub t: __sdk::TimeDuration, + pub u: __sdk::Uuid, } impl __sdk::InModule for EveryPrimitiveStruct { diff --git a/sdks/rust/tests/test-client/src/module_bindings/every_vec_struct_type.rs b/sdks/rust/tests/test-client/src/module_bindings/every_vec_struct_type.rs index 0356089cda9..e2251ceeccf 100644 --- a/sdks/rust/tests/test-client/src/module_bindings/every_vec_struct_type.rs +++ b/sdks/rust/tests/test-client/src/module_bindings/every_vec_struct_type.rs @@ -27,6 +27,7 @@ pub struct EveryVecStruct { pub r: Vec<__sdk::ConnectionId>, pub s: Vec<__sdk::Timestamp>, pub t: Vec<__sdk::TimeDuration>, + pub u: Vec<__sdk::Uuid>, } impl __sdk::InModule for EveryVecStruct { diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_4_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_4_reducer.rs new file mode 100644 index 00000000000..d7c9dd9402f --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_4_reducer.rs @@ -0,0 +1,99 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertCallUuidV4Args {} + +impl From for super::Reducer { + fn from(args: InsertCallUuidV4Args) -> Self { + Self::InsertCallUuidV4 + } +} + +impl __sdk::InModule for InsertCallUuidV4Args { + type Module = super::RemoteModule; +} + +pub struct InsertCallUuidV4CallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_call_uuid_v4`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_call_uuid_v_4 { + /// Request that the remote module invoke the reducer `insert_call_uuid_v4` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_call_uuid_v_4`] callbacks. + fn insert_call_uuid_v_4(&self) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_call_uuid_v4`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertCallUuidV4CallbackId`] can be passed to [`Self::remove_on_insert_call_uuid_v_4`] + /// to cancel the callback. + fn on_insert_call_uuid_v_4( + &self, + callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> InsertCallUuidV4CallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_call_uuid_v_4`], + /// causing it not to run in the future. + fn remove_on_insert_call_uuid_v_4(&self, callback: InsertCallUuidV4CallbackId); +} + +impl insert_call_uuid_v_4 for super::RemoteReducers { + fn insert_call_uuid_v_4(&self) -> __sdk::Result<()> { + self.imp.call_reducer("insert_call_uuid_v4", InsertCallUuidV4Args {}) + } + fn on_insert_call_uuid_v_4( + &self, + mut callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> InsertCallUuidV4CallbackId { + InsertCallUuidV4CallbackId(self.imp.on_reducer( + "insert_call_uuid_v4", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertCallUuidV4 {}, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx) + }), + )) + } + fn remove_on_insert_call_uuid_v_4(&self, callback: InsertCallUuidV4CallbackId) { + self.imp.remove_on_reducer("insert_call_uuid_v4", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_call_uuid_v4`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_call_uuid_v_4 { + /// Set the call-reducer flags for the reducer `insert_call_uuid_v4` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_call_uuid_v_4(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_call_uuid_v_4 for super::SetReducerFlags { + fn insert_call_uuid_v_4(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_call_uuid_v4", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_7_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_7_reducer.rs new file mode 100644 index 00000000000..f6d28b93b99 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_call_uuid_v_7_reducer.rs @@ -0,0 +1,99 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertCallUuidV7Args {} + +impl From for super::Reducer { + fn from(args: InsertCallUuidV7Args) -> Self { + Self::InsertCallUuidV7 + } +} + +impl __sdk::InModule for InsertCallUuidV7Args { + type Module = super::RemoteModule; +} + +pub struct InsertCallUuidV7CallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_call_uuid_v7`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_call_uuid_v_7 { + /// Request that the remote module invoke the reducer `insert_call_uuid_v7` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_call_uuid_v_7`] callbacks. + fn insert_call_uuid_v_7(&self) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_call_uuid_v7`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertCallUuidV7CallbackId`] can be passed to [`Self::remove_on_insert_call_uuid_v_7`] + /// to cancel the callback. + fn on_insert_call_uuid_v_7( + &self, + callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> InsertCallUuidV7CallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_call_uuid_v_7`], + /// causing it not to run in the future. + fn remove_on_insert_call_uuid_v_7(&self, callback: InsertCallUuidV7CallbackId); +} + +impl insert_call_uuid_v_7 for super::RemoteReducers { + fn insert_call_uuid_v_7(&self) -> __sdk::Result<()> { + self.imp.call_reducer("insert_call_uuid_v7", InsertCallUuidV7Args {}) + } + fn on_insert_call_uuid_v_7( + &self, + mut callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> InsertCallUuidV7CallbackId { + InsertCallUuidV7CallbackId(self.imp.on_reducer( + "insert_call_uuid_v7", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertCallUuidV7 {}, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx) + }), + )) + } + fn remove_on_insert_call_uuid_v_7(&self, callback: InsertCallUuidV7CallbackId) { + self.imp.remove_on_reducer("insert_call_uuid_v7", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_call_uuid_v7`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_call_uuid_v_7 { + /// Set the call-reducer flags for the reducer `insert_call_uuid_v7` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_call_uuid_v_7(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_call_uuid_v_7 for super::SetReducerFlags { + fn insert_call_uuid_v_7(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_call_uuid_v7", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_one_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_one_uuid_reducer.rs new file mode 100644 index 00000000000..4907cbe753f --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_one_uuid_reducer.rs @@ -0,0 +1,101 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertOneUuidArgs { + pub u: __sdk::Uuid, +} + +impl From for super::Reducer { + fn from(args: InsertOneUuidArgs) -> Self { + Self::InsertOneUuid { u: args.u } + } +} + +impl __sdk::InModule for InsertOneUuidArgs { + type Module = super::RemoteModule; +} + +pub struct InsertOneUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_one_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_one_uuid { + /// Request that the remote module invoke the reducer `insert_one_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_one_uuid`] callbacks. + fn insert_one_uuid(&self, u: __sdk::Uuid) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_one_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertOneUuidCallbackId`] can be passed to [`Self::remove_on_insert_one_uuid`] + /// to cancel the callback. + fn on_insert_one_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid) + Send + 'static, + ) -> InsertOneUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_one_uuid`], + /// causing it not to run in the future. + fn remove_on_insert_one_uuid(&self, callback: InsertOneUuidCallbackId); +} + +impl insert_one_uuid for super::RemoteReducers { + fn insert_one_uuid(&self, u: __sdk::Uuid) -> __sdk::Result<()> { + self.imp.call_reducer("insert_one_uuid", InsertOneUuidArgs { u }) + } + fn on_insert_one_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid) + Send + 'static, + ) -> InsertOneUuidCallbackId { + InsertOneUuidCallbackId(self.imp.on_reducer( + "insert_one_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertOneUuid { u }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u) + }), + )) + } + fn remove_on_insert_one_uuid(&self, callback: InsertOneUuidCallbackId) { + self.imp.remove_on_reducer("insert_one_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_one_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_one_uuid { + /// Set the call-reducer flags for the reducer `insert_one_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_one_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_one_uuid for super::SetReducerFlags { + fn insert_one_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_one_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_option_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_option_uuid_reducer.rs new file mode 100644 index 00000000000..a9d2640151b --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_option_uuid_reducer.rs @@ -0,0 +1,101 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertOptionUuidArgs { + pub u: Option<__sdk::Uuid>, +} + +impl From for super::Reducer { + fn from(args: InsertOptionUuidArgs) -> Self { + Self::InsertOptionUuid { u: args.u } + } +} + +impl __sdk::InModule for InsertOptionUuidArgs { + type Module = super::RemoteModule; +} + +pub struct InsertOptionUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_option_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_option_uuid { + /// Request that the remote module invoke the reducer `insert_option_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_option_uuid`] callbacks. + fn insert_option_uuid(&self, u: Option<__sdk::Uuid>) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_option_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertOptionUuidCallbackId`] can be passed to [`Self::remove_on_insert_option_uuid`] + /// to cancel the callback. + fn on_insert_option_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &Option<__sdk::Uuid>) + Send + 'static, + ) -> InsertOptionUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_option_uuid`], + /// causing it not to run in the future. + fn remove_on_insert_option_uuid(&self, callback: InsertOptionUuidCallbackId); +} + +impl insert_option_uuid for super::RemoteReducers { + fn insert_option_uuid(&self, u: Option<__sdk::Uuid>) -> __sdk::Result<()> { + self.imp.call_reducer("insert_option_uuid", InsertOptionUuidArgs { u }) + } + fn on_insert_option_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &Option<__sdk::Uuid>) + Send + 'static, + ) -> InsertOptionUuidCallbackId { + InsertOptionUuidCallbackId(self.imp.on_reducer( + "insert_option_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertOptionUuid { u }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u) + }), + )) + } + fn remove_on_insert_option_uuid(&self, callback: InsertOptionUuidCallbackId) { + self.imp.remove_on_reducer("insert_option_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_option_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_option_uuid { + /// Set the call-reducer flags for the reducer `insert_option_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_option_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_option_uuid for super::SetReducerFlags { + fn insert_option_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_option_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_pk_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_pk_uuid_reducer.rs new file mode 100644 index 00000000000..35666a1782d --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_pk_uuid_reducer.rs @@ -0,0 +1,105 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertPkUuidArgs { + pub u: __sdk::Uuid, + pub data: i32, +} + +impl From for super::Reducer { + fn from(args: InsertPkUuidArgs) -> Self { + Self::InsertPkUuid { + u: args.u, + data: args.data, + } + } +} + +impl __sdk::InModule for InsertPkUuidArgs { + type Module = super::RemoteModule; +} + +pub struct InsertPkUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_pk_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_pk_uuid { + /// Request that the remote module invoke the reducer `insert_pk_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_pk_uuid`] callbacks. + fn insert_pk_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_pk_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertPkUuidCallbackId`] can be passed to [`Self::remove_on_insert_pk_uuid`] + /// to cancel the callback. + fn on_insert_pk_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> InsertPkUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_pk_uuid`], + /// causing it not to run in the future. + fn remove_on_insert_pk_uuid(&self, callback: InsertPkUuidCallbackId); +} + +impl insert_pk_uuid for super::RemoteReducers { + fn insert_pk_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()> { + self.imp.call_reducer("insert_pk_uuid", InsertPkUuidArgs { u, data }) + } + fn on_insert_pk_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> InsertPkUuidCallbackId { + InsertPkUuidCallbackId(self.imp.on_reducer( + "insert_pk_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertPkUuid { u, data }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u, data) + }), + )) + } + fn remove_on_insert_pk_uuid(&self, callback: InsertPkUuidCallbackId) { + self.imp.remove_on_reducer("insert_pk_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_pk_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_pk_uuid { + /// Set the call-reducer flags for the reducer `insert_pk_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_pk_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_pk_uuid for super::SetReducerFlags { + fn insert_pk_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_pk_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_unique_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_unique_uuid_reducer.rs new file mode 100644 index 00000000000..c020d5adc31 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_unique_uuid_reducer.rs @@ -0,0 +1,106 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertUniqueUuidArgs { + pub u: __sdk::Uuid, + pub data: i32, +} + +impl From for super::Reducer { + fn from(args: InsertUniqueUuidArgs) -> Self { + Self::InsertUniqueUuid { + u: args.u, + data: args.data, + } + } +} + +impl __sdk::InModule for InsertUniqueUuidArgs { + type Module = super::RemoteModule; +} + +pub struct InsertUniqueUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_unique_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_unique_uuid { + /// Request that the remote module invoke the reducer `insert_unique_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_unique_uuid`] callbacks. + fn insert_unique_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_unique_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertUniqueUuidCallbackId`] can be passed to [`Self::remove_on_insert_unique_uuid`] + /// to cancel the callback. + fn on_insert_unique_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> InsertUniqueUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_unique_uuid`], + /// causing it not to run in the future. + fn remove_on_insert_unique_uuid(&self, callback: InsertUniqueUuidCallbackId); +} + +impl insert_unique_uuid for super::RemoteReducers { + fn insert_unique_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()> { + self.imp + .call_reducer("insert_unique_uuid", InsertUniqueUuidArgs { u, data }) + } + fn on_insert_unique_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> InsertUniqueUuidCallbackId { + InsertUniqueUuidCallbackId(self.imp.on_reducer( + "insert_unique_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertUniqueUuid { u, data }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u, data) + }), + )) + } + fn remove_on_insert_unique_uuid(&self, callback: InsertUniqueUuidCallbackId) { + self.imp.remove_on_reducer("insert_unique_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_unique_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_unique_uuid { + /// Set the call-reducer flags for the reducer `insert_unique_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_unique_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_unique_uuid for super::SetReducerFlags { + fn insert_unique_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_unique_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/insert_vec_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/insert_vec_uuid_reducer.rs new file mode 100644 index 00000000000..a9535ca93c9 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/insert_vec_uuid_reducer.rs @@ -0,0 +1,101 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct InsertVecUuidArgs { + pub u: Vec<__sdk::Uuid>, +} + +impl From for super::Reducer { + fn from(args: InsertVecUuidArgs) -> Self { + Self::InsertVecUuid { u: args.u } + } +} + +impl __sdk::InModule for InsertVecUuidArgs { + type Module = super::RemoteModule; +} + +pub struct InsertVecUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `insert_vec_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait insert_vec_uuid { + /// Request that the remote module invoke the reducer `insert_vec_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_insert_vec_uuid`] callbacks. + fn insert_vec_uuid(&self, u: Vec<__sdk::Uuid>) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `insert_vec_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`InsertVecUuidCallbackId`] can be passed to [`Self::remove_on_insert_vec_uuid`] + /// to cancel the callback. + fn on_insert_vec_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &Vec<__sdk::Uuid>) + Send + 'static, + ) -> InsertVecUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_insert_vec_uuid`], + /// causing it not to run in the future. + fn remove_on_insert_vec_uuid(&self, callback: InsertVecUuidCallbackId); +} + +impl insert_vec_uuid for super::RemoteReducers { + fn insert_vec_uuid(&self, u: Vec<__sdk::Uuid>) -> __sdk::Result<()> { + self.imp.call_reducer("insert_vec_uuid", InsertVecUuidArgs { u }) + } + fn on_insert_vec_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &Vec<__sdk::Uuid>) + Send + 'static, + ) -> InsertVecUuidCallbackId { + InsertVecUuidCallbackId(self.imp.on_reducer( + "insert_vec_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::InsertVecUuid { u }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u) + }), + )) + } + fn remove_on_insert_vec_uuid(&self, callback: InsertVecUuidCallbackId) { + self.imp.remove_on_reducer("insert_vec_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `insert_vec_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_insert_vec_uuid { + /// Set the call-reducer flags for the reducer `insert_vec_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn insert_vec_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_insert_vec_uuid for super::SetReducerFlags { + fn insert_vec_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("insert_vec_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/mod.rs b/sdks/rust/tests/test-client/src/module_bindings/mod.rs index 5957aee2af3..bfc915ac600 100644 --- a/sdks/rust/tests/test-client/src/module_bindings/mod.rs +++ b/sdks/rust/tests/test-client/src/module_bindings/mod.rs @@ -29,6 +29,7 @@ pub mod delete_pk_u_32_reducer; pub mod delete_pk_u_32_two_reducer; pub mod delete_pk_u_64_reducer; pub mod delete_pk_u_8_reducer; +pub mod delete_pk_uuid_reducer; pub mod delete_unique_bool_reducer; pub mod delete_unique_connection_id_reducer; pub mod delete_unique_i_128_reducer; @@ -45,6 +46,7 @@ pub mod delete_unique_u_256_reducer; pub mod delete_unique_u_32_reducer; pub mod delete_unique_u_64_reducer; pub mod delete_unique_u_8_reducer; +pub mod delete_unique_uuid_reducer; pub mod enum_with_payload_type; pub mod every_primitive_struct_type; pub mod every_vec_struct_type; @@ -55,6 +57,8 @@ pub mod indexed_table_2_type; pub mod indexed_table_table; pub mod indexed_table_type; pub mod insert_call_timestamp_reducer; +pub mod insert_call_uuid_v_4_reducer; +pub mod insert_call_uuid_v_7_reducer; pub mod insert_caller_one_connection_id_reducer; pub mod insert_caller_one_identity_reducer; pub mod insert_caller_pk_connection_id_reducer; @@ -92,11 +96,13 @@ pub mod insert_one_u_32_reducer; pub mod insert_one_u_64_reducer; pub mod insert_one_u_8_reducer; pub mod insert_one_unit_struct_reducer; +pub mod insert_one_uuid_reducer; pub mod insert_option_every_primitive_struct_reducer; pub mod insert_option_i_32_reducer; pub mod insert_option_identity_reducer; pub mod insert_option_simple_enum_reducer; pub mod insert_option_string_reducer; +pub mod insert_option_uuid_reducer; pub mod insert_option_vec_option_i_32_reducer; pub mod insert_pk_bool_reducer; pub mod insert_pk_connection_id_reducer; @@ -116,6 +122,7 @@ pub mod insert_pk_u_32_reducer; pub mod insert_pk_u_32_two_reducer; pub mod insert_pk_u_64_reducer; pub mod insert_pk_u_8_reducer; +pub mod insert_pk_uuid_reducer; pub mod insert_primitives_as_strings_reducer; pub mod insert_table_holds_table_reducer; pub mod insert_unique_bool_reducer; @@ -135,6 +142,7 @@ pub mod insert_unique_u_32_reducer; pub mod insert_unique_u_32_update_pk_u_32_reducer; pub mod insert_unique_u_64_reducer; pub mod insert_unique_u_8_reducer; +pub mod insert_unique_uuid_reducer; pub mod insert_user_reducer; pub mod insert_vec_bool_reducer; pub mod insert_vec_byte_struct_reducer; @@ -161,6 +169,7 @@ pub mod insert_vec_u_32_reducer; pub mod insert_vec_u_64_reducer; pub mod insert_vec_u_8_reducer; pub mod insert_vec_unit_struct_reducer; +pub mod insert_vec_uuid_reducer; pub mod large_table_table; pub mod large_table_type; pub mod no_op_succeeds_reducer; @@ -214,6 +223,8 @@ pub mod one_u_8_table; pub mod one_u_8_type; pub mod one_unit_struct_table; pub mod one_unit_struct_type; +pub mod one_uuid_table; +pub mod one_uuid_type; pub mod option_every_primitive_struct_table; pub mod option_every_primitive_struct_type; pub mod option_i_32_table; @@ -224,6 +235,8 @@ pub mod option_simple_enum_table; pub mod option_simple_enum_type; pub mod option_string_table; pub mod option_string_type; +pub mod option_uuid_table; +pub mod option_uuid_type; pub mod option_vec_option_i_32_table; pub mod option_vec_option_i_32_type; pub mod pk_bool_table; @@ -262,10 +275,13 @@ pub mod pk_u_64_table; pub mod pk_u_64_type; pub mod pk_u_8_table; pub mod pk_u_8_type; +pub mod pk_uuid_table; +pub mod pk_uuid_type; pub mod scheduled_table_table; pub mod scheduled_table_type; pub mod send_scheduled_message_reducer; pub mod simple_enum_type; +pub mod sorted_uuids_insert_reducer; pub mod table_holds_table_table; pub mod table_holds_table_type; pub mod unique_bool_table; @@ -300,6 +316,8 @@ pub mod unique_u_64_table; pub mod unique_u_64_type; pub mod unique_u_8_table; pub mod unique_u_8_type; +pub mod unique_uuid_table; +pub mod unique_uuid_type; pub mod unit_struct_type; pub mod update_indexed_simple_enum_reducer; pub mod update_pk_bool_reducer; @@ -320,6 +338,7 @@ pub mod update_pk_u_32_reducer; pub mod update_pk_u_32_two_reducer; pub mod update_pk_u_64_reducer; pub mod update_pk_u_8_reducer; +pub mod update_pk_uuid_reducer; pub mod update_unique_bool_reducer; pub mod update_unique_connection_id_reducer; pub mod update_unique_i_128_reducer; @@ -336,6 +355,7 @@ pub mod update_unique_u_256_reducer; pub mod update_unique_u_32_reducer; pub mod update_unique_u_64_reducer; pub mod update_unique_u_8_reducer; +pub mod update_unique_uuid_reducer; pub mod users_table; pub mod users_type; pub mod vec_bool_table; @@ -388,6 +408,8 @@ pub mod vec_u_8_table; pub mod vec_u_8_type; pub mod vec_unit_struct_table; pub mod vec_unit_struct_type; +pub mod vec_uuid_table; +pub mod vec_uuid_type; pub use b_tree_u_32_type::BTreeU32; pub use btree_u_32_table::*; @@ -423,6 +445,7 @@ pub use delete_pk_u_32_reducer::{delete_pk_u_32, set_flags_for_delete_pk_u_32, D pub use delete_pk_u_32_two_reducer::{delete_pk_u_32_two, set_flags_for_delete_pk_u_32_two, DeletePkU32TwoCallbackId}; pub use delete_pk_u_64_reducer::{delete_pk_u_64, set_flags_for_delete_pk_u_64, DeletePkU64CallbackId}; pub use delete_pk_u_8_reducer::{delete_pk_u_8, set_flags_for_delete_pk_u_8, DeletePkU8CallbackId}; +pub use delete_pk_uuid_reducer::{delete_pk_uuid, set_flags_for_delete_pk_uuid, DeletePkUuidCallbackId}; pub use delete_unique_bool_reducer::{ delete_unique_bool, set_flags_for_delete_unique_bool, DeleteUniqueBoolCallbackId, }; @@ -455,6 +478,9 @@ pub use delete_unique_u_256_reducer::{ pub use delete_unique_u_32_reducer::{delete_unique_u_32, set_flags_for_delete_unique_u_32, DeleteUniqueU32CallbackId}; pub use delete_unique_u_64_reducer::{delete_unique_u_64, set_flags_for_delete_unique_u_64, DeleteUniqueU64CallbackId}; pub use delete_unique_u_8_reducer::{delete_unique_u_8, set_flags_for_delete_unique_u_8, DeleteUniqueU8CallbackId}; +pub use delete_unique_uuid_reducer::{ + delete_unique_uuid, set_flags_for_delete_unique_uuid, DeleteUniqueUuidCallbackId, +}; pub use enum_with_payload_type::EnumWithPayload; pub use every_primitive_struct_type::EveryPrimitiveStruct; pub use every_vec_struct_type::EveryVecStruct; @@ -467,6 +493,12 @@ pub use indexed_table_type::IndexedTable; pub use insert_call_timestamp_reducer::{ insert_call_timestamp, set_flags_for_insert_call_timestamp, InsertCallTimestampCallbackId, }; +pub use insert_call_uuid_v_4_reducer::{ + insert_call_uuid_v_4, set_flags_for_insert_call_uuid_v_4, InsertCallUuidV4CallbackId, +}; +pub use insert_call_uuid_v_7_reducer::{ + insert_call_uuid_v_7, set_flags_for_insert_call_uuid_v_7, InsertCallUuidV7CallbackId, +}; pub use insert_caller_one_connection_id_reducer::{ insert_caller_one_connection_id, set_flags_for_insert_caller_one_connection_id, InsertCallerOneConnectionIdCallbackId, @@ -551,6 +583,7 @@ pub use insert_one_u_8_reducer::{insert_one_u_8, set_flags_for_insert_one_u_8, I pub use insert_one_unit_struct_reducer::{ insert_one_unit_struct, set_flags_for_insert_one_unit_struct, InsertOneUnitStructCallbackId, }; +pub use insert_one_uuid_reducer::{insert_one_uuid, set_flags_for_insert_one_uuid, InsertOneUuidCallbackId}; pub use insert_option_every_primitive_struct_reducer::{ insert_option_every_primitive_struct, set_flags_for_insert_option_every_primitive_struct, InsertOptionEveryPrimitiveStructCallbackId, @@ -565,6 +598,9 @@ pub use insert_option_simple_enum_reducer::{ pub use insert_option_string_reducer::{ insert_option_string, set_flags_for_insert_option_string, InsertOptionStringCallbackId, }; +pub use insert_option_uuid_reducer::{ + insert_option_uuid, set_flags_for_insert_option_uuid, InsertOptionUuidCallbackId, +}; pub use insert_option_vec_option_i_32_reducer::{ insert_option_vec_option_i_32, set_flags_for_insert_option_vec_option_i_32, InsertOptionVecOptionI32CallbackId, }; @@ -592,6 +628,7 @@ pub use insert_pk_u_32_reducer::{insert_pk_u_32, set_flags_for_insert_pk_u_32, I pub use insert_pk_u_32_two_reducer::{insert_pk_u_32_two, set_flags_for_insert_pk_u_32_two, InsertPkU32TwoCallbackId}; pub use insert_pk_u_64_reducer::{insert_pk_u_64, set_flags_for_insert_pk_u_64, InsertPkU64CallbackId}; pub use insert_pk_u_8_reducer::{insert_pk_u_8, set_flags_for_insert_pk_u_8, InsertPkU8CallbackId}; +pub use insert_pk_uuid_reducer::{insert_pk_uuid, set_flags_for_insert_pk_uuid, InsertPkUuidCallbackId}; pub use insert_primitives_as_strings_reducer::{ insert_primitives_as_strings, set_flags_for_insert_primitives_as_strings, InsertPrimitivesAsStringsCallbackId, }; @@ -634,6 +671,9 @@ pub use insert_unique_u_32_update_pk_u_32_reducer::{ }; pub use insert_unique_u_64_reducer::{insert_unique_u_64, set_flags_for_insert_unique_u_64, InsertUniqueU64CallbackId}; pub use insert_unique_u_8_reducer::{insert_unique_u_8, set_flags_for_insert_unique_u_8, InsertUniqueU8CallbackId}; +pub use insert_unique_uuid_reducer::{ + insert_unique_uuid, set_flags_for_insert_unique_uuid, InsertUniqueUuidCallbackId, +}; pub use insert_user_reducer::{insert_user, set_flags_for_insert_user, InsertUserCallbackId}; pub use insert_vec_bool_reducer::{insert_vec_bool, set_flags_for_insert_vec_bool, InsertVecBoolCallbackId}; pub use insert_vec_byte_struct_reducer::{ @@ -679,6 +719,7 @@ pub use insert_vec_u_8_reducer::{insert_vec_u_8, set_flags_for_insert_vec_u_8, I pub use insert_vec_unit_struct_reducer::{ insert_vec_unit_struct, set_flags_for_insert_vec_unit_struct, InsertVecUnitStructCallbackId, }; +pub use insert_vec_uuid_reducer::{insert_vec_uuid, set_flags_for_insert_vec_uuid, InsertVecUuidCallbackId}; pub use large_table_table::*; pub use large_table_type::LargeTable; pub use no_op_succeeds_reducer::{no_op_succeeds, set_flags_for_no_op_succeeds, NoOpSucceedsCallbackId}; @@ -732,6 +773,8 @@ pub use one_u_8_table::*; pub use one_u_8_type::OneU8; pub use one_unit_struct_table::*; pub use one_unit_struct_type::OneUnitStruct; +pub use one_uuid_table::*; +pub use one_uuid_type::OneUuid; pub use option_every_primitive_struct_table::*; pub use option_every_primitive_struct_type::OptionEveryPrimitiveStruct; pub use option_i_32_table::*; @@ -742,6 +785,8 @@ pub use option_simple_enum_table::*; pub use option_simple_enum_type::OptionSimpleEnum; pub use option_string_table::*; pub use option_string_type::OptionString; +pub use option_uuid_table::*; +pub use option_uuid_type::OptionUuid; pub use option_vec_option_i_32_table::*; pub use option_vec_option_i_32_type::OptionVecOptionI32; pub use pk_bool_table::*; @@ -780,12 +825,17 @@ pub use pk_u_64_table::*; pub use pk_u_64_type::PkU64; pub use pk_u_8_table::*; pub use pk_u_8_type::PkU8; +pub use pk_uuid_table::*; +pub use pk_uuid_type::PkUuid; pub use scheduled_table_table::*; pub use scheduled_table_type::ScheduledTable; pub use send_scheduled_message_reducer::{ send_scheduled_message, set_flags_for_send_scheduled_message, SendScheduledMessageCallbackId, }; pub use simple_enum_type::SimpleEnum; +pub use sorted_uuids_insert_reducer::{ + set_flags_for_sorted_uuids_insert, sorted_uuids_insert, SortedUuidsInsertCallbackId, +}; pub use table_holds_table_table::*; pub use table_holds_table_type::TableHoldsTable; pub use unique_bool_table::*; @@ -820,6 +870,8 @@ pub use unique_u_64_table::*; pub use unique_u_64_type::UniqueU64; pub use unique_u_8_table::*; pub use unique_u_8_type::UniqueU8; +pub use unique_uuid_table::*; +pub use unique_uuid_type::UniqueUuid; pub use unit_struct_type::UnitStruct; pub use update_indexed_simple_enum_reducer::{ set_flags_for_update_indexed_simple_enum, update_indexed_simple_enum, UpdateIndexedSimpleEnumCallbackId, @@ -848,6 +900,7 @@ pub use update_pk_u_32_reducer::{set_flags_for_update_pk_u_32, update_pk_u_32, U pub use update_pk_u_32_two_reducer::{set_flags_for_update_pk_u_32_two, update_pk_u_32_two, UpdatePkU32TwoCallbackId}; pub use update_pk_u_64_reducer::{set_flags_for_update_pk_u_64, update_pk_u_64, UpdatePkU64CallbackId}; pub use update_pk_u_8_reducer::{set_flags_for_update_pk_u_8, update_pk_u_8, UpdatePkU8CallbackId}; +pub use update_pk_uuid_reducer::{set_flags_for_update_pk_uuid, update_pk_uuid, UpdatePkUuidCallbackId}; pub use update_unique_bool_reducer::{ set_flags_for_update_unique_bool, update_unique_bool, UpdateUniqueBoolCallbackId, }; @@ -880,6 +933,9 @@ pub use update_unique_u_256_reducer::{ pub use update_unique_u_32_reducer::{set_flags_for_update_unique_u_32, update_unique_u_32, UpdateUniqueU32CallbackId}; pub use update_unique_u_64_reducer::{set_flags_for_update_unique_u_64, update_unique_u_64, UpdateUniqueU64CallbackId}; pub use update_unique_u_8_reducer::{set_flags_for_update_unique_u_8, update_unique_u_8, UpdateUniqueU8CallbackId}; +pub use update_unique_uuid_reducer::{ + set_flags_for_update_unique_uuid, update_unique_uuid, UpdateUniqueUuidCallbackId, +}; pub use users_table::*; pub use users_type::Users; pub use vec_bool_table::*; @@ -932,6 +988,8 @@ pub use vec_u_8_table::*; pub use vec_u_8_type::VecU8; pub use vec_unit_struct_table::*; pub use vec_unit_struct_type::VecUnitStruct; +pub use vec_uuid_table::*; +pub use vec_uuid_type::VecUuid; #[derive(Clone, PartialEq, Debug)] @@ -1023,6 +1081,9 @@ pub enum Reducer { DeletePkU8 { n: u8, }, + DeletePkUuid { + u: __sdk::Uuid, + }, DeleteUniqueBool { b: bool, }, @@ -1071,7 +1132,12 @@ pub enum Reducer { DeleteUniqueU8 { n: u8, }, + DeleteUniqueUuid { + u: __sdk::Uuid, + }, InsertCallTimestamp, + InsertCallUuidV4, + InsertCallUuidV7, InsertCallerOneConnectionId, InsertCallerOneIdentity, InsertCallerPkConnectionId { @@ -1197,6 +1263,9 @@ pub enum Reducer { InsertOneUnitStruct { s: UnitStruct, }, + InsertOneUuid { + u: __sdk::Uuid, + }, InsertOptionEveryPrimitiveStruct { s: Option, }, @@ -1212,6 +1281,9 @@ pub enum Reducer { InsertOptionString { s: Option, }, + InsertOptionUuid { + u: Option<__sdk::Uuid>, + }, InsertOptionVecOptionI32 { v: Option>>, }, @@ -1287,6 +1359,10 @@ pub enum Reducer { n: u8, data: i32, }, + InsertPkUuid { + u: __sdk::Uuid, + data: i32, + }, InsertPrimitivesAsStrings { s: EveryPrimitiveStruct, }, @@ -1363,6 +1439,10 @@ pub enum Reducer { n: u8, data: i32, }, + InsertUniqueUuid { + u: __sdk::Uuid, + data: i32, + }, InsertUser { name: String, identity: __sdk::Identity, @@ -1442,10 +1522,14 @@ pub enum Reducer { InsertVecUnitStruct { s: Vec, }, + InsertVecUuid { + u: Vec<__sdk::Uuid>, + }, NoOpSucceeds, SendScheduledMessage { arg: ScheduledTable, }, + SortedUuidsInsert, UpdateIndexedSimpleEnum { a: SimpleEnum, b: SimpleEnum, @@ -1522,6 +1606,10 @@ pub enum Reducer { n: u8, data: i32, }, + UpdatePkUuid { + u: __sdk::Uuid, + data: i32, + }, UpdateUniqueBool { b: bool, data: i32, @@ -1586,6 +1674,10 @@ pub enum Reducer { n: u8, data: i32, }, + UpdateUniqueUuid { + u: __sdk::Uuid, + data: i32, + }, } impl __sdk::InModule for Reducer { @@ -1615,6 +1707,7 @@ impl __sdk::Reducer for Reducer { Reducer::DeletePkU32Two { .. } => "delete_pk_u32_two", Reducer::DeletePkU64 { .. } => "delete_pk_u64", Reducer::DeletePkU8 { .. } => "delete_pk_u8", + Reducer::DeletePkUuid { .. } => "delete_pk_uuid", Reducer::DeleteUniqueBool { .. } => "delete_unique_bool", Reducer::DeleteUniqueConnectionId { .. } => "delete_unique_connection_id", Reducer::DeleteUniqueI128 { .. } => "delete_unique_i128", @@ -1631,7 +1724,10 @@ impl __sdk::Reducer for Reducer { Reducer::DeleteUniqueU32 { .. } => "delete_unique_u32", Reducer::DeleteUniqueU64 { .. } => "delete_unique_u64", Reducer::DeleteUniqueU8 { .. } => "delete_unique_u8", + Reducer::DeleteUniqueUuid { .. } => "delete_unique_uuid", Reducer::InsertCallTimestamp => "insert_call_timestamp", + Reducer::InsertCallUuidV4 => "insert_call_uuid_v4", + Reducer::InsertCallUuidV7 => "insert_call_uuid_v7", Reducer::InsertCallerOneConnectionId => "insert_caller_one_connection_id", Reducer::InsertCallerOneIdentity => "insert_caller_one_identity", Reducer::InsertCallerPkConnectionId { .. } => "insert_caller_pk_connection_id", @@ -1669,11 +1765,13 @@ impl __sdk::Reducer for Reducer { Reducer::InsertOneU64 { .. } => "insert_one_u64", Reducer::InsertOneU8 { .. } => "insert_one_u8", Reducer::InsertOneUnitStruct { .. } => "insert_one_unit_struct", + Reducer::InsertOneUuid { .. } => "insert_one_uuid", Reducer::InsertOptionEveryPrimitiveStruct { .. } => "insert_option_every_primitive_struct", Reducer::InsertOptionI32 { .. } => "insert_option_i32", Reducer::InsertOptionIdentity { .. } => "insert_option_identity", Reducer::InsertOptionSimpleEnum { .. } => "insert_option_simple_enum", Reducer::InsertOptionString { .. } => "insert_option_string", + Reducer::InsertOptionUuid { .. } => "insert_option_uuid", Reducer::InsertOptionVecOptionI32 { .. } => "insert_option_vec_option_i32", Reducer::InsertPkBool { .. } => "insert_pk_bool", Reducer::InsertPkConnectionId { .. } => "insert_pk_connection_id", @@ -1693,6 +1791,7 @@ impl __sdk::Reducer for Reducer { Reducer::InsertPkU32Two { .. } => "insert_pk_u32_two", Reducer::InsertPkU64 { .. } => "insert_pk_u64", Reducer::InsertPkU8 { .. } => "insert_pk_u8", + Reducer::InsertPkUuid { .. } => "insert_pk_uuid", Reducer::InsertPrimitivesAsStrings { .. } => "insert_primitives_as_strings", Reducer::InsertTableHoldsTable { .. } => "insert_table_holds_table", Reducer::InsertUniqueBool { .. } => "insert_unique_bool", @@ -1712,6 +1811,7 @@ impl __sdk::Reducer for Reducer { Reducer::InsertUniqueU32UpdatePkU32 { .. } => "insert_unique_u32_update_pk_u32", Reducer::InsertUniqueU64 { .. } => "insert_unique_u64", Reducer::InsertUniqueU8 { .. } => "insert_unique_u8", + Reducer::InsertUniqueUuid { .. } => "insert_unique_uuid", Reducer::InsertUser { .. } => "insert_user", Reducer::InsertVecBool { .. } => "insert_vec_bool", Reducer::InsertVecByteStruct { .. } => "insert_vec_byte_struct", @@ -1738,8 +1838,10 @@ impl __sdk::Reducer for Reducer { Reducer::InsertVecU64 { .. } => "insert_vec_u64", Reducer::InsertVecU8 { .. } => "insert_vec_u8", Reducer::InsertVecUnitStruct { .. } => "insert_vec_unit_struct", + Reducer::InsertVecUuid { .. } => "insert_vec_uuid", Reducer::NoOpSucceeds => "no_op_succeeds", Reducer::SendScheduledMessage { .. } => "send_scheduled_message", + Reducer::SortedUuidsInsert => "sorted_uuids_insert", Reducer::UpdateIndexedSimpleEnum { .. } => "update_indexed_simple_enum", Reducer::UpdatePkBool { .. } => "update_pk_bool", Reducer::UpdatePkConnectionId { .. } => "update_pk_connection_id", @@ -1759,6 +1861,7 @@ impl __sdk::Reducer for Reducer { Reducer::UpdatePkU32Two { .. } => "update_pk_u32_two", Reducer::UpdatePkU64 { .. } => "update_pk_u64", Reducer::UpdatePkU8 { .. } => "update_pk_u8", + Reducer::UpdatePkUuid { .. } => "update_pk_uuid", Reducer::UpdateUniqueBool { .. } => "update_unique_bool", Reducer::UpdateUniqueConnectionId { .. } => "update_unique_connection_id", Reducer::UpdateUniqueI128 { .. } => "update_unique_i128", @@ -1775,6 +1878,7 @@ impl __sdk::Reducer for Reducer { Reducer::UpdateUniqueU32 { .. } => "update_unique_u32", Reducer::UpdateUniqueU64 { .. } => "update_unique_u64", Reducer::UpdateUniqueU8 { .. } => "update_unique_u8", + Reducer::UpdateUniqueUuid { .. } => "update_unique_uuid", _ => unreachable!(), } } @@ -1888,6 +1992,11 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { &value.args, )? .into()), + "delete_pk_uuid" => Ok(__sdk::parse_reducer_args::( + "delete_pk_uuid", + &value.args, + )? + .into()), "delete_unique_bool" => Ok( __sdk::parse_reducer_args::( "delete_unique_bool", @@ -1991,10 +2100,31 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { )? .into(), ), + "delete_unique_uuid" => Ok( + __sdk::parse_reducer_args::( + "delete_unique_uuid", + &value.args, + )? + .into(), + ), "insert_call_timestamp" => Ok(__sdk::parse_reducer_args::< insert_call_timestamp_reducer::InsertCallTimestampArgs, >("insert_call_timestamp", &value.args)? .into()), + "insert_call_uuid_v4" => Ok( + __sdk::parse_reducer_args::( + "insert_call_uuid_v4", + &value.args, + )? + .into(), + ), + "insert_call_uuid_v7" => Ok( + __sdk::parse_reducer_args::( + "insert_call_uuid_v7", + &value.args, + )? + .into(), + ), "insert_caller_one_connection_id" => Ok(__sdk::parse_reducer_args::< insert_caller_one_connection_id_reducer::InsertCallerOneConnectionIdArgs, >("insert_caller_one_connection_id", &value.args)? @@ -2177,6 +2307,11 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { insert_one_unit_struct_reducer::InsertOneUnitStructArgs, >("insert_one_unit_struct", &value.args)? .into()), + "insert_one_uuid" => Ok(__sdk::parse_reducer_args::( + "insert_one_uuid", + &value.args, + )? + .into()), "insert_option_every_primitive_struct" => { Ok(__sdk::parse_reducer_args::< insert_option_every_primitive_struct_reducer::InsertOptionEveryPrimitiveStructArgs, @@ -2202,6 +2337,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { insert_option_string_reducer::InsertOptionStringArgs, >("insert_option_string", &value.args)? .into()), + "insert_option_uuid" => Ok( + __sdk::parse_reducer_args::( + "insert_option_uuid", + &value.args, + )? + .into(), + ), "insert_option_vec_option_i32" => Ok(__sdk::parse_reducer_args::< insert_option_vec_option_i_32_reducer::InsertOptionVecOptionI32Args, >("insert_option_vec_option_i32", &value.args)? @@ -2300,6 +2442,11 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { &value.args, )? .into()), + "insert_pk_uuid" => Ok(__sdk::parse_reducer_args::( + "insert_pk_uuid", + &value.args, + )? + .into()), "insert_primitives_as_strings" => Ok(__sdk::parse_reducer_args::< insert_primitives_as_strings_reducer::InsertPrimitivesAsStringsArgs, >("insert_primitives_as_strings", &value.args)? @@ -2415,6 +2562,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { )? .into(), ), + "insert_unique_uuid" => Ok( + __sdk::parse_reducer_args::( + "insert_unique_uuid", + &value.args, + )? + .into(), + ), "insert_user" => Ok(__sdk::parse_reducer_args::( "insert_user", &value.args, @@ -2549,6 +2703,11 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { insert_vec_unit_struct_reducer::InsertVecUnitStructArgs, >("insert_vec_unit_struct", &value.args)? .into()), + "insert_vec_uuid" => Ok(__sdk::parse_reducer_args::( + "insert_vec_uuid", + &value.args, + )? + .into()), "no_op_succeeds" => Ok(__sdk::parse_reducer_args::( "no_op_succeeds", &value.args, @@ -2558,6 +2717,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { send_scheduled_message_reducer::SendScheduledMessageArgs, >("send_scheduled_message", &value.args)? .into()), + "sorted_uuids_insert" => Ok( + __sdk::parse_reducer_args::( + "sorted_uuids_insert", + &value.args, + )? + .into(), + ), "update_indexed_simple_enum" => Ok(__sdk::parse_reducer_args::< update_indexed_simple_enum_reducer::UpdateIndexedSimpleEnumArgs, >("update_indexed_simple_enum", &value.args)? @@ -2656,6 +2822,11 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { &value.args, )? .into()), + "update_pk_uuid" => Ok(__sdk::parse_reducer_args::( + "update_pk_uuid", + &value.args, + )? + .into()), "update_unique_bool" => Ok( __sdk::parse_reducer_args::( "update_unique_bool", @@ -2759,6 +2930,13 @@ impl TryFrom<__ws::ReducerCallInfo<__ws::BsatnFormat>> for Reducer { )? .into(), ), + "update_unique_uuid" => Ok( + __sdk::parse_reducer_args::( + "update_unique_uuid", + &value.args, + )? + .into(), + ), unknown => Err(__sdk::InternalError::unknown_name("reducer", unknown, "ReducerCallInfo").into()), } } @@ -2798,11 +2976,13 @@ pub struct DbUpdate { one_u_64: __sdk::TableUpdate, one_u_8: __sdk::TableUpdate, one_unit_struct: __sdk::TableUpdate, + one_uuid: __sdk::TableUpdate, option_every_primitive_struct: __sdk::TableUpdate, option_i_32: __sdk::TableUpdate, option_identity: __sdk::TableUpdate, option_simple_enum: __sdk::TableUpdate, option_string: __sdk::TableUpdate, + option_uuid: __sdk::TableUpdate, option_vec_option_i_32: __sdk::TableUpdate, pk_bool: __sdk::TableUpdate, pk_connection_id: __sdk::TableUpdate, @@ -2822,6 +3002,7 @@ pub struct DbUpdate { pk_u_32_two: __sdk::TableUpdate, pk_u_64: __sdk::TableUpdate, pk_u_8: __sdk::TableUpdate, + pk_uuid: __sdk::TableUpdate, scheduled_table: __sdk::TableUpdate, table_holds_table: __sdk::TableUpdate, unique_bool: __sdk::TableUpdate, @@ -2840,6 +3021,7 @@ pub struct DbUpdate { unique_u_32: __sdk::TableUpdate, unique_u_64: __sdk::TableUpdate, unique_u_8: __sdk::TableUpdate, + unique_uuid: __sdk::TableUpdate, users: __sdk::TableUpdate, vec_bool: __sdk::TableUpdate, vec_byte_struct: __sdk::TableUpdate, @@ -2866,6 +3048,7 @@ pub struct DbUpdate { vec_u_64: __sdk::TableUpdate, vec_u_8: __sdk::TableUpdate, vec_unit_struct: __sdk::TableUpdate, + vec_uuid: __sdk::TableUpdate, } impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { @@ -2964,6 +3147,9 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { "one_unit_struct" => db_update .one_unit_struct .append(one_unit_struct_table::parse_table_update(table_update)?), + "one_uuid" => db_update + .one_uuid + .append(one_uuid_table::parse_table_update(table_update)?), "option_every_primitive_struct" => db_update .option_every_primitive_struct .append(option_every_primitive_struct_table::parse_table_update(table_update)?), @@ -2979,6 +3165,9 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { "option_string" => db_update .option_string .append(option_string_table::parse_table_update(table_update)?), + "option_uuid" => db_update + .option_uuid + .append(option_uuid_table::parse_table_update(table_update)?), "option_vec_option_i32" => db_update .option_vec_option_i_32 .append(option_vec_option_i_32_table::parse_table_update(table_update)?), @@ -3032,6 +3221,9 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { .pk_u_64 .append(pk_u_64_table::parse_table_update(table_update)?), "pk_u8" => db_update.pk_u_8.append(pk_u_8_table::parse_table_update(table_update)?), + "pk_uuid" => db_update + .pk_uuid + .append(pk_uuid_table::parse_table_update(table_update)?), "scheduled_table" => db_update .scheduled_table .append(scheduled_table_table::parse_table_update(table_update)?), @@ -3086,6 +3278,9 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { "unique_u8" => db_update .unique_u_8 .append(unique_u_8_table::parse_table_update(table_update)?), + "unique_uuid" => db_update + .unique_uuid + .append(unique_uuid_table::parse_table_update(table_update)?), "users" => db_update.users.append(users_table::parse_table_update(table_update)?), "vec_bool" => db_update .vec_bool @@ -3162,6 +3357,9 @@ impl TryFrom<__ws::DatabaseUpdate<__ws::BsatnFormat>> for DbUpdate { "vec_unit_struct" => db_update .vec_unit_struct .append(vec_unit_struct_table::parse_table_update(table_update)?), + "vec_uuid" => db_update + .vec_uuid + .append(vec_uuid_table::parse_table_update(table_update)?), unknown => { return Err(__sdk::InternalError::unknown_name("table", unknown, "DatabaseUpdate").into()); @@ -3217,6 +3415,7 @@ impl __sdk::DbUpdate for DbUpdate { diff.one_u_64 = cache.apply_diff_to_table::("one_u64", &self.one_u_64); diff.one_u_8 = cache.apply_diff_to_table::("one_u8", &self.one_u_8); diff.one_unit_struct = cache.apply_diff_to_table::("one_unit_struct", &self.one_unit_struct); + diff.one_uuid = cache.apply_diff_to_table::("one_uuid", &self.one_uuid); diff.option_every_primitive_struct = cache.apply_diff_to_table::( "option_every_primitive_struct", &self.option_every_primitive_struct, @@ -3226,6 +3425,7 @@ impl __sdk::DbUpdate for DbUpdate { diff.option_simple_enum = cache.apply_diff_to_table::("option_simple_enum", &self.option_simple_enum); diff.option_string = cache.apply_diff_to_table::("option_string", &self.option_string); + diff.option_uuid = cache.apply_diff_to_table::("option_uuid", &self.option_uuid); diff.option_vec_option_i_32 = cache.apply_diff_to_table::("option_vec_option_i32", &self.option_vec_option_i_32); diff.pk_bool = cache @@ -3282,6 +3482,9 @@ impl __sdk::DbUpdate for DbUpdate { diff.pk_u_8 = cache .apply_diff_to_table::("pk_u8", &self.pk_u_8) .with_updates_by_pk(|row| &row.n); + diff.pk_uuid = cache + .apply_diff_to_table::("pk_uuid", &self.pk_uuid) + .with_updates_by_pk(|row| &row.u); diff.scheduled_table = cache .apply_diff_to_table::("scheduled_table", &self.scheduled_table) .with_updates_by_pk(|row| &row.scheduled_id); @@ -3304,6 +3507,7 @@ impl __sdk::DbUpdate for DbUpdate { diff.unique_u_32 = cache.apply_diff_to_table::("unique_u32", &self.unique_u_32); diff.unique_u_64 = cache.apply_diff_to_table::("unique_u64", &self.unique_u_64); diff.unique_u_8 = cache.apply_diff_to_table::("unique_u8", &self.unique_u_8); + diff.unique_uuid = cache.apply_diff_to_table::("unique_uuid", &self.unique_uuid); diff.users = cache .apply_diff_to_table::("users", &self.users) .with_updates_by_pk(|row| &row.identity); @@ -3338,6 +3542,7 @@ impl __sdk::DbUpdate for DbUpdate { diff.vec_u_64 = cache.apply_diff_to_table::("vec_u64", &self.vec_u_64); diff.vec_u_8 = cache.apply_diff_to_table::("vec_u8", &self.vec_u_8); diff.vec_unit_struct = cache.apply_diff_to_table::("vec_unit_struct", &self.vec_unit_struct); + diff.vec_uuid = cache.apply_diff_to_table::("vec_uuid", &self.vec_uuid); diff } @@ -3377,11 +3582,13 @@ pub struct AppliedDiff<'r> { one_u_64: __sdk::TableAppliedDiff<'r, OneU64>, one_u_8: __sdk::TableAppliedDiff<'r, OneU8>, one_unit_struct: __sdk::TableAppliedDiff<'r, OneUnitStruct>, + one_uuid: __sdk::TableAppliedDiff<'r, OneUuid>, option_every_primitive_struct: __sdk::TableAppliedDiff<'r, OptionEveryPrimitiveStruct>, option_i_32: __sdk::TableAppliedDiff<'r, OptionI32>, option_identity: __sdk::TableAppliedDiff<'r, OptionIdentity>, option_simple_enum: __sdk::TableAppliedDiff<'r, OptionSimpleEnum>, option_string: __sdk::TableAppliedDiff<'r, OptionString>, + option_uuid: __sdk::TableAppliedDiff<'r, OptionUuid>, option_vec_option_i_32: __sdk::TableAppliedDiff<'r, OptionVecOptionI32>, pk_bool: __sdk::TableAppliedDiff<'r, PkBool>, pk_connection_id: __sdk::TableAppliedDiff<'r, PkConnectionId>, @@ -3401,6 +3608,7 @@ pub struct AppliedDiff<'r> { pk_u_32_two: __sdk::TableAppliedDiff<'r, PkU32Two>, pk_u_64: __sdk::TableAppliedDiff<'r, PkU64>, pk_u_8: __sdk::TableAppliedDiff<'r, PkU8>, + pk_uuid: __sdk::TableAppliedDiff<'r, PkUuid>, scheduled_table: __sdk::TableAppliedDiff<'r, ScheduledTable>, table_holds_table: __sdk::TableAppliedDiff<'r, TableHoldsTable>, unique_bool: __sdk::TableAppliedDiff<'r, UniqueBool>, @@ -3419,6 +3627,7 @@ pub struct AppliedDiff<'r> { unique_u_32: __sdk::TableAppliedDiff<'r, UniqueU32>, unique_u_64: __sdk::TableAppliedDiff<'r, UniqueU64>, unique_u_8: __sdk::TableAppliedDiff<'r, UniqueU8>, + unique_uuid: __sdk::TableAppliedDiff<'r, UniqueUuid>, users: __sdk::TableAppliedDiff<'r, Users>, vec_bool: __sdk::TableAppliedDiff<'r, VecBool>, vec_byte_struct: __sdk::TableAppliedDiff<'r, VecByteStruct>, @@ -3445,6 +3654,7 @@ pub struct AppliedDiff<'r> { vec_u_64: __sdk::TableAppliedDiff<'r, VecU64>, vec_u_8: __sdk::TableAppliedDiff<'r, VecU8>, vec_unit_struct: __sdk::TableAppliedDiff<'r, VecUnitStruct>, + vec_uuid: __sdk::TableAppliedDiff<'r, VecUuid>, __unused: std::marker::PhantomData<&'r ()>, } @@ -3500,6 +3710,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks.invoke_table_row_callbacks::("one_u64", &self.one_u_64, event); callbacks.invoke_table_row_callbacks::("one_u8", &self.one_u_8, event); callbacks.invoke_table_row_callbacks::("one_unit_struct", &self.one_unit_struct, event); + callbacks.invoke_table_row_callbacks::("one_uuid", &self.one_uuid, event); callbacks.invoke_table_row_callbacks::( "option_every_primitive_struct", &self.option_every_primitive_struct, @@ -3509,6 +3720,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks.invoke_table_row_callbacks::("option_identity", &self.option_identity, event); callbacks.invoke_table_row_callbacks::("option_simple_enum", &self.option_simple_enum, event); callbacks.invoke_table_row_callbacks::("option_string", &self.option_string, event); + callbacks.invoke_table_row_callbacks::("option_uuid", &self.option_uuid, event); callbacks.invoke_table_row_callbacks::( "option_vec_option_i32", &self.option_vec_option_i_32, @@ -3532,6 +3744,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks.invoke_table_row_callbacks::("pk_u32_two", &self.pk_u_32_two, event); callbacks.invoke_table_row_callbacks::("pk_u64", &self.pk_u_64, event); callbacks.invoke_table_row_callbacks::("pk_u8", &self.pk_u_8, event); + callbacks.invoke_table_row_callbacks::("pk_uuid", &self.pk_uuid, event); callbacks.invoke_table_row_callbacks::("scheduled_table", &self.scheduled_table, event); callbacks.invoke_table_row_callbacks::("table_holds_table", &self.table_holds_table, event); callbacks.invoke_table_row_callbacks::("unique_bool", &self.unique_bool, event); @@ -3554,6 +3767,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks.invoke_table_row_callbacks::("unique_u32", &self.unique_u_32, event); callbacks.invoke_table_row_callbacks::("unique_u64", &self.unique_u_64, event); callbacks.invoke_table_row_callbacks::("unique_u8", &self.unique_u_8, event); + callbacks.invoke_table_row_callbacks::("unique_uuid", &self.unique_uuid, event); callbacks.invoke_table_row_callbacks::("users", &self.users, event); callbacks.invoke_table_row_callbacks::("vec_bool", &self.vec_bool, event); callbacks.invoke_table_row_callbacks::("vec_byte_struct", &self.vec_byte_struct, event); @@ -3592,6 +3806,7 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> { callbacks.invoke_table_row_callbacks::("vec_u64", &self.vec_u_64, event); callbacks.invoke_table_row_callbacks::("vec_u8", &self.vec_u_8, event); callbacks.invoke_table_row_callbacks::("vec_unit_struct", &self.vec_unit_struct, event); + callbacks.invoke_table_row_callbacks::("vec_uuid", &self.vec_uuid, event); } } @@ -4341,11 +4556,13 @@ impl __sdk::SpacetimeModule for RemoteModule { one_u_64_table::register_table(client_cache); one_u_8_table::register_table(client_cache); one_unit_struct_table::register_table(client_cache); + one_uuid_table::register_table(client_cache); option_every_primitive_struct_table::register_table(client_cache); option_i_32_table::register_table(client_cache); option_identity_table::register_table(client_cache); option_simple_enum_table::register_table(client_cache); option_string_table::register_table(client_cache); + option_uuid_table::register_table(client_cache); option_vec_option_i_32_table::register_table(client_cache); pk_bool_table::register_table(client_cache); pk_connection_id_table::register_table(client_cache); @@ -4365,6 +4582,7 @@ impl __sdk::SpacetimeModule for RemoteModule { pk_u_32_two_table::register_table(client_cache); pk_u_64_table::register_table(client_cache); pk_u_8_table::register_table(client_cache); + pk_uuid_table::register_table(client_cache); scheduled_table_table::register_table(client_cache); table_holds_table_table::register_table(client_cache); unique_bool_table::register_table(client_cache); @@ -4383,6 +4601,7 @@ impl __sdk::SpacetimeModule for RemoteModule { unique_u_32_table::register_table(client_cache); unique_u_64_table::register_table(client_cache); unique_u_8_table::register_table(client_cache); + unique_uuid_table::register_table(client_cache); users_table::register_table(client_cache); vec_bool_table::register_table(client_cache); vec_byte_struct_table::register_table(client_cache); @@ -4409,5 +4628,6 @@ impl __sdk::SpacetimeModule for RemoteModule { vec_u_64_table::register_table(client_cache); vec_u_8_table::register_table(client_cache); vec_unit_struct_table::register_table(client_cache); + vec_uuid_table::register_table(client_cache); } } diff --git a/sdks/rust/tests/test-client/src/module_bindings/one_uuid_table.rs b/sdks/rust/tests/test-client/src/module_bindings/one_uuid_table.rs new file mode 100644 index 00000000000..4c9788a7cd8 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/one_uuid_table.rs @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::one_uuid_type::OneUuid; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `one_uuid`. +/// +/// Obtain a handle from the [`OneUuidTableAccess::one_uuid`] method on [`super::RemoteTables`], +/// like `ctx.db.one_uuid()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.one_uuid().on_insert(...)`. +pub struct OneUuidTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `one_uuid`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait OneUuidTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`OneUuidTableHandle`], which mediates access to the table `one_uuid`. + fn one_uuid(&self) -> OneUuidTableHandle<'_>; +} + +impl OneUuidTableAccess for super::RemoteTables { + fn one_uuid(&self) -> OneUuidTableHandle<'_> { + OneUuidTableHandle { + imp: self.imp.get_table::("one_uuid"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct OneUuidInsertCallbackId(__sdk::CallbackId); +pub struct OneUuidDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for OneUuidTableHandle<'ctx> { + type Row = OneUuid; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = OneUuidInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> OneUuidInsertCallbackId { + OneUuidInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: OneUuidInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = OneUuidDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> OneUuidDeleteCallbackId { + OneUuidDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: OneUuidDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("one_uuid"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/one_uuid_type.rs b/sdks/rust/tests/test-client/src/module_bindings/one_uuid_type.rs new file mode 100644 index 00000000000..bdd7083c06d --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/one_uuid_type.rs @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct OneUuid { + pub u: __sdk::Uuid, +} + +impl __sdk::InModule for OneUuid { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/option_uuid_table.rs b/sdks/rust/tests/test-client/src/module_bindings/option_uuid_table.rs new file mode 100644 index 00000000000..d7ba3fa64d5 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/option_uuid_table.rs @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::option_uuid_type::OptionUuid; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `option_uuid`. +/// +/// Obtain a handle from the [`OptionUuidTableAccess::option_uuid`] method on [`super::RemoteTables`], +/// like `ctx.db.option_uuid()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.option_uuid().on_insert(...)`. +pub struct OptionUuidTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `option_uuid`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait OptionUuidTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`OptionUuidTableHandle`], which mediates access to the table `option_uuid`. + fn option_uuid(&self) -> OptionUuidTableHandle<'_>; +} + +impl OptionUuidTableAccess for super::RemoteTables { + fn option_uuid(&self) -> OptionUuidTableHandle<'_> { + OptionUuidTableHandle { + imp: self.imp.get_table::("option_uuid"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct OptionUuidInsertCallbackId(__sdk::CallbackId); +pub struct OptionUuidDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for OptionUuidTableHandle<'ctx> { + type Row = OptionUuid; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = OptionUuidInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> OptionUuidInsertCallbackId { + OptionUuidInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: OptionUuidInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = OptionUuidDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> OptionUuidDeleteCallbackId { + OptionUuidDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: OptionUuidDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("option_uuid"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/option_uuid_type.rs b/sdks/rust/tests/test-client/src/module_bindings/option_uuid_type.rs new file mode 100644 index 00000000000..222099f1619 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/option_uuid_type.rs @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct OptionUuid { + pub u: Option<__sdk::Uuid>, +} + +impl __sdk::InModule for OptionUuid { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/pk_uuid_table.rs b/sdks/rust/tests/test-client/src/module_bindings/pk_uuid_table.rs new file mode 100644 index 00000000000..6ff45a9d5a1 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/pk_uuid_table.rs @@ -0,0 +1,111 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::pk_uuid_type::PkUuid; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `pk_uuid`. +/// +/// Obtain a handle from the [`PkUuidTableAccess::pk_uuid`] method on [`super::RemoteTables`], +/// like `ctx.db.pk_uuid()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.pk_uuid().on_insert(...)`. +pub struct PkUuidTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `pk_uuid`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait PkUuidTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`PkUuidTableHandle`], which mediates access to the table `pk_uuid`. + fn pk_uuid(&self) -> PkUuidTableHandle<'_>; +} + +impl PkUuidTableAccess for super::RemoteTables { + fn pk_uuid(&self) -> PkUuidTableHandle<'_> { + PkUuidTableHandle { + imp: self.imp.get_table::("pk_uuid"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct PkUuidInsertCallbackId(__sdk::CallbackId); +pub struct PkUuidDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for PkUuidTableHandle<'ctx> { + type Row = PkUuid; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = PkUuidInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PkUuidInsertCallbackId { + PkUuidInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: PkUuidInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = PkUuidDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> PkUuidDeleteCallbackId { + PkUuidDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: PkUuidDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("pk_uuid"); +} +pub struct PkUuidUpdateCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::TableWithPrimaryKey for PkUuidTableHandle<'ctx> { + type UpdateCallbackId = PkUuidUpdateCallbackId; + + fn on_update( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row, &Self::Row) + Send + 'static, + ) -> PkUuidUpdateCallbackId { + PkUuidUpdateCallbackId(self.imp.on_update(Box::new(callback))) + } + + fn remove_on_update(&self, callback: PkUuidUpdateCallbackId) { + self.imp.remove_on_update(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/pk_uuid_type.rs b/sdks/rust/tests/test-client/src/module_bindings/pk_uuid_type.rs new file mode 100644 index 00000000000..3beaf019b45 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/pk_uuid_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct PkUuid { + pub u: __sdk::Uuid, + pub data: i32, +} + +impl __sdk::InModule for PkUuid { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/sorted_uuids_insert_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/sorted_uuids_insert_reducer.rs new file mode 100644 index 00000000000..bc38b0fae43 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/sorted_uuids_insert_reducer.rs @@ -0,0 +1,99 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct SortedUuidsInsertArgs {} + +impl From for super::Reducer { + fn from(args: SortedUuidsInsertArgs) -> Self { + Self::SortedUuidsInsert + } +} + +impl __sdk::InModule for SortedUuidsInsertArgs { + type Module = super::RemoteModule; +} + +pub struct SortedUuidsInsertCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `sorted_uuids_insert`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait sorted_uuids_insert { + /// Request that the remote module invoke the reducer `sorted_uuids_insert` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_sorted_uuids_insert`] callbacks. + fn sorted_uuids_insert(&self) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `sorted_uuids_insert`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`SortedUuidsInsertCallbackId`] can be passed to [`Self::remove_on_sorted_uuids_insert`] + /// to cancel the callback. + fn on_sorted_uuids_insert( + &self, + callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> SortedUuidsInsertCallbackId; + /// Cancel a callback previously registered by [`Self::on_sorted_uuids_insert`], + /// causing it not to run in the future. + fn remove_on_sorted_uuids_insert(&self, callback: SortedUuidsInsertCallbackId); +} + +impl sorted_uuids_insert for super::RemoteReducers { + fn sorted_uuids_insert(&self) -> __sdk::Result<()> { + self.imp.call_reducer("sorted_uuids_insert", SortedUuidsInsertArgs {}) + } + fn on_sorted_uuids_insert( + &self, + mut callback: impl FnMut(&super::ReducerEventContext) + Send + 'static, + ) -> SortedUuidsInsertCallbackId { + SortedUuidsInsertCallbackId(self.imp.on_reducer( + "sorted_uuids_insert", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::SortedUuidsInsert {}, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx) + }), + )) + } + fn remove_on_sorted_uuids_insert(&self, callback: SortedUuidsInsertCallbackId) { + self.imp.remove_on_reducer("sorted_uuids_insert", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `sorted_uuids_insert`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_sorted_uuids_insert { + /// Set the call-reducer flags for the reducer `sorted_uuids_insert` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn sorted_uuids_insert(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_sorted_uuids_insert for super::SetReducerFlags { + fn sorted_uuids_insert(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("sorted_uuids_insert", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/unique_uuid_table.rs b/sdks/rust/tests/test-client/src/module_bindings/unique_uuid_table.rs new file mode 100644 index 00000000000..d8cd46cd93b --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/unique_uuid_table.rs @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::unique_uuid_type::UniqueUuid; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `unique_uuid`. +/// +/// Obtain a handle from the [`UniqueUuidTableAccess::unique_uuid`] method on [`super::RemoteTables`], +/// like `ctx.db.unique_uuid()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.unique_uuid().on_insert(...)`. +pub struct UniqueUuidTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `unique_uuid`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait UniqueUuidTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`UniqueUuidTableHandle`], which mediates access to the table `unique_uuid`. + fn unique_uuid(&self) -> UniqueUuidTableHandle<'_>; +} + +impl UniqueUuidTableAccess for super::RemoteTables { + fn unique_uuid(&self) -> UniqueUuidTableHandle<'_> { + UniqueUuidTableHandle { + imp: self.imp.get_table::("unique_uuid"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct UniqueUuidInsertCallbackId(__sdk::CallbackId); +pub struct UniqueUuidDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for UniqueUuidTableHandle<'ctx> { + type Row = UniqueUuid; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = UniqueUuidInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> UniqueUuidInsertCallbackId { + UniqueUuidInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: UniqueUuidInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = UniqueUuidDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> UniqueUuidDeleteCallbackId { + UniqueUuidDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: UniqueUuidDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("unique_uuid"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/unique_uuid_type.rs b/sdks/rust/tests/test-client/src/module_bindings/unique_uuid_type.rs new file mode 100644 index 00000000000..c7d34736780 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/unique_uuid_type.rs @@ -0,0 +1,16 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct UniqueUuid { + pub u: __sdk::Uuid, + pub data: i32, +} + +impl __sdk::InModule for UniqueUuid { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/update_pk_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/update_pk_uuid_reducer.rs new file mode 100644 index 00000000000..0949d2ee3c3 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/update_pk_uuid_reducer.rs @@ -0,0 +1,105 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct UpdatePkUuidArgs { + pub u: __sdk::Uuid, + pub data: i32, +} + +impl From for super::Reducer { + fn from(args: UpdatePkUuidArgs) -> Self { + Self::UpdatePkUuid { + u: args.u, + data: args.data, + } + } +} + +impl __sdk::InModule for UpdatePkUuidArgs { + type Module = super::RemoteModule; +} + +pub struct UpdatePkUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `update_pk_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait update_pk_uuid { + /// Request that the remote module invoke the reducer `update_pk_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_update_pk_uuid`] callbacks. + fn update_pk_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `update_pk_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`UpdatePkUuidCallbackId`] can be passed to [`Self::remove_on_update_pk_uuid`] + /// to cancel the callback. + fn on_update_pk_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> UpdatePkUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_update_pk_uuid`], + /// causing it not to run in the future. + fn remove_on_update_pk_uuid(&self, callback: UpdatePkUuidCallbackId); +} + +impl update_pk_uuid for super::RemoteReducers { + fn update_pk_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()> { + self.imp.call_reducer("update_pk_uuid", UpdatePkUuidArgs { u, data }) + } + fn on_update_pk_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> UpdatePkUuidCallbackId { + UpdatePkUuidCallbackId(self.imp.on_reducer( + "update_pk_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::UpdatePkUuid { u, data }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u, data) + }), + )) + } + fn remove_on_update_pk_uuid(&self, callback: UpdatePkUuidCallbackId) { + self.imp.remove_on_reducer("update_pk_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `update_pk_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_update_pk_uuid { + /// Set the call-reducer flags for the reducer `update_pk_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn update_pk_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_update_pk_uuid for super::SetReducerFlags { + fn update_pk_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("update_pk_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/update_unique_uuid_reducer.rs b/sdks/rust/tests/test-client/src/module_bindings/update_unique_uuid_reducer.rs new file mode 100644 index 00000000000..465950b563f --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/update_unique_uuid_reducer.rs @@ -0,0 +1,106 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub(super) struct UpdateUniqueUuidArgs { + pub u: __sdk::Uuid, + pub data: i32, +} + +impl From for super::Reducer { + fn from(args: UpdateUniqueUuidArgs) -> Self { + Self::UpdateUniqueUuid { + u: args.u, + data: args.data, + } + } +} + +impl __sdk::InModule for UpdateUniqueUuidArgs { + type Module = super::RemoteModule; +} + +pub struct UpdateUniqueUuidCallbackId(__sdk::CallbackId); + +#[allow(non_camel_case_types)] +/// Extension trait for access to the reducer `update_unique_uuid`. +/// +/// Implemented for [`super::RemoteReducers`]. +pub trait update_unique_uuid { + /// Request that the remote module invoke the reducer `update_unique_uuid` to run as soon as possible. + /// + /// This method returns immediately, and errors only if we are unable to send the request. + /// The reducer will run asynchronously in the future, + /// and its status can be observed by listening for [`Self::on_update_unique_uuid`] callbacks. + fn update_unique_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()>; + /// Register a callback to run whenever we are notified of an invocation of the reducer `update_unique_uuid`. + /// + /// Callbacks should inspect the [`__sdk::ReducerEvent`] contained in the [`super::ReducerEventContext`] + /// to determine the reducer's status. + /// + /// The returned [`UpdateUniqueUuidCallbackId`] can be passed to [`Self::remove_on_update_unique_uuid`] + /// to cancel the callback. + fn on_update_unique_uuid( + &self, + callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> UpdateUniqueUuidCallbackId; + /// Cancel a callback previously registered by [`Self::on_update_unique_uuid`], + /// causing it not to run in the future. + fn remove_on_update_unique_uuid(&self, callback: UpdateUniqueUuidCallbackId); +} + +impl update_unique_uuid for super::RemoteReducers { + fn update_unique_uuid(&self, u: __sdk::Uuid, data: i32) -> __sdk::Result<()> { + self.imp + .call_reducer("update_unique_uuid", UpdateUniqueUuidArgs { u, data }) + } + fn on_update_unique_uuid( + &self, + mut callback: impl FnMut(&super::ReducerEventContext, &__sdk::Uuid, &i32) + Send + 'static, + ) -> UpdateUniqueUuidCallbackId { + UpdateUniqueUuidCallbackId(self.imp.on_reducer( + "update_unique_uuid", + Box::new(move |ctx: &super::ReducerEventContext| { + let super::ReducerEventContext { + event: + __sdk::ReducerEvent { + reducer: super::Reducer::UpdateUniqueUuid { u, data }, + .. + }, + .. + } = ctx + else { + unreachable!() + }; + callback(ctx, u, data) + }), + )) + } + fn remove_on_update_unique_uuid(&self, callback: UpdateUniqueUuidCallbackId) { + self.imp.remove_on_reducer("update_unique_uuid", callback.0) + } +} + +#[allow(non_camel_case_types)] +#[doc(hidden)] +/// Extension trait for setting the call-flags for the reducer `update_unique_uuid`. +/// +/// Implemented for [`super::SetReducerFlags`]. +/// +/// This type is currently unstable and may be removed without a major version bump. +pub trait set_flags_for_update_unique_uuid { + /// Set the call-reducer flags for the reducer `update_unique_uuid` to `flags`. + /// + /// This type is currently unstable and may be removed without a major version bump. + fn update_unique_uuid(&self, flags: __ws::CallReducerFlags); +} + +impl set_flags_for_update_unique_uuid for super::SetReducerFlags { + fn update_unique_uuid(&self, flags: __ws::CallReducerFlags) { + self.imp.set_call_reducer_flags("update_unique_uuid", flags); + } +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/vec_uuid_table.rs b/sdks/rust/tests/test-client/src/module_bindings/vec_uuid_table.rs new file mode 100644 index 00000000000..4a96bcdc229 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/vec_uuid_table.rs @@ -0,0 +1,95 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use super::vec_uuid_type::VecUuid; +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +/// Table handle for the table `vec_uuid`. +/// +/// Obtain a handle from the [`VecUuidTableAccess::vec_uuid`] method on [`super::RemoteTables`], +/// like `ctx.db.vec_uuid()`. +/// +/// Users are encouraged not to explicitly reference this type, +/// but to directly chain method calls, +/// like `ctx.db.vec_uuid().on_insert(...)`. +pub struct VecUuidTableHandle<'ctx> { + imp: __sdk::TableHandle, + ctx: std::marker::PhantomData<&'ctx super::RemoteTables>, +} + +#[allow(non_camel_case_types)] +/// Extension trait for access to the table `vec_uuid`. +/// +/// Implemented for [`super::RemoteTables`]. +pub trait VecUuidTableAccess { + #[allow(non_snake_case)] + /// Obtain a [`VecUuidTableHandle`], which mediates access to the table `vec_uuid`. + fn vec_uuid(&self) -> VecUuidTableHandle<'_>; +} + +impl VecUuidTableAccess for super::RemoteTables { + fn vec_uuid(&self) -> VecUuidTableHandle<'_> { + VecUuidTableHandle { + imp: self.imp.get_table::("vec_uuid"), + ctx: std::marker::PhantomData, + } + } +} + +pub struct VecUuidInsertCallbackId(__sdk::CallbackId); +pub struct VecUuidDeleteCallbackId(__sdk::CallbackId); + +impl<'ctx> __sdk::Table for VecUuidTableHandle<'ctx> { + type Row = VecUuid; + type EventContext = super::EventContext; + + fn count(&self) -> u64 { + self.imp.count() + } + fn iter(&self) -> impl Iterator + '_ { + self.imp.iter() + } + + type InsertCallbackId = VecUuidInsertCallbackId; + + fn on_insert( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> VecUuidInsertCallbackId { + VecUuidInsertCallbackId(self.imp.on_insert(Box::new(callback))) + } + + fn remove_on_insert(&self, callback: VecUuidInsertCallbackId) { + self.imp.remove_on_insert(callback.0) + } + + type DeleteCallbackId = VecUuidDeleteCallbackId; + + fn on_delete( + &self, + callback: impl FnMut(&Self::EventContext, &Self::Row) + Send + 'static, + ) -> VecUuidDeleteCallbackId { + VecUuidDeleteCallbackId(self.imp.on_delete(Box::new(callback))) + } + + fn remove_on_delete(&self, callback: VecUuidDeleteCallbackId) { + self.imp.remove_on_delete(callback.0) + } +} + +#[doc(hidden)] +pub(super) fn register_table(client_cache: &mut __sdk::ClientCache) { + let _table = client_cache.get_or_make_table::("vec_uuid"); +} + +#[doc(hidden)] +pub(super) fn parse_table_update( + raw_updates: __ws::TableUpdate<__ws::BsatnFormat>, +) -> __sdk::Result<__sdk::TableUpdate> { + __sdk::TableUpdate::parse_table_update(raw_updates).map_err(|e| { + __sdk::InternalError::failed_parse("TableUpdate", "TableUpdate") + .with_cause(e) + .into() + }) +} diff --git a/sdks/rust/tests/test-client/src/module_bindings/vec_uuid_type.rs b/sdks/rust/tests/test-client/src/module_bindings/vec_uuid_type.rs new file mode 100644 index 00000000000..ff729171f18 --- /dev/null +++ b/sdks/rust/tests/test-client/src/module_bindings/vec_uuid_type.rs @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE +// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. + +#![allow(unused, clippy::all)] +use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws}; + +#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)] +#[sats(crate = __lib)] +pub struct VecUuid { + pub u: Vec<__sdk::Uuid>, +} + +impl __sdk::InModule for VecUuid { + type Module = super::RemoteModule; +} diff --git a/sdks/rust/tests/test-client/src/pk_test_table.rs b/sdks/rust/tests/test-client/src/pk_test_table.rs index 5dce2828c35..27eff8618f5 100644 --- a/sdks/rust/tests/test-client/src/pk_test_table.rs +++ b/sdks/rust/tests/test-client/src/pk_test_table.rs @@ -1,5 +1,5 @@ use crate::module_bindings::*; -use spacetimedb_sdk::{i256, u256, ConnectionId, Event, Identity, Table, TableWithPrimaryKey}; +use spacetimedb_sdk::{i256, u256, ConnectionId, Event, Identity, Table, TableWithPrimaryKey, Uuid}; use std::sync::Arc; use test_counter::TestCounter; @@ -375,4 +375,15 @@ impl_pk_test_table! { accessor_method = pk_connection_id; } + PkUuid { + Key = Uuid; + key_field_name = u; + insert_reducer = insert_pk_uuid; + insert_reducer_event = InsertPkUuid; + delete_reducer = delete_pk_uuid; + delete_reducer_event = DeletePkUuid; + update_reducer = update_pk_uuid; + update_reducer_event = UpdatePkUuid; + accessor_method = pk_uuid; + } } diff --git a/sdks/rust/tests/test-client/src/simple_test_table.rs b/sdks/rust/tests/test-client/src/simple_test_table.rs index 60a06eaa2cf..a5f5af7d6b8 100644 --- a/sdks/rust/tests/test-client/src/simple_test_table.rs +++ b/sdks/rust/tests/test-client/src/simple_test_table.rs @@ -1,5 +1,5 @@ use crate::module_bindings::*; -use spacetimedb_sdk::{i256, u256, ConnectionId, Event, Identity, Table, Timestamp}; +use spacetimedb_sdk::{i256, u256, ConnectionId, Event, Identity, Table, Timestamp, Uuid}; use std::sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -193,6 +193,14 @@ impl_simple_test_table! { accessor_method = one_timestamp; } + OneUuid { + Contents = Uuid; + field_name = u; + insert_reducer = insert_one_uuid; + insert_reducer_event = InsertOneUuid; + accessor_method = one_uuid; + } + OneSimpleEnum { Contents = SimpleEnum; field_name = e; @@ -378,6 +386,14 @@ impl_simple_test_table! { accessor_method = vec_timestamp; } + VecUuid { + Contents = Vec; + field_name = u; + insert_reducer = insert_vec_uuid; + insert_reducer_event = InsertVecUuid; + accessor_method = vec_uuid; + } + VecSimpleEnum { Contents = Vec; field_name = e; @@ -442,6 +458,13 @@ impl_simple_test_table! { insert_reducer_event = InsertOptionIdentity; accessor_method = option_identity; } + OptionUuid { + Contents = Option; + field_name = u; + insert_reducer = insert_option_uuid; + insert_reducer_event = InsertOptionUuid; + accessor_method = option_uuid; + } OptionSimpleEnum { Contents = Option; field_name = e; diff --git a/sdks/rust/tests/test-client/src/unique_test_table.rs b/sdks/rust/tests/test-client/src/unique_test_table.rs index cf1304765dc..3ab24fa3018 100644 --- a/sdks/rust/tests/test-client/src/unique_test_table.rs +++ b/sdks/rust/tests/test-client/src/unique_test_table.rs @@ -1,5 +1,5 @@ use crate::module_bindings::*; -use spacetimedb_sdk::{i256, u256, ConnectionId, Event, Identity, Table}; +use spacetimedb_sdk::{i256, u256, ConnectionId, Event, Identity, Table, Uuid}; use std::sync::Arc; use test_counter::TestCounter; @@ -279,4 +279,14 @@ impl_unique_test_table! { delete_reducer_event = DeleteUniqueConnectionId; accessor_method = unique_connection_id; } + + UniqueUuid { + Key = Uuid; + key_field_name = u; + insert_reducer = insert_unique_uuid; + insert_reducer_event = InsertUniqueUuid; + delete_reducer = delete_unique_uuid; + delete_reducer_event = DeleteUniqueUuid; + accessor_method = unique_uuid; + } } diff --git a/sdks/rust/tests/test.rs b/sdks/rust/tests/test.rs index cabac445f8a..d147e997932 100644 --- a/sdks/rust/tests/test.rs +++ b/sdks/rust/tests/test.rs @@ -93,8 +93,28 @@ macro_rules! declare_tests_with_suffix { } #[test] - fn insert_call_timestamp() { - make_test("insert-call-timestamp").run(); + fn insert_call_uuid_v4() { + make_test("insert-call-uuid-v4").run(); + } + + #[test] + fn insert_call_uuid_v7() { + make_test("insert-call-uuid-v7").run(); + } + + #[test] + fn insert_uuid() { + make_test("insert-uuid").run(); + } + + #[test] + fn delete_uuid() { + make_test("delete-uuid").run(); + } + + #[test] + fn update_uuid() { + make_test("delete-uuid").run(); } #[test] @@ -260,6 +280,11 @@ macro_rules! declare_tests_with_suffix { fn overlapping_subscriptions() { make_test("overlapping-subscriptions").run(); } + + #[test] + fn sorted_uuids_insert() { + make_test("sorted-uuids-insert").run(); + } } }; } diff --git a/sdks/unreal/tests/test.rs b/sdks/unreal/tests/test.rs index e1cdeaa78de..0da811bf7db 100644 --- a/sdks/unreal/tests/test.rs +++ b/sdks/unreal/tests/test.rs @@ -224,6 +224,18 @@ fn unreal_insert_call_timestamp() { make_test("InsertCallTimestampTest").run(); } +#[test] +#[serial(Group2)] +fn unreal_insert_call_uuid_v4() { + make_test("InsertCallUuidV4Test").run(); +} + +#[test] +#[serial(Group2)] +fn unreal_insert_call_uuid_v7() { + make_test("InsertCallUuidV7Test").run(); +} + // ---------------- GROUP 3 ---------------- #[test] #[serial(Group3)] diff --git a/smoketests/tests/pg_wire.py b/smoketests/tests/pg_wire.py index 24402e2ec9f..211baebe48d 100644 --- a/smoketests/tests/pg_wire.py +++ b/smoketests/tests/pg_wire.py @@ -4,11 +4,12 @@ import tomllib import psycopg2 + class SqlFormat(Smoketest): AUTOPUBLISH = False MODULE_CODE = """ use spacetimedb::sats::{i256, u256}; -use spacetimedb::{ConnectionId, Identity, ReducerContext, SpacetimeType, Table, Timestamp, TimeDuration}; +use spacetimedb::{ConnectionId, Identity, ReducerContext, SpacetimeType, Table, Timestamp, TimeDuration, Uuid}; #[derive(Copy, Clone)] #[spacetimedb::table(name = t_ints, public)] @@ -54,6 +55,7 @@ class SqlFormat(Smoketest): connection_id: ConnectionId, timestamp: Timestamp, duration: TimeDuration, + uuid: Uuid, } #[spacetimedb::table(name = t_others_tuple, public)] @@ -127,6 +129,7 @@ class SqlFormat(Smoketest): connection_id: ConnectionId::ZERO, timestamp: Timestamp::UNIX_EPOCH, duration: TimeDuration::from_micros(1000 * 10000), + uuid: Uuid::NIL, }; ctx.db.t_others().insert(tuple.clone()); ctx.db.t_others_tuple().insert(TOthersTuple { tuple }); @@ -209,14 +212,14 @@ def test_sql_format(self): {"u8": 105, "u16": 1050, "u32": 83892, "u64": 48937498, "u128": 4378528978889, "u256": 4378528978889} (1 row)""") self.assertSql(token, "SELECT * FROM t_others", """\ -bool | f32 | f64 | str | bytes | identity | connection_id | timestamp | duration -------+-----------+---------------------+---------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+---------- - t | 594806.56 | -3454353.3453890434 | This is spacetimedb | \\x01020304050607 | \\x0000000000000000000000000000000000000000000000000000000000000001 | \\x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | PT10S +bool | f32 | f64 | str | bytes | identity | connection_id | timestamp | duration | uuid +------+-----------+---------------------+---------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+----------+-------------------------------------- + t | 594806.56 | -3454353.3453890434 | This is spacetimedb | \\x01020304050607 | \\x0000000000000000000000000000000000000000000000000000000000000001 | \\x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | PT10S | 00000000-0000-0000-0000-000000000000 (1 row)""") self.assertSql(token, "SELECT * FROM t_others_tuple", """\ tuple ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - {"bool": true, "f32": 594806.56, "f64": -3454353.3453890434, "str": "This is spacetimedb", "bytes": "0x01020304050607", "identity": "0x0000000000000000000000000000000000000000000000000000000000000001", "connection_id": "0x00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00+00:00", "duration": "PT10S"} +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {"bool": true, "f32": 594806.56, "f64": -3454353.3453890434, "str": "This is spacetimedb", "bytes": "0x01020304050607", "identity": "0x0000000000000000000000000000000000000000000000000000000000000001", "connection_id": "0x00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00+00:00", "duration": "PT10S", "uuid": "00000000-0000-0000-0000-000000000000"} (1 row)""") self.assertSql(token, "SELECT * FROM t_simple_enum", """\ id | action diff --git a/smoketests/tests/sql.py b/smoketests/tests/sql.py index c90d63c3661..3cba56787c0 100644 --- a/smoketests/tests/sql.py +++ b/smoketests/tests/sql.py @@ -4,7 +4,7 @@ class SqlFormat(Smoketest): MODULE_CODE = """ use spacetimedb::sats::{i256, u256}; -use spacetimedb::{table, ConnectionId, Identity, ReducerContext, Table, Timestamp, TimeDuration}; +use spacetimedb::{table, ConnectionId, Identity, ReducerContext, Table, Timestamp, TimeDuration, Uuid}; #[derive(Copy, Clone)] #[spacetimedb::table(name = t_ints)] @@ -50,6 +50,7 @@ class SqlFormat(Smoketest): connection_id: ConnectionId, timestamp: Timestamp, duration: TimeDuration, + uuid: Uuid, } #[spacetimedb::table(name = t_others_tuple)] @@ -91,6 +92,7 @@ class SqlFormat(Smoketest): connection_id: ConnectionId::ZERO, timestamp: Timestamp::UNIX_EPOCH, duration: TimeDuration::ZERO, + uuid: Uuid::NIL, }; ctx.db.t_others().insert(tuple.clone()); ctx.db.t_others_tuple().insert(TOthersTuple { tuple }); @@ -130,12 +132,12 @@ def test_sql_format(self): (u8 = 105, u16 = 1050, u32 = 83892, u64 = 48937498, u128 = 4378528978889, u256 = 4378528978889) """) self.assertSql("SELECT * FROM t_others", """\ - bool | f32 | f64 | str | bytes | identity | connection_id | timestamp | duration -------+-----------+--------------------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+----------- - true | 594806.56 | -3454353.345389043 | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000001 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000 + bool | f32 | f64 | str | bytes | identity | connection_id | timestamp | duration | uuid +------+-----------+--------------------+-----------------------+------------------+--------------------------------------------------------------------+------------------------------------+---------------------------+-----------+---------------------------------------- + true | 594806.56 | -3454353.345389043 | "This is spacetimedb" | 0x01020304050607 | 0x0000000000000000000000000000000000000000000000000000000000000001 | 0x00000000000000000000000000000000 | 1970-01-01T00:00:00+00:00 | +0.000000 | "00000000-0000-0000-0000-000000000000" """) self.assertSql("SELECT * FROM t_others_tuple", """\ - tuple ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - (bool = true, f32 = 594806.56, f64 = -3454353.345389043, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000001, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000) + tuple +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + (bool = true, f32 = 594806.56, f64 = -3454353.345389043, str = "This is spacetimedb", bytes = 0x01020304050607, identity = 0x0000000000000000000000000000000000000000000000000000000000000001, connection_id = 0x00000000000000000000000000000000, timestamp = 1970-01-01T00:00:00+00:00, duration = +0.000000, uuid = "00000000-0000-0000-0000-000000000000") """)