From 0a492c7afc4ba704aaf28dd09b5b22b4fd938d45 Mon Sep 17 00:00:00 2001 From: joshua-spacetime Date: Wed, 25 Feb 2026 13:49:37 -0800 Subject: [PATCH] Make event tables inaccessible from views --- .../Codegen.Tests/fixtures/diag/Lib.cs | 33 ++ .../ExtraCompilationErrors.verified.txt | 94 +++++- .../diag/snapshots/Module#FFI.verified.cs | 296 +++++++++++++++++- .../Module#ViewAuditEvent.verified.cs | 103 ++++++ .../server/snapshots/Module#FFI.verified.cs | 34 -- crates/bindings-csharp/Codegen/Module.cs | 4 +- crates/bindings-macro/src/table.rs | 40 ++- crates/bindings-typescript/src/lib/schema.ts | 3 +- crates/bindings-typescript/src/lib/table.ts | 12 +- .../src/lib/table_schema.ts | 9 +- .../bindings-typescript/src/server/db_view.ts | 13 +- .../bindings-typescript/src/server/runtime.ts | 51 ++- .../src/server/view.test-d.ts | 34 ++ .../bindings-typescript/src/server/views.ts | 6 +- crates/bindings/tests/ui/views.rs | 33 +- crates/bindings/tests/ui/views.stderr | 90 ++++-- 16 files changed, 757 insertions(+), 98 deletions(-) create mode 100644 crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#ViewAuditEvent.verified.cs diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs index 47d5cf291f5..7a5828bb40d 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/Lib.cs @@ -491,6 +491,13 @@ public partial struct Player public Identity Identity; } +[SpacetimeDB.Table(Event = true)] +public partial struct ViewAuditEvent +{ + [PrimaryKey] + public ulong Id; +} + public struct NotSpacetimeType { } public partial class Module @@ -612,4 +619,30 @@ ViewContext ctx ctx.Db.Player.Identity.Delete(0); return null; } + + [SpacetimeDB.View(Accessor = "event_table_view", Public = true)] + public static Player? EventTableView(ViewContext ctx) + { + var _ = ctx.Db.ViewAuditEvent; + return null; + } + + [SpacetimeDB.View(Accessor = "event_table_view_anon", Public = true)] + public static Player? EventTableViewAnon(AnonymousViewContext ctx) + { + var _ = ctx.Db.ViewAuditEvent; + return null; + } + + [SpacetimeDB.View(Accessor = "event_table_view_query_builder", Public = true)] + public static IQuery EventTableViewQueryBuilder(ViewContext ctx) + { + return ctx.From.ViewAuditEvent(); + } + + [SpacetimeDB.View(Accessor = "event_table_view_anon_query_builder", Public = true)] + public static IQuery EventTableViewAnonQueryBuilder(AnonymousViewContext ctx) + { + return ctx.From.ViewAuditEvent(); + } } diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt index 8e960c9f38a..ca2b9b98136 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/ExtraCompilationErrors.verified.txt @@ -23,7 +23,53 @@ } }, {/* -SpacetimeDB.Internal.Module.RegisterTable(); + { + var _ = ctx.Db.ViewAuditEvent; + ^^^^^^^^^^^^^^ + return null; +*/ + Message: 'LocalReadOnly' does not contain a definition for 'ViewAuditEvent' and no accessible extension method 'ViewAuditEvent' accepting a first argument of type 'LocalReadOnly' could be found (are you missing a using directive or an assembly reference?), + Severity: Error, + Descriptor: { + Id: CS1061, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1061), + MessageFormat: '{0}' does not contain a definition for '{1}' and no accessible extension method '{1}' accepting a first argument of type '{0}' could be found (are you missing a using directive or an assembly reference?), + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + { + var _ = ctx.Db.ViewAuditEvent; + ^^^^^^^^^^^^^^ + return null; +*/ + Message: 'LocalReadOnly' does not contain a definition for 'ViewAuditEvent' and no accessible extension method 'ViewAuditEvent' accepting a first argument of type 'LocalReadOnly' could be found (are you missing a using directive or an assembly reference?), + Severity: Error, + Descriptor: { + Id: CS1061, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1061), + MessageFormat: '{0}' does not contain a definition for '{1}' and no accessible extension method '{1}' accepting a first argument of type '{0}' could be found (are you missing a using directive or an assembly reference?), + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* +SpacetimeDB.Internal.Module.RegisterTable(); SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FILTER); ^^^^^^^^^ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FOURTH_FILTER); @@ -160,6 +206,52 @@ SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FOU ] } }, + {/* + { + return ctx.From.ViewAuditEvent(); + ^^^^^^^^^^^^^^ + } +*/ + Message: 'QueryBuilder' does not contain a definition for 'ViewAuditEvent' and no accessible extension method 'ViewAuditEvent' accepting a first argument of type 'QueryBuilder' could be found (are you missing a using directive or an assembly reference?), + Severity: Error, + Descriptor: { + Id: CS1061, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1061), + MessageFormat: '{0}' does not contain a definition for '{1}' and no accessible extension method '{1}' accepting a first argument of type '{0}' could be found (are you missing a using directive or an assembly reference?), + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, + {/* + { + return ctx.From.ViewAuditEvent(); + ^^^^^^^^^^^^^^ + } +*/ + Message: 'QueryBuilder' does not contain a definition for 'ViewAuditEvent' and no accessible extension method 'ViewAuditEvent' accepting a first argument of type 'QueryBuilder' could be found (are you missing a using directive or an assembly reference?), + Severity: Error, + Descriptor: { + Id: CS1061, + Title: , + HelpLink: https://msdn.microsoft.com/query/roslyn.query?appId=roslyn&k=k(CS1061), + MessageFormat: '{0}' does not contain a definition for '{1}' and no accessible extension method '{1}' accepting a first argument of type '{0}' could be found (are you missing a using directive or an assembly reference?), + Category: Compiler, + DefaultSeverity: Error, + IsEnabledByDefault: true, + CustomTags: [ + Compiler, + Telemetry, + NotConfigurable + ] + } + }, {/* // Valid Filter, but [ClientVisibilityFilter] is disabled [SpacetimeDB.ClientVisibilityFilter] diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs index e69962b169c..8a6396f3ad7 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#FFI.verified.cs @@ -825,6 +825,7 @@ public sealed class Local : global::SpacetimeDB.LocalBase new(); public global::SpacetimeDB.Internal.TableHandles.TestUniqueNotEquatable TestUniqueNotEquatable => new(); + public global::SpacetimeDB.Internal.TableHandles.ViewAuditEvent ViewAuditEvent => new(); } public sealed record ViewContext : DbContext, Internal.IViewContext @@ -1805,6 +1806,177 @@ internal PrimaryKeyFieldUniqueIndex() public PrimaryKeyFieldUniqueIndex PrimaryKeyField => new(); } + + public readonly struct ViewAuditEvent + : global::SpacetimeDB.Internal.ITableView + { + public static global::ViewAuditEvent ReadGenFields( + System.IO.BinaryReader reader, + global::ViewAuditEvent row + ) + { + return row; + } + + public static SpacetimeDB.Internal.RawTableDefV10 MakeTableDesc( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new( + SourceName: nameof(ViewAuditEvent), + ProductTypeRef: (uint) + new global::ViewAuditEvent.BSATN().GetAlgebraicType(registrar).Ref_, + PrimaryKey: [0], + Indexes: + [ + new( + SourceName: "ViewAuditEvent_Id_idx_btree", + AccessorName: "Id", + Algorithm: new SpacetimeDB.Internal.RawIndexAlgorithm.BTree([0]) + ) + ], + Constraints: + [ + global::SpacetimeDB.Internal.ITableView< + ViewAuditEvent, + global::ViewAuditEvent + >.MakeUniqueConstraint(0) + ], + Sequences: [], + TableType: SpacetimeDB.Internal.TableType.User, + TableAccess: SpacetimeDB.Internal.TableAccess.Private, + DefaultValues: [], + IsEvent: true + ); + + public static SpacetimeDB.Internal.RawScheduleDefV10? MakeScheduleDesc() => null; + + public ulong Count => + global::SpacetimeDB.Internal.ITableView< + ViewAuditEvent, + global::ViewAuditEvent + >.DoCount(); + + public IEnumerable Iter() => + global::SpacetimeDB.Internal.ITableView< + ViewAuditEvent, + global::ViewAuditEvent + >.DoIter(); + + public global::ViewAuditEvent Insert(global::ViewAuditEvent row) => + global::SpacetimeDB.Internal.ITableView< + ViewAuditEvent, + global::ViewAuditEvent + >.DoInsert(row); + + public bool Delete(global::ViewAuditEvent row) => + global::SpacetimeDB.Internal.ITableView< + ViewAuditEvent, + global::ViewAuditEvent + >.DoDelete(row); + + public sealed class IdUniqueIndex + : UniqueIndex + { + internal IdUniqueIndex() + : base("ViewAuditEvent_Id_idx_btree") { } + + // Important: don't move this to the base class. + // C# generics don't play well with nullable types and can't accept both struct-type-based and class-type-based + // `globalName` in one generic definition, leading to buggy `Row?` expansion for either one or another. + public global::ViewAuditEvent? Find(ulong key) => FindSingle(key); + + public global::ViewAuditEvent Update(global::ViewAuditEvent row) => DoUpdate(row); + } + + public IdUniqueIndex Id => new(); + } +} + +sealed class event_table_viewViewDispatcher : global::SpacetimeDB.Internal.IView +{ + public SpacetimeDB.Internal.RawViewDefV10 MakeViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV10( + SourceName: "event_table_view", + Index: 0, + IsPublic: true, + IsAnonymous: false, + Params: [], + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( + registrar + ) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IViewContext ctx + ) + { + try + { + var returnValue = Module.EventTableView((SpacetimeDB.ViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + listSerializer.Write(writer, listValue); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error("Error in view 'event_table_view': " + e); + throw; + } + } +} + +sealed class event_table_view_query_builderViewDispatcher : global::SpacetimeDB.Internal.IView +{ + public SpacetimeDB.Internal.RawViewDefV10 MakeViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV10( + SourceName: "event_table_view_query_builder", + Index: 1, + IsPublic: true, + IsAnonymous: false, + Params: [], + ReturnType: new SpacetimeDB.BSATN.ValueOption< + ViewAuditEvent, + ViewAuditEvent.BSATN + >().GetAlgebraicType(registrar) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IViewContext ctx + ) + { + try + { + var returnValue = Module.EventTableViewQueryBuilder((SpacetimeDB.ViewContext)ctx); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RawSql( + returnValue.ToSql() + ); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error("Error in view 'event_table_view_query_builder': " + e); + throw; + } + } } sealed class view_def_ienumerable_return_from_filterViewDispatcher @@ -1815,7 +1987,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_ienumerable_return_from_filter", - Index: 0, + Index: 2, IsPublic: true, IsAnonymous: false, Params: [], @@ -1862,7 +2034,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_ienumerable_return_from_iter", - Index: 1, + Index: 3, IsPublic: true, IsAnonymous: false, Params: [], @@ -1906,7 +2078,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_no_context", - Index: 2, + Index: 4, IsPublic: true, IsAnonymous: false, Params: [], @@ -1947,7 +2119,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_no_public", - Index: 3, + Index: 5, IsPublic: false, IsAnonymous: false, Params: [], @@ -1988,7 +2160,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_wrong_context", - Index: 4, + Index: 6, IsPublic: true, IsAnonymous: false, Params: [], @@ -2029,7 +2201,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_wrong_return", - Index: 5, + Index: 7, IsPublic: true, IsAnonymous: false, Params: [], @@ -2068,7 +2240,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_no_delete", - Index: 6, + Index: 8, IsPublic: true, IsAnonymous: false, Params: [], @@ -2113,7 +2285,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_no_insert", - Index: 7, + Index: 9, IsPublic: true, IsAnonymous: false, Params: [], @@ -2151,6 +2323,98 @@ public byte[] Invoke( } } +sealed class event_table_view_anonViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView +{ + public SpacetimeDB.Internal.RawViewDefV10 MakeAnonymousViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV10( + SourceName: "event_table_view_anon", + Index: 0, + IsPublic: true, + IsAnonymous: true, + Params: [], + ReturnType: new SpacetimeDB.BSATN.ValueOption().GetAlgebraicType( + registrar + ) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IAnonymousViewContext ctx + ) + { + try + { + var returnValue = Module.EventTableViewAnon((SpacetimeDB.AnonymousViewContext)ctx); + var listSerializer = SpacetimeDB.BSATN.ValueOption< + Player, + Player.BSATN + >.GetListSerializer(); + var listValue = ModuleRegistration.ToListOrEmpty(returnValue); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RowData(default); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + listSerializer.Write(writer, listValue); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error("Error in view 'event_table_view_anon': " + e); + throw; + } + } +} + +sealed class event_table_view_anon_query_builderViewDispatcher + : global::SpacetimeDB.Internal.IAnonymousView +{ + public SpacetimeDB.Internal.RawViewDefV10 MakeAnonymousViewDef( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + new global::SpacetimeDB.Internal.RawViewDefV10( + SourceName: "event_table_view_anon_query_builder", + Index: 1, + IsPublic: true, + IsAnonymous: true, + Params: [], + ReturnType: new SpacetimeDB.BSATN.ValueOption< + ViewAuditEvent, + ViewAuditEvent.BSATN + >().GetAlgebraicType(registrar) + ); + + public byte[] Invoke( + System.IO.BinaryReader reader, + global::SpacetimeDB.Internal.IAnonymousViewContext ctx + ) + { + try + { + var returnValue = Module.EventTableViewAnonQueryBuilder( + (SpacetimeDB.AnonymousViewContext)ctx + ); + var header = new global::SpacetimeDB.Internal.ViewResultHeader.RawSql( + returnValue.ToSql() + ); + var headerRW = new global::SpacetimeDB.Internal.ViewResultHeader.BSATN(); + using var output = new System.IO.MemoryStream(); + using var writer = new System.IO.BinaryWriter(output); + headerRW.Write(writer, header); + return output.ToArray(); + } + catch (System.Exception e) + { + global::SpacetimeDB.Log.Error( + "Error in view 'event_table_view_anon_query_builder': " + e + ); + throw; + } + } +} + sealed class view_def_index_no_mutationViewDispatcher : global::SpacetimeDB.Internal.IAnonymousView { public SpacetimeDB.Internal.RawViewDefV10 MakeAnonymousViewDef( @@ -2158,7 +2422,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_index_no_mutation", - Index: 0, + Index: 2, IsPublic: true, IsAnonymous: true, Params: [], @@ -2203,7 +2467,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_no_anon_identity", - Index: 1, + Index: 3, IsPublic: true, IsAnonymous: true, Params: [], @@ -2248,7 +2512,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_no_iter", - Index: 2, + Index: 4, IsPublic: true, IsAnonymous: true, Params: [], @@ -2294,7 +2558,7 @@ SpacetimeDB.BSATN.ITypeRegistrar registrar ) => new global::SpacetimeDB.Internal.RawViewDefV10( SourceName: "view_def_returns_not_a_spacetime_type", - Index: 3, + Index: 5, IsPublic: true, IsAnonymous: true, Params: [], @@ -2842,6 +3106,8 @@ public static void Main() // IMPORTANT: The order in which we register views matters. // It must correspond to the order in which we call `GenerateDispatcherClass`. // See the comment on `GenerateDispatcherClass` for more explanation. + SpacetimeDB.Internal.Module.RegisterView(); + SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); @@ -2850,6 +3116,8 @@ public static void Main() SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); SpacetimeDB.Internal.Module.RegisterView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); + SpacetimeDB.Internal.Module.RegisterAnonymousView(); SpacetimeDB.Internal.Module.RegisterAnonymousView(); SpacetimeDB.Internal.Module.RegisterAnonymousView(); SpacetimeDB.Internal.Module.RegisterAnonymousView(); @@ -2899,6 +3167,10 @@ public static void Main() global::TestUniqueNotEquatable, SpacetimeDB.Internal.TableHandles.TestUniqueNotEquatable >(); + SpacetimeDB.Internal.Module.RegisterTable< + global::ViewAuditEvent, + SpacetimeDB.Internal.TableHandles.ViewAuditEvent + >(); SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FILTER); SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_FOURTH_FILTER); SpacetimeDB.Internal.Module.RegisterClientVisibilityFilter(global::Module.MY_SECOND_FILTER); diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#ViewAuditEvent.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#ViewAuditEvent.verified.cs new file mode 100644 index 00000000000..eda9096fb74 --- /dev/null +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/diag/snapshots/Module#ViewAuditEvent.verified.cs @@ -0,0 +1,103 @@ +//HintName: ViewAuditEvent.cs +// +#nullable enable + +partial struct ViewAuditEvent + : System.IEquatable, + SpacetimeDB.BSATN.IStructuralReadWrite +{ + public void ReadFields(System.IO.BinaryReader reader) + { + Id = BSATN.IdRW.Read(reader); + } + + public void WriteFields(System.IO.BinaryWriter writer) + { + BSATN.IdRW.Write(writer, Id); + } + + object SpacetimeDB.BSATN.IStructuralReadWrite.GetSerializer() + { + return new BSATN(); + } + + public override string ToString() => + $"ViewAuditEvent {{ Id = {SpacetimeDB.BSATN.StringUtil.GenericToString(Id)} }}"; + + public readonly partial struct BSATN : SpacetimeDB.BSATN.IReadWrite + { + internal static readonly SpacetimeDB.BSATN.U64 IdRW = new(); + + public ViewAuditEvent Read(System.IO.BinaryReader reader) + { + var ___result = new ViewAuditEvent(); + ___result.ReadFields(reader); + return ___result; + } + + public void Write(System.IO.BinaryWriter writer, ViewAuditEvent value) + { + value.WriteFields(writer); + } + + public SpacetimeDB.BSATN.AlgebraicType.Ref GetAlgebraicType( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => + registrar.RegisterType(_ => new SpacetimeDB.BSATN.AlgebraicType.Product( + new SpacetimeDB.BSATN.AggregateElement[] + { + new("Id", IdRW.GetAlgebraicType(registrar)) + } + )); + + SpacetimeDB.BSATN.AlgebraicType SpacetimeDB.BSATN.IReadWrite.GetAlgebraicType( + SpacetimeDB.BSATN.ITypeRegistrar registrar + ) => GetAlgebraicType(registrar); + } + + public override int GetHashCode() + { + var ___hashId = Id.GetHashCode(); + return ___hashId; + } + +#nullable enable + public bool Equals(ViewAuditEvent that) + { + var ___eqId = this.Id.Equals(that.Id); + return ___eqId; + } + + public override bool Equals(object? that) + { + if (that == null) + { + return false; + } + var that_ = that as ViewAuditEvent?; + if (((object?)that_) == null) + { + return false; + } + return Equals(that_); + } + + public static bool operator ==(ViewAuditEvent this_, ViewAuditEvent that) + { + if (((object?)this_) == null || ((object?)that) == null) + { + return object.Equals(this_, that); + } + return this_.Equals(that); + } + + public static bool operator !=(ViewAuditEvent this_, ViewAuditEvent that) + { + if (((object?)this_) == null || ((object?)that) == null) + { + return !object.Equals(this_, that); + } + return !this_.Equals(that); + } +#nullable restore +} // ViewAuditEvent diff --git a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs index addc736a618..3aa17379639 100644 --- a/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs +++ b/crates/bindings-csharp/Codegen.Tests/fixtures/server/snapshots/Module#FFI.verified.cs @@ -181,30 +181,6 @@ public readonly partial struct QueryBuilder ); } - public readonly struct PrivateTableCols - { - internal PrivateTableCols(string tableName) { } - } - - public readonly struct PrivateTableIxCols - { - internal PrivateTableIxCols(string tableName) { } - } - - public readonly partial struct QueryBuilder - { - public global::SpacetimeDB.Table< - global::PrivateTable, - PrivateTableCols, - PrivateTableIxCols - > PrivateTable() => - new( - "PrivateTable", - new PrivateTableCols("PrivateTable"), - new PrivateTableIxCols("PrivateTable") - ); - } - public readonly struct PublicTableCols { public readonly global::SpacetimeDB.Col Id; @@ -2026,15 +2002,6 @@ internal BarIndex() public BarIndex Bar => new(); } - public sealed class PrivateTableReadOnly - : global::SpacetimeDB.Internal.ReadOnlyTableView - { - internal PrivateTableReadOnly() - : base("PrivateTable") { } - - public ulong Count => DoCount(); - } - public sealed class PublicTableReadOnly : global::SpacetimeDB.Internal.ReadOnlyTableView { @@ -2138,7 +2105,6 @@ public sealed partial class LocalReadOnly internal global::SpacetimeDB.Internal.ViewHandles.BTreeViewsReadOnly BTreeViews => new(); public global::SpacetimeDB.Internal.ViewHandles.MultiTable1ReadOnly MultiTable1 => new(); public global::SpacetimeDB.Internal.ViewHandles.MultiTable2ReadOnly MultiTable2 => new(); - public global::SpacetimeDB.Internal.ViewHandles.PrivateTableReadOnly PrivateTable => new(); public global::SpacetimeDB.Internal.ViewHandles.PublicTableReadOnly PublicTable => new(); internal global::SpacetimeDB.Internal.ViewHandles.RegressionMultipleUniqueIndexesHadSameNameReadOnly RegressionMultipleUniqueIndexesHadSameName => new(); diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index b5bf7d9c3d7..62eca6e9d77 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -853,7 +853,7 @@ public IEnumerable GenerateReadOnlyAccessors() yield break; } - foreach (var accessor in TableAccessors) + foreach (var accessor in TableAccessors.Where(accessor => !accessor.IsEvent)) { var globalName = $"global::{FullName}"; @@ -888,7 +888,7 @@ public IEnumerable GenerateQueryBuilderMembers() var vis = SyntaxFacts.GetText(Visibility); var globalRowName = $"global::{FullName}"; - foreach (var accessor in TableAccessors) + foreach (var accessor in TableAccessors.Where(accessor => !accessor.IsEvent)) { var tableName = accessor.Name; var colsTypeName = $"{accessor.Name}Cols"; diff --git a/crates/bindings-macro/src/table.rs b/crates/bindings-macro/src/table.rs index ae2cbbc4334..5cbc2c88e29 100644 --- a/crates/bindings-macro/src/table.rs +++ b/crates/bindings-macro/src/table.rs @@ -1167,18 +1167,22 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R } }; - let trait_def_view = quote_spanned! {table_ident.span()=> - #[allow(non_camel_case_types, dead_code)] - #vis trait #view_trait_ident { + let trait_def_view = if args.event.is_none() { + quote_spanned! {table_ident.span()=> #[allow(non_camel_case_types, dead_code)] - fn #table_ident(&self) -> &#viewhandle_ident; - } - impl #view_trait_ident for spacetimedb::LocalReadOnly { - #[inline] - fn #table_ident(&self) -> &#viewhandle_ident { - &#viewhandle_ident {} + #vis trait #view_trait_ident { + #[allow(non_camel_case_types, dead_code)] + fn #table_ident(&self) -> &#viewhandle_ident; + } + impl #view_trait_ident for spacetimedb::LocalReadOnly { + #[inline] + fn #table_ident(&self) -> &#viewhandle_ident { + &#viewhandle_ident {} + } } } + } else { + quote! {} }; let cols_struct_fields = fields.iter().map(|col| { @@ -1250,14 +1254,18 @@ pub(crate) fn table_impl(mut args: TableArgs, item: &syn::DeriveInput) -> syn::R }; - let table_query_handle_def = quote! { - #[allow(non_camel_case_types, dead_code)] - #vis trait #query_trait_ident { - fn #table_ident(&self) -> spacetimedb::query_builder::Table<#original_struct_ident> { - spacetimedb::query_builder::Table::new(stringify!(#table_ident)) + let table_query_handle_def = if args.event.is_none() { + quote! { + #[allow(non_camel_case_types, dead_code)] + #vis trait #query_trait_ident { + fn #table_ident(&self) -> spacetimedb::query_builder::Table<#original_struct_ident> { + spacetimedb::query_builder::Table::new(stringify!(#table_ident)) + } } - } - impl #query_trait_ident for spacetimedb::QueryBuilder {} + impl #query_trait_ident for spacetimedb::QueryBuilder {} + } + } else { + quote! {} }; let tablehandle_def = quote! { diff --git a/crates/bindings-typescript/src/lib/schema.ts b/crates/bindings-typescript/src/lib/schema.ts index fed510ba74f..57f9e763135 100644 --- a/crates/bindings-typescript/src/lib/schema.ts +++ b/crates/bindings-typescript/src/lib/schema.ts @@ -63,6 +63,7 @@ export interface TableToSchema< rowType: T['rowSpacetimeType']; indexes: T['idxs']; constraints: T['constraints']; + isEvent: T['isEvent']; } export function tablesToSchema< @@ -122,7 +123,7 @@ export function tableToSchema< }; }) as T['idxs'], tableDef, - ...(tableDef.isEvent ? { isEvent: true } : {}), + isEvent: schema.isEvent, }; } diff --git a/crates/bindings-typescript/src/lib/table.ts b/crates/bindings-typescript/src/lib/table.ts index f51763e5211..6c4c899843b 100644 --- a/crates/bindings-typescript/src/lib/table.ts +++ b/crates/bindings-typescript/src/lib/table.ts @@ -203,6 +203,15 @@ type OptsConstraints> = Opts extends { ? Constraints : CoerceArray<[]>; +/** + * Extracts the event-table flag from TableOpts, defaulting to false. + */ +type OptsIsEvent> = Opts extends { + event: infer IsEvent extends boolean; +} + ? IsEvent + : false; + /** * Table * @@ -299,7 +308,7 @@ export function table>( >, ] : [] -): TableSchema, OptsIndices> { +): TableSchema, OptsIndices, OptsIsEvent> { const { name, public: isPublic = false, @@ -499,5 +508,6 @@ export function table>( idxs: {} as OptsIndices, constraints: constraints as OptsConstraints, schedule, + isEvent: isEvent as OptsIsEvent, }; } diff --git a/crates/bindings-typescript/src/lib/table_schema.ts b/crates/bindings-typescript/src/lib/table_schema.ts index e9ce375adb9..dde14975656 100644 --- a/crates/bindings-typescript/src/lib/table_schema.ts +++ b/crates/bindings-typescript/src/lib/table_schema.ts @@ -11,6 +11,7 @@ import type { ColumnBuilder, RowBuilder } from './type_builders'; export type TableSchema< Row extends Record>, Idx extends readonly IndexOpts[], + IsEvent extends boolean = boolean, > = { /** * The name of the table. @@ -56,9 +57,15 @@ export type TableSchema< scheduleAtCol: number; reducer: () => ReducerExport | ProcedureExport; }; + + /** + * Whether this table is an event table. + */ + readonly isEvent: IsEvent; }; export type UntypedTableSchema = TableSchema< Record>, - readonly IndexOpts[] + readonly IndexOpts[], + boolean >; diff --git a/crates/bindings-typescript/src/server/db_view.ts b/crates/bindings-typescript/src/server/db_view.ts index 9e0350ff863..06681e80e2c 100644 --- a/crates/bindings-typescript/src/server/db_view.ts +++ b/crates/bindings-typescript/src/server/db_view.ts @@ -2,12 +2,23 @@ import type { UntypedSchemaDef } from '../lib/schema'; import type { ReadonlyTable, Table } from '../lib/table'; import type { Values } from '../lib/type_util'; +/** + * Removes event tables from a schema type. + */ +export type NonEventSchema = { + tables: { + readonly [AccName in keyof SchemaDef['tables'] as SchemaDef['tables'][AccName]['isEvent'] extends true + ? never + : AccName]: SchemaDef['tables'][AccName]; + }; +}; + /** * A type representing a read-only database view, mapping table names to their corresponding read-only Table handles. */ export type ReadonlyDbView = { readonly [Tbl in Values< - SchemaDef['tables'] + NonEventSchema['tables'] > as Tbl['accessorName']]: ReadonlyTable; }; diff --git a/crates/bindings-typescript/src/server/runtime.ts b/crates/bindings-typescript/src/server/runtime.ts index c68f3d765d5..db79444a8e8 100644 --- a/crates/bindings-typescript/src/server/runtime.ts +++ b/crates/bindings-typescript/src/server/runtime.ts @@ -36,8 +36,13 @@ import { type UntypedSchemaDef } from '../lib/schema'; import { type RowType, type Table, type TableMethods } from '../lib/table'; import { hasOwn } from '../lib/util'; import { type AnonymousViewCtx, type ViewCtx } from './views'; -import { isRowTypedQuery, makeQueryBuilder, toSql } from './query'; -import type { DbView } from './db_view'; +import { + isRowTypedQuery, + makeQueryBuilder, + toSql, + type QueryBuilder, +} from './query'; +import type { DbView, NonEventSchema } from './db_view'; import { getErrorConstructor, SenderError } from './errors'; import { Range, type Bound } from './range'; import { makeRandom, type Random } from './rng'; @@ -273,6 +278,9 @@ export const makeHooks = (schema: SchemaInner): ModuleHooks => class ModuleHooksImpl implements ModuleHooks { #schema: SchemaInner; #dbView_: DbView | undefined; + #viewSchema_: NonEventSchema | undefined; + #viewDbView_: DbView | undefined; + #viewFrom_: QueryBuilder> | undefined; #reducerArgsDeserializers; /** Cache the `ReducerCtx` object to avoid allocating anew for ever reducer call. */ #reducerCtx_: InstanceType | undefined; @@ -295,6 +303,25 @@ class ModuleHooksImpl implements ModuleHooks { )); } + get #viewSchema() { + return (this.#viewSchema_ ??= withoutEventTables(this.#schema.schemaType)); + } + + get #viewDbView() { + return (this.#viewDbView_ ??= freeze( + Object.fromEntries( + Object.values(this.#viewSchema.tables).map(table => [ + table.accessorName, + makeTableView(this.#schema.typespace, table.tableDef), + ]) + ) + )); + } + + get #viewFrom() { + return (this.#viewFrom_ ??= makeQueryBuilder(this.#viewSchema)); + } + get #reducerCtx() { return (this.#reducerCtx_ ??= new ReducerCtxImpl( Identity.zero(), @@ -356,8 +383,8 @@ class ModuleHooksImpl implements ModuleHooks { // this is the non-readonly DbView, but the typing for the user will be // the readonly one, and if they do call mutating functions it will fail // at runtime - db: this.#dbView, - from: makeQueryBuilder(moduleCtx.schemaType), + db: this.#viewDbView, + from: this.#viewFrom, }); const args = deserializeParams(new BinaryReader(argsBuf)); const ret = callUserFunction(fn, ctx, args); @@ -380,8 +407,8 @@ class ModuleHooksImpl implements ModuleHooks { // this is the non-readonly DbView, but the typing for the user will be // the readonly one, and if they do call mutating functions it will fail // at runtime - db: this.#dbView, - from: makeQueryBuilder(moduleCtx.schemaType), + db: this.#viewDbView, + from: this.#viewFrom, }); const args = deserializeParams(new BinaryReader(argsBuf)); const ret = callUserFunction(fn, ctx, args); @@ -418,6 +445,18 @@ class ModuleHooksImpl implements ModuleHooks { const BINARY_WRITER = new BinaryWriter(0); const BINARY_READER = new BinaryReader(new Uint8Array()); +function withoutEventTables( + schema: SchemaDef +): NonEventSchema { + return { + tables: Object.fromEntries( + Object.values(schema.tables) + .filter(table => !table.isEvent) + .map(table => [table.accessorName, table]) + ) as NonEventSchema['tables'], + }; +} + function makeTableView( typespace: Typespace, table: RawTableDefV10 diff --git a/crates/bindings-typescript/src/server/view.test-d.ts b/crates/bindings-typescript/src/server/view.test-d.ts index 6816b85141b..c9a7fc5affc 100644 --- a/crates/bindings-typescript/src/server/view.test-d.ts +++ b/crates/bindings-typescript/src/server/view.test-d.ts @@ -61,9 +61,21 @@ const order = table( } ); +const eventOnly = table( + { + name: 'eventOnly', + event: true, + }, + { + id: t.u32().primaryKey(), + person_id: t.u32(), + } +); + const spacetime = schema({ person, order, + eventOnly, personWithExtra, personReordered, personWithMissing, @@ -164,3 +176,25 @@ spacetime.anonymousView({ name: 'v5', public: true }, arrayRetValue, ctx => { .leftSemijoin(ctx.from.order, (p, o) => p.id.eq(o.id)) .build(); }); + +spacetime.view({ name: 'v6', public: true }, arrayRetValue, ctx => { + // @ts-expect-error event tables should not be exposed in view db context. + const _eventRows = ctx.db.eventOnly; + return ctx.from.person; +}); + +spacetime.anonymousView({ name: 'v7', public: true }, arrayRetValue, ctx => { + // @ts-expect-error event tables should not be exposed in view db context. + const _eventRows = ctx.db.eventOnly; + return ctx.from.person; +}); + +spacetime.view({ name: 'v8', public: true }, arrayRetValue, ctx => { + // @ts-expect-error event tables should not be exposed in view query builder context. + return ctx.from.eventOnly; +}); + +spacetime.anonymousView({ name: 'v9', public: true }, arrayRetValue, ctx => { + // @ts-expect-error event tables should not be exposed in view query builder context. + return ctx.from.eventOnly; +}); diff --git a/crates/bindings-typescript/src/server/views.ts b/crates/bindings-typescript/src/server/views.ts index accd0c92563..4d0dae77197 100644 --- a/crates/bindings-typescript/src/server/views.ts +++ b/crates/bindings-typescript/src/server/views.ts @@ -17,7 +17,7 @@ import { type TypeBuilder, } from '../lib/type_builders'; import { bsatnBaseSize, toPascalCase } from '../lib/util'; -import type { ReadonlyDbView } from './db_view'; +import type { NonEventSchema, ReadonlyDbView } from './db_view'; import { type QueryBuilder, type RowTypedQuery } from './query'; import { exportContext, @@ -75,12 +75,12 @@ export function makeAnonViewExport< export type ViewCtx = Readonly<{ sender: Identity; db: ReadonlyDbView; - from: QueryBuilder; + from: QueryBuilder>; }>; export type AnonymousViewCtx = Readonly<{ db: ReadonlyDbView; - from: QueryBuilder; + from: QueryBuilder>; }>; export type ViewOpts = { diff --git a/crates/bindings/tests/ui/views.rs b/crates/bindings/tests/ui/views.rs index 23a353b0583..a580d3504c3 100644 --- a/crates/bindings/tests/ui/views.rs +++ b/crates/bindings/tests/ui/views.rs @@ -144,7 +144,6 @@ fn sched_table_view(_: &ViewContext, _args: ScheduledTable) -> Vec { vec![] } - #[table(accessor = player_info)] struct PlayerInfo { #[unique] @@ -203,4 +202,36 @@ fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { ctx.from.xyz().build() } +#[table(accessor = audit_event, event)] +struct AuditEvent { + #[unique] + id: u64, +} + +/// Event tables should not be available through `ctx.db` in views. +#[view(accessor = event_table_view, public)] +fn event_table_view(ctx: &ViewContext) -> Vec { + let _ = ctx.db.audit_event(); + vec![] +} + +/// Event tables should not be available through `ctx.db` in views. +#[view(accessor = event_table_view_anon, public)] +fn event_table_view_anon(ctx: &AnonymousViewContext) -> Vec { + let _ = ctx.db.audit_event(); + vec![] +} + +/// Event tables should not be available through `ctx.from` in views. +#[view(accessor = event_table_view_query_builder, public)] +fn event_table_view_query_builder(ctx: &ViewContext) -> impl Query { + ctx.from.audit_event() +} + +/// Event tables should not be available through `ctx.from` in views. +#[view(accessor = event_table_view_anon_query_builder, public)] +fn event_table_view_anon_query_builder(ctx: &AnonymousViewContext) -> impl Query { + ctx.from.audit_event() +} + fn main() {} diff --git a/crates/bindings/tests/ui/views.stderr b/crates/bindings/tests/ui/views.stderr index 8a91ab089e7..543af208a74 100644 --- a/crates/bindings/tests/ui/views.stderr +++ b/crates/bindings/tests/ui/views.stderr @@ -61,20 +61,20 @@ error[E0425]: cannot find type `ScheduledTable` in this scope | ^^^^^^^^^^^^^^ not found in this scope error[E0425]: cannot find type `T` in this scope - --> tests/ui/views.rs:202:60 + --> tests/ui/views.rs:201:60 | -202 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { +201 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { | ^ not found in this scope | help: you might be missing a type parameter | -202 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { +201 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { | +++ error[E0425]: cannot find type `T` in this scope - --> tests/ui/views.rs:202:60 + --> tests/ui/views.rs:201:60 | -202 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { +201 | fn view_nonexistent_table(ctx: &ViewContext) -> impl Query { | ^ not found in this scope error[E0277]: the trait bound `ViewKind: ViewKindTrait` is not satisfied @@ -181,13 +181,13 @@ error[E0599]: no method named `delete` found for struct `UniqueColumnReadOnly tests/ui/views.rs:50:30 | 50 | read_only.db.test().id().delete(&0); - | ^^^^^^ method not found in `UniqueColumnReadOnly` + | ^^^^^^ method not found in `UniqueColumnReadOnly` error[E0599]: no method named `update` found for struct `UniqueColumnReadOnly` in the current scope --> tests/ui/views.rs:57:30 | 57 | read_only.db.test().id().update(Test { id: 0, x: 0 }); - | ^^^^^^ method not found in `UniqueColumnReadOnly` + | ^^^^^^ method not found in `UniqueColumnReadOnly` error[E0599]: no method named `delete` found for struct `RangedIndexReadOnly` in the current scope --> tests/ui/views.rs:64:29 @@ -336,8 +336,8 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` AlgebraicTypeRef Arc ArrayType + AuditEvent Box - ColumnAttribute and $N others = note: required for `Option` to implement `ViewReturn` @@ -405,15 +405,15 @@ help: the trait `SpacetimeType` is not implemented for `NotSpacetimeType` AlgebraicTypeRef Arc ArrayType + AuditEvent Box - ColumnAttribute and $N others = note: required for `Option` to implement `SpacetimeType` error[E0277]: the trait bound `{integer}: RHS` is not satisfied - --> tests/ui/views.rs:160:49 + --> tests/ui/views.rs:159:49 | -160 | ctx.from.player().r#where(|a| a.identity.eq(42)).build() +159 | ctx.from.player().r#where(|a| a.identity.eq(42)).build() | -- ^^ the trait `RHS` is not implemented for `{integer}` | | | required by a bound introduced by this call @@ -435,9 +435,9 @@ note: required by a bound in `Col::::eq` | ^^^^^^^^^ required by this bound in `Col::::eq` error[E0277]: the trait bound `u32: RHS` is not satisfied - --> tests/ui/views.rs:166:49 + --> tests/ui/views.rs:165:49 | -166 | ctx.from.player_info().r#where(|a| a.age.eq(4200u32)).build() +165 | ctx.from.player_info().r#where(|a| a.age.eq(4200u32)).build() | -- ^^^^^^^ the trait `RHS` is not implemented for `u32` | | | required by a bound introduced by this call @@ -457,9 +457,9 @@ note: required by a bound in `Col::::eq` = note: this error originates in the macro `impl_rhs` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> tests/ui/views.rs:175:62 + --> tests/ui/views.rs:174:62 | -175 | .left_semijoin(ctx.from.player(), |a, b| a.weight.eq(b.identity)) +174 | .left_semijoin(ctx.from.player(), |a, b| a.weight.eq(b.identity)) | -- ^^^^^^^^^^ expected `IxCol`, found `IxCol` | | | arguments to this method are incorrect @@ -473,15 +473,67 @@ note: method defined here | ^^ error[E0609]: no field `age` on type `&PlayerInfoIxCols` - --> tests/ui/views.rs:185:72 + --> tests/ui/views.rs:184:72 | -185 | .right_semijoin(ctx.from.player_info(), |a, b| a.identity.eq(b.age)) +184 | .right_semijoin(ctx.from.player_info(), |a, b| a.identity.eq(b.age)) | ^^^ unknown field | = note: available fields are: `identity`, `weight` error[E0599]: no method named `xyz` found for struct `QueryBuilder` in the current scope - --> tests/ui/views.rs:203:14 + --> tests/ui/views.rs:202:14 | -203 | ctx.from.xyz().build() +202 | ctx.from.xyz().build() | ^^^ method not found in `QueryBuilder` + +error[E0599]: no method named `audit_event` found for struct `LocalReadOnly` in the current scope + --> tests/ui/views.rs:214:20 + | +214 | let _ = ctx.db.audit_event(); + | ^^^^^^^^^^^ method not found in `LocalReadOnly` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `audit_event` defines an item `audit_event`, perhaps you need to implement it + --> tests/ui/views.rs:205:20 + | +205 | #[table(accessor = audit_event, event)] + | ^^^^^^^^^^^ + +error[E0599]: no method named `audit_event` found for struct `LocalReadOnly` in the current scope + --> tests/ui/views.rs:221:20 + | +221 | let _ = ctx.db.audit_event(); + | ^^^^^^^^^^^ method not found in `LocalReadOnly` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `audit_event` defines an item `audit_event`, perhaps you need to implement it + --> tests/ui/views.rs:205:20 + | +205 | #[table(accessor = audit_event, event)] + | ^^^^^^^^^^^ + +error[E0599]: no method named `audit_event` found for struct `QueryBuilder` in the current scope + --> tests/ui/views.rs:228:14 + | +228 | ctx.from.audit_event() + | ^^^^^^^^^^^ method not found in `QueryBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `audit_event` defines an item `audit_event`, perhaps you need to implement it + --> tests/ui/views.rs:205:20 + | +205 | #[table(accessor = audit_event, event)] + | ^^^^^^^^^^^ + +error[E0599]: no method named `audit_event` found for struct `QueryBuilder` in the current scope + --> tests/ui/views.rs:234:14 + | +234 | ctx.from.audit_event() + | ^^^^^^^^^^^ method not found in `QueryBuilder` + | + = help: items from traits can only be used if the trait is implemented and in scope +note: `audit_event` defines an item `audit_event`, perhaps you need to implement it + --> tests/ui/views.rs:205:20 + | +205 | #[table(accessor = audit_event, event)] + | ^^^^^^^^^^^