From d369085745e5cc2703fad32a2683a5a49a4fa9ee Mon Sep 17 00:00:00 2001 From: Frederik Wallner Date: Wed, 25 Feb 2026 11:34:34 +0100 Subject: [PATCH 1/2] feat: Enhance FieldKeys and FieldType to support nested fields in Schema. Close #18. --- .../src/lib/firestore/query/query.ts | 78 +++++++++++++------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/packages/effect-firebase/src/lib/firestore/query/query.ts b/packages/effect-firebase/src/lib/firestore/query/query.ts index 1470d26..7a16dc1 100644 --- a/packages/effect-firebase/src/lib/firestore/query/query.ts +++ b/packages/effect-firebase/src/lib/firestore/query/query.ts @@ -19,23 +19,50 @@ import { // ============================================================================ /** - * Extract field keys from a Schema struct (Model). + * Extract field keys from a Schema struct (Model), including nested fields as dot notation strings. + * + * For example, given a schema: + * ```ts + * const PostSchema = Schema.Struct({ + * title: Schema.String, + * author: Schema.Struct({ + * name: Schema.String, + * age: Schema.Number + * }) + * }); + * + * type PostSchema = Schema.Schema.Type + * type Keys = FieldKeys // "title" | "author.name" | "author.age" + ``` */ -export type FieldKeys = S extends { readonly fields: infer F } - ? keyof F & string - : never; +type DotPrefix = T extends '' ? '' : `.${T}` +export type FieldKeys = ( + S extends object ? + { [K in Exclude]: `${K}${DotPrefix>}` }[Exclude] + : '') extends infer D ? Extract : never /** - * Extract the type of a specific field from a Schema. + * Extract the type of a specific field from a Schema, including nested fields. + * + * For example, + * `FieldType` would be `string`. + * `FieldType` TypeError. + * `FieldType` would be `string`. + * `FieldType` would be `number`. */ -export type FieldType = S extends { +export type FieldType> = S extends { readonly Type: infer T; } - ? K extends keyof T - ? T[K] - : never + ? FieldType + : K extends `${infer Head}.${infer Tail}` + ? Head extends keyof S + ? FieldType> + : never + : K extends keyof S + ? S[K] : never; + // ============================================================================ // Query Builder Type // ============================================================================ @@ -75,6 +102,7 @@ export const where = = string & FieldKeys>( value: FieldType ): Query => [new Where({ field, op, value })] as Query; + /** * Create an orderBy constraint with type-safe field names. * @@ -221,8 +249,8 @@ export const addWhere = op: WhereFilterOp, value: FieldType ) => - (query: Query): Query => - [...query, new Where({ field, op, value })] as Query; + (query: Query): Query => + [...query, new Where({ field, op, value })] as Query; /** * Pipeable version of orderBy that appends to an existing query. @@ -240,8 +268,8 @@ export const addOrderBy = field: K, direction: OrderByDirection = 'asc' ) => - (query: Query): Query => - [...query, new OrderBy({ field, direction })] as Query; + (query: Query): Query => + [...query, new OrderBy({ field, direction })] as Query; /** * Pipeable version of limit that appends to an existing query. @@ -256,48 +284,48 @@ export const addOrderBy = */ export const addLimit = (count: number) => - (query: Query): Query => - [...query, new Limit({ count })] as Query; + (query: Query): Query => + [...query, new Limit({ count })] as Query; /** * Pipeable version of limitToLast that appends to an existing query. */ export const addLimitToLast = (count: number) => - (query: Query): Query => - [...query, new LimitToLast({ count })] as Query; + (query: Query): Query => + [...query, new LimitToLast({ count })] as Query; /** * Pipeable version of startAt that appends to an existing query. */ export const addStartAt = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new StartAt({ values })] as Query; + (query: Query): Query => + [...query, new StartAt({ values })] as Query; /** * Pipeable version of startAfter that appends to an existing query. */ export const addStartAfter = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new StartAfter({ values })] as Query; + (query: Query): Query => + [...query, new StartAfter({ values })] as Query; /** * Pipeable version of endAt that appends to an existing query. */ export const addEndAt = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new EndAt({ values })] as Query; + (query: Query): Query => + [...query, new EndAt({ values })] as Query; /** * Pipeable version of endBefore that appends to an existing query. */ export const addEndBefore = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new EndBefore({ values })] as Query; + (query: Query): Query => + [...query, new EndBefore({ values })] as Query; // ============================================================================ // Utility Functions From 560dfbca6772e6250323b2b3e96587f4dc386edb Mon Sep 17 00:00:00 2001 From: christophe-g Date: Tue, 3 Mar 2026 09:45:33 +0100 Subject: [PATCH 2/2] chore: revert indent spacing --- .../src/lib/firestore/query/query.ts | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/effect-firebase/src/lib/firestore/query/query.ts b/packages/effect-firebase/src/lib/firestore/query/query.ts index 7a16dc1..77758aa 100644 --- a/packages/effect-firebase/src/lib/firestore/query/query.ts +++ b/packages/effect-firebase/src/lib/firestore/query/query.ts @@ -102,7 +102,6 @@ export const where = = string & FieldKeys>( value: FieldType ): Query => [new Where({ field, op, value })] as Query; - /** * Create an orderBy constraint with type-safe field names. * @@ -249,8 +248,8 @@ export const addWhere = op: WhereFilterOp, value: FieldType ) => - (query: Query): Query => - [...query, new Where({ field, op, value })] as Query; + (query: Query): Query => + [...query, new Where({ field, op, value })] as Query; /** * Pipeable version of orderBy that appends to an existing query. @@ -268,8 +267,8 @@ export const addOrderBy = field: K, direction: OrderByDirection = 'asc' ) => - (query: Query): Query => - [...query, new OrderBy({ field, direction })] as Query; + (query: Query): Query => + [...query, new OrderBy({ field, direction })] as Query; /** * Pipeable version of limit that appends to an existing query. @@ -284,48 +283,48 @@ export const addOrderBy = */ export const addLimit = (count: number) => - (query: Query): Query => - [...query, new Limit({ count })] as Query; + (query: Query): Query => + [...query, new Limit({ count })] as Query; /** * Pipeable version of limitToLast that appends to an existing query. */ export const addLimitToLast = (count: number) => - (query: Query): Query => - [...query, new LimitToLast({ count })] as Query; + (query: Query): Query => + [...query, new LimitToLast({ count })] as Query; /** * Pipeable version of startAt that appends to an existing query. */ export const addStartAt = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new StartAt({ values })] as Query; + (query: Query): Query => + [...query, new StartAt({ values })] as Query; /** * Pipeable version of startAfter that appends to an existing query. */ export const addStartAfter = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new StartAfter({ values })] as Query; + (query: Query): Query => + [...query, new StartAfter({ values })] as Query; /** * Pipeable version of endAt that appends to an existing query. */ export const addEndAt = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new EndAt({ values })] as Query; + (query: Query): Query => + [...query, new EndAt({ values })] as Query; /** * Pipeable version of endBefore that appends to an existing query. */ export const addEndBefore = (...values: ReadonlyArray) => - (query: Query): Query => - [...query, new EndBefore({ values })] as Query; + (query: Query): Query => + [...query, new EndBefore({ values })] as Query; // ============================================================================ // Utility Functions