diff --git a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/index.ts b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/index.ts index 63c1524da89..9be31ab3c68 100644 --- a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/index.ts +++ b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/index.ts @@ -1,7 +1,7 @@ // THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE // WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD. -// This was generated using spacetimedb cli version 2.5.0 (commit edfab0febd6b95f2749493266f39b1a4b4edcccb). +// This was generated using spacetimedb cli version 2.5.0 (commit fe03de5421d9e7cdeb4362e356245f5b96758f77). /* eslint-disable */ /* tslint:disable */ @@ -97,7 +97,7 @@ const tablesSchema = __schema({ }, Person2Row ), - person_at_level_2: __table( + personAtLevel2: __table( { name: 'Level2Person', indexes: [], @@ -117,23 +117,78 @@ const reducersSchema = __reducers( /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ const proceduresSchema = __procedures(); +type __SchemaWithTableAccessorAliases = Omit< + typeof tablesSchema.schemaType, + 'tables' +> & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `personAtLevel2` instead. This alias will be removed in the next major version. */ + readonly person_at_level_2: Omit< + (typeof tablesSchema.schemaType.tables)['personAtLevel2'], + 'accessorName' + > & { readonly accessorName: 'person_at_level_2' }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: '2.5.0' as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType + .tables as __SchemaWithTableAccessorAliases['tables'], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + person_at_level_2: 'personAtLevel2', +} as const; + +function __withTableAccessorAliases( + target: T, + freeze = false +): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & + Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries( + tableAccessorAliases + )) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl['db']; +export type DbView = __DbViewBase & { + /** @deprecated Use `personAtLevel2` instead. This alias will be removed in the next major version. */ + readonly person_at_level_2: __DbViewBase['personAtLevel2']; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `personAtLevel2` instead. This alias will be removed in the next major version. */ + readonly person_at_level_2: __TablesBase['personAtLevel2']; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = - __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases( + tablesBase, + true +) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap( @@ -144,17 +199,25 @@ export const reducers = __convertToAccessorMap( export const procedures = __convertToAccessorMap(proceduresSchema.procedures); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit< + __EventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface< - typeof REMOTE_MODULE ->; +export type ReducerEventContext = Omit< + __ReducerEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface< - typeof REMOTE_MODULE ->; +export type SubscriptionEventContext = Omit< + __SubscriptionEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit< + __ErrorContextInterface, + 'db' +> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -168,6 +231,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder( diff --git a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_2_table.ts b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_2_table.ts index 5a94f4ea501..9359fb15439 100644 --- a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_2_table.ts +++ b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_2_table.ts @@ -12,10 +12,10 @@ import { import { Person3Info } from './types'; export default __t.row({ - person2Id: __t.u32().primaryKey().name('Person2Id'), - firstName: __t.string().name('FirstName'), - playerRef: __t.u32(), + person2Id: __t.u32().primaryKey().name('person_2_id'), + firstName: __t.string().name('first_name'), + playerRef: __t.u32().name('player_ref'), get personInfo() { - return Person3Info; + return Person3Info.name('person_info'); }, }); diff --git a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_at_level_2_table.ts b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_at_level_2_table.ts index 5a94f4ea501..9359fb15439 100644 --- a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_at_level_2_table.ts +++ b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/person_at_level_2_table.ts @@ -12,10 +12,10 @@ import { import { Person3Info } from './types'; export default __t.row({ - person2Id: __t.u32().primaryKey().name('Person2Id'), - firstName: __t.string().name('FirstName'), - playerRef: __t.u32(), + person2Id: __t.u32().primaryKey().name('person_2_id'), + firstName: __t.string().name('first_name'), + playerRef: __t.u32().name('player_ref'), get personInfo() { - return Person3Info; + return Person3Info.name('person_info'); }, }); diff --git a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/player_1_table.ts b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/player_1_table.ts index ac5c3648fea..fba1e5f12f3 100644 --- a/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/player_1_table.ts +++ b/crates/bindings-typescript/case-conversion-test-client/src/module_bindings/player_1_table.ts @@ -12,10 +12,10 @@ import { import { Player2Status } from './types'; export default __t.row({ - player1Id: __t.u32().primaryKey().name('Player1Id'), + player1Id: __t.u32().primaryKey().name('player_1_id'), playerName: __t.string().name('player_name'), - currentLevel2: __t.u32(), + currentLevel2: __t.u32().name('current_level_2'), get status3Field() { - return Player2Status; + return Player2Status.name('status_3_field'); }, }); diff --git a/crates/bindings-typescript/test-app/src/module_bindings/index.ts b/crates/bindings-typescript/test-app/src/module_bindings/index.ts index 49090beab53..524bd8d05fc 100644 --- a/crates/bindings-typescript/test-app/src/module_bindings/index.ts +++ b/crates/bindings-typescript/test-app/src/module_bindings/index.ts @@ -65,7 +65,7 @@ const tablesSchema = __schema({ }, PlayerRow ), - unindexed_player: __table( + unindexedPlayer: __table( { name: 'unindexed_player', indexes: [ @@ -107,7 +107,7 @@ const tablesSchema = __schema({ }, UserRow ), - my_user_procedural: __table( + myUserProcedural: __table( { name: 'my_user_procedural', indexes: [], @@ -125,23 +125,88 @@ const reducersSchema = __reducers( /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ const proceduresSchema = __procedures(); +type __SchemaWithTableAccessorAliases = Omit< + typeof tablesSchema.schemaType, + 'tables' +> & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `unindexedPlayer` instead. This alias will be removed in the next major version. */ + readonly unindexed_player: Omit< + (typeof tablesSchema.schemaType.tables)['unindexedPlayer'], + 'accessorName' + > & { readonly accessorName: 'unindexed_player' }; + /** @deprecated Use `myUserProcedural` instead. This alias will be removed in the next major version. */ + readonly my_user_procedural: Omit< + (typeof tablesSchema.schemaType.tables)['myUserProcedural'], + 'accessorName' + > & { readonly accessorName: 'my_user_procedural' }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: '2.3.0' as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType + .tables as __SchemaWithTableAccessorAliases['tables'], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + unindexed_player: 'unindexedPlayer', + my_user_procedural: 'myUserProcedural', +} as const; + +function __withTableAccessorAliases( + target: T, + freeze = false +): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & + Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries( + tableAccessorAliases + )) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl['db']; +export type DbView = __DbViewBase & { + /** @deprecated Use `unindexedPlayer` instead. This alias will be removed in the next major version. */ + readonly unindexed_player: __DbViewBase['unindexedPlayer']; + /** @deprecated Use `myUserProcedural` instead. This alias will be removed in the next major version. */ + readonly my_user_procedural: __DbViewBase['myUserProcedural']; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `unindexedPlayer` instead. This alias will be removed in the next major version. */ + readonly unindexed_player: __TablesBase['unindexedPlayer']; + /** @deprecated Use `myUserProcedural` instead. This alias will be removed in the next major version. */ + readonly my_user_procedural: __TablesBase['myUserProcedural']; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = - __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases( + tablesBase, + true +) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap( @@ -152,17 +217,25 @@ export const reducers = __convertToAccessorMap( export const procedures = __convertToAccessorMap(proceduresSchema.procedures); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit< + __EventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface< - typeof REMOTE_MODULE ->; +export type ReducerEventContext = Omit< + __ReducerEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface< - typeof REMOTE_MODULE ->; +export type SubscriptionEventContext = Omit< + __SubscriptionEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit< + __ErrorContextInterface, + 'db' +> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -176,6 +249,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder( diff --git a/crates/bindings-typescript/test-react-router-app/src/module_bindings/index.ts b/crates/bindings-typescript/test-react-router-app/src/module_bindings/index.ts index b9d553082d4..5a7a64d1e2b 100644 --- a/crates/bindings-typescript/test-react-router-app/src/module_bindings/index.ts +++ b/crates/bindings-typescript/test-react-router-app/src/module_bindings/index.ts @@ -65,7 +65,7 @@ const tablesSchema = __schema({ }, CounterRow ), - offline_user: __table( + offlineUser: __table( { name: 'offline_user', indexes: [ @@ -118,23 +118,78 @@ const reducersSchema = __reducers( /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ const proceduresSchema = __procedures(); +type __SchemaWithTableAccessorAliases = Omit< + typeof tablesSchema.schemaType, + 'tables' +> & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `offlineUser` instead. This alias will be removed in the next major version. */ + readonly offline_user: Omit< + (typeof tablesSchema.schemaType.tables)['offlineUser'], + 'accessorName' + > & { readonly accessorName: 'offline_user' }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: '2.0.0' as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType + .tables as __SchemaWithTableAccessorAliases['tables'], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + offline_user: 'offlineUser', +} as const; + +function __withTableAccessorAliases( + target: T, + freeze = false +): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & + Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries( + tableAccessorAliases + )) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl['db']; +export type DbView = __DbViewBase & { + /** @deprecated Use `offlineUser` instead. This alias will be removed in the next major version. */ + readonly offline_user: __DbViewBase['offlineUser']; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `offlineUser` instead. This alias will be removed in the next major version. */ + readonly offline_user: __TablesBase['offlineUser']; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = - __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases( + tablesBase, + true +) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap( @@ -142,17 +197,25 @@ export const reducers = __convertToAccessorMap( ); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit< + __EventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface< - typeof REMOTE_MODULE ->; +export type ReducerEventContext = Omit< + __ReducerEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface< - typeof REMOTE_MODULE ->; +export type SubscriptionEventContext = Omit< + __SubscriptionEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit< + __ErrorContextInterface, + 'db' +> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -166,6 +229,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder( diff --git a/crates/bindings-typescript/test-solid-router/src/module_bindings/index.ts b/crates/bindings-typescript/test-solid-router/src/module_bindings/index.ts index b9d553082d4..5a7a64d1e2b 100644 --- a/crates/bindings-typescript/test-solid-router/src/module_bindings/index.ts +++ b/crates/bindings-typescript/test-solid-router/src/module_bindings/index.ts @@ -65,7 +65,7 @@ const tablesSchema = __schema({ }, CounterRow ), - offline_user: __table( + offlineUser: __table( { name: 'offline_user', indexes: [ @@ -118,23 +118,78 @@ const reducersSchema = __reducers( /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ const proceduresSchema = __procedures(); +type __SchemaWithTableAccessorAliases = Omit< + typeof tablesSchema.schemaType, + 'tables' +> & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `offlineUser` instead. This alias will be removed in the next major version. */ + readonly offline_user: Omit< + (typeof tablesSchema.schemaType.tables)['offlineUser'], + 'accessorName' + > & { readonly accessorName: 'offline_user' }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: '2.0.0' as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType + .tables as __SchemaWithTableAccessorAliases['tables'], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + offline_user: 'offlineUser', +} as const; + +function __withTableAccessorAliases( + target: T, + freeze = false +): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & + Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries( + tableAccessorAliases + )) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl['db']; +export type DbView = __DbViewBase & { + /** @deprecated Use `offlineUser` instead. This alias will be removed in the next major version. */ + readonly offline_user: __DbViewBase['offlineUser']; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `offlineUser` instead. This alias will be removed in the next major version. */ + readonly offline_user: __TablesBase['offlineUser']; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = - __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases( + tablesBase, + true +) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap( @@ -142,17 +197,25 @@ export const reducers = __convertToAccessorMap( ); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit< + __EventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface< - typeof REMOTE_MODULE ->; +export type ReducerEventContext = Omit< + __ReducerEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface< - typeof REMOTE_MODULE ->; +export type SubscriptionEventContext = Omit< + __SubscriptionEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit< + __ErrorContextInterface, + 'db' +> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -166,6 +229,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder( diff --git a/crates/bindings-typescript/tests/client_query.test.ts b/crates/bindings-typescript/tests/client_query.test.ts index b29a44ba185..3e3b225ac6e 100644 --- a/crates/bindings-typescript/tests/client_query.test.ts +++ b/crates/bindings-typescript/tests/client_query.test.ts @@ -4,6 +4,15 @@ import { and, not, or, toSql } from '../src/lib/query'; import { tables } from '../test-app/src/module_bindings'; describe('ClientQuery.toSql', () => { + it('keeps deprecated snake_case table aliases working', () => { + assertType(tables.unindexed_player); + expect(tables.unindexed_player).toBe(tables.unindexedPlayer); + + const sql = toSql(tables.unindexed_player.build()); + + expect(sql).toBe('SELECT * FROM "unindexed_player"'); + }); + it('renders a full-table scan when no filters are applied', () => { const sql = toSql(tables.player.build()); @@ -89,7 +98,7 @@ describe('ClientQuery.toSql', () => { it('renders semijoin queries without additional filters', () => { const sql = toSql( tables.player - .rightSemijoin(tables.unindexed_player, (player, other) => + .rightSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .build() @@ -104,7 +113,7 @@ describe('ClientQuery.toSql', () => { const sql = toSql( tables.player .where(row => row.id.eq(42)) - .rightSemijoin(tables.unindexed_player, (player, other) => + .rightSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .build() @@ -119,7 +128,7 @@ describe('ClientQuery.toSql', () => { const sql = toSql( tables.player .where(row => row.name.eq("O'Brian")) - .rightSemijoin(tables.unindexed_player, (player, other) => + .rightSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .build() @@ -134,7 +143,7 @@ describe('ClientQuery.toSql', () => { const sql = toSql( tables.player .where(row => and(row.name.eq('Alice'), row.id.eq(30))) - .rightSemijoin(tables.unindexed_player, (player, other) => + .rightSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .build() @@ -196,7 +205,7 @@ describe('ClientQuery.toSql', () => { it('basic semijoin', () => { const sql = toSql( tables.player - .rightSemijoin(tables.unindexed_player, (player, other) => + .rightSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .build() @@ -209,7 +218,7 @@ describe('ClientQuery.toSql', () => { it('basic left semijoin', () => { const sql = toSql( tables.player - .leftSemijoin(tables.unindexed_player, (player, other) => + .leftSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .build() @@ -252,7 +261,7 @@ describe('ClientQuery.toSql', () => { const sql = toSql( tables.player .where(row => row.id.eq(42)) - .rightSemijoin(tables.unindexed_player, (player, other) => + .rightSemijoin(tables.unindexedPlayer, (player, other) => other.id.eq(player.id) ) .where(row => row.name.eq('Gadget')) @@ -286,7 +295,7 @@ describe('useTable type compatibility', () => { it('rightSemijoin result is assignable to useTable query param', () => { assertType( - tables.player.rightSemijoin(tables.unindexed_player, (p, o) => + tables.player.rightSemijoin(tables.unindexedPlayer, (p, o) => o.id.eq(p.id) ) ); @@ -294,7 +303,7 @@ describe('useTable type compatibility', () => { it('leftSemijoin result is assignable to useTable query param', () => { assertType( - tables.player.leftSemijoin(tables.unindexed_player, (p, o) => + tables.player.leftSemijoin(tables.unindexedPlayer, (p, o) => o.id.eq(p.id) ) ); @@ -303,7 +312,7 @@ describe('useTable type compatibility', () => { it('semijoin with .where() is assignable to useTable query param', () => { assertType( tables.player - .rightSemijoin(tables.unindexed_player, (p, o) => o.id.eq(p.id)) + .rightSemijoin(tables.unindexedPlayer, (p, o) => o.id.eq(p.id)) .where(r => r.name.eq('test')) ); }); @@ -320,7 +329,7 @@ describe('useTable type compatibility', () => { it('semijoin result exposes toSql() returning string', () => { const sql: string = tables.player - .rightSemijoin(tables.unindexed_player, (p, o) => o.id.eq(p.id)) + .rightSemijoin(tables.unindexedPlayer, (p, o) => o.id.eq(p.id)) .toSql(); expect(typeof sql).toBe('string'); }); diff --git a/crates/bindings-typescript/tests/db_connection.test.ts b/crates/bindings-typescript/tests/db_connection.test.ts index 7a0da07bf52..2a1a3efc0a4 100644 --- a/crates/bindings-typescript/tests/db_connection.test.ts +++ b/crates/bindings-typescript/tests/db_connection.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from 'vitest'; +import { assertType, beforeEach, describe, expect, test } from 'vitest'; import { BinaryWriter, ConnectionId, @@ -149,6 +149,19 @@ function encodeMyUserProcedural(value: MyUserViewRow): Uint8Array { } describe('DbConnection', () => { + test('keeps deprecated snake_case db aliases working', async () => { + const client = DbConnection.builder() + .withUri('ws://127.0.0.1:1234') + .withDatabaseName('db') + .withWSFn(() => Promise.reject(new Error('expected test failure'))) + .build(); + + assertType(client.db.unindexed_player); + expect(client.db.unindexed_player).toBe(client.db.unindexedPlayer); + + await client['wsPromise']; + }); + test('call onConnectError callback after websocket connection failed to be established', async () => { const onConnectErrorPromise = new Deferred(); @@ -847,11 +860,11 @@ describe('DbConnection', () => { // `onUpdate` is only available when the generated view row binding carries // primary-key metadata. - client.db.my_user_procedural.onInsert((_ctx, row) => { + client.db.myUserProcedural.onInsert((_ctx, row) => { expect(row).toEqual(initialRow); initialInsertPromise.resolve(); }); - client.db.my_user_procedural.onUpdate((_ctx, oldRow, newRow) => { + client.db.myUserProcedural.onUpdate((_ctx, oldRow, newRow) => { updates.push({ oldRow, newRow, @@ -876,7 +889,7 @@ describe('DbConnection', () => { await initialInsertPromise.promise; expect(client.db.player.count()).toEqual(1n); - expect(client.db.my_user_procedural.count()).toEqual(1n); + expect(client.db.myUserProcedural.count()).toEqual(1n); // A delete and insert with the same primary key in one transaction should // be coalesced by the client cache into `onUpdate`, not separate delete and @@ -909,8 +922,8 @@ describe('DbConnection', () => { }, ]); expect(client.db.player.count()).toEqual(1n); - expect(client.db.my_user_procedural.count()).toEqual(1n); - expect([...client.db.my_user_procedural.iter()][0]).toEqual(updatedRow); + expect(client.db.myUserProcedural.count()).toEqual(1n); + expect([...client.db.myUserProcedural.iter()][0]).toEqual(updatedRow); }); test('Filtering works', async () => { diff --git a/crates/bindings-typescript/tests/query_error_message.test.ts b/crates/bindings-typescript/tests/query_error_message.test.ts index f016c97cc28..20a9285258b 100644 --- a/crates/bindings-typescript/tests/query_error_message.test.ts +++ b/crates/bindings-typescript/tests/query_error_message.test.ts @@ -28,7 +28,7 @@ import { and } from ${JSON.stringify(imports.query)}; import { tables } from ${JSON.stringify(imports.moduleBindings)}; tables.player - .leftSemijoin(tables.unindexed_player, (l, r) => ${semijoinPredicateExpr}) + .leftSemijoin(tables.unindexedPlayer, (l, r) => ${semijoinPredicateExpr}) .build(); `; diff --git a/crates/bindings-typescript/tests/table_cache.test.ts b/crates/bindings-typescript/tests/table_cache.test.ts index a3b1f2becc6..558faf06b48 100644 --- a/crates/bindings-typescript/tests/table_cache.test.ts +++ b/crates/bindings-typescript/tests/table_cache.test.ts @@ -101,13 +101,13 @@ function runTest( describe('TableCache', () => { describe('Unindexed player table', () => { - const newTable = () => new TableCacheImpl(tables.unindexed_player.tableDef); + const newTable = () => new TableCacheImpl(tables.unindexedPlayer.tableDef); const mkOperation = ( type: 'insert' | 'delete', row: Infer ) => { const rowId = AlgebraicType.intoMapKey( - { tag: 'Product', value: tables.unindexed_player.rowType }, + { tag: 'Product', value: tables.unindexedPlayer.rowType }, row ); return { diff --git a/crates/codegen/src/typescript.rs b/crates/codegen/src/typescript.rs index 28bc1fb4c91..457d8960d1a 100644 --- a/crates/codegen/src/typescript.rs +++ b/crates/codegen/src/typescript.rs @@ -27,6 +27,10 @@ use spacetimedb_lib::version::spacetimedb_lib_version; const INDENT: &str = " "; +fn ts_string_literal(s: &str) -> String { + serde_json::to_string(s).expect("serializing a string literal cannot fail") +} + pub struct TypeScript; impl Lang for TypeScript { @@ -196,10 +200,17 @@ impl Lang for TypeScript { writeln!(out, "/** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */"); writeln!(out, "const tablesSchema = __schema({{"); out.indent(1); + let mut table_accessor_aliases = Vec::new(); + let mut table_accessor_names = BTreeSet::new(); for table in iter_tables(module, options.visibility) { let type_ref = table.product_type_ref; + let table_accessor = table.accessor_name.deref().to_case(Case::Camel); let table_name_pascalcase = table.accessor_name.deref().to_case(Case::Pascal); - writeln!(out, "{}: __table({{", table.accessor_name); + table_accessor_names.insert(table_accessor.clone()); + if table.accessor_name.deref() != table_accessor { + table_accessor_aliases.push((table.accessor_name.to_string(), table_accessor.clone())); + } + writeln!(out, "{table_accessor}: __table({{"); out.indent(1); write_table_opts( module, @@ -215,8 +226,13 @@ impl Lang for TypeScript { } for view in iter_views(module) { let type_ref = view.product_type_ref; + let view_accessor = view.accessor_name.deref().to_case(Case::Camel); let view_name_pascalcase = view.accessor_name.deref().to_case(Case::Pascal); - writeln!(out, "{}: __table({{", view.accessor_name); + table_accessor_names.insert(view_accessor.clone()); + if view.accessor_name.deref() != view_accessor { + table_accessor_aliases.push((view.accessor_name.to_string(), view_accessor.clone())); + } + writeln!(out, "{view_accessor}: __table({{"); out.indent(1); write_table_opts(module, out, type_ref, &view.name, iter::empty(), iter::empty(), false); out.dedent(1); @@ -225,6 +241,9 @@ impl Lang for TypeScript { out.dedent(1); writeln!(out, "}});"); + table_accessor_aliases.retain(|(deprecated_accessor, _)| !table_accessor_names.contains(deprecated_accessor)); + let has_table_accessor_aliases = !table_accessor_aliases.is_empty(); + writeln!(out); writeln!(out, "/** The schema information for all reducers in this module. This is defined the same way as the reducers would have been defined in the server, except the body of the reducer is omitted in code generation. */"); writeln!(out, "const reducersSchema = __reducers("); @@ -258,6 +277,34 @@ impl Lang for TypeScript { out.dedent(1); writeln!(out, ");"); + if has_table_accessor_aliases { + writeln!(out); + writeln!( + out, + "type __SchemaWithTableAccessorAliases = Omit & {{" + ); + out.indent(1); + writeln!(out, "tables: typeof tablesSchema.schemaType.tables & {{"); + out.indent(1); + for (deprecated_accessor, target_accessor) in &table_accessor_aliases { + writeln!( + out, + "/** @deprecated Use `{target_accessor}` instead. This alias will be removed in the next major version. */" + ); + writeln!( + out, + "readonly {}: Omit & {{ readonly accessorName: {} }};", + ts_string_literal(deprecated_accessor), + ts_string_literal(target_accessor), + ts_string_literal(deprecated_accessor) + ); + } + out.dedent(1); + writeln!(out, "}};"); + out.dedent(1); + writeln!(out, "}};"); + } + writeln!(out); writeln!( out, @@ -270,25 +317,144 @@ impl Lang for TypeScript { writeln!(out, "cliVersion: \"{}\" as const,", spacetimedb_lib_version()); out.dedent(1); writeln!(out, "}},"); - writeln!(out, "tables: tablesSchema.schemaType.tables,"); + if has_table_accessor_aliases { + writeln!( + out, + "tables: tablesSchema.schemaType.tables as __SchemaWithTableAccessorAliases[\"tables\"]," + ); + } else { + writeln!(out, "tables: tablesSchema.schemaType.tables,"); + } writeln!(out, "reducers: reducersSchema.reducersType.reducers,"); writeln!(out, "...proceduresSchema,"); out.dedent(1); writeln!(out, "}} satisfies __RemoteModule<"); out.indent(1); - writeln!(out, "typeof tablesSchema.schemaType,"); + if has_table_accessor_aliases { + writeln!(out, "__SchemaWithTableAccessorAliases,"); + } else { + writeln!(out, "typeof tablesSchema.schemaType,"); + } writeln!(out, "typeof reducersSchema.reducersType,"); writeln!(out, "typeof proceduresSchema"); out.dedent(1); writeln!(out, ">;"); out.dedent(1); + if has_table_accessor_aliases { + writeln!(out); + writeln!(out, "const tableAccessorAliases = {{"); + out.indent(1); + for (deprecated_accessor, target_accessor) in &table_accessor_aliases { + writeln!( + out, + "{}: {},", + ts_string_literal(deprecated_accessor), + ts_string_literal(target_accessor) + ); + } + out.dedent(1); + writeln!(out, "}} as const;"); + + writeln!(out); + writeln!( + out, + "function __withTableAccessorAliases(target: T, freeze = false): T {{" + ); + out.indent(1); + writeln!( + out, + "const out = Object.create(Object.getPrototypeOf(target)) as T & Record;" + ); + writeln!( + out, + "Object.defineProperties(out, Object.getOwnPropertyDescriptors(target));" + ); + writeln!( + out, + "for (const [deprecatedAccessor, targetAccessor] of Object.entries(tableAccessorAliases)) {{" + ); + out.indent(1); + writeln!(out, "if (deprecatedAccessor in out) {{"); + out.indent(1); + writeln!(out, "continue;"); + out.dedent(1); + writeln!(out, "}}"); + writeln!(out, "Object.defineProperty(out, deprecatedAccessor, {{"); + out.indent(1); + writeln!(out, "enumerable: true,"); + writeln!(out, "configurable: false,"); + writeln!(out, "get: () => out[targetAccessor],"); + out.dedent(1); + writeln!(out, "}});"); + out.dedent(1); + writeln!(out, "}}"); + writeln!(out, "return freeze ? Object.freeze(out) : out;"); + out.dedent(1); + writeln!(out, "}}"); + + writeln!(out); + writeln!( + out, + "type __DbViewBase = __DbConnectionImpl[\"db\"];" + ); + writeln!(out, "export type DbView = __DbViewBase & {{"); + out.indent(1); + for (deprecated_accessor, target_accessor) in &table_accessor_aliases { + writeln!( + out, + "/** @deprecated Use `{target_accessor}` instead. This alias will be removed in the next major version. */" + ); + writeln!( + out, + "readonly {}: __DbViewBase[{}];", + ts_string_literal(deprecated_accessor), + ts_string_literal(target_accessor) + ); + } + out.dedent(1); + writeln!(out, "}};"); + + writeln!(out); + writeln!( + out, + "type __TablesBase = __QueryBuilder;" + ); + writeln!(out, "export type Tables = __TablesBase & {{"); + out.indent(1); + for (deprecated_accessor, target_accessor) in &table_accessor_aliases { + writeln!( + out, + "/** @deprecated Use `{target_accessor}` instead. This alias will be removed in the next major version. */" + ); + writeln!( + out, + "readonly {}: __TablesBase[{}];", + ts_string_literal(deprecated_accessor), + ts_string_literal(target_accessor) + ); + } + out.dedent(1); + writeln!(out, "}};"); + } + writeln!(out); writeln!(out, "/** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */"); - writeln!( - out, - "export const tables: __QueryBuilder = __makeQueryBuilder(tablesSchema.schemaType);" - ); + if has_table_accessor_aliases { + writeln!( + out, + "const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType);" + ); + writeln!( + out, + "export const tables: Tables = __withTableAccessorAliases(tablesBase, true) as Tables;" + ); + } else { + writeln!( + out, + "export const tables: __QueryBuilder = __makeQueryBuilder(tablesSchema.schemaType);" + ); + } writeln!(out); writeln!(out, "/** The reducers available in this remote SpacetimeDB module. */"); writeln!( @@ -311,31 +477,59 @@ impl Lang for TypeScript { out, "/** The context type returned in callbacks for all possible events. */" ); - writeln!( - out, - "export type EventContext = __EventContextInterface;" - ); + if has_table_accessor_aliases { + writeln!( + out, + "export type EventContext = Omit<__EventContextInterface, \"db\"> & {{ db: DbView }};" + ); + } else { + writeln!( + out, + "export type EventContext = __EventContextInterface;" + ); + } writeln!(out, "/** The context type returned in callbacks for reducer events. */"); - writeln!( - out, - "export type ReducerEventContext = __ReducerEventContextInterface;" - ); + if has_table_accessor_aliases { + writeln!( + out, + "export type ReducerEventContext = Omit<__ReducerEventContextInterface, \"db\"> & {{ db: DbView }};" + ); + } else { + writeln!( + out, + "export type ReducerEventContext = __ReducerEventContextInterface;" + ); + } writeln!( out, "/** The context type returned in callbacks for subscription events. */" ); - writeln!( - out, - "export type SubscriptionEventContext = __SubscriptionEventContextInterface;" - ); + if has_table_accessor_aliases { + writeln!( + out, + "export type SubscriptionEventContext = Omit<__SubscriptionEventContextInterface, \"db\"> & {{ db: DbView }};" + ); + } else { + writeln!( + out, + "export type SubscriptionEventContext = __SubscriptionEventContextInterface;" + ); + } writeln!(out, "/** The context type returned in callbacks for error events. */"); - writeln!( - out, - "export type ErrorContext = __ErrorContextInterface;" - ); + if has_table_accessor_aliases { + writeln!( + out, + "export type ErrorContext = Omit<__ErrorContextInterface, \"db\"> & {{ db: DbView }};" + ); + } else { + writeln!( + out, + "export type ErrorContext = __ErrorContextInterface;" + ); + } writeln!(out, "/** The subscription handle type to manage active subscriptions created from a {{@link SubscriptionBuilder}}. */"); writeln!( @@ -370,6 +564,22 @@ impl Lang for TypeScript { "export class DbConnection extends __DbConnectionImpl {{" ); out.indent(1); + if has_table_accessor_aliases { + writeln!(out, "declare db: DbView;"); + + writeln!(out); + writeln!( + out, + "constructor(config: __DbConnectionConfig) {{" + ); + out.indent(1); + writeln!(out, "super(config);"); + writeln!(out, "this.db = __withTableAccessorAliases(this.db) as DbView;"); + out.dedent(1); + writeln!(out, "}}"); + + writeln!(out); + } writeln!(out, "/** Creates a new {{@link DbConnectionBuilder}} to configure and connect to the remote SpacetimeDB instance. */"); writeln!(out, "static builder = (): DbConnectionBuilder => {{"); out.indent(1); diff --git a/crates/codegen/tests/codegen.rs b/crates/codegen/tests/codegen.rs index 06dc3ebe8fc..0b2c4e483f2 100644 --- a/crates/codegen/tests/codegen.rs +++ b/crates/codegen/tests/codegen.rs @@ -39,3 +39,24 @@ declare_tests! { test_codegen_typescript => TypeScript, test_codegen_rust => Rust, } + +#[test] +fn test_typescript_table_handles_are_camel_case() { + let module = compiled_module(); + let index = generate(module, &TypeScript, &CodegenOptions::default()) + .into_iter() + .find(|file| file.filename == "index.ts") + .expect("typescript codegen should emit index.ts") + .code; + + assert!(index.contains("loggedOutPlayer: __table({")); + assert!(!index.contains("logged_out_player: __table({")); + assert!(index.contains("myPlayer: __table({")); + assert!(!index.contains("my_player: __table({")); + assert!(index.contains(r#""logged_out_player": "loggedOutPlayer""#)); + assert!(index.contains(r#"readonly "logged_out_player": __TablesBase["loggedOutPlayer"];"#)); + assert!(index.contains(r#"readonly "logged_out_player": __DbViewBase["loggedOutPlayer"];"#)); + assert!(index.contains( + r#"/** @deprecated Use `loggedOutPlayer` instead. This alias will be removed in the next major version. */"# + )); +} diff --git a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap index 8616888c54f..47fd5846b62 100644 --- a/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap +++ b/crates/codegen/tests/snapshots/codegen__codegen_typescript.snap @@ -187,7 +187,7 @@ import TestFRow from "./test_f_table"; /** The schema information for all tables in this module. This is defined the same was as the tables would have been defined in the server. */ const tablesSchema = __schema({ - logged_out_player: __table({ + loggedOutPlayer: __table({ name: 'logged_out_player', indexes: [ { accessor: 'identity', name: 'logged_out_player_identity_idx_btree', algorithm: 'btree', columns: [ @@ -239,21 +239,21 @@ const tablesSchema = __schema({ { name: 'player_player_id_key', constraint: 'unique', columns: ['playerId'] }, ], }, PlayerRow), - test_d: __table({ + testD: __table({ name: 'test_d', indexes: [ ], constraints: [ ], }, TestDRow), - test_f: __table({ + testF: __table({ name: 'test_f', indexes: [ ], constraints: [ ], }, TestFRow), - my_player: __table({ + myPlayer: __table({ name: 'my_player', indexes: [ ], @@ -286,22 +286,83 @@ const proceduresSchema = __procedures( __procedureSchema("with_tx", WithTxProcedure.params, WithTxProcedure.returnType), ); +type __SchemaWithTableAccessorAliases = Omit & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `loggedOutPlayer` instead. This alias will be removed in the next major version. */ + readonly "logged_out_player": Omit & { readonly accessorName: "logged_out_player" }; + /** @deprecated Use `testD` instead. This alias will be removed in the next major version. */ + readonly "test_d": Omit & { readonly accessorName: "test_d" }; + /** @deprecated Use `testF` instead. This alias will be removed in the next major version. */ + readonly "test_f": Omit & { readonly accessorName: "test_f" }; + /** @deprecated Use `myPlayer` instead. This alias will be removed in the next major version. */ + readonly "my_player": Omit & { readonly accessorName: "my_player" }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: "2.5.0" as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType.tables as __SchemaWithTableAccessorAliases["tables"], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + "logged_out_player": "loggedOutPlayer", + "test_d": "testD", + "test_f": "testF", + "my_player": "myPlayer", +} as const; + +function __withTableAccessorAliases(target: T, freeze = false): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries(tableAccessorAliases)) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl["db"]; +export type DbView = __DbViewBase & { + /** @deprecated Use `loggedOutPlayer` instead. This alias will be removed in the next major version. */ + readonly "logged_out_player": __DbViewBase["loggedOutPlayer"]; + /** @deprecated Use `testD` instead. This alias will be removed in the next major version. */ + readonly "test_d": __DbViewBase["testD"]; + /** @deprecated Use `testF` instead. This alias will be removed in the next major version. */ + readonly "test_f": __DbViewBase["testF"]; + /** @deprecated Use `myPlayer` instead. This alias will be removed in the next major version. */ + readonly "my_player": __DbViewBase["myPlayer"]; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `loggedOutPlayer` instead. This alias will be removed in the next major version. */ + readonly "logged_out_player": __TablesBase["loggedOutPlayer"]; + /** @deprecated Use `testD` instead. This alias will be removed in the next major version. */ + readonly "test_d": __TablesBase["testD"]; + /** @deprecated Use `testF` instead. This alias will be removed in the next major version. */ + readonly "test_f": __TablesBase["testF"]; + /** @deprecated Use `myPlayer` instead. This alias will be removed in the next major version. */ + readonly "my_player": __TablesBase["myPlayer"]; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases(tablesBase, true) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers); @@ -310,13 +371,13 @@ export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reduc export const procedures = __convertToAccessorMap(proceduresSchema.procedures); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit<__EventContextInterface, "db"> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface; +export type ReducerEventContext = Omit<__ReducerEventContextInterface, "db"> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface; +export type SubscriptionEventContext = Omit<__SubscriptionEventContextInterface, "db"> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit<__ErrorContextInterface, "db"> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -328,6 +389,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder(REMOTE_MODULE, (config: __DbConnectionConfig) => new DbConnection(config)); diff --git a/demo/Blackholio/client-ts/src/game/GameManager.ts b/demo/Blackholio/client-ts/src/game/GameManager.ts index e350c108083..1265434a17b 100644 --- a/demo/Blackholio/client-ts/src/game/GameManager.ts +++ b/demo/Blackholio/client-ts/src/game/GameManager.ts @@ -104,7 +104,7 @@ export class GameManager { connection.db.player.onDelete((_ctx, player) => { this.players.delete(player.playerId); }); - connection.db.consume_entity_event.onInsert((_ctx, event) => + connection.db.consumeEntityEvent.onInsert((_ctx, event) => this.consumeEntityEventOnInsert(event) ); } diff --git a/demo/Blackholio/client-ts/src/module_bindings/index.ts b/demo/Blackholio/client-ts/src/module_bindings/index.ts index 6120bf25542..81afe450da0 100644 --- a/demo/Blackholio/client-ts/src/module_bindings/index.ts +++ b/demo/Blackholio/client-ts/src/module_bindings/index.ts @@ -79,7 +79,7 @@ const tablesSchema = __schema({ { name: 'config_id_key', constraint: 'unique', columns: ['id'] }, ], }, ConfigRow), - consume_entity_event: __table({ + consumeEntityEvent: __table({ name: 'consume_entity_event', indexes: [ ], @@ -139,22 +139,62 @@ const reducersSchema = __reducers( const proceduresSchema = __procedures( ); +type __SchemaWithTableAccessorAliases = Omit & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `consumeEntityEvent` instead. This alias will be removed in the next major version. */ + readonly "consume_entity_event": Omit & { readonly accessorName: "consume_entity_event" }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: "2.3.0" as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType.tables as __SchemaWithTableAccessorAliases["tables"], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + "consume_entity_event": "consumeEntityEvent", +} as const; + +function __withTableAccessorAliases(target: T, freeze = false): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries(tableAccessorAliases)) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl["db"]; +export type DbView = __DbViewBase & { + /** @deprecated Use `consumeEntityEvent` instead. This alias will be removed in the next major version. */ + readonly "consume_entity_event": __DbViewBase["consumeEntityEvent"]; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `consumeEntityEvent` instead. This alias will be removed in the next major version. */ + readonly "consume_entity_event": __TablesBase["consumeEntityEvent"]; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases(tablesBase, true) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reducers); @@ -163,13 +203,13 @@ export const reducers = __convertToAccessorMap(reducersSchema.reducersType.reduc export const procedures = __convertToAccessorMap(proceduresSchema.procedures); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit<__EventContextInterface, "db"> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface; +export type ReducerEventContext = Omit<__ReducerEventContextInterface, "db"> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface; +export type SubscriptionEventContext = Omit<__SubscriptionEventContextInterface, "db"> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit<__ErrorContextInterface, "db"> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -181,6 +221,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder(REMOTE_MODULE, (config: __DbConnectionConfig) => new DbConnection(config)); @@ -191,4 +238,3 @@ export class DbConnection extends __DbConnectionImpl { return new SubscriptionBuilder(this); }; } - diff --git a/templates/hangman-react-ts/src/App.tsx b/templates/hangman-react-ts/src/App.tsx index 6f80c29884e..64486fb9755 100644 --- a/templates/hangman-react-ts/src/App.tsx +++ b/templates/hangman-react-ts/src/App.tsx @@ -192,7 +192,7 @@ function App() { const { identity, isActive: connected } = useSpacetimeDB(); const [rounds] = useTable(tables.currentRound); const [players] = useTable(tables.player); - const [boards] = useTable(tables.my_progress); + const [boards] = useTable(tables.myProgress); const [results] = useTable(tables.roundResult); const setName = useReducer(reducers.setName); const guessLetter = useReducer(reducers.guessLetter); diff --git a/templates/hangman-react-ts/src/module_bindings/index.ts b/templates/hangman-react-ts/src/module_bindings/index.ts index 8ca848b15a5..6852d40b6fe 100644 --- a/templates/hangman-react-ts/src/module_bindings/index.ts +++ b/templates/hangman-react-ts/src/module_bindings/index.ts @@ -108,7 +108,7 @@ const tablesSchema = __schema({ }, RoundResultRow ), - my_progress: __table( + myProgress: __table( { name: 'my_progress', indexes: [], @@ -127,23 +127,98 @@ const reducersSchema = __reducers( /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ const proceduresSchema = __procedures(); +type __SchemaWithTableAccessorAliases = Omit< + typeof tablesSchema.schemaType, + 'tables' +> & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `currentRound` instead. This alias will be removed in the next major version. */ + readonly current_round: Omit< + (typeof tablesSchema.schemaType.tables)['currentRound'], + 'accessorName' + > & { readonly accessorName: 'current_round' }; + /** @deprecated Use `roundResult` instead. This alias will be removed in the next major version. */ + readonly round_result: Omit< + (typeof tablesSchema.schemaType.tables)['roundResult'], + 'accessorName' + > & { readonly accessorName: 'round_result' }; + /** @deprecated Use `myProgress` instead. This alias will be removed in the next major version. */ + readonly my_progress: Omit< + (typeof tablesSchema.schemaType.tables)['myProgress'], + 'accessorName' + > & { readonly accessorName: 'my_progress' }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: '2.2.0' as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType + .tables as __SchemaWithTableAccessorAliases['tables'], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + current_round: 'currentRound', + round_result: 'roundResult', + my_progress: 'myProgress', +} as const; + +function __withTableAccessorAliases( + target: T, + freeze = false +): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & + Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries( + tableAccessorAliases + )) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl['db']; +export type DbView = __DbViewBase & { + /** @deprecated Use `currentRound` instead. This alias will be removed in the next major version. */ + readonly current_round: __DbViewBase['currentRound']; + /** @deprecated Use `roundResult` instead. This alias will be removed in the next major version. */ + readonly round_result: __DbViewBase['roundResult']; + /** @deprecated Use `myProgress` instead. This alias will be removed in the next major version. */ + readonly my_progress: __DbViewBase['myProgress']; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `currentRound` instead. This alias will be removed in the next major version. */ + readonly current_round: __TablesBase['currentRound']; + /** @deprecated Use `roundResult` instead. This alias will be removed in the next major version. */ + readonly round_result: __TablesBase['roundResult']; + /** @deprecated Use `myProgress` instead. This alias will be removed in the next major version. */ + readonly my_progress: __TablesBase['myProgress']; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = - __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases( + tablesBase, + true +) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap( @@ -154,17 +229,25 @@ export const reducers = __convertToAccessorMap( export const procedures = __convertToAccessorMap(proceduresSchema.procedures); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit< + __EventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface< - typeof REMOTE_MODULE ->; +export type ReducerEventContext = Omit< + __ReducerEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface< - typeof REMOTE_MODULE ->; +export type SubscriptionEventContext = Omit< + __SubscriptionEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit< + __ErrorContextInterface, + 'db' +> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -178,6 +261,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder( diff --git a/templates/money-exchange-react-ts/src/App.tsx b/templates/money-exchange-react-ts/src/App.tsx index c45ee166e44..85c838bb66c 100644 --- a/templates/money-exchange-react-ts/src/App.tsx +++ b/templates/money-exchange-react-ts/src/App.tsx @@ -118,9 +118,9 @@ function NameForm({ function App() { const { identity, isActive: connected } = useSpacetimeDB(); - const [accounts] = useTable(tables.my_account); + const [accounts] = useTable(tables.myAccount); const [directory] = useTable(tables.directory); - const [changes] = useTable(tables.my_account_changes); + const [changes] = useTable(tables.myAccountChanges); const setName = useReducer(reducers.setName); const sendTransfer = useReducer(reducers.transfer); const [editingName, setEditingName] = useState(false); diff --git a/templates/money-exchange-react-ts/src/module_bindings/index.ts b/templates/money-exchange-react-ts/src/module_bindings/index.ts index b41cebe950a..c4bf96e2c56 100644 --- a/templates/money-exchange-react-ts/src/module_bindings/index.ts +++ b/templates/money-exchange-react-ts/src/module_bindings/index.ts @@ -80,7 +80,7 @@ const tablesSchema = __schema({ }, DirectoryRow ), - my_account: __table( + myAccount: __table( { name: 'my_account', indexes: [], @@ -88,7 +88,7 @@ const tablesSchema = __schema({ }, MyAccountRow ), - my_account_changes: __table( + myAccountChanges: __table( { name: 'my_account_changes', indexes: [], @@ -107,23 +107,88 @@ const reducersSchema = __reducers( /** The schema information for all procedures in this module. This is defined the same way as the procedures would have been defined in the server. */ const proceduresSchema = __procedures(); +type __SchemaWithTableAccessorAliases = Omit< + typeof tablesSchema.schemaType, + 'tables' +> & { + tables: typeof tablesSchema.schemaType.tables & { + /** @deprecated Use `myAccount` instead. This alias will be removed in the next major version. */ + readonly my_account: Omit< + (typeof tablesSchema.schemaType.tables)['myAccount'], + 'accessorName' + > & { readonly accessorName: 'my_account' }; + /** @deprecated Use `myAccountChanges` instead. This alias will be removed in the next major version. */ + readonly my_account_changes: Omit< + (typeof tablesSchema.schemaType.tables)['myAccountChanges'], + 'accessorName' + > & { readonly accessorName: 'my_account_changes' }; + }; +}; + /** The remote SpacetimeDB module schema, both runtime and type information. */ const REMOTE_MODULE = { versionInfo: { cliVersion: '2.2.0' as const, }, - tables: tablesSchema.schemaType.tables, + tables: tablesSchema.schemaType + .tables as __SchemaWithTableAccessorAliases['tables'], reducers: reducersSchema.reducersType.reducers, ...proceduresSchema, } satisfies __RemoteModule< - typeof tablesSchema.schemaType, + __SchemaWithTableAccessorAliases, typeof reducersSchema.reducersType, typeof proceduresSchema >; +const tableAccessorAliases = { + my_account: 'myAccount', + my_account_changes: 'myAccountChanges', +} as const; + +function __withTableAccessorAliases( + target: T, + freeze = false +): T { + const out = Object.create(Object.getPrototypeOf(target)) as T & + Record; + Object.defineProperties(out, Object.getOwnPropertyDescriptors(target)); + for (const [deprecatedAccessor, targetAccessor] of Object.entries( + tableAccessorAliases + )) { + if (deprecatedAccessor in out) { + continue; + } + Object.defineProperty(out, deprecatedAccessor, { + enumerable: true, + configurable: false, + get: () => out[targetAccessor], + }); + } + return freeze ? Object.freeze(out) : out; +} + +type __DbViewBase = __DbConnectionImpl['db']; +export type DbView = __DbViewBase & { + /** @deprecated Use `myAccount` instead. This alias will be removed in the next major version. */ + readonly my_account: __DbViewBase['myAccount']; + /** @deprecated Use `myAccountChanges` instead. This alias will be removed in the next major version. */ + readonly my_account_changes: __DbViewBase['myAccountChanges']; +}; + +type __TablesBase = __QueryBuilder; +export type Tables = __TablesBase & { + /** @deprecated Use `myAccount` instead. This alias will be removed in the next major version. */ + readonly my_account: __TablesBase['myAccount']; + /** @deprecated Use `myAccountChanges` instead. This alias will be removed in the next major version. */ + readonly my_account_changes: __TablesBase['myAccountChanges']; +}; + /** The tables available in this remote SpacetimeDB module. Each table reference doubles as a query builder. */ -export const tables: __QueryBuilder = - __makeQueryBuilder(tablesSchema.schemaType); +const tablesBase: __TablesBase = __makeQueryBuilder(tablesSchema.schemaType); +export const tables: Tables = __withTableAccessorAliases( + tablesBase, + true +) as Tables; /** The reducers available in this remote SpacetimeDB module. */ export const reducers = __convertToAccessorMap( @@ -134,17 +199,25 @@ export const reducers = __convertToAccessorMap( export const procedures = __convertToAccessorMap(proceduresSchema.procedures); /** The context type returned in callbacks for all possible events. */ -export type EventContext = __EventContextInterface; +export type EventContext = Omit< + __EventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for reducer events. */ -export type ReducerEventContext = __ReducerEventContextInterface< - typeof REMOTE_MODULE ->; +export type ReducerEventContext = Omit< + __ReducerEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for subscription events. */ -export type SubscriptionEventContext = __SubscriptionEventContextInterface< - typeof REMOTE_MODULE ->; +export type SubscriptionEventContext = Omit< + __SubscriptionEventContextInterface, + 'db' +> & { db: DbView }; /** The context type returned in callbacks for error events. */ -export type ErrorContext = __ErrorContextInterface; +export type ErrorContext = Omit< + __ErrorContextInterface, + 'db' +> & { db: DbView }; /** The subscription handle type to manage active subscriptions created from a {@link SubscriptionBuilder}. */ export type SubscriptionHandle = __SubscriptionHandleImpl; @@ -158,6 +231,13 @@ export class DbConnectionBuilder extends __DbConnectionBuilder {} /** The typed database connection to manage connections to the remote SpacetimeDB instance. This class has type information specific to the generated module. */ export class DbConnection extends __DbConnectionImpl { + declare db: DbView; + + constructor(config: __DbConnectionConfig) { + super(config); + this.db = __withTableAccessorAliases(this.db) as DbView; + } + /** Creates a new {@link DbConnectionBuilder} to configure and connect to the remote SpacetimeDB instance. */ static builder = (): DbConnectionBuilder => { return new DbConnectionBuilder(