@@ -2,6 +2,7 @@ import { ConnectionId } from './connection_id';
22import { Identity } from './identity' ;
33import type { ColumnIndex , IndexColumns , IndexOpts } from './indexes' ;
44import type { UntypedSchemaDef } from './schema' ;
5+ import type { UntypedTableDef } from './table' ;
56import type { UntypedTableSchema } from './table_schema' ;
67import { Timestamp } from './timestamp' ;
78import type {
@@ -222,6 +223,25 @@ export type QueryBuilder<SchemaDef extends UntypedSchemaDef> = {
222223 > as Tbl [ 'accessorName' ] ] : TableRef < Tbl > & From < Tbl > ;
223224} & { } ;
224225
226+ /**
227+ * The type of `q.from` in an `addQuery` callback.
228+ *
229+ * Root-level tables appear as direct properties (same as `QueryBuilder`).
230+ * Declared namespaces appear as sub-objects — each is itself a `QueryBuilder` for that
231+ * namespace's schema, so `q.from.<namespace>.<table>` is fully typed.
232+ *
233+ * When `SchemaDef['namespaces']` is absent or `{}`, no namespace properties appear —
234+ * accessing an undeclared namespace is a compile error.
235+ */
236+ export type SubscriptionFromBuilder < SchemaDef extends UntypedSchemaDef > =
237+ QueryBuilder < SchemaDef > & {
238+ readonly [ NS in keyof NonNullable < SchemaDef [ 'namespaces' ] > ] : NonNullable <
239+ SchemaDef [ 'namespaces' ]
240+ > [ NS ] extends UntypedSchemaDef
241+ ? QueryBuilder < NonNullable < SchemaDef [ 'namespaces' ] > [ NS ] >
242+ : never ;
243+ } ;
244+
225245/**
226246 * A runtime reference to a table. This materializes the RowExpr for us.
227247 * TODO: Maybe add the full SchemaDef to the type signature depending on how joins will work.
@@ -334,6 +354,38 @@ export function makeQueryBuilder<SchemaDef extends UntypedSchemaDef>(
334354 return Object . freeze ( qb ) as QueryBuilder < SchemaDef > ;
335355}
336356
357+ /**
358+ * Builds the `q.from` object for use in `addQuery` callbacks.
359+ *
360+ * Tables whose `sourceName` contains no `.` are placed at the root.
361+ * Tables with a dotted `sourceName` (e.g. `"namespace.table"`) are grouped under a
362+ * sub-object keyed by the namespace alias, with the part after the dot as the
363+ * property key within that namespace.
364+ */
365+ export function makeFromBuilder < SchemaDef extends UntypedSchemaDef > (
366+ tables : SchemaDef [ 'tables' ]
367+ ) : SubscriptionFromBuilder < SchemaDef > {
368+ const result : Record < string , unknown > = Object . create ( null ) ;
369+ const namespaces : Record < string , Record < string , unknown > > = Object . create ( null ) ;
370+
371+ for ( const table of Object . values ( tables ) as UntypedTableDef [ ] ) {
372+ const dotIdx = table . sourceName . indexOf ( '.' ) ;
373+ if ( dotIdx === - 1 ) {
374+ result [ table . accessorName ] = createTableRefFromDef ( table as any ) ;
375+ } else {
376+ const ns = table . sourceName . slice ( 0 , dotIdx ) ;
377+ const key = table . sourceName . slice ( dotIdx + 1 ) ;
378+ ( namespaces [ ns ] ??= Object . create ( null ) ) [ key ] = createTableRefFromDef ( table as any ) ;
379+ }
380+ }
381+
382+ for ( const [ ns , nsTables ] of Object . entries ( namespaces ) ) {
383+ result [ ns ] = Object . freeze ( nsTables ) ;
384+ }
385+
386+ return Object . freeze ( result ) as unknown as SubscriptionFromBuilder < SchemaDef > ;
387+ }
388+
337389function createRowExpr < TableDef extends TypedTableDef > (
338390 tableDef : TableDef
339391) : RowExpr < TableDef > {
@@ -873,7 +925,10 @@ function literalValueToSql(value: unknown): string {
873925}
874926
875927function quoteIdentifier ( name : string ) : string {
876- return `"${ name . replace ( / " / g, '""' ) } "` ;
928+ return name
929+ . split ( '.' )
930+ . map ( part => `"${ part . replace ( / " / g, '""' ) } "` )
931+ . join ( '.' ) ;
877932}
878933
879934function isLiteralExpr < Value > (
0 commit comments