@proofkit/fmodata 0.1.0-alpha.16 → 0.1.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/esm/client/builders/expand-builder.d.ts +3 -1
- package/dist/esm/client/builders/expand-builder.js +3 -2
- package/dist/esm/client/builders/expand-builder.js.map +1 -1
- package/dist/esm/client/builders/query-string-builder.d.ts +2 -0
- package/dist/esm/client/builders/query-string-builder.js +1 -1
- package/dist/esm/client/builders/query-string-builder.js.map +1 -1
- package/dist/esm/client/builders/response-processor.d.ts +2 -0
- package/dist/esm/client/builders/response-processor.js +3 -2
- package/dist/esm/client/builders/response-processor.js.map +1 -1
- package/dist/esm/client/builders/select-mixin.d.ts +2 -1
- package/dist/esm/client/builders/select-mixin.js +2 -2
- package/dist/esm/client/builders/select-mixin.js.map +1 -1
- package/dist/esm/client/builders/select-utils.d.ts +10 -0
- package/dist/esm/client/builders/select-utils.js +10 -2
- package/dist/esm/client/builders/select-utils.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +2 -1
- package/dist/esm/client/entity-set.js +5 -2
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +8 -0
- package/dist/esm/client/filemaker-odata.js +14 -0
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/query/query-builder.d.ts +1 -0
- package/dist/esm/client/query/query-builder.js +20 -9
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query/response-processor.d.ts +2 -0
- package/dist/esm/client/record-builder.d.ts +9 -7
- package/dist/esm/client/record-builder.js +41 -10
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +72 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/logger.test.d.ts +1 -0
- package/dist/esm/orm/operators.js +3 -1
- package/dist/esm/orm/operators.js.map +1 -1
- package/dist/esm/orm/table.d.ts +1 -1
- package/dist/esm/orm/table.js.map +1 -1
- package/dist/esm/types.d.ts +2 -0
- package/dist/esm/types.js.map +1 -1
- package/package.json +1 -1
- package/src/client/builders/expand-builder.ts +6 -2
- package/src/client/builders/query-string-builder.ts +3 -1
- package/src/client/builders/response-processor.ts +4 -1
- package/src/client/builders/select-mixin.ts +3 -1
- package/src/client/builders/select-utils.ts +25 -3
- package/src/client/entity-set.ts +7 -11
- package/src/client/error-parser.ts +4 -10
- package/src/client/filemaker-odata.ts +18 -0
- package/src/client/query/query-builder.ts +19 -6
- package/src/client/query/response-processor.ts +2 -0
- package/src/client/record-builder.ts +68 -28
- package/src/index.ts +2 -0
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +140 -0
- package/src/orm/operators.ts +6 -1
- package/src/orm/table.ts +1 -1
- package/src/types.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-builder.js","sources":["../../../src/client/record-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataFieldResponse,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExecuteMethodOptions,\n} from \"../types\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n ValidateNoContainerFields,\n} from \"../orm/table\";\nimport { getTableName, getNavigationPaths } from \"../orm/table\";\nimport { safeJsonParse } from \"./sanitize-json\";\nimport { parseErrorResponse } from \"./error-parser\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { isColumn, type Column } from \"../orm/column\";\nimport {\n type ExpandConfig,\n type ExpandedRelations,\n ExpandBuilder,\n resolveTableId,\n mergeExecuteOptions,\n processODataResponse,\n getSchemaFromTable,\n processSelectWithRenames,\n buildSelectExpandQueryString,\n createODataRequest,\n} from \"./builders/index\";\n\n/**\n * Extract the value type from a Column.\n * This uses the phantom type stored in Column to get the actual value type.\n */\ntype ExtractColumnType<C> = C extends Column<infer T, any> ? T : never;\n\n/**\n * Map a select object to its return type.\n * For each key in the select object, extract the type from the corresponding Column.\n */\ntype MapSelectToReturnType<\n TSelect extends Record<string, Column<any, any, any, any>>,\n TSchema extends Record<string, any>,\n> = {\n [K in keyof TSelect]: ExtractColumnType<TSelect[K]>;\n};\n\n// Return type for RecordBuilder execute\nexport type RecordReturnType<\n Schema extends Record<string, any>,\n IsSingleField extends boolean,\n FieldKey extends keyof Schema,\n Selected extends\n | keyof Schema\n | Record<string, Column<any, any, ExtractTableName<FMTable<any, any>>>>,\n Expands extends ExpandedRelations,\n> = IsSingleField extends true\n ? Schema[FieldKey]\n : // Use tuple wrapping [Selected] extends [...] to prevent distribution over unions\n [Selected] extends [Record<string, Column<any, any, any, any>>]\n ? MapSelectToReturnType<Selected, Schema> & {\n [K in keyof Expands]: Pick<\n Expands[K][\"schema\"],\n Expands[K][\"selected\"]\n >[];\n }\n : // Use tuple wrapping to prevent distribution over union of keys\n [Selected] extends [keyof Schema]\n ? Pick<Schema, Selected> & {\n [K in keyof Expands]: Pick<\n Expands[K][\"schema\"],\n Expands[K][\"selected\"]\n >[];\n }\n : never;\n\nexport class RecordBuilder<\n Occ extends FMTable<any, any> = FMTable<any, any>,\n IsSingleField extends boolean = false,\n FieldKey extends keyof InferSchemaOutputFromFMTable<\n NonNullable<Occ>\n > = keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n Selected extends\n | keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>\n | Record<\n string,\n Column<any, any, ExtractTableName<NonNullable<Occ>>>\n > = keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n Expands extends ExpandedRelations = {},\n> implements\n ExecutableBuilder<\n RecordReturnType<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n IsSingleField,\n FieldKey,\n Selected,\n Expands\n >\n >\n{\n private table: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private recordId: string | number;\n private operation?: \"getSingleField\" | \"navigate\";\n private operationParam?: string;\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n\n private databaseUseEntityIds: boolean;\n\n // Properties for select/expand support\n private selectedFields?: string[];\n private expandConfigs: ExpandConfig[] = [];\n // Mapping from field names to output keys (for renamed fields in select)\n private fieldMapping?: Record<string, string>;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n recordId: string | number;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.recordId = config.recordId;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n return mergeExecuteOptions(options, this.databaseUseEntityIds);\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableId(useEntityIds?: boolean): string {\n if (!this.table) {\n throw new Error(\"Table occurrence is required\");\n }\n return resolveTableId(\n this.table,\n getTableName(this.table),\n this.context,\n useEntityIds,\n );\n }\n\n /**\n * Creates a new RecordBuilder with modified configuration.\n * Used by select() to create new instances.\n */\n private cloneWithChanges<\n NewSelected extends\n | keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>\n | Record<\n string,\n Column<any, any, ExtractTableName<NonNullable<Occ>>>\n > = Selected,\n >(changes: {\n selectedFields?: string[];\n fieldMapping?: Record<string, string>;\n }): RecordBuilder<Occ, false, FieldKey, NewSelected, Expands> {\n const newBuilder = new RecordBuilder<\n Occ,\n false,\n FieldKey,\n NewSelected,\n Expands\n >({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.selectedFields = changes.selectedFields ?? this.selectedFields;\n newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;\n newBuilder.expandConfigs = [...this.expandConfigs];\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n getSingleField<\n K extends keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n >(\n field: K,\n ): RecordBuilder<\n Occ,\n true,\n K,\n keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n {}\n > {\n const newBuilder = new RecordBuilder<\n Occ,\n true,\n K,\n keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n {}\n >({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.operation = \"getSingleField\";\n newBuilder.operationParam = field.toString();\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n /**\n * Select fields using column references.\n * Allows renaming fields by using different keys in the object.\n * Container fields cannot be selected and will cause a type error.\n *\n * @example\n * db.from(contacts).get(\"uuid\").select({\n * name: contacts.name,\n * userEmail: contacts.email // renamed!\n * })\n *\n * @param fields - Object mapping output keys to column references (container fields excluded)\n * @returns RecordBuilder with updated selected fields\n */\n select<\n TSelect extends Record<\n string,\n Column<any, any, ExtractTableName<Occ>, false>\n >,\n >(fields: TSelect): RecordBuilder<Occ, false, FieldKey, TSelect, Expands> {\n const tableName = getTableName(this.table);\n const { selectedFields, fieldMapping } = processSelectWithRenames(\n fields,\n tableName,\n );\n\n return this.cloneWithChanges({\n selectedFields,\n fieldMapping:\n Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,\n }) as any;\n }\n\n /**\n * Expand a navigation property to include related records.\n * Supports nested select, filter, orderBy, and expand operations.\n *\n * @example\n * ```typescript\n * // Simple expand with FMTable object\n * const contact = await db.from(contacts).get(\"uuid\").expand(users).execute();\n *\n * // Expand with select\n * const contact = await db.from(contacts).get(\"uuid\")\n * .expand(users, b => b.select({ username: users.username, email: users.email }))\n * .execute();\n * ```\n */\n expand<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n callback?: (\n builder: QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n >,\n ) => QueryBuilder<TargetTable, any, any, any, any>,\n ): RecordBuilder<\n Occ,\n false,\n FieldKey,\n Selected,\n Expands & {\n [K in ExtractTableName<TargetTable>]: {\n schema: InferSchemaOutputFromFMTable<TargetTable>;\n selected: keyof InferSchemaOutputFromFMTable<TargetTable>;\n };\n }\n > {\n // Create new builder with updated types\n const newBuilder = new RecordBuilder<Occ, false, FieldKey, Selected, any>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Copy existing state\n newBuilder.selectedFields = this.selectedFields;\n newBuilder.fieldMapping = this.fieldMapping;\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n\n // Use ExpandBuilder.processExpand to handle the expand logic\n const expandBuilder = new ExpandBuilder(this.databaseUseEntityIds);\n type TargetBuilder = QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n >;\n const expandConfig = expandBuilder.processExpand<\n TargetTable,\n TargetBuilder\n >(\n targetTable,\n this.table ?? undefined,\n callback,\n () =>\n new QueryBuilder<TargetTable>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }),\n );\n\n newBuilder.expandConfigs.push(expandConfig);\n return newBuilder as any;\n }\n\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n > {\n // Extract name and validate\n const relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (this.table) {\n const navigationPaths = getNavigationPaths(this.table);\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n console.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create QueryBuilder with target table\n const builder = new QueryBuilder<TargetTable>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Store the navigation info - we'll use it in execute\n // Use relation name as-is (entity ID handling is done in QueryBuilder)\n const relationId = relationName;\n\n // If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path\n let sourceTableName: string;\n let baseRelation: string | undefined;\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // Build the base path: /sourceTable/relation('recordId')/newRelation\n sourceTableName = this.navigateSourceTableName;\n baseRelation = this.navigateRelation;\n } else {\n // Normal record navigation: /tableName('recordId')/relation\n // Use table ID if available, otherwise table name\n if (!this.table) {\n throw new Error(\"Table occurrence is required for navigation\");\n }\n sourceTableName = resolveTableId(\n this.table,\n getTableName(this.table),\n this.context,\n this.databaseUseEntityIds,\n );\n }\n\n (builder as any).navigation = {\n recordId: this.recordId,\n relation: relationId,\n sourceTableName,\n baseRelation,\n };\n\n return builder;\n }\n\n /**\n * Builds the complete query string including $select and $expand parameters.\n */\n private buildQueryString(): string {\n return buildSelectExpandQueryString({\n selectedFields: this.selectedFields,\n expandConfigs: this.expandConfigs,\n table: this.table,\n useEntityIds: this.databaseUseEntityIds,\n });\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n RecordReturnType<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n IsSingleField,\n FieldKey,\n Selected,\n Expands\n >,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.getTableId(\n options?.useEntityIds ?? this.databaseUseEntityIds,\n );\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n let response = result.data;\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = response as ODataFieldResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>[FieldKey]\n >;\n return { data: fieldResponse.value as any, error: undefined };\n }\n\n // Use shared response processor\n const expandBuilder = new ExpandBuilder(\n mergedOptions.useEntityIds ?? false,\n );\n const expandValidationConfigs = expandBuilder.buildValidationConfigs(\n this.expandConfigs,\n );\n\n return processODataResponse(response, {\n table: this.table,\n schema: getSchemaFromTable(this.table),\n singleMode: \"exact\",\n selectedFields: this.selectedFields,\n expandValidationConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n });\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n /**\n * Returns the query string for this record builder (for testing purposes).\n */\n getQueryString(): string {\n let path: string;\n\n // Build the path depending on navigation context\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n path = `/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Use getTableId to respect entity ID settings (same as getRequestConfig)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n path = `/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n return `${path}/${this.operationParam}`;\n }\n\n const queryString = this.buildQueryString();\n return `${path}${queryString}`;\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n return createODataRequest(baseUrl, config, options);\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n RecordReturnType<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n IsSingleField,\n FieldKey,\n Selected,\n Expands\n >\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = this.table ? getTableName(this.table) : \"unknown\";\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n const rawResponse = await safeJsonParse(response);\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = rawResponse as ODataFieldResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>[FieldKey]\n >;\n return { data: fieldResponse.value as any, error: undefined };\n }\n\n // Use shared response processor\n const mergedOptions = mergeExecuteOptions(\n options,\n this.databaseUseEntityIds,\n );\n const expandBuilder = new ExpandBuilder(\n mergedOptions.useEntityIds ?? false,\n );\n const expandValidationConfigs = expandBuilder.buildValidationConfigs(\n this.expandConfigs,\n );\n\n return processODataResponse(rawResponse, {\n table: this.table,\n schema: getSchemaFromTable(this.table),\n singleMode: \"exact\",\n selectedFields: this.selectedFields,\n expandValidationConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"record-builder.js","sources":["../../../src/client/record-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataFieldResponse,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExecuteMethodOptions,\n} from \"../types\";\nimport type {\n FMTable,\n InferSchemaOutputFromFMTable,\n ValidExpandTarget,\n ExtractTableName,\n ValidateNoContainerFields,\n} from \"../orm/table\";\nimport { getTableName, getNavigationPaths } from \"../orm/table\";\nimport { safeJsonParse } from \"./sanitize-json\";\nimport { parseErrorResponse } from \"./error-parser\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { isColumn, type Column } from \"../orm/column\";\nimport {\n type ExpandConfig,\n type ExpandedRelations,\n ExpandBuilder,\n resolveTableId,\n mergeExecuteOptions,\n processODataResponse,\n getSchemaFromTable,\n processSelectWithRenames,\n buildSelectExpandQueryString,\n createODataRequest,\n} from \"./builders/index\";\nimport { createLogger, InternalLogger, Logger } from \"../logger\";\n\n/**\n * Extract the value type from a Column.\n * This uses the phantom type stored in Column to get the actual value type.\n */\ntype ExtractColumnType<C> = C extends Column<infer T, any> ? T : never;\n\n/**\n * Map a select object to its return type.\n * For each key in the select object, extract the type from the corresponding Column.\n */\ntype MapSelectToReturnType<\n TSelect extends Record<string, Column<any, any, any, any>>,\n TSchema extends Record<string, any>,\n> = {\n [K in keyof TSelect]: ExtractColumnType<TSelect[K]>;\n};\n\n// Return type for RecordBuilder execute\nexport type RecordReturnType<\n Schema extends Record<string, any>,\n IsSingleField extends boolean,\n FieldColumn extends Column<any, any, any, any> | undefined,\n Selected extends\n | keyof Schema\n | Record<string, Column<any, any, ExtractTableName<FMTable<any, any>>>>,\n Expands extends ExpandedRelations,\n> = IsSingleField extends true\n ? FieldColumn extends Column<infer TOutput, any, any, any>\n ? TOutput\n : never\n : // Use tuple wrapping [Selected] extends [...] to prevent distribution over unions\n [Selected] extends [Record<string, Column<any, any, any, any>>]\n ? MapSelectToReturnType<Selected, Schema> & {\n [K in keyof Expands]: Pick<\n Expands[K][\"schema\"],\n Expands[K][\"selected\"]\n >[];\n }\n : // Use tuple wrapping to prevent distribution over union of keys\n [Selected] extends [keyof Schema]\n ? Pick<Schema, Selected> & {\n [K in keyof Expands]: Pick<\n Expands[K][\"schema\"],\n Expands[K][\"selected\"]\n >[];\n }\n : never;\n\nexport class RecordBuilder<\n Occ extends FMTable<any, any> = FMTable<any, any>,\n IsSingleField extends boolean = false,\n FieldColumn extends Column<any, any, any, any> | undefined = undefined,\n Selected extends\n | keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>\n | Record<\n string,\n Column<any, any, ExtractTableName<NonNullable<Occ>>>\n > = keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n Expands extends ExpandedRelations = {},\n> implements\n ExecutableBuilder<\n RecordReturnType<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n IsSingleField,\n FieldColumn,\n Selected,\n Expands\n >\n >\n{\n private table: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private recordId: string | number;\n private operation?: \"getSingleField\" | \"navigate\";\n private operationParam?: string;\n private operationColumn?: Column<any, any, any, any>;\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n\n private databaseUseEntityIds: boolean;\n\n // Properties for select/expand support\n private selectedFields?: string[];\n private expandConfigs: ExpandConfig[] = [];\n // Mapping from field names to output keys (for renamed fields in select)\n private fieldMapping?: Record<string, string>;\n\n private logger: InternalLogger;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n recordId: string | number;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.recordId = config.recordId;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.logger = config.context?._getLogger?.() ?? createLogger();\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n return mergeExecuteOptions(options, this.databaseUseEntityIds);\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableId(useEntityIds?: boolean): string {\n if (!this.table) {\n throw new Error(\"Table occurrence is required\");\n }\n return resolveTableId(\n this.table,\n getTableName(this.table),\n this.context,\n useEntityIds,\n );\n }\n\n /**\n * Creates a new RecordBuilder with modified configuration.\n * Used by select() to create new instances.\n */\n private cloneWithChanges<\n NewSelected extends\n | keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>\n | Record<\n string,\n Column<any, any, ExtractTableName<NonNullable<Occ>>>\n > = Selected,\n >(changes: {\n selectedFields?: string[];\n fieldMapping?: Record<string, string>;\n }): RecordBuilder<Occ, false, FieldColumn, NewSelected, Expands> {\n const newBuilder = new RecordBuilder<\n Occ,\n false,\n FieldColumn,\n NewSelected,\n Expands\n >({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.selectedFields = changes.selectedFields ?? this.selectedFields;\n newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;\n newBuilder.expandConfigs = [...this.expandConfigs];\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n newBuilder.operationColumn = this.operationColumn;\n return newBuilder;\n }\n\n getSingleField<\n TColumn extends Column<any, any, ExtractTableName<NonNullable<Occ>>, any>,\n >(\n column: TColumn,\n ): RecordBuilder<\n Occ,\n true,\n TColumn,\n keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n {}\n > {\n // Runtime validation: ensure column is from the correct table\n const tableName = getTableName(this.table);\n if (!column.isFromTable(tableName)) {\n throw new Error(\n `Column ${column.toString()} is not from table ${tableName}`,\n );\n }\n\n const newBuilder = new RecordBuilder<\n Occ,\n true,\n TColumn,\n keyof InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n {}\n >({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n newBuilder.operation = \"getSingleField\";\n newBuilder.operationColumn = column;\n newBuilder.operationParam = column.getFieldIdentifier(\n this.databaseUseEntityIds,\n );\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n /**\n * Select fields using column references.\n * Allows renaming fields by using different keys in the object.\n * Container fields cannot be selected and will cause a type error.\n *\n * @example\n * db.from(contacts).get(\"uuid\").select({\n * name: contacts.name,\n * userEmail: contacts.email // renamed!\n * })\n *\n * @param fields - Object mapping output keys to column references (container fields excluded)\n * @returns RecordBuilder with updated selected fields\n */\n select<\n TSelect extends Record<\n string,\n Column<any, any, ExtractTableName<Occ>, false>\n >,\n >(fields: TSelect): RecordBuilder<Occ, false, FieldColumn, TSelect, Expands> {\n const tableName = getTableName(this.table);\n const { selectedFields, fieldMapping } = processSelectWithRenames(\n fields,\n tableName,\n this.logger,\n );\n\n return this.cloneWithChanges({\n selectedFields,\n fieldMapping:\n Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,\n }) as any;\n }\n\n /**\n * Expand a navigation property to include related records.\n * Supports nested select, filter, orderBy, and expand operations.\n *\n * @example\n * ```typescript\n * // Simple expand with FMTable object\n * const contact = await db.from(contacts).get(\"uuid\").expand(users).execute();\n *\n * // Expand with select\n * const contact = await db.from(contacts).get(\"uuid\")\n * .expand(users, b => b.select({ username: users.username, email: users.email }))\n * .execute();\n * ```\n */\n expand<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n callback?: (\n builder: QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n >,\n ) => QueryBuilder<TargetTable, any, any, any, any>,\n ): RecordBuilder<\n Occ,\n false,\n FieldColumn,\n Selected,\n Expands & {\n [K in ExtractTableName<TargetTable>]: {\n schema: InferSchemaOutputFromFMTable<TargetTable>;\n selected: keyof InferSchemaOutputFromFMTable<TargetTable>;\n };\n }\n > {\n // Create new builder with updated types\n const newBuilder = new RecordBuilder<\n Occ,\n false,\n FieldColumn,\n Selected,\n any\n >({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Copy existing state\n newBuilder.selectedFields = this.selectedFields;\n newBuilder.fieldMapping = this.fieldMapping;\n newBuilder.expandConfigs = [...this.expandConfigs];\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n newBuilder.operationColumn = this.operationColumn;\n\n // Use ExpandBuilder.processExpand to handle the expand logic\n const expandBuilder = new ExpandBuilder(\n this.databaseUseEntityIds,\n this.logger,\n );\n type TargetBuilder = QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n >;\n const expandConfig = expandBuilder.processExpand<\n TargetTable,\n TargetBuilder\n >(\n targetTable,\n this.table ?? undefined,\n callback,\n () =>\n new QueryBuilder<TargetTable>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n }),\n );\n\n newBuilder.expandConfigs.push(expandConfig);\n return newBuilder as any;\n }\n\n navigate<TargetTable extends FMTable<any, any>>(\n targetTable: ValidExpandTarget<Occ, TargetTable>,\n ): QueryBuilder<\n TargetTable,\n keyof InferSchemaOutputFromFMTable<TargetTable>,\n false,\n false\n > {\n // Extract name and validate\n const relationName = getTableName(targetTable);\n\n // Runtime validation: Check if relation name is in navigationPaths\n if (this.table) {\n const navigationPaths = getNavigationPaths(this.table);\n if (navigationPaths && !navigationPaths.includes(relationName)) {\n this.logger.warn(\n `Cannot navigate to \"${relationName}\". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(\", \") : \"none\"}`,\n );\n }\n }\n\n // Create QueryBuilder with target table\n const builder = new QueryBuilder<TargetTable>({\n occurrence: targetTable,\n databaseName: this.databaseName,\n context: this.context,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n\n // Store the navigation info - we'll use it in execute\n // Use relation name as-is (entity ID handling is done in QueryBuilder)\n const relationId = relationName;\n\n // If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path\n let sourceTableName: string;\n let baseRelation: string | undefined;\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // Build the base path: /sourceTable/relation('recordId')/newRelation\n sourceTableName = this.navigateSourceTableName;\n baseRelation = this.navigateRelation;\n } else {\n // Normal record navigation: /tableName('recordId')/relation\n // Use table ID if available, otherwise table name\n if (!this.table) {\n throw new Error(\"Table occurrence is required for navigation\");\n }\n sourceTableName = resolveTableId(\n this.table,\n getTableName(this.table),\n this.context,\n this.databaseUseEntityIds,\n );\n }\n\n (builder as any).navigation = {\n recordId: this.recordId,\n relation: relationId,\n sourceTableName,\n baseRelation,\n };\n\n return builder;\n }\n\n /**\n * Builds the complete query string including $select and $expand parameters.\n */\n private buildQueryString(): string {\n return buildSelectExpandQueryString({\n selectedFields: this.selectedFields,\n expandConfigs: this.expandConfigs,\n table: this.table,\n useEntityIds: this.databaseUseEntityIds,\n logger: this.logger,\n });\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ConditionallyWithODataAnnotations<\n RecordReturnType<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n IsSingleField,\n FieldColumn,\n Selected,\n Expands\n >,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.getTableId(\n options?.useEntityIds ?? this.databaseUseEntityIds,\n );\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n let response = result.data;\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n // The type is extracted from the Column stored in FieldColumn generic\n const fieldResponse = response as ODataFieldResponse<any>;\n return { data: fieldResponse.value as any, error: undefined };\n }\n\n // Use shared response processor\n const expandBuilder = new ExpandBuilder(\n mergedOptions.useEntityIds ?? false,\n this.logger,\n );\n const expandValidationConfigs = expandBuilder.buildValidationConfigs(\n this.expandConfigs,\n );\n\n return processODataResponse(response, {\n table: this.table,\n schema: getSchemaFromTable(this.table),\n singleMode: \"exact\",\n selectedFields: this.selectedFields,\n expandValidationConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n });\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationColumn) {\n // Use the column's getFieldIdentifier to support entity IDs\n url += `/${this.operationColumn.getFieldIdentifier(\n this.databaseUseEntityIds,\n )}`;\n } else if (this.operation === \"getSingleField\" && this.operationParam) {\n // Fallback for backwards compatibility (shouldn't happen in normal flow)\n url += `/${this.operationParam}`;\n } else {\n // Add query string for select/expand (only when not getting a single field)\n const queryString = this.buildQueryString();\n url += queryString;\n }\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n /**\n * Returns the query string for this record builder (for testing purposes).\n */\n getQueryString(): string {\n let path: string;\n\n // Build the path depending on navigation context\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n path = `/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Use getTableId to respect entity ID settings (same as getRequestConfig)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n path = `/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationColumn) {\n return `${path}/${this.operationColumn.getFieldIdentifier(\n this.databaseUseEntityIds,\n )}`;\n } else if (this.operation === \"getSingleField\" && this.operationParam) {\n // Fallback for backwards compatibility (shouldn't happen in normal flow)\n return `${path}/${this.operationParam}`;\n }\n\n const queryString = this.buildQueryString();\n return `${path}${queryString}`;\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n return createODataRequest(baseUrl, config, options);\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n RecordReturnType<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n IsSingleField,\n FieldColumn,\n Selected,\n Expands\n >\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = this.table ? getTableName(this.table) : \"unknown\";\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n const rawResponse = await safeJsonParse(response);\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n // The type is extracted from the Column stored in FieldColumn generic\n const fieldResponse = rawResponse as ODataFieldResponse<any>;\n return { data: fieldResponse.value as any, error: undefined };\n }\n\n // Use shared response processor\n const mergedOptions = mergeExecuteOptions(\n options,\n this.databaseUseEntityIds,\n );\n const expandBuilder = new ExpandBuilder(\n mergedOptions.useEntityIds ?? false,\n this.logger,\n );\n const expandValidationConfigs = expandBuilder.buildValidationConfigs(\n this.expandConfigs,\n );\n\n return processODataResponse(rawResponse, {\n table: this.table,\n schema: getSchemaFromTable(this.table),\n singleMode: \"exact\",\n selectedFields: this.selectedFields,\n expandValidationConfigs,\n skipValidation: options?.skipValidation,\n useEntityIds: mergedOptions.useEntityIds,\n fieldMapping: this.fieldMapping,\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { TimeoutError, AbortError, NetworkError, RetryLimitError, CircuitOpenErr
|
|
|
8
8
|
export type { FFetchOptions } from '@fetchkit/ffetch';
|
|
9
9
|
export { FMODataError, HTTPError, ODataError, SchemaLockedError, ValidationError, ResponseStructureError, RecordCountMismatchError, ResponseParseError, BatchTruncatedError, isHTTPError, isValidationError, isODataError, isSchemaLockedError, isResponseStructureError, isRecordCountMismatchError, isResponseParseError, isBatchTruncatedError, isFMODataError, } from './errors.js';
|
|
10
10
|
export type { FMODataErrorType } from './errors.js';
|
|
11
|
+
export type { Logger } from './logger.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const TTY_COLORS: {
|
|
2
|
+
readonly reset: "\u001B[0m";
|
|
3
|
+
readonly bright: "\u001B[1m";
|
|
4
|
+
readonly dim: "\u001B[2m";
|
|
5
|
+
readonly undim: "\u001B[22m";
|
|
6
|
+
readonly underscore: "\u001B[4m";
|
|
7
|
+
readonly blink: "\u001B[5m";
|
|
8
|
+
readonly reverse: "\u001B[7m";
|
|
9
|
+
readonly hidden: "\u001B[8m";
|
|
10
|
+
readonly fg: {
|
|
11
|
+
readonly black: "\u001B[30m";
|
|
12
|
+
readonly red: "\u001B[31m";
|
|
13
|
+
readonly green: "\u001B[32m";
|
|
14
|
+
readonly yellow: "\u001B[33m";
|
|
15
|
+
readonly blue: "\u001B[34m";
|
|
16
|
+
readonly magenta: "\u001B[35m";
|
|
17
|
+
readonly cyan: "\u001B[36m";
|
|
18
|
+
readonly white: "\u001B[37m";
|
|
19
|
+
};
|
|
20
|
+
readonly bg: {
|
|
21
|
+
readonly black: "\u001B[40m";
|
|
22
|
+
readonly red: "\u001B[41m";
|
|
23
|
+
readonly green: "\u001B[42m";
|
|
24
|
+
readonly yellow: "\u001B[43m";
|
|
25
|
+
readonly blue: "\u001B[44m";
|
|
26
|
+
readonly magenta: "\u001B[45m";
|
|
27
|
+
readonly cyan: "\u001B[46m";
|
|
28
|
+
readonly white: "\u001B[47m";
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type LogLevel = "debug" | "info" | "success" | "warn" | "error";
|
|
32
|
+
export declare const levels: readonly ["debug", "info", "success", "warn", "error"];
|
|
33
|
+
export declare function shouldPublishLog(currentLogLevel: LogLevel, logLevel: LogLevel): boolean;
|
|
34
|
+
export interface Logger {
|
|
35
|
+
disabled?: boolean | undefined;
|
|
36
|
+
disableColors?: boolean | undefined;
|
|
37
|
+
level?: Exclude<LogLevel, "success"> | undefined;
|
|
38
|
+
log?: ((level: Exclude<LogLevel, "success">, message: string, ...args: any[]) => void) | undefined;
|
|
39
|
+
}
|
|
40
|
+
export type LogHandlerParams = Parameters<NonNullable<Logger["log"]>> extends [LogLevel, ...infer Rest] ? Rest : never;
|
|
41
|
+
export type InternalLogger = {
|
|
42
|
+
[K in LogLevel]: (...params: LogHandlerParams) => void;
|
|
43
|
+
} & {
|
|
44
|
+
get level(): LogLevel;
|
|
45
|
+
};
|
|
46
|
+
export declare const createLogger: (options?: Logger | undefined) => InternalLogger;
|
|
47
|
+
export declare const logger: InternalLogger;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const TTY_COLORS = {
|
|
2
|
+
reset: "\x1B[0m",
|
|
3
|
+
bright: "\x1B[1m",
|
|
4
|
+
dim: "\x1B[2m",
|
|
5
|
+
fg: {
|
|
6
|
+
red: "\x1B[31m",
|
|
7
|
+
green: "\x1B[32m",
|
|
8
|
+
yellow: "\x1B[33m",
|
|
9
|
+
blue: "\x1B[34m",
|
|
10
|
+
magenta: "\x1B[35m"
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const levels = ["debug", "info", "success", "warn", "error"];
|
|
14
|
+
function shouldPublishLog(currentLogLevel, logLevel) {
|
|
15
|
+
return levels.indexOf(logLevel) >= levels.indexOf(currentLogLevel);
|
|
16
|
+
}
|
|
17
|
+
const levelColors = {
|
|
18
|
+
info: TTY_COLORS.fg.blue,
|
|
19
|
+
success: TTY_COLORS.fg.green,
|
|
20
|
+
warn: TTY_COLORS.fg.yellow,
|
|
21
|
+
error: TTY_COLORS.fg.red,
|
|
22
|
+
debug: TTY_COLORS.fg.magenta
|
|
23
|
+
};
|
|
24
|
+
const formatMessage = (level, message, colorsEnabled) => {
|
|
25
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
26
|
+
if (colorsEnabled) {
|
|
27
|
+
return `${TTY_COLORS.dim}${timestamp}${TTY_COLORS.reset} ${levelColors[level]}${level.toUpperCase()}${TTY_COLORS.reset} ${TTY_COLORS.bright}[FMODATA]:${TTY_COLORS.reset} ${message}`;
|
|
28
|
+
}
|
|
29
|
+
return `${timestamp} ${level.toUpperCase()} [FMODATA]: ${message}`;
|
|
30
|
+
};
|
|
31
|
+
const createLogger = (options) => {
|
|
32
|
+
const enabled = (options == null ? void 0 : options.disabled) !== true;
|
|
33
|
+
const logLevel = (options == null ? void 0 : options.level) ?? "error";
|
|
34
|
+
const colorsEnabled = (options == null ? void 0 : options.disableColors) !== true;
|
|
35
|
+
const LogFunc = (level, message, args = []) => {
|
|
36
|
+
if (!enabled || !shouldPublishLog(logLevel, level)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const formattedMessage = formatMessage(level, message, colorsEnabled);
|
|
40
|
+
if (!options || typeof options.log !== "function") {
|
|
41
|
+
if (level === "error") {
|
|
42
|
+
console.error(formattedMessage, ...args);
|
|
43
|
+
} else if (level === "warn") {
|
|
44
|
+
console.warn(formattedMessage, ...args);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(formattedMessage, ...args);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
options.log(level === "success" ? "info" : level, message, ...args);
|
|
51
|
+
};
|
|
52
|
+
const logger2 = Object.fromEntries(
|
|
53
|
+
levels.map((level) => [
|
|
54
|
+
level,
|
|
55
|
+
(...[message, ...args]) => LogFunc(level, message, args)
|
|
56
|
+
])
|
|
57
|
+
);
|
|
58
|
+
return {
|
|
59
|
+
...logger2,
|
|
60
|
+
get level() {
|
|
61
|
+
return logLevel;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
createLogger();
|
|
66
|
+
export {
|
|
67
|
+
TTY_COLORS,
|
|
68
|
+
createLogger,
|
|
69
|
+
levels,
|
|
70
|
+
shouldPublishLog
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sources":["../../src/logger.ts"],"sourcesContent":["export const TTY_COLORS = {\n reset: \"\\x1b[0m\",\n bright: \"\\x1b[1m\",\n dim: \"\\x1b[2m\",\n undim: \"\\x1b[22m\",\n underscore: \"\\x1b[4m\",\n blink: \"\\x1b[5m\",\n reverse: \"\\x1b[7m\",\n hidden: \"\\x1b[8m\",\n fg: {\n black: \"\\x1b[30m\",\n red: \"\\x1b[31m\",\n green: \"\\x1b[32m\",\n yellow: \"\\x1b[33m\",\n blue: \"\\x1b[34m\",\n magenta: \"\\x1b[35m\",\n cyan: \"\\x1b[36m\",\n white: \"\\x1b[37m\",\n },\n bg: {\n black: \"\\x1b[40m\",\n red: \"\\x1b[41m\",\n green: \"\\x1b[42m\",\n yellow: \"\\x1b[43m\",\n blue: \"\\x1b[44m\",\n magenta: \"\\x1b[45m\",\n cyan: \"\\x1b[46m\",\n white: \"\\x1b[47m\",\n },\n} as const;\n\nexport type LogLevel = \"debug\" | \"info\" | \"success\" | \"warn\" | \"error\";\n\nexport const levels = [\"debug\", \"info\", \"success\", \"warn\", \"error\"] as const;\n\nexport function shouldPublishLog(\n currentLogLevel: LogLevel,\n logLevel: LogLevel,\n): boolean {\n return levels.indexOf(logLevel) >= levels.indexOf(currentLogLevel);\n}\n\nexport interface Logger {\n disabled?: boolean | undefined;\n disableColors?: boolean | undefined;\n level?: Exclude<LogLevel, \"success\"> | undefined;\n log?:\n | ((\n level: Exclude<LogLevel, \"success\">,\n message: string,\n ...args: any[]\n ) => void)\n | undefined;\n}\n\nexport type LogHandlerParams =\n Parameters<NonNullable<Logger[\"log\"]>> extends [LogLevel, ...infer Rest]\n ? Rest\n : never;\n\nconst levelColors: Record<LogLevel, string> = {\n info: TTY_COLORS.fg.blue,\n success: TTY_COLORS.fg.green,\n warn: TTY_COLORS.fg.yellow,\n error: TTY_COLORS.fg.red,\n debug: TTY_COLORS.fg.magenta,\n};\n\nconst formatMessage = (\n level: LogLevel,\n message: string,\n colorsEnabled: boolean,\n): string => {\n const timestamp = new Date().toISOString();\n\n if (colorsEnabled) {\n return `${TTY_COLORS.dim}${timestamp}${TTY_COLORS.reset} ${\n levelColors[level]\n }${level.toUpperCase()}${TTY_COLORS.reset} ${TTY_COLORS.bright}[FMODATA]:${\n TTY_COLORS.reset\n } ${message}`;\n }\n\n return `${timestamp} ${level.toUpperCase()} [FMODATA]: ${message}`;\n};\n\nexport type InternalLogger = {\n [K in LogLevel]: (...params: LogHandlerParams) => void;\n} & {\n get level(): LogLevel;\n};\n\nexport const createLogger = (options?: Logger | undefined): InternalLogger => {\n const enabled = options?.disabled !== true;\n const logLevel = options?.level ?? \"error\";\n\n const colorsEnabled = options?.disableColors !== true;\n\n const LogFunc = (\n level: LogLevel,\n message: string,\n args: any[] = [],\n ): void => {\n if (!enabled || !shouldPublishLog(logLevel, level)) {\n return;\n }\n\n const formattedMessage = formatMessage(level, message, colorsEnabled);\n\n if (!options || typeof options.log !== \"function\") {\n if (level === \"error\") {\n console.error(formattedMessage, ...args);\n } else if (level === \"warn\") {\n console.warn(formattedMessage, ...args);\n } else {\n console.log(formattedMessage, ...args);\n }\n return;\n }\n\n options.log(level === \"success\" ? \"info\" : level, message, ...args);\n };\n\n const logger = Object.fromEntries(\n levels.map((level) => [\n level,\n (...[message, ...args]: LogHandlerParams) =>\n LogFunc(level, message, args),\n ]),\n ) as Record<LogLevel, (...params: LogHandlerParams) => void>;\n\n return {\n ...logger,\n get level() {\n return logLevel;\n },\n };\n};\n\nexport const logger = createLogger();\n"],"names":["logger"],"mappings":"AAAO,MAAM,aAAa;AAAA,EACxB,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,KAAK;AAAA,EAML,IAAI;AAAA,IAEF,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,SAAS;AAAA,EAGX;AAWF;AAIO,MAAM,SAAS,CAAC,SAAS,QAAQ,WAAW,QAAQ,OAAO;AAElD,SAAA,iBACd,iBACA,UACS;AACT,SAAO,OAAO,QAAQ,QAAQ,KAAK,OAAO,QAAQ,eAAe;AACnE;AAoBA,MAAM,cAAwC;AAAA,EAC5C,MAAM,WAAW,GAAG;AAAA,EACpB,SAAS,WAAW,GAAG;AAAA,EACvB,MAAM,WAAW,GAAG;AAAA,EACpB,OAAO,WAAW,GAAG;AAAA,EACrB,OAAO,WAAW,GAAG;AACvB;AAEA,MAAM,gBAAgB,CACpB,OACA,SACA,kBACW;AACX,QAAM,aAAY,oBAAI,KAAK,GAAE,YAAY;AAEzC,MAAI,eAAe;AACV,WAAA,GAAG,WAAW,GAAG,GAAG,SAAS,GAAG,WAAW,KAAK,IACrD,YAAY,KAAK,CACnB,GAAG,MAAM,YAAY,CAAC,GAAG,WAAW,KAAK,IAAI,WAAW,MAAM,aAC5D,WAAW,KACb,IAAI,OAAO;AAAA,EAAA;AAGb,SAAO,GAAG,SAAS,IAAI,MAAM,aAAa,eAAe,OAAO;AAClE;AAQa,MAAA,eAAe,CAAC,YAAiD;AACtE,QAAA,WAAU,mCAAS,cAAa;AAChC,QAAA,YAAW,mCAAS,UAAS;AAE7B,QAAA,iBAAgB,mCAAS,mBAAkB;AAEjD,QAAM,UAAU,CACd,OACA,SACA,OAAc,CAAA,MACL;AACT,QAAI,CAAC,WAAW,CAAC,iBAAiB,UAAU,KAAK,GAAG;AAClD;AAAA,IAAA;AAGF,UAAM,mBAAmB,cAAc,OAAO,SAAS,aAAa;AAEpE,QAAI,CAAC,WAAW,OAAO,QAAQ,QAAQ,YAAY;AACjD,UAAI,UAAU,SAAS;AACb,gBAAA,MAAM,kBAAkB,GAAG,IAAI;AAAA,MAAA,WAC9B,UAAU,QAAQ;AACnB,gBAAA,KAAK,kBAAkB,GAAG,IAAI;AAAA,MAAA,OACjC;AACG,gBAAA,IAAI,kBAAkB,GAAG,IAAI;AAAA,MAAA;AAEvC;AAAA,IAAA;AAGF,YAAQ,IAAI,UAAU,YAAY,SAAS,OAAO,SAAS,GAAG,IAAI;AAAA,EACpE;AAEA,QAAMA,UAAS,OAAO;AAAA,IACpB,OAAO,IAAI,CAAC,UAAU;AAAA,MACpB;AAAA,MACA,IAAI,CAAC,SAAY,OAAI,MACnB,QAAQ,OAAO,SAAS,IAAI;AAAA,IAC/B,CAAA;AAAA,EACH;AAEO,SAAA;AAAA,IACL,GAAGA;AAAAA,IACH,IAAI,QAAQ;AACH,aAAA;AAAA,IAAA;AAAA,EAEX;AACF;AAEsB,aAAa;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isColumn } from "./column.js";
|
|
2
|
+
import { needsFieldQuoting } from "../client/builders/select-utils.js";
|
|
2
3
|
class FilterExpression {
|
|
3
4
|
constructor(operator, operands) {
|
|
4
5
|
this.operator = operator;
|
|
@@ -110,7 +111,8 @@ class FilterExpression {
|
|
|
110
111
|
}
|
|
111
112
|
_operandToString(operand, useEntityIds, column) {
|
|
112
113
|
if (isColumn(operand)) {
|
|
113
|
-
|
|
114
|
+
const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);
|
|
115
|
+
return needsFieldQuoting(fieldIdentifier) ? `"${fieldIdentifier}"` : fieldIdentifier;
|
|
114
116
|
}
|
|
115
117
|
let value = operand;
|
|
116
118
|
if (column == null ? void 0 : column.inputValidator) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operators.js","sources":["../../../src/orm/operators.ts"],"sourcesContent":["import type { Column } from \"./column\";\nimport { isColumn } from \"./column\";\n\n/**\n * FilterExpression represents a filter condition that can be used in where() clauses.\n * Internal representation of operator expressions that get converted to OData filter syntax.\n */\nexport class FilterExpression {\n constructor(\n public readonly operator: string,\n public readonly operands: (Column | any | FilterExpression)[],\n ) {}\n\n /**\n * Convert this expression to OData filter syntax.\n * @internal Used by QueryBuilder\n */\n toODataFilter(useEntityIds?: boolean): string {\n switch (this.operator) {\n // Comparison operators\n case \"eq\":\n return this._binaryOp(\"eq\", useEntityIds);\n case \"ne\":\n return this._binaryOp(\"ne\", useEntityIds);\n case \"gt\":\n return this._binaryOp(\"gt\", useEntityIds);\n case \"gte\":\n return this._binaryOp(\"ge\", useEntityIds);\n case \"lt\":\n return this._binaryOp(\"lt\", useEntityIds);\n case \"lte\":\n return this._binaryOp(\"le\", useEntityIds);\n case \"in\":\n return this._inOp(useEntityIds);\n case \"notIn\":\n return this._notInOp(useEntityIds);\n\n // String operators\n case \"contains\":\n return this._functionOp(\"contains\", useEntityIds);\n case \"startsWith\":\n return this._functionOp(\"startswith\", useEntityIds);\n case \"endsWith\":\n return this._functionOp(\"endswith\", useEntityIds);\n\n // Null checks\n case \"isNull\":\n return this._isNullOp(useEntityIds);\n case \"isNotNull\":\n return this._isNotNullOp(useEntityIds);\n\n // Logical operators\n case \"and\":\n return this._logicalOp(\"and\", useEntityIds);\n case \"or\":\n return this._logicalOp(\"or\", useEntityIds);\n case \"not\":\n return this._notOp(useEntityIds);\n\n default:\n throw new Error(`Unknown operator: ${this.operator}`);\n }\n }\n\n private _binaryOp(op: string, useEntityIds?: boolean): string {\n const [left, right] = this.operands;\n // For binary ops, the column is typically the first operand and value is the second\n // But we also support column-to-column comparisons, so check both\n const columnForValue =\n isColumn(left) && !isColumn(right)\n ? left\n : isColumn(right) && !isColumn(left)\n ? right\n : undefined;\n const leftStr = this._operandToString(left, useEntityIds, columnForValue);\n const rightStr = this._operandToString(right, useEntityIds, columnForValue);\n return `${leftStr} ${op} ${rightStr}`;\n }\n\n private _functionOp(fnName: string, useEntityIds?: boolean): string {\n const [column, value] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valueStr = this._operandToString(value, useEntityIds, columnInstance);\n return `${fnName}(${columnStr}, ${valueStr})`;\n }\n\n private _inOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valuesStr = (values as any[])\n .map((v) => this._operandToString(v, useEntityIds, columnInstance))\n .join(\", \");\n return `${columnStr} in (${valuesStr})`;\n }\n\n private _notInOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valuesStr = (values as any[])\n .map((v) => this._operandToString(v, useEntityIds, columnInstance))\n .join(\", \");\n return `not (${columnStr} in (${valuesStr}))`;\n }\n\n private _isNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} eq null`;\n }\n\n private _isNotNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} ne null`;\n }\n\n private _logicalOp(op: string, useEntityIds?: boolean): string {\n const expressions = this.operands.map((expr) => {\n if (expr instanceof FilterExpression) {\n const innerExpr = expr.toODataFilter(useEntityIds);\n // Wrap in parens if it's a logical expression to ensure precedence\n if (expr.operator === \"and\" || expr.operator === \"or\") {\n return `(${innerExpr})`;\n }\n return innerExpr;\n }\n throw new Error(\"Logical operators require FilterExpression operands\");\n });\n return expressions.join(` ${op} `);\n }\n\n private _notOp(useEntityIds?: boolean): string {\n const [expr] = this.operands;\n if (expr instanceof FilterExpression) {\n return `not (${expr.toODataFilter(useEntityIds)})`;\n }\n throw new Error(\"NOT operator requires a FilterExpression operand\");\n }\n\n private _operandToString(\n operand: any,\n useEntityIds?: boolean,\n column?: Column<any, any, any, any>,\n ): string {\n if (isColumn(operand)) {\n return operand.getFieldIdentifier(useEntityIds);\n }\n\n // If we have a column with an input validator, apply it to transform the value\n let value = operand;\n if (column?.inputValidator) {\n try {\n const result = column.inputValidator[\"~standard\"].validate(value);\n // Handle async validators (though they shouldn't be async for filters)\n if (result instanceof Promise) {\n // For filters, we can't use async validators, so skip transformation\n // This is a limitation - async validators won't work in filters\n value = operand;\n } else if (\"issues\" in result && result.issues) {\n // Validation failed, use original value\n value = operand;\n } else if (\"value\" in result) {\n // Validation succeeded, use transformed value\n value = result.value;\n }\n } catch (error) {\n // If validation throws, use the original value (will likely cause a query error)\n // This maintains backward compatibility and allows the server to handle validation\n value = operand;\n }\n }\n\n if (typeof value === \"string\") {\n return `'${value.replace(/'/g, \"''\")}'`; // Escape single quotes\n }\n if (value === null || value === undefined) {\n return \"null\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n return String(value);\n }\n}\n\n// ============================================================================\n// Comparison Operators\n// ============================================================================\n\n/**\n * Equal operator - checks if column equals a value or another column.\n *\n * @example\n * eq(users.name, \"John\") // name equals \"John\"\n * eq(users.id, contacts.id_user) // cross-table comparison\n */\nexport function eq<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression;\nexport function eq<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput>,\n): FilterExpression;\nexport function eq(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"eq\", [column, value]);\n}\n\n/**\n * Not equal operator - checks if column does not equal a value or another column.\n *\n * @example\n * ne(users.status, \"inactive\") // status not equal to \"inactive\"\n * ne(users.id, contacts.id_user) // cross-table comparison\n */\nexport function ne<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression;\nexport function ne<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput>,\n): FilterExpression;\nexport function ne(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"ne\", [column, value]);\n}\n\n/**\n * Greater than operator - checks if column is greater than a value.\n *\n * @example\n * gt(users.age, 18) // age greater than 18\n */\nexport function gt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gt\", [column, value]);\n}\n\n/**\n * Greater than or equal operator - checks if column is >= a value.\n *\n * @example\n * gte(users.age, 18) // age >= 18\n */\nexport function gte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gte\", [column, value]);\n}\n\n/**\n * Less than operator - checks if column is less than a value.\n *\n * @example\n * lt(users.age, 65) // age less than 65\n */\nexport function lt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lt\", [column, value]);\n}\n\n/**\n * Less than or equal operator - checks if column is <= a value.\n *\n * @example\n * lte(users.age, 65) // age <= 65\n */\nexport function lte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lte\", [column, value]);\n}\n\n// ============================================================================\n// String Operators\n// ============================================================================\n\n/**\n * Contains operator - checks if a string column contains a substring.\n *\n * @example\n * contains(users.name, \"John\") // name contains \"John\"\n */\nexport function contains<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"contains\", [column, value]);\n}\n\n/**\n * Starts with operator - checks if a string column starts with a prefix.\n *\n * @example\n * startsWith(users.email, \"admin\") // email starts with \"admin\"\n */\nexport function startsWith<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"startsWith\", [column, value]);\n}\n\n/**\n * Ends with operator - checks if a string column ends with a suffix.\n *\n * @example\n * endsWith(users.email, \"@example.com\") // email ends with \"@example.com\"\n */\nexport function endsWith<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"endsWith\", [column, value]);\n}\n\n// ============================================================================\n// Array Operators\n// ============================================================================\n\n/**\n * In array operator - checks if column value is in an array of values.\n *\n * @example\n * inArray(users.status, [\"active\", \"pending\"]) // status is \"active\" or \"pending\"\n */\nexport function inArray<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n values: NoInfer<TInput>[],\n): FilterExpression {\n return new FilterExpression(\"in\", [column, values]);\n}\n\n/**\n * Not in array operator - checks if column value is not in an array of values.\n *\n * @example\n * notInArray(users.status, [\"deleted\", \"banned\"]) // status is neither \"deleted\" nor \"banned\"\n */\nexport function notInArray<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n values: NoInfer<TInput>[],\n): FilterExpression {\n return new FilterExpression(\"notIn\", [column, values]);\n}\n\n// ============================================================================\n// Null Check Operators\n// ============================================================================\n\n/**\n * Is null operator - checks if column value is null.\n *\n * @example\n * isNull(users.deletedAt) // deletedAt is null\n */\nexport function isNull<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n): FilterExpression {\n return new FilterExpression(\"isNull\", [column]);\n}\n\n/**\n * Is not null operator - checks if column value is not null.\n *\n * @example\n * isNotNull(users.email) // email is not null\n */\nexport function isNotNull<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n): FilterExpression {\n return new FilterExpression(\"isNotNull\", [column]);\n}\n\n// ============================================================================\n// Logical Operators\n// ============================================================================\n\n/**\n * AND operator - combines multiple filter expressions with logical AND.\n * All expressions must be true for the record to match.\n *\n * @example\n * and(\n * eq(users.active, true),\n * gt(users.age, 18)\n * ) // active is true AND age > 18\n */\nexport function and(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"AND operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"and\", expressions);\n}\n\n/**\n * OR operator - combines multiple filter expressions with logical OR.\n * At least one expression must be true for the record to match.\n *\n * @example\n * or(\n * eq(users.role, \"admin\"),\n * eq(users.role, \"moderator\")\n * ) // role is \"admin\" OR \"moderator\"\n */\nexport function or(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"OR operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"or\", expressions);\n}\n\n/**\n * NOT operator - negates a filter expression.\n *\n * @example\n * not(eq(users.status, \"deleted\")) // status is NOT \"deleted\"\n */\nexport function not(expression: FilterExpression): FilterExpression {\n return new FilterExpression(\"not\", [expression]);\n}\n\n// ============================================================================\n// OrderBy Operators\n// ============================================================================\n\n/**\n * OrderByExpression represents a sort order specification for a column.\n * Used in orderBy() clauses to provide type-safe sorting with direction.\n */\nexport class OrderByExpression<TableName extends string = string> {\n constructor(\n public readonly column: Column<any, any, TableName>,\n public readonly direction: \"asc\" | \"desc\",\n ) {}\n}\n\n/**\n * Type guard to check if a value is an OrderByExpression instance.\n */\nexport function isOrderByExpression(value: any): value is OrderByExpression {\n return value instanceof OrderByExpression;\n}\n\n/**\n * Ascending order operator - sorts a column in ascending order.\n *\n * @example\n * asc(users.name) // Sort by name ascending\n */\nexport function asc<TableName extends string>(\n column: Column<any, any, TableName>,\n): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"asc\");\n}\n\n/**\n * Descending order operator - sorts a column in descending order.\n *\n * @example\n * desc(users.age) // Sort by age descending\n */\nexport function desc<TableName extends string>(\n column: Column<any, any, TableName>,\n): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"desc\");\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"operators.js","sources":["../../../src/orm/operators.ts"],"sourcesContent":["import type { Column } from \"./column\";\nimport { isColumn } from \"./column\";\nimport { needsFieldQuoting } from \"../client/builders/select-utils\";\n\n/**\n * FilterExpression represents a filter condition that can be used in where() clauses.\n * Internal representation of operator expressions that get converted to OData filter syntax.\n */\nexport class FilterExpression {\n constructor(\n public readonly operator: string,\n public readonly operands: (Column | any | FilterExpression)[],\n ) {}\n\n /**\n * Convert this expression to OData filter syntax.\n * @internal Used by QueryBuilder\n */\n toODataFilter(useEntityIds?: boolean): string {\n switch (this.operator) {\n // Comparison operators\n case \"eq\":\n return this._binaryOp(\"eq\", useEntityIds);\n case \"ne\":\n return this._binaryOp(\"ne\", useEntityIds);\n case \"gt\":\n return this._binaryOp(\"gt\", useEntityIds);\n case \"gte\":\n return this._binaryOp(\"ge\", useEntityIds);\n case \"lt\":\n return this._binaryOp(\"lt\", useEntityIds);\n case \"lte\":\n return this._binaryOp(\"le\", useEntityIds);\n case \"in\":\n return this._inOp(useEntityIds);\n case \"notIn\":\n return this._notInOp(useEntityIds);\n\n // String operators\n case \"contains\":\n return this._functionOp(\"contains\", useEntityIds);\n case \"startsWith\":\n return this._functionOp(\"startswith\", useEntityIds);\n case \"endsWith\":\n return this._functionOp(\"endswith\", useEntityIds);\n\n // Null checks\n case \"isNull\":\n return this._isNullOp(useEntityIds);\n case \"isNotNull\":\n return this._isNotNullOp(useEntityIds);\n\n // Logical operators\n case \"and\":\n return this._logicalOp(\"and\", useEntityIds);\n case \"or\":\n return this._logicalOp(\"or\", useEntityIds);\n case \"not\":\n return this._notOp(useEntityIds);\n\n default:\n throw new Error(`Unknown operator: ${this.operator}`);\n }\n }\n\n private _binaryOp(op: string, useEntityIds?: boolean): string {\n const [left, right] = this.operands;\n // For binary ops, the column is typically the first operand and value is the second\n // But we also support column-to-column comparisons, so check both\n const columnForValue =\n isColumn(left) && !isColumn(right)\n ? left\n : isColumn(right) && !isColumn(left)\n ? right\n : undefined;\n const leftStr = this._operandToString(left, useEntityIds, columnForValue);\n const rightStr = this._operandToString(right, useEntityIds, columnForValue);\n return `${leftStr} ${op} ${rightStr}`;\n }\n\n private _functionOp(fnName: string, useEntityIds?: boolean): string {\n const [column, value] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valueStr = this._operandToString(value, useEntityIds, columnInstance);\n return `${fnName}(${columnStr}, ${valueStr})`;\n }\n\n private _inOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valuesStr = (values as any[])\n .map((v) => this._operandToString(v, useEntityIds, columnInstance))\n .join(\", \");\n return `${columnStr} in (${valuesStr})`;\n }\n\n private _notInOp(useEntityIds?: boolean): string {\n const [column, values] = this.operands;\n const columnInstance = isColumn(column) ? column : undefined;\n const columnStr = this._operandToString(column, useEntityIds);\n const valuesStr = (values as any[])\n .map((v) => this._operandToString(v, useEntityIds, columnInstance))\n .join(\", \");\n return `not (${columnStr} in (${valuesStr}))`;\n }\n\n private _isNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} eq null`;\n }\n\n private _isNotNullOp(useEntityIds?: boolean): string {\n const [column] = this.operands;\n const columnStr = this._operandToString(column, useEntityIds);\n return `${columnStr} ne null`;\n }\n\n private _logicalOp(op: string, useEntityIds?: boolean): string {\n const expressions = this.operands.map((expr) => {\n if (expr instanceof FilterExpression) {\n const innerExpr = expr.toODataFilter(useEntityIds);\n // Wrap in parens if it's a logical expression to ensure precedence\n if (expr.operator === \"and\" || expr.operator === \"or\") {\n return `(${innerExpr})`;\n }\n return innerExpr;\n }\n throw new Error(\"Logical operators require FilterExpression operands\");\n });\n return expressions.join(` ${op} `);\n }\n\n private _notOp(useEntityIds?: boolean): string {\n const [expr] = this.operands;\n if (expr instanceof FilterExpression) {\n return `not (${expr.toODataFilter(useEntityIds)})`;\n }\n throw new Error(\"NOT operator requires a FilterExpression operand\");\n }\n\n private _operandToString(\n operand: any,\n useEntityIds?: boolean,\n column?: Column<any, any, any, any>,\n ): string {\n if (isColumn(operand)) {\n const fieldIdentifier = operand.getFieldIdentifier(useEntityIds);\n // Quote field names in OData filters per FileMaker OData API requirements\n return needsFieldQuoting(fieldIdentifier)\n ? `\"${fieldIdentifier}\"`\n : fieldIdentifier;\n }\n\n // If we have a column with an input validator, apply it to transform the value\n let value = operand;\n if (column?.inputValidator) {\n try {\n const result = column.inputValidator[\"~standard\"].validate(value);\n // Handle async validators (though they shouldn't be async for filters)\n if (result instanceof Promise) {\n // For filters, we can't use async validators, so skip transformation\n // This is a limitation - async validators won't work in filters\n value = operand;\n } else if (\"issues\" in result && result.issues) {\n // Validation failed, use original value\n value = operand;\n } else if (\"value\" in result) {\n // Validation succeeded, use transformed value\n value = result.value;\n }\n } catch (error) {\n // If validation throws, use the original value (will likely cause a query error)\n // This maintains backward compatibility and allows the server to handle validation\n value = operand;\n }\n }\n\n if (typeof value === \"string\") {\n return `'${value.replace(/'/g, \"''\")}'`; // Escape single quotes\n }\n if (value === null || value === undefined) {\n return \"null\";\n }\n if (value instanceof Date) {\n return value.toISOString();\n }\n return String(value);\n }\n}\n\n// ============================================================================\n// Comparison Operators\n// ============================================================================\n\n/**\n * Equal operator - checks if column equals a value or another column.\n *\n * @example\n * eq(users.name, \"John\") // name equals \"John\"\n * eq(users.id, contacts.id_user) // cross-table comparison\n */\nexport function eq<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression;\nexport function eq<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput>,\n): FilterExpression;\nexport function eq(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"eq\", [column, value]);\n}\n\n/**\n * Not equal operator - checks if column does not equal a value or another column.\n *\n * @example\n * ne(users.status, \"inactive\") // status not equal to \"inactive\"\n * ne(users.id, contacts.id_user) // cross-table comparison\n */\nexport function ne<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression;\nexport function ne<TOutput, TInput>(\n column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput>,\n): FilterExpression;\nexport function ne(column: Column, value: any): FilterExpression {\n return new FilterExpression(\"ne\", [column, value]);\n}\n\n/**\n * Greater than operator - checks if column is greater than a value.\n *\n * @example\n * gt(users.age, 18) // age greater than 18\n */\nexport function gt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gt\", [column, value]);\n}\n\n/**\n * Greater than or equal operator - checks if column is >= a value.\n *\n * @example\n * gte(users.age, 18) // age >= 18\n */\nexport function gte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"gte\", [column, value]);\n}\n\n/**\n * Less than operator - checks if column is less than a value.\n *\n * @example\n * lt(users.age, 65) // age less than 65\n */\nexport function lt<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lt\", [column, value]);\n}\n\n/**\n * Less than or equal operator - checks if column is <= a value.\n *\n * @example\n * lte(users.age, 65) // age <= 65\n */\nexport function lte<TOutput extends number | string | Date | null, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"lte\", [column, value]);\n}\n\n// ============================================================================\n// String Operators\n// ============================================================================\n\n/**\n * Contains operator - checks if a string column contains a substring.\n *\n * @example\n * contains(users.name, \"John\") // name contains \"John\"\n */\nexport function contains<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"contains\", [column, value]);\n}\n\n/**\n * Starts with operator - checks if a string column starts with a prefix.\n *\n * @example\n * startsWith(users.email, \"admin\") // email starts with \"admin\"\n */\nexport function startsWith<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"startsWith\", [column, value]);\n}\n\n/**\n * Ends with operator - checks if a string column ends with a suffix.\n *\n * @example\n * endsWith(users.email, \"@example.com\") // email ends with \"@example.com\"\n */\nexport function endsWith<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n value: NoInfer<TInput>,\n): FilterExpression {\n return new FilterExpression(\"endsWith\", [column, value]);\n}\n\n// ============================================================================\n// Array Operators\n// ============================================================================\n\n/**\n * In array operator - checks if column value is in an array of values.\n *\n * @example\n * inArray(users.status, [\"active\", \"pending\"]) // status is \"active\" or \"pending\"\n */\nexport function inArray<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n values: NoInfer<TInput>[],\n): FilterExpression {\n return new FilterExpression(\"in\", [column, values]);\n}\n\n/**\n * Not in array operator - checks if column value is not in an array of values.\n *\n * @example\n * notInArray(users.status, [\"deleted\", \"banned\"]) // status is neither \"deleted\" nor \"banned\"\n */\nexport function notInArray<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n values: NoInfer<TInput>[],\n): FilterExpression {\n return new FilterExpression(\"notIn\", [column, values]);\n}\n\n// ============================================================================\n// Null Check Operators\n// ============================================================================\n\n/**\n * Is null operator - checks if column value is null.\n *\n * @example\n * isNull(users.deletedAt) // deletedAt is null\n */\nexport function isNull<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n): FilterExpression {\n return new FilterExpression(\"isNull\", [column]);\n}\n\n/**\n * Is not null operator - checks if column value is not null.\n *\n * @example\n * isNotNull(users.email) // email is not null\n */\nexport function isNotNull<TOutput, TInput>(\n column: Column<TOutput, TInput>,\n): FilterExpression {\n return new FilterExpression(\"isNotNull\", [column]);\n}\n\n// ============================================================================\n// Logical Operators\n// ============================================================================\n\n/**\n * AND operator - combines multiple filter expressions with logical AND.\n * All expressions must be true for the record to match.\n *\n * @example\n * and(\n * eq(users.active, true),\n * gt(users.age, 18)\n * ) // active is true AND age > 18\n */\nexport function and(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"AND operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"and\", expressions);\n}\n\n/**\n * OR operator - combines multiple filter expressions with logical OR.\n * At least one expression must be true for the record to match.\n *\n * @example\n * or(\n * eq(users.role, \"admin\"),\n * eq(users.role, \"moderator\")\n * ) // role is \"admin\" OR \"moderator\"\n */\nexport function or(...expressions: FilterExpression[]): FilterExpression {\n if (expressions.length === 0) {\n throw new Error(\"OR operator requires at least one expression\");\n }\n if (expressions.length === 1 && expressions[0] !== undefined) {\n return expressions[0];\n }\n return new FilterExpression(\"or\", expressions);\n}\n\n/**\n * NOT operator - negates a filter expression.\n *\n * @example\n * not(eq(users.status, \"deleted\")) // status is NOT \"deleted\"\n */\nexport function not(expression: FilterExpression): FilterExpression {\n return new FilterExpression(\"not\", [expression]);\n}\n\n// ============================================================================\n// OrderBy Operators\n// ============================================================================\n\n/**\n * OrderByExpression represents a sort order specification for a column.\n * Used in orderBy() clauses to provide type-safe sorting with direction.\n */\nexport class OrderByExpression<TableName extends string = string> {\n constructor(\n public readonly column: Column<any, any, TableName>,\n public readonly direction: \"asc\" | \"desc\",\n ) {}\n}\n\n/**\n * Type guard to check if a value is an OrderByExpression instance.\n */\nexport function isOrderByExpression(value: any): value is OrderByExpression {\n return value instanceof OrderByExpression;\n}\n\n/**\n * Ascending order operator - sorts a column in ascending order.\n *\n * @example\n * asc(users.name) // Sort by name ascending\n */\nexport function asc<TableName extends string>(\n column: Column<any, any, TableName>,\n): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"asc\");\n}\n\n/**\n * Descending order operator - sorts a column in descending order.\n *\n * @example\n * desc(users.age) // Sort by age descending\n */\nexport function desc<TableName extends string>(\n column: Column<any, any, TableName>,\n): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"desc\");\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/orm/table.d.ts
CHANGED
|
@@ -338,5 +338,5 @@ export declare function getTableId<T extends FMTable<any, any>>(table: T): strin
|
|
|
338
338
|
* @param table - FMTable instance
|
|
339
339
|
* @returns Object with all columns from the table
|
|
340
340
|
*/
|
|
341
|
-
export declare function getTableColumns<T extends
|
|
341
|
+
export declare function getTableColumns<T extends FMTable<any, any>>(table: T): ColumnMap<T[typeof FMTableFields], ExtractTableName<T>>;
|
|
342
342
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sources":["../../../src/orm/table.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FieldBuilder, type ContainerDbType } from \"./field-builders\";\nimport type { FieldBuilder as FieldBuilderType } from \"./field-builders\";\nimport { Column, createColumn } from \"./column\";\nimport { z } from \"zod/v4\";\n\n/**\n * Extract the output type from a FieldBuilder.\n * This is what you get when reading from the database.\n *\n * This type extracts the TOutput type parameter, which is set by readValidator()\n * and represents the transformed/validated output type.\n */\nexport type InferFieldOutput<F> =\n F extends FieldBuilder<infer TOutput, any, any, any> ? TOutput : never;\n\n/**\n * Extract the input type from a FieldBuilder.\n * This is what you pass when writing to the database.\n *\n * This type extracts the TInput type parameter, which is set by writeValidator()\n * and represents the transformed/validated input type.\n */\ntype InferFieldInput<F> =\n F extends FieldBuilder<any, infer TInput, any, any> ? TInput : never;\n\n/**\n * Build a schema type from field builders (output/read types).\n */\ntype InferSchemaFromFields<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in keyof TFields]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Build an input schema type from field builders (input/write types).\n * Used for insert and update operations.\n */\ntype InferInputSchemaFromFields<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in keyof TFields]: InferFieldInput<TFields[K]>;\n};\n\n/**\n * Check if a field is a container field by inspecting its TDbType.\n * Container fields have a branded TDbType that extends ContainerDbType.\n */\ntype IsContainerField<F> =\n F extends FieldBuilder<any, any, infer TDbType, any>\n ? NonNullable<TDbType> extends ContainerDbType\n ? true\n : false\n : false;\n\n/**\n * Extract only selectable (non-container) field keys from a fields record.\n * Container fields are excluded because they cannot be selected via $select in FileMaker OData.\n */\ntype SelectableFieldKeys<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in keyof TFields]: IsContainerField<TFields[K]> extends true ? never : K;\n}[keyof TFields];\n\n/**\n * Build a schema type excluding container fields (for query return types).\n * This is used to ensure container fields don't appear in the return type\n * when using defaultSelect: \"schema\" or \"all\".\n */\ntype InferSelectableSchemaFromFields<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in SelectableFieldKeys<TFields>]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Internal Symbols for table properties (hidden from IDE autocomplete).\n * These are used to store internal configuration that shouldn't be visible\n * when users access table columns.\n * @internal - Not exported from public API, only accessible via FMTable.Symbol\n */\nconst FMTableName = Symbol.for(\"fmodata:FMTableName\");\nconst FMTableEntityId = Symbol.for(\"fmodata:FMTableEntityId\");\nconst FMTableSchema = Symbol.for(\"fmodata:FMTableSchema\");\nconst FMTableFields = Symbol.for(\"fmodata:FMTableFields\");\nconst FMTableNavigationPaths = Symbol.for(\"fmodata:FMTableNavigationPaths\");\nconst FMTableDefaultSelect = Symbol.for(\"fmodata:FMTableDefaultSelect\");\nconst FMTableBaseTableConfig = Symbol.for(\"fmodata:FMTableBaseTableConfig\");\nconst FMTableUseEntityIds = Symbol.for(\"fmodata:FMTableUseEntityIds\");\n\n/**\n * Base table class with Symbol-based internal properties.\n * This follows the Drizzle ORM pattern where internal configuration\n * is stored via Symbols, keeping it hidden from IDE autocomplete.\n */\nexport class FMTable<\n TFields extends Record<string, FieldBuilder<any, any, any, any>> = any,\n TName extends string = string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> {\n /**\n * Internal Symbols for accessing table metadata.\n * @internal - Not intended for public use. Access table properties via columns instead.\n */\n static readonly Symbol = {\n Name: FMTableName,\n EntityId: FMTableEntityId,\n UseEntityIds: FMTableUseEntityIds,\n Schema: FMTableSchema,\n Fields: FMTableFields,\n NavigationPaths: FMTableNavigationPaths,\n DefaultSelect: FMTableDefaultSelect,\n BaseTableConfig: FMTableBaseTableConfig,\n };\n\n /** @internal */\n [FMTableName]: TName;\n\n /** @internal */\n [FMTableEntityId]?: `FMTID:${string}`;\n\n /** @internal */\n [FMTableUseEntityIds]?: boolean;\n\n /** @internal */\n [FMTableSchema]: StandardSchemaV1<any, InferSchemaFromFields<TFields>>;\n\n /** @internal */\n [FMTableFields]: TFields;\n\n /** @internal */\n [FMTableNavigationPaths]: TNavigationPaths;\n\n /** @internal */\n [FMTableDefaultSelect]:\n | \"all\"\n | \"schema\"\n | Record<string, Column<any, any, TName>>;\n\n /** @internal */\n [FMTableBaseTableConfig]: {\n schema: Record<keyof TFields, StandardSchemaV1>;\n inputSchema?: Record<keyof TFields, StandardSchemaV1>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n\n constructor(config: {\n name: TName;\n entityId?: `FMTID:${string}`;\n useEntityIds?: boolean;\n schema: StandardSchemaV1<any, InferSchemaFromFields<TFields>>;\n fields: TFields;\n navigationPaths: TNavigationPaths;\n defaultSelect: \"all\" | \"schema\" | Record<string, Column<any, any, TName>>;\n baseTableConfig: {\n schema: Record<keyof TFields, StandardSchemaV1>;\n inputSchema?: Record<keyof TFields, StandardSchemaV1>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n }) {\n this[FMTableName] = config.name;\n this[FMTableEntityId] = config.entityId;\n this[FMTableUseEntityIds] = config.useEntityIds;\n this[FMTableSchema] = config.schema;\n this[FMTableFields] = config.fields;\n this[FMTableNavigationPaths] = config.navigationPaths;\n this[FMTableDefaultSelect] = config.defaultSelect;\n this[FMTableBaseTableConfig] = config.baseTableConfig;\n }\n}\n\n/**\n * Type helper to extract the column map from fields.\n * Table name is baked into each column type for validation.\n * Container fields are marked with IsContainer=true.\n * Columns include both output type (for reading) and input type (for writing/filtering).\n */\nexport type ColumnMap<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = {\n [K in keyof TFields]: Column<\n InferFieldOutput<TFields[K]>,\n InferFieldInput<TFields[K]>,\n TName,\n IsContainerField<TFields[K]>\n >;\n};\n\n/**\n * Extract only selectable (non-container) columns from a table.\n * This is used to prevent selecting container fields in queries.\n */\nexport type SelectableColumnMap<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = {\n [K in SelectableFieldKeys<TFields>]: Column<\n InferFieldOutput<TFields[K]>,\n InferFieldInput<TFields[K]>,\n TName,\n false\n >;\n};\n\n/**\n * Validates that a select object doesn't contain container field columns.\n * Returns never if any container fields are found, otherwise returns the original type.\n */\nexport type ValidateNoContainerFields<\n TSelect extends Record<string, Column<any, any, any, any>>,\n> = {\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true>\n ? never\n : TSelect[K];\n} extends TSelect\n ? TSelect\n : {\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true>\n ? \"❌ Container fields cannot be selected. Use .getSingleField() instead.\"\n : TSelect[K];\n };\n\n/**\n * Extract the keys from a defaultSelect function's return type.\n * Used to infer which fields are selected by default for type narrowing.\n */\ntype ExtractDefaultSelectKeys<\n TDefaultSelect,\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = TDefaultSelect extends (columns: ColumnMap<TFields, TName>) => infer R\n ? keyof R\n : TDefaultSelect extends \"schema\"\n ? keyof TFields\n : keyof TFields; // \"all\" defaults to all keys\n\n/**\n * Complete table type with both metadata (via Symbols) and column accessors.\n * This is the return type of fmTableOccurrence - users see columns directly,\n * but internal config is hidden via Symbols.\n */\nexport type FMTableWithColumns<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> = FMTable<TFields, TName, TNavigationPaths> & ColumnMap<TFields, TName>;\n\n/**\n * Options for fmTableOccurrence function.\n * Provides autocomplete-friendly typing while preserving inference for navigationPaths.\n */\nexport interface FMTableOccurrenceOptions<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> {\n /** The entity ID (FMTID) for this table occurrence */\n entityId?: `FMTID:${string}`;\n\n /**\n * Default select behavior:\n * - \"all\": Select all fields (including related tables)\n * - \"schema\": Select only schema-defined fields (default)\n * - function: Custom selection from columns\n */\n defaultSelect?:\n | \"all\"\n | \"schema\"\n | ((\n columns: ColumnMap<TFields, TName>,\n ) => Record<string, Column<any, any, TName>>);\n\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: readonly string[];\n\n /** Whether to use entity IDs (FMTID/FMFID) instead of names in queries */\n useEntityIds?: boolean;\n}\n\n/**\n * Create a table occurrence with field builders.\n * This is the main API for defining tables in the new ORM style.\n *\n * @example\n * const users = fmTableOccurrence(\"users\", {\n * id: textField().primaryKey().entityId(\"FMFID:1\"),\n * name: textField().notNull().entityId(\"FMFID:6\"),\n * active: numberField()\n * .outputValidator(z.coerce.boolean())\n * .inputValidator(z.boolean().transform(v => v ? 1 : 0))\n * .entityId(\"FMFID:7\"),\n * }, {\n * entityId: \"FMTID:100\",\n * defaultSelect: \"schema\",\n * navigationPaths: [\"contacts\"],\n * });\n *\n * // Access columns\n * users.id // Column<string, \"id\">\n * users.name // Column<string, \"name\">\n *\n * // Use in queries\n * db.from(users).select(\"id\", \"name\").where(eq(users.active, true))\n */\nexport function fmTableOccurrence<\n const TName extends string,\n const TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n const TNavPaths extends readonly string[] = readonly [],\n>(\n name: TName,\n fields: TFields,\n options?: FMTableOccurrenceOptions<TFields, TName> & {\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: TNavPaths;\n },\n): FMTableWithColumns<TFields, TName, TNavPaths> {\n // Extract configuration from field builders\n const fieldConfigs = Object.entries(fields).map(([fieldName, builder]) => ({\n fieldName,\n config: (builder as any)._getConfig(),\n }));\n\n // Find primary key field\n const primaryKeyField = fieldConfigs.find((f) => f.config.primaryKey);\n const idField = primaryKeyField?.fieldName;\n\n // Collect required fields (notNull fields)\n const required = fieldConfigs\n .filter((f) => f.config.notNull)\n .map((f) => f.fieldName);\n\n // Collect read-only fields\n const readOnly = fieldConfigs\n .filter((f) => f.config.readOnly)\n .map((f) => f.fieldName);\n\n // Collect container fields (cannot be selected via $select)\n const containerFields = fieldConfigs\n .filter((f) => f.config.fieldType === \"container\")\n .map((f) => f.fieldName);\n\n // Collect entity IDs\n const fmfIds: Record<string, `FMFID:${string}`> = {};\n for (const { fieldName, config } of fieldConfigs) {\n if (config.entityId) {\n fmfIds[fieldName] = config.entityId;\n }\n }\n\n // Build Zod schema from field builders (output/read validators)\n const zodSchema: Record<string, StandardSchemaV1> = {};\n // Build input schema from field builders (input/write validators)\n const inputSchema: Record<string, StandardSchemaV1> = {};\n\n for (const { fieldName, config } of fieldConfigs) {\n // Use outputValidator if provided, otherwise create a basic validator\n if (config.outputValidator) {\n zodSchema[fieldName] = config.outputValidator;\n } else {\n // Create a default validator based on field type and nullability\n let validator: any;\n switch (config.fieldType) {\n case \"text\":\n case \"date\":\n case \"time\":\n case \"timestamp\":\n case \"container\":\n case \"calculated\":\n validator = z.string();\n break;\n case \"number\":\n validator = z.number();\n break;\n default:\n validator = z.unknown();\n }\n\n // Add nullability if not marked as notNull\n if (!config.notNull) {\n validator = validator.nullable();\n }\n\n zodSchema[fieldName] = validator;\n }\n\n // Store inputValidator if provided (for write operations)\n if (config.inputValidator) {\n inputSchema[fieldName] = config.inputValidator;\n }\n }\n\n // Create a schema validator for the entire table\n const tableSchema = z.object(zodSchema) as unknown as StandardSchemaV1<\n any,\n InferSchemaFromFields<TFields>\n >;\n\n // Build BaseTable-compatible config\n const baseTableConfig = {\n schema: zodSchema as Record<keyof TFields, StandardSchemaV1>,\n inputSchema: (Object.keys(inputSchema).length > 0\n ? inputSchema\n : undefined) as Record<keyof TFields, StandardSchemaV1> | undefined,\n idField: idField as keyof TFields | undefined,\n required: required as readonly (keyof TFields)[],\n readOnly: readOnly as readonly (keyof TFields)[],\n containerFields: containerFields as readonly (keyof TFields)[],\n fmfIds: (Object.keys(fmfIds).length > 0 ? fmfIds : undefined) as\n | Record<keyof TFields, `FMFID:${string}`>\n | undefined,\n };\n\n // Create column instances\n const columns = {} as ColumnMap<TFields, TName>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n const config = (builder as any)._getConfig();\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: config.entityId,\n tableName: name,\n tableEntityId: options?.entityId,\n inputValidator: config.inputValidator,\n });\n }\n\n // Resolve defaultSelect: if it's a function, call it with columns; otherwise use as-is\n const defaultSelectOption = options?.defaultSelect ?? \"schema\";\n const resolvedDefaultSelect:\n | \"all\"\n | \"schema\"\n | Record<string, Column<any, any, TName>> =\n typeof defaultSelectOption === \"function\"\n ? defaultSelectOption(columns as ColumnMap<TFields, TName>)\n : defaultSelectOption;\n\n // Create the FMTable instance with Symbol-based internal properties\n const navigationPaths = (options?.navigationPaths ?? []) as TNavPaths;\n const table = new FMTable<TFields, TName, TNavPaths>({\n name,\n entityId: options?.entityId,\n useEntityIds: options?.useEntityIds,\n schema: tableSchema,\n fields,\n navigationPaths,\n defaultSelect: resolvedDefaultSelect,\n baseTableConfig,\n });\n\n // Assign columns to the table instance (making them accessible directly)\n Object.assign(table, columns);\n\n return table as FMTableWithColumns<TFields, TName, TNavPaths>;\n}\n\n// /**\n// * Type guard to check if a value is a TableOccurrence or FMTable.\n// * Supports both Symbol-based (new) and underscore-prefixed (legacy) formats.\n// */\n// function isTableOccurrence(value: any): value is TableOccurrence {\n// if (!value || typeof value !== \"object\") {\n// return false;\n// }\n\n// // Check for Symbol-based format (new FMTable class)\n// if (\n// FMTableName in value &&\n// FMTableSchema in value &&\n// FMTableFields in value\n// ) {\n// return typeof value[FMTableName] === \"string\";\n// }\n\n// // Check for underscore-prefixed format (legacy interface)\n// if (\"_name\" in value && \"_schema\" in value && \"_fields\" in value) {\n// return typeof value._name === \"string\";\n// }\n\n// return false;\n// }\n\n/**\n * Helper to extract the schema type from a TableOccurrence or FMTable.\n */\nexport type InferTableSchema<T> =\n T extends FMTable<infer TFields, any>\n ? InferSchemaFromFields<TFields>\n : never;\n\n/**\n * Extract the schema type from an FMTable instance.\n * This is used to infer the schema from table objects passed to db.from(), expand(), etc.\n */\nexport type InferSchemaOutputFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? InferSchemaFromFields<TFields>\n : never;\n\n/**\n * Extract the input schema type from an FMTable instance.\n * This is used for insert and update operations where we need write types.\n */\nexport type InferInputSchemaFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? InferInputSchemaFromFields<TFields>\n : never;\n\n/**\n * Helper type to check if a FieldBuilder's input type excludes null and undefined.\n * This checks the TInput type parameter, which preserves nullability from notNull().\n */\ntype FieldInputExcludesNullish<F> =\n F extends FieldBuilder<any, infer TInput, any>\n ? null extends TInput\n ? false\n : undefined extends TInput\n ? false\n : true\n : false;\n\n/**\n * Check if a FieldBuilder is readOnly at the type level\n */\ntype IsFieldReadOnly<F> =\n F extends FieldBuilderType<any, any, any, infer ReadOnly>\n ? ReadOnly extends true\n ? true\n : false\n : false;\n\n/**\n * Compute insert data type from FMTable, making notNull fields required.\n * Fields are required if their FieldBuilder's TInput type excludes null/undefined.\n * All other fields are optional (can be omitted).\n * readOnly fields are excluded (including primaryKey/idField since they're automatically readOnly).\n */\nexport type InsertDataFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? K\n : never]: InferFieldInput<TFields[K]>;\n } & {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? never\n : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Compute update data type from FMTable.\n * All fields are optional, but readOnly fields are excluded (including primaryKey/idField).\n */\nexport type UpdateDataFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Extract the table name type from an FMTable.\n * This is a workaround since we can't directly index Symbols in types.\n */\nexport type ExtractTableName<T extends FMTable<any, any>> =\n T extends FMTable<any, infer Name> ? Name : never;\n\n/**\n * Validates that a target table's name matches one of the source table's navigationPaths.\n * Used to ensure type-safe expand/navigate operations.\n */\nexport type ValidExpandTarget<\n SourceTable extends FMTable<any, any, any> | undefined,\n TargetTable extends FMTable<any, any, any>,\n> =\n SourceTable extends FMTable<any, any, infer SourceNavPaths>\n ? ExtractTableName<TargetTable> extends SourceNavPaths[number]\n ? TargetTable\n : never\n : TargetTable;\n\n// ============================================================================\n// Helper Functions for Accessing FMTable Internal Properties\n// ============================================================================\n\n/**\n * Get the table name from an FMTable instance.\n * @param table - FMTable instance\n * @returns The table name\n */\nexport function getTableName<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableName];\n}\n\n/**\n * Get the entity ID (FMTID) from an FMTable instance.\n * @param table - FMTable instance\n * @returns The entity ID or undefined if not using entity IDs\n */\nexport function getTableEntityId<T extends FMTable<any, any>>(\n table: T,\n): string | undefined {\n return table[FMTableEntityId];\n}\n\n/**\n * Get the schema validator from an FMTable instance.\n * @param table - FMTable instance\n * @returns The StandardSchemaV1 validator\n */\nexport function getTableSchema<T extends FMTable<any, any>>(\n table: T,\n): StandardSchemaV1 {\n return table[FMTableSchema];\n}\n\n/**\n * Get the fields from an FMTable instance.\n * @param table - FMTable instance\n * @returns The fields record\n */\nexport function getTableFields<T extends FMTable<any, any>>(table: T) {\n return table[FMTableFields];\n}\n\n/**\n * Get the navigation paths from an FMTable instance.\n * @param table - FMTable instance\n * @returns Array of navigation path names\n */\nexport function getNavigationPaths<T extends FMTable<any, any>>(\n table: T,\n): readonly string[] {\n return table[FMTableNavigationPaths];\n}\n\n/**\n * Get the default select configuration from an FMTable instance.\n * @param table - FMTable instance\n * @returns Default select configuration\n */\nexport function getDefaultSelect<T extends FMTable<any, any>>(table: T) {\n return table[FMTableDefaultSelect];\n}\n\n/**\n * Get the base table configuration from an FMTable instance.\n * This provides access to schema, idField, required fields, readOnly fields, and field IDs.\n * @param table - FMTable instance\n * @returns Base table configuration object\n */\nexport function getBaseTableConfig<T extends FMTable<any, any>>(table: T) {\n return table[FMTableBaseTableConfig];\n}\n\n/**\n * Check if an FMTable instance is using entity IDs (both FMTID and FMFIDs).\n * @param table - FMTable instance\n * @returns True if using entity IDs, false otherwise\n */\nexport function isUsingEntityIds<T extends FMTable<any, any>>(\n table: T,\n): boolean {\n return (\n table[FMTableEntityId] !== undefined &&\n table[FMTableBaseTableConfig].fmfIds !== undefined\n );\n}\n\n/**\n * Get the field ID (FMFID) for a given field name, or the field name itself if not using IDs.\n * @param table - FMTable instance\n * @param fieldName - Field name to get the ID for\n * @returns The FMFID string or the original field name\n */\nexport function getFieldId<T extends FMTable<any, any>>(\n table: T,\n fieldName: string,\n): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds && fieldName in config.fmfIds) {\n const fieldId = config.fmfIds[fieldName];\n if (fieldId) {\n return fieldId;\n }\n }\n return fieldName;\n}\n\n/**\n * Get the field name for a given field ID (FMFID), or the ID itself if not found.\n * @param table - FMTable instance\n * @param fieldId - The FMFID to get the field name for\n * @returns The field name or the original ID\n */\nexport function getFieldName<T extends FMTable<any, any>>(\n table: T,\n fieldId: string,\n): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds) {\n for (const [fieldName, fmfId] of Object.entries(config.fmfIds)) {\n if (fmfId === fieldId) {\n return fieldName;\n }\n }\n }\n return fieldId;\n}\n/**\n * Get the table ID (FMTID or name) from an FMTable instance.\n * Returns the FMTID if available, otherwise returns the table name.\n * @param table - FMTable instance\n * @returns The FMTID string or the table name\n */\nexport function getTableId<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableEntityId] ?? table[FMTableName];\n}\n\n/**\n * Get all columns from a table as an object.\n * Useful for selecting all fields except some using destructuring.\n *\n * @example\n * const { password, ...cols } = getTableColumns(users)\n * db.from(users).list().select(cols)\n *\n * @param table - FMTable instance\n * @returns Object with all columns from the table\n */\nexport function getTableColumns<T extends FMTableWithColumns<any, any>>(\n table: T,\n): ColumnMap<T[typeof FMTableFields], ExtractTableName<T>> {\n const fields = table[FMTableFields];\n const tableName = table[FMTableName];\n const tableEntityId = table[FMTableEntityId];\n const baseConfig = table[FMTableBaseTableConfig];\n\n const columns = {} as ColumnMap<T[typeof FMTableFields], ExtractTableName<T>>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n const config = (builder as any)._getConfig();\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: baseConfig.fmfIds?.[fieldName],\n tableName: tableName,\n tableEntityId: tableEntityId,\n inputValidator: config.inputValidator,\n });\n }\n\n return columns;\n}\n"],"names":["_a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"table.js","sources":["../../../src/orm/table.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { FieldBuilder, type ContainerDbType } from \"./field-builders\";\nimport type { FieldBuilder as FieldBuilderType } from \"./field-builders\";\nimport { Column, createColumn } from \"./column\";\nimport { z } from \"zod/v4\";\n\n/**\n * Extract the output type from a FieldBuilder.\n * This is what you get when reading from the database.\n *\n * This type extracts the TOutput type parameter, which is set by readValidator()\n * and represents the transformed/validated output type.\n */\nexport type InferFieldOutput<F> =\n F extends FieldBuilder<infer TOutput, any, any, any> ? TOutput : never;\n\n/**\n * Extract the input type from a FieldBuilder.\n * This is what you pass when writing to the database.\n *\n * This type extracts the TInput type parameter, which is set by writeValidator()\n * and represents the transformed/validated input type.\n */\ntype InferFieldInput<F> =\n F extends FieldBuilder<any, infer TInput, any, any> ? TInput : never;\n\n/**\n * Build a schema type from field builders (output/read types).\n */\ntype InferSchemaFromFields<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in keyof TFields]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Build an input schema type from field builders (input/write types).\n * Used for insert and update operations.\n */\ntype InferInputSchemaFromFields<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in keyof TFields]: InferFieldInput<TFields[K]>;\n};\n\n/**\n * Check if a field is a container field by inspecting its TDbType.\n * Container fields have a branded TDbType that extends ContainerDbType.\n */\ntype IsContainerField<F> =\n F extends FieldBuilder<any, any, infer TDbType, any>\n ? NonNullable<TDbType> extends ContainerDbType\n ? true\n : false\n : false;\n\n/**\n * Extract only selectable (non-container) field keys from a fields record.\n * Container fields are excluded because they cannot be selected via $select in FileMaker OData.\n */\ntype SelectableFieldKeys<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in keyof TFields]: IsContainerField<TFields[K]> extends true ? never : K;\n}[keyof TFields];\n\n/**\n * Build a schema type excluding container fields (for query return types).\n * This is used to ensure container fields don't appear in the return type\n * when using defaultSelect: \"schema\" or \"all\".\n */\ntype InferSelectableSchemaFromFields<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n> = {\n [K in SelectableFieldKeys<TFields>]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Internal Symbols for table properties (hidden from IDE autocomplete).\n * These are used to store internal configuration that shouldn't be visible\n * when users access table columns.\n * @internal - Not exported from public API, only accessible via FMTable.Symbol\n */\nconst FMTableName = Symbol.for(\"fmodata:FMTableName\");\nconst FMTableEntityId = Symbol.for(\"fmodata:FMTableEntityId\");\nconst FMTableSchema = Symbol.for(\"fmodata:FMTableSchema\");\nconst FMTableFields = Symbol.for(\"fmodata:FMTableFields\");\nconst FMTableNavigationPaths = Symbol.for(\"fmodata:FMTableNavigationPaths\");\nconst FMTableDefaultSelect = Symbol.for(\"fmodata:FMTableDefaultSelect\");\nconst FMTableBaseTableConfig = Symbol.for(\"fmodata:FMTableBaseTableConfig\");\nconst FMTableUseEntityIds = Symbol.for(\"fmodata:FMTableUseEntityIds\");\n\n/**\n * Base table class with Symbol-based internal properties.\n * This follows the Drizzle ORM pattern where internal configuration\n * is stored via Symbols, keeping it hidden from IDE autocomplete.\n */\nexport class FMTable<\n TFields extends Record<string, FieldBuilder<any, any, any, any>> = any,\n TName extends string = string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> {\n /**\n * Internal Symbols for accessing table metadata.\n * @internal - Not intended for public use. Access table properties via columns instead.\n */\n static readonly Symbol = {\n Name: FMTableName,\n EntityId: FMTableEntityId,\n UseEntityIds: FMTableUseEntityIds,\n Schema: FMTableSchema,\n Fields: FMTableFields,\n NavigationPaths: FMTableNavigationPaths,\n DefaultSelect: FMTableDefaultSelect,\n BaseTableConfig: FMTableBaseTableConfig,\n };\n\n /** @internal */\n [FMTableName]: TName;\n\n /** @internal */\n [FMTableEntityId]?: `FMTID:${string}`;\n\n /** @internal */\n [FMTableUseEntityIds]?: boolean;\n\n /** @internal */\n [FMTableSchema]: StandardSchemaV1<any, InferSchemaFromFields<TFields>>;\n\n /** @internal */\n [FMTableFields]: TFields;\n\n /** @internal */\n [FMTableNavigationPaths]: TNavigationPaths;\n\n /** @internal */\n [FMTableDefaultSelect]:\n | \"all\"\n | \"schema\"\n | Record<string, Column<any, any, TName>>;\n\n /** @internal */\n [FMTableBaseTableConfig]: {\n schema: Record<keyof TFields, StandardSchemaV1>;\n inputSchema?: Record<keyof TFields, StandardSchemaV1>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n\n constructor(config: {\n name: TName;\n entityId?: `FMTID:${string}`;\n useEntityIds?: boolean;\n schema: StandardSchemaV1<any, InferSchemaFromFields<TFields>>;\n fields: TFields;\n navigationPaths: TNavigationPaths;\n defaultSelect: \"all\" | \"schema\" | Record<string, Column<any, any, TName>>;\n baseTableConfig: {\n schema: Record<keyof TFields, StandardSchemaV1>;\n inputSchema?: Record<keyof TFields, StandardSchemaV1>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n }) {\n this[FMTableName] = config.name;\n this[FMTableEntityId] = config.entityId;\n this[FMTableUseEntityIds] = config.useEntityIds;\n this[FMTableSchema] = config.schema;\n this[FMTableFields] = config.fields;\n this[FMTableNavigationPaths] = config.navigationPaths;\n this[FMTableDefaultSelect] = config.defaultSelect;\n this[FMTableBaseTableConfig] = config.baseTableConfig;\n }\n}\n\n/**\n * Type helper to extract the column map from fields.\n * Table name is baked into each column type for validation.\n * Container fields are marked with IsContainer=true.\n * Columns include both output type (for reading) and input type (for writing/filtering).\n */\nexport type ColumnMap<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = {\n [K in keyof TFields]: Column<\n InferFieldOutput<TFields[K]>,\n InferFieldInput<TFields[K]>,\n TName,\n IsContainerField<TFields[K]>\n >;\n};\n\n/**\n * Extract only selectable (non-container) columns from a table.\n * This is used to prevent selecting container fields in queries.\n */\nexport type SelectableColumnMap<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = {\n [K in SelectableFieldKeys<TFields>]: Column<\n InferFieldOutput<TFields[K]>,\n InferFieldInput<TFields[K]>,\n TName,\n false\n >;\n};\n\n/**\n * Validates that a select object doesn't contain container field columns.\n * Returns never if any container fields are found, otherwise returns the original type.\n */\nexport type ValidateNoContainerFields<\n TSelect extends Record<string, Column<any, any, any, any>>,\n> = {\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true>\n ? never\n : TSelect[K];\n} extends TSelect\n ? TSelect\n : {\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true>\n ? \"❌ Container fields cannot be selected. Use .getSingleField() instead.\"\n : TSelect[K];\n };\n\n/**\n * Extract the keys from a defaultSelect function's return type.\n * Used to infer which fields are selected by default for type narrowing.\n */\ntype ExtractDefaultSelectKeys<\n TDefaultSelect,\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = TDefaultSelect extends (columns: ColumnMap<TFields, TName>) => infer R\n ? keyof R\n : TDefaultSelect extends \"schema\"\n ? keyof TFields\n : keyof TFields; // \"all\" defaults to all keys\n\n/**\n * Complete table type with both metadata (via Symbols) and column accessors.\n * This is the return type of fmTableOccurrence - users see columns directly,\n * but internal config is hidden via Symbols.\n */\nexport type FMTableWithColumns<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> = FMTable<TFields, TName, TNavigationPaths> & ColumnMap<TFields, TName>;\n\n/**\n * Options for fmTableOccurrence function.\n * Provides autocomplete-friendly typing while preserving inference for navigationPaths.\n */\nexport interface FMTableOccurrenceOptions<\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> {\n /** The entity ID (FMTID) for this table occurrence */\n entityId?: `FMTID:${string}`;\n\n /**\n * Default select behavior:\n * - \"all\": Select all fields (including related tables)\n * - \"schema\": Select only schema-defined fields (default)\n * - function: Custom selection from columns\n */\n defaultSelect?:\n | \"all\"\n | \"schema\"\n | ((\n columns: ColumnMap<TFields, TName>,\n ) => Record<string, Column<any, any, TName>>);\n\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: readonly string[];\n\n /** Whether to use entity IDs (FMTID/FMFID) instead of names in queries */\n useEntityIds?: boolean;\n}\n\n/**\n * Create a table occurrence with field builders.\n * This is the main API for defining tables in the new ORM style.\n *\n * @example\n * const users = fmTableOccurrence(\"users\", {\n * id: textField().primaryKey().entityId(\"FMFID:1\"),\n * name: textField().notNull().entityId(\"FMFID:6\"),\n * active: numberField()\n * .outputValidator(z.coerce.boolean())\n * .inputValidator(z.boolean().transform(v => v ? 1 : 0))\n * .entityId(\"FMFID:7\"),\n * }, {\n * entityId: \"FMTID:100\",\n * defaultSelect: \"schema\",\n * navigationPaths: [\"contacts\"],\n * });\n *\n * // Access columns\n * users.id // Column<string, \"id\">\n * users.name // Column<string, \"name\">\n *\n * // Use in queries\n * db.from(users).select(\"id\", \"name\").where(eq(users.active, true))\n */\nexport function fmTableOccurrence<\n const TName extends string,\n const TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n const TNavPaths extends readonly string[] = readonly [],\n>(\n name: TName,\n fields: TFields,\n options?: FMTableOccurrenceOptions<TFields, TName> & {\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: TNavPaths;\n },\n): FMTableWithColumns<TFields, TName, TNavPaths> {\n // Extract configuration from field builders\n const fieldConfigs = Object.entries(fields).map(([fieldName, builder]) => ({\n fieldName,\n config: (builder as any)._getConfig(),\n }));\n\n // Find primary key field\n const primaryKeyField = fieldConfigs.find((f) => f.config.primaryKey);\n const idField = primaryKeyField?.fieldName;\n\n // Collect required fields (notNull fields)\n const required = fieldConfigs\n .filter((f) => f.config.notNull)\n .map((f) => f.fieldName);\n\n // Collect read-only fields\n const readOnly = fieldConfigs\n .filter((f) => f.config.readOnly)\n .map((f) => f.fieldName);\n\n // Collect container fields (cannot be selected via $select)\n const containerFields = fieldConfigs\n .filter((f) => f.config.fieldType === \"container\")\n .map((f) => f.fieldName);\n\n // Collect entity IDs\n const fmfIds: Record<string, `FMFID:${string}`> = {};\n for (const { fieldName, config } of fieldConfigs) {\n if (config.entityId) {\n fmfIds[fieldName] = config.entityId;\n }\n }\n\n // Build Zod schema from field builders (output/read validators)\n const zodSchema: Record<string, StandardSchemaV1> = {};\n // Build input schema from field builders (input/write validators)\n const inputSchema: Record<string, StandardSchemaV1> = {};\n\n for (const { fieldName, config } of fieldConfigs) {\n // Use outputValidator if provided, otherwise create a basic validator\n if (config.outputValidator) {\n zodSchema[fieldName] = config.outputValidator;\n } else {\n // Create a default validator based on field type and nullability\n let validator: any;\n switch (config.fieldType) {\n case \"text\":\n case \"date\":\n case \"time\":\n case \"timestamp\":\n case \"container\":\n case \"calculated\":\n validator = z.string();\n break;\n case \"number\":\n validator = z.number();\n break;\n default:\n validator = z.unknown();\n }\n\n // Add nullability if not marked as notNull\n if (!config.notNull) {\n validator = validator.nullable();\n }\n\n zodSchema[fieldName] = validator;\n }\n\n // Store inputValidator if provided (for write operations)\n if (config.inputValidator) {\n inputSchema[fieldName] = config.inputValidator;\n }\n }\n\n // Create a schema validator for the entire table\n const tableSchema = z.object(zodSchema) as unknown as StandardSchemaV1<\n any,\n InferSchemaFromFields<TFields>\n >;\n\n // Build BaseTable-compatible config\n const baseTableConfig = {\n schema: zodSchema as Record<keyof TFields, StandardSchemaV1>,\n inputSchema: (Object.keys(inputSchema).length > 0\n ? inputSchema\n : undefined) as Record<keyof TFields, StandardSchemaV1> | undefined,\n idField: idField as keyof TFields | undefined,\n required: required as readonly (keyof TFields)[],\n readOnly: readOnly as readonly (keyof TFields)[],\n containerFields: containerFields as readonly (keyof TFields)[],\n fmfIds: (Object.keys(fmfIds).length > 0 ? fmfIds : undefined) as\n | Record<keyof TFields, `FMFID:${string}`>\n | undefined,\n };\n\n // Create column instances\n const columns = {} as ColumnMap<TFields, TName>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n const config = (builder as any)._getConfig();\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: config.entityId,\n tableName: name,\n tableEntityId: options?.entityId,\n inputValidator: config.inputValidator,\n });\n }\n\n // Resolve defaultSelect: if it's a function, call it with columns; otherwise use as-is\n const defaultSelectOption = options?.defaultSelect ?? \"schema\";\n const resolvedDefaultSelect:\n | \"all\"\n | \"schema\"\n | Record<string, Column<any, any, TName>> =\n typeof defaultSelectOption === \"function\"\n ? defaultSelectOption(columns as ColumnMap<TFields, TName>)\n : defaultSelectOption;\n\n // Create the FMTable instance with Symbol-based internal properties\n const navigationPaths = (options?.navigationPaths ?? []) as TNavPaths;\n const table = new FMTable<TFields, TName, TNavPaths>({\n name,\n entityId: options?.entityId,\n useEntityIds: options?.useEntityIds,\n schema: tableSchema,\n fields,\n navigationPaths,\n defaultSelect: resolvedDefaultSelect,\n baseTableConfig,\n });\n\n // Assign columns to the table instance (making them accessible directly)\n Object.assign(table, columns);\n\n return table as FMTableWithColumns<TFields, TName, TNavPaths>;\n}\n\n// /**\n// * Type guard to check if a value is a TableOccurrence or FMTable.\n// * Supports both Symbol-based (new) and underscore-prefixed (legacy) formats.\n// */\n// function isTableOccurrence(value: any): value is TableOccurrence {\n// if (!value || typeof value !== \"object\") {\n// return false;\n// }\n\n// // Check for Symbol-based format (new FMTable class)\n// if (\n// FMTableName in value &&\n// FMTableSchema in value &&\n// FMTableFields in value\n// ) {\n// return typeof value[FMTableName] === \"string\";\n// }\n\n// // Check for underscore-prefixed format (legacy interface)\n// if (\"_name\" in value && \"_schema\" in value && \"_fields\" in value) {\n// return typeof value._name === \"string\";\n// }\n\n// return false;\n// }\n\n/**\n * Helper to extract the schema type from a TableOccurrence or FMTable.\n */\nexport type InferTableSchema<T> =\n T extends FMTable<infer TFields, any>\n ? InferSchemaFromFields<TFields>\n : never;\n\n/**\n * Extract the schema type from an FMTable instance.\n * This is used to infer the schema from table objects passed to db.from(), expand(), etc.\n */\nexport type InferSchemaOutputFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? InferSchemaFromFields<TFields>\n : never;\n\n/**\n * Extract the input schema type from an FMTable instance.\n * This is used for insert and update operations where we need write types.\n */\nexport type InferInputSchemaFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? InferInputSchemaFromFields<TFields>\n : never;\n\n/**\n * Helper type to check if a FieldBuilder's input type excludes null and undefined.\n * This checks the TInput type parameter, which preserves nullability from notNull().\n */\ntype FieldInputExcludesNullish<F> =\n F extends FieldBuilder<any, infer TInput, any>\n ? null extends TInput\n ? false\n : undefined extends TInput\n ? false\n : true\n : false;\n\n/**\n * Check if a FieldBuilder is readOnly at the type level\n */\ntype IsFieldReadOnly<F> =\n F extends FieldBuilderType<any, any, any, infer ReadOnly>\n ? ReadOnly extends true\n ? true\n : false\n : false;\n\n/**\n * Compute insert data type from FMTable, making notNull fields required.\n * Fields are required if their FieldBuilder's TInput type excludes null/undefined.\n * All other fields are optional (can be omitted).\n * readOnly fields are excluded (including primaryKey/idField since they're automatically readOnly).\n */\nexport type InsertDataFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? K\n : never]: InferFieldInput<TFields[K]>;\n } & {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? never\n : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Compute update data type from FMTable.\n * All fields are optional, but readOnly fields are excluded (including primaryKey/idField).\n */\nexport type UpdateDataFromFMTable<T extends FMTable<any, any>> =\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Extract the table name type from an FMTable.\n * This is a workaround since we can't directly index Symbols in types.\n */\nexport type ExtractTableName<T extends FMTable<any, any>> =\n T extends FMTable<any, infer Name> ? Name : never;\n\n/**\n * Validates that a target table's name matches one of the source table's navigationPaths.\n * Used to ensure type-safe expand/navigate operations.\n */\nexport type ValidExpandTarget<\n SourceTable extends FMTable<any, any, any> | undefined,\n TargetTable extends FMTable<any, any, any>,\n> =\n SourceTable extends FMTable<any, any, infer SourceNavPaths>\n ? ExtractTableName<TargetTable> extends SourceNavPaths[number]\n ? TargetTable\n : never\n : TargetTable;\n\n// ============================================================================\n// Helper Functions for Accessing FMTable Internal Properties\n// ============================================================================\n\n/**\n * Get the table name from an FMTable instance.\n * @param table - FMTable instance\n * @returns The table name\n */\nexport function getTableName<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableName];\n}\n\n/**\n * Get the entity ID (FMTID) from an FMTable instance.\n * @param table - FMTable instance\n * @returns The entity ID or undefined if not using entity IDs\n */\nexport function getTableEntityId<T extends FMTable<any, any>>(\n table: T,\n): string | undefined {\n return table[FMTableEntityId];\n}\n\n/**\n * Get the schema validator from an FMTable instance.\n * @param table - FMTable instance\n * @returns The StandardSchemaV1 validator\n */\nexport function getTableSchema<T extends FMTable<any, any>>(\n table: T,\n): StandardSchemaV1 {\n return table[FMTableSchema];\n}\n\n/**\n * Get the fields from an FMTable instance.\n * @param table - FMTable instance\n * @returns The fields record\n */\nexport function getTableFields<T extends FMTable<any, any>>(table: T) {\n return table[FMTableFields];\n}\n\n/**\n * Get the navigation paths from an FMTable instance.\n * @param table - FMTable instance\n * @returns Array of navigation path names\n */\nexport function getNavigationPaths<T extends FMTable<any, any>>(\n table: T,\n): readonly string[] {\n return table[FMTableNavigationPaths];\n}\n\n/**\n * Get the default select configuration from an FMTable instance.\n * @param table - FMTable instance\n * @returns Default select configuration\n */\nexport function getDefaultSelect<T extends FMTable<any, any>>(table: T) {\n return table[FMTableDefaultSelect];\n}\n\n/**\n * Get the base table configuration from an FMTable instance.\n * This provides access to schema, idField, required fields, readOnly fields, and field IDs.\n * @param table - FMTable instance\n * @returns Base table configuration object\n */\nexport function getBaseTableConfig<T extends FMTable<any, any>>(table: T) {\n return table[FMTableBaseTableConfig];\n}\n\n/**\n * Check if an FMTable instance is using entity IDs (both FMTID and FMFIDs).\n * @param table - FMTable instance\n * @returns True if using entity IDs, false otherwise\n */\nexport function isUsingEntityIds<T extends FMTable<any, any>>(\n table: T,\n): boolean {\n return (\n table[FMTableEntityId] !== undefined &&\n table[FMTableBaseTableConfig].fmfIds !== undefined\n );\n}\n\n/**\n * Get the field ID (FMFID) for a given field name, or the field name itself if not using IDs.\n * @param table - FMTable instance\n * @param fieldName - Field name to get the ID for\n * @returns The FMFID string or the original field name\n */\nexport function getFieldId<T extends FMTable<any, any>>(\n table: T,\n fieldName: string,\n): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds && fieldName in config.fmfIds) {\n const fieldId = config.fmfIds[fieldName];\n if (fieldId) {\n return fieldId;\n }\n }\n return fieldName;\n}\n\n/**\n * Get the field name for a given field ID (FMFID), or the ID itself if not found.\n * @param table - FMTable instance\n * @param fieldId - The FMFID to get the field name for\n * @returns The field name or the original ID\n */\nexport function getFieldName<T extends FMTable<any, any>>(\n table: T,\n fieldId: string,\n): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds) {\n for (const [fieldName, fmfId] of Object.entries(config.fmfIds)) {\n if (fmfId === fieldId) {\n return fieldName;\n }\n }\n }\n return fieldId;\n}\n/**\n * Get the table ID (FMTID or name) from an FMTable instance.\n * Returns the FMTID if available, otherwise returns the table name.\n * @param table - FMTable instance\n * @returns The FMTID string or the table name\n */\nexport function getTableId<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableEntityId] ?? table[FMTableName];\n}\n\n/**\n * Get all columns from a table as an object.\n * Useful for selecting all fields except some using destructuring.\n *\n * @example\n * const { password, ...cols } = getTableColumns(users)\n * db.from(users).list().select(cols)\n *\n * @param table - FMTable instance\n * @returns Object with all columns from the table\n */\nexport function getTableColumns<T extends FMTable<any, any>>(\n table: T,\n): ColumnMap<T[typeof FMTableFields], ExtractTableName<T>> {\n const fields = table[FMTableFields];\n const tableName = table[FMTableName];\n const tableEntityId = table[FMTableEntityId];\n const baseConfig = table[FMTableBaseTableConfig];\n\n const columns = {} as ColumnMap<T[typeof FMTableFields], ExtractTableName<T>>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n const config = (builder as any)._getConfig();\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: baseConfig.fmfIds?.[fieldName],\n tableName: tableName,\n tableEntityId: tableEntityId,\n inputValidator: config.inputValidator,\n });\n }\n\n return columns;\n}\n"],"names":["_a"],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
2
2
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
3
|
+
import { InternalLogger } from './logger.js';
|
|
3
4
|
export type Auth = {
|
|
4
5
|
username: string;
|
|
5
6
|
password: string;
|
|
@@ -36,6 +37,7 @@ export interface ExecutionContext {
|
|
|
36
37
|
_setUseEntityIds?(useEntityIds: boolean): void;
|
|
37
38
|
_getUseEntityIds?(): boolean;
|
|
38
39
|
_getBaseUrl?(): string;
|
|
40
|
+
_getLogger?(): InternalLogger;
|
|
39
41
|
}
|
|
40
42
|
export type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {
|
|
41
43
|
[K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output> ? Output : never;
|