@proofkit/fmodata 0.1.0-beta.23 → 0.1.0-beta.25

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.
Files changed (45) hide show
  1. package/README.md +4 -4
  2. package/dist/esm/client/batch-builder.js.map +1 -1
  3. package/dist/esm/client/batch-request.js.map +1 -1
  4. package/dist/esm/client/builders/default-select.js +1 -1
  5. package/dist/esm/client/builders/default-select.js.map +1 -1
  6. package/dist/esm/client/builders/expand-builder.js +3 -1
  7. package/dist/esm/client/builders/expand-builder.js.map +1 -1
  8. package/dist/esm/client/builders/query-string-builder.js.map +1 -1
  9. package/dist/esm/client/builders/response-processor.js.map +1 -1
  10. package/dist/esm/client/builders/select-mixin.js.map +1 -1
  11. package/dist/esm/client/builders/select-utils.js.map +1 -1
  12. package/dist/esm/client/builders/table-utils.js.map +1 -1
  13. package/dist/esm/client/database.d.ts +11 -1
  14. package/dist/esm/client/database.js +13 -0
  15. package/dist/esm/client/database.js.map +1 -1
  16. package/dist/esm/client/delete-builder.js.map +1 -1
  17. package/dist/esm/client/entity-set.js +18 -2
  18. package/dist/esm/client/entity-set.js.map +1 -1
  19. package/dist/esm/client/error-parser.js.map +1 -1
  20. package/dist/esm/client/filemaker-odata.d.ts +2 -0
  21. package/dist/esm/client/filemaker-odata.js +3 -0
  22. package/dist/esm/client/filemaker-odata.js.map +1 -1
  23. package/dist/esm/client/insert-builder.js.map +1 -1
  24. package/dist/esm/client/query/query-builder.js.map +1 -1
  25. package/dist/esm/client/query/url-builder.js.map +1 -1
  26. package/dist/esm/client/record-builder.js.map +1 -1
  27. package/dist/esm/client/sanitize-json.js.map +1 -1
  28. package/dist/esm/client/schema-manager.js.map +1 -1
  29. package/dist/esm/client/update-builder.js.map +1 -1
  30. package/dist/esm/client/webhook-builder.js.map +1 -1
  31. package/dist/esm/errors.js.map +1 -1
  32. package/dist/esm/logger.js.map +1 -1
  33. package/dist/esm/orm/column.js.map +1 -1
  34. package/dist/esm/orm/field-builders.js.map +1 -1
  35. package/dist/esm/orm/operators.js.map +1 -1
  36. package/dist/esm/orm/table.js.map +1 -1
  37. package/dist/esm/transform.js.map +1 -1
  38. package/dist/esm/types.js.map +1 -1
  39. package/dist/esm/validation.js.map +1 -1
  40. package/package.json +16 -19
  41. package/src/client/builders/default-select.ts +3 -1
  42. package/src/client/builders/expand-builder.ts +3 -1
  43. package/src/client/database.ts +17 -1
  44. package/src/client/entity-set.ts +22 -2
  45. package/src/client/filemaker-odata.ts +3 -0
@@ -1 +1 @@
1
- {"version":3,"file":"update-builder.js","sources":["../../../src/client/update-builder.ts"],"sourcesContent":["import type { FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { FMTable, InferSchemaOutputFromFMTable } from \"../orm/table\";\nimport { getBaseTableConfig, getTableId as getTableIdHelper, getTableName, isUsingEntityIds } from \"../orm/table\";\nimport { transformFieldNamesToIds } from \"../transform\";\nimport type { ExecutableBuilder, ExecuteMethodOptions, ExecuteOptions, ExecutionContext, Result } from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport { validateAndTransformInput } from \"../validation\";\nimport { parseErrorResponse } from \"./error-parser\";\nimport { QueryBuilder } from \"./query-builder\";\n\n/**\n * Initial update builder returned from EntitySet.update(data)\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class UpdateBuilder<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n Occ extends FMTable<any, any>,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> {\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n private readonly table: Occ;\n private readonly data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n private readonly returnPreference: ReturnPreference;\n\n private readonly databaseUseEntityIds: boolean;\n private readonly databaseIncludeSpecialColumns: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n databaseIncludeSpecialColumns?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = config.returnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;\n }\n\n /**\n * Update a single record by ID\n * Returns updated count by default, or full record if returnFullRecord was set to true\n */\n byId(id: string | number): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {\n return new ExecutableUpdateBuilder<Occ, true, ReturnPreference>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byId\",\n recordId: id,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Update records matching a filter query\n * Returns updated count by default, or full record if returnFullRecord was set to true\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n });\n\n // Let the user configure it\n const configuredBuilder = fn(queryBuilder);\n\n return new ExecutableUpdateBuilder<Occ, true, ReturnPreference>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable update builder - has execute() method\n * Returned after calling .byId() or .where()\n * Can return either updated count or full record based on returnFullRecord option\n */\nexport class ExecutableUpdateBuilder<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n Occ extends FMTable<any, any>,\n _IsByFilter extends boolean,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> implements\n ExecutableBuilder<ReturnPreference extends \"minimal\" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>\n{\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n private readonly table: Occ;\n private readonly data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n private readonly mode: \"byId\" | \"byFilter\";\n private readonly recordId?: string | number;\n private readonly queryBuilder?: QueryBuilder<Occ>;\n private readonly returnPreference: ReturnPreference;\n private readonly databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<Occ>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\n this.returnPreference = config.returnPreference;\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 // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\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 const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n if (!isUsingEntityIds(this.table)) {\n throw new Error(\n `useEntityIds is true but table \"${getTableName(this.table)}\" does not have entity IDs configured`,\n );\n }\n return getTableIdHelper(this.table);\n }\n\n return getTableName(this.table);\n }\n\n async execute(\n options?: ExecuteMethodOptions<ExecuteOptions>,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n\n // Validate and transform input data using input validators (writeValidators)\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n // If validation fails, return error immediately\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n } as any;\n }\n }\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n\n const transformedData =\n this.table && shouldUseIds ? transformFieldNamesToIds(validatedData, this.table) : validatedData;\n\n let url: string;\n\n if (this.mode === \"byId\") {\n // Update single record by ID: PATCH /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Update by filter: PATCH /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based update\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // The query string will have the tableId already transformed by QueryBuilder\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const tableName = getTableName(this.table);\n let queryParams: string;\n if (queryString.startsWith(`/${tableId}`)) {\n queryParams = queryString.slice(`/${tableId}`.length);\n } else if (queryString.startsWith(`/${tableName}`)) {\n queryParams = queryString.slice(`/${tableName}`.length);\n } else {\n queryParams = queryString;\n }\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Set Prefer header based on returnPreference\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.returnPreference === \"representation\") {\n headers.Prefer = \"return=representation\";\n }\n\n // Make PATCH request with JSON body\n const result = await this.context._makeRequest(url, {\n method: \"PATCH\",\n headers,\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: response as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof response === \"number\") {\n updatedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n updatedCount = (response as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n // Note: Input validation happens in execute() and processResponse() for batch operations\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.table && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.table) : this.data;\n\n let url: string;\n\n if (this.mode === \"byId\") {\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based update\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const tableName = getTableName(this.table);\n let queryParams: string;\n if (queryString.startsWith(`/${tableId}`)) {\n queryParams = queryString.slice(`/${tableId}`.length);\n } else if (queryString.startsWith(`/${tableName}`)) {\n queryParams = queryString.slice(`/${tableName}`.length);\n } else {\n queryParams = queryString;\n }\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"PATCH\",\n url,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n _options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = getTableName(this.table);\n const error = await parseErrorResponse(response, response.url || `/${this.databaseName}/${tableName}`);\n return { data: undefined, error };\n }\n\n // Check for empty response (204 No Content)\n const text = await response.text();\n if (!text || text.trim() === \"\") {\n // For 204 No Content, check the fmodata.affected_rows header\n const affectedRows = response.headers.get(\"fmodata.affected_rows\");\n const updatedCount = affectedRows ? Number.parseInt(affectedRows, 10) : 1;\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n\n const rawResponse = JSON.parse(text);\n\n // Validate and transform input data using input validators (writeValidators)\n // This is needed for processResponse because it's called from batch operations\n // where the data hasn't been validated yet\n let _validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n try {\n _validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n } as any;\n }\n }\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: rawResponse as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n updatedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n updatedCount = (rawResponse as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n}\n"],"names":["getTableIdHelper","updatedCount"],"mappings":"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"}
1
+ {"version":3,"file":"update-builder.js","sources":["../../../src/client/update-builder.ts"],"sourcesContent":["import type { FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { FMTable, InferSchemaOutputFromFMTable } from \"../orm/table\";\nimport { getBaseTableConfig, getTableId as getTableIdHelper, getTableName, isUsingEntityIds } from \"../orm/table\";\nimport { transformFieldNamesToIds } from \"../transform\";\nimport type { ExecutableBuilder, ExecuteMethodOptions, ExecuteOptions, ExecutionContext, Result } from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport { validateAndTransformInput } from \"../validation\";\nimport { parseErrorResponse } from \"./error-parser\";\nimport { QueryBuilder } from \"./query-builder\";\n\n/**\n * Initial update builder returned from EntitySet.update(data)\n * Requires calling .byId() or .where() before .execute() is available\n */\nexport class UpdateBuilder<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n Occ extends FMTable<any, any>,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> {\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n private readonly table: Occ;\n private readonly data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n private readonly returnPreference: ReturnPreference;\n\n private readonly databaseUseEntityIds: boolean;\n private readonly databaseIncludeSpecialColumns: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n databaseIncludeSpecialColumns?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = config.returnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;\n }\n\n /**\n * Update a single record by ID\n * Returns updated count by default, or full record if returnFullRecord was set to true\n */\n byId(id: string | number): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {\n return new ExecutableUpdateBuilder<Occ, true, ReturnPreference>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byId\",\n recordId: id,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n\n /**\n * Update records matching a filter query\n * Returns updated count by default, or full record if returnFullRecord was set to true\n * @param fn Callback that receives a QueryBuilder for building the filter\n */\n where(fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>): ExecutableUpdateBuilder<Occ, true, ReturnPreference> {\n // Create a QueryBuilder for the user to configure\n const queryBuilder = new QueryBuilder<Occ>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n });\n\n // Let the user configure it\n const configuredBuilder = fn(queryBuilder);\n\n return new ExecutableUpdateBuilder<Occ, true, ReturnPreference>({\n occurrence: this.table,\n databaseName: this.databaseName,\n context: this.context,\n data: this.data,\n mode: \"byFilter\",\n queryBuilder: configuredBuilder,\n returnPreference: this.returnPreference,\n databaseUseEntityIds: this.databaseUseEntityIds,\n });\n }\n}\n\n/**\n * Executable update builder - has execute() method\n * Returned after calling .byId() or .where()\n * Can return either updated count or full record based on returnFullRecord option\n */\nexport class ExecutableUpdateBuilder<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n Occ extends FMTable<any, any>,\n _IsByFilter extends boolean,\n ReturnPreference extends \"minimal\" | \"representation\" = \"minimal\",\n> implements\n ExecutableBuilder<ReturnPreference extends \"minimal\" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>\n{\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n private readonly table: Occ;\n private readonly data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n private readonly mode: \"byId\" | \"byFilter\";\n private readonly recordId?: string | number;\n private readonly queryBuilder?: QueryBuilder<Occ>;\n private readonly returnPreference: ReturnPreference;\n private readonly databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<Occ>>;\n mode: \"byId\" | \"byFilter\";\n recordId?: string | number;\n queryBuilder?: QueryBuilder<Occ>;\n returnPreference: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.mode = config.mode;\n this.recordId = config.recordId;\n this.queryBuilder = config.queryBuilder;\n this.returnPreference = config.returnPreference;\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 // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\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 const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n if (!isUsingEntityIds(this.table)) {\n throw new Error(\n `useEntityIds is true but table \"${getTableName(this.table)}\" does not have entity IDs configured`,\n );\n }\n return getTableIdHelper(this.table);\n }\n\n return getTableName(this.table);\n }\n\n async execute(\n options?: ExecuteMethodOptions<ExecuteOptions>,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n\n // Validate and transform input data using input validators (writeValidators)\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n // If validation fails, return error immediately\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n } as any;\n }\n }\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n\n const transformedData =\n this.table && shouldUseIds ? transformFieldNamesToIds(validatedData, this.table) : validatedData;\n\n let url: string;\n\n if (this.mode === \"byId\") {\n // Update single record by ID: PATCH /{database}/{table}('id')\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n // Update by filter: PATCH /{database}/{table}?$filter=...\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based update\");\n }\n\n // Get the query string from the configured QueryBuilder\n const queryString = this.queryBuilder.getQueryString();\n // The query string will have the tableId already transformed by QueryBuilder\n // Remove the leading \"/\" and table name from the query string as we'll build our own URL\n const tableName = getTableName(this.table);\n let queryParams: string;\n if (queryString.startsWith(`/${tableId}`)) {\n queryParams = queryString.slice(`/${tableId}`.length);\n } else if (queryString.startsWith(`/${tableName}`)) {\n queryParams = queryString.slice(`/${tableName}`.length);\n } else {\n queryParams = queryString;\n }\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n // Set Prefer header based on returnPreference\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.returnPreference === \"representation\") {\n headers.Prefer = \"return=representation\";\n }\n\n // Make PATCH request with JSON body\n const result = await this.context._makeRequest(url, {\n method: \"PATCH\",\n headers,\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n const response = result.data;\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: response as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof response === \"number\") {\n updatedCount = response;\n } else if (response && typeof response === \"object\") {\n // Check if the response has a count property (fallback)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n updatedCount = (response as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n // Note: Input validation happens in execute() and processResponse() for batch operations\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.table && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.table) : this.data;\n\n let url: string;\n\n if (this.mode === \"byId\") {\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n } else {\n if (!this.queryBuilder) {\n throw new Error(\"Query builder is required for filter-based update\");\n }\n\n const queryString = this.queryBuilder.getQueryString();\n const tableName = getTableName(this.table);\n let queryParams: string;\n if (queryString.startsWith(`/${tableId}`)) {\n queryParams = queryString.slice(`/${tableId}`.length);\n } else if (queryString.startsWith(`/${tableName}`)) {\n queryParams = queryString.slice(`/${tableName}`.length);\n } else {\n queryParams = queryString;\n }\n\n url = `/${this.databaseName}/${tableId}${queryParams}`;\n }\n\n return {\n method: \"PATCH\",\n url,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n _options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { updatedCount: number } : InferSchemaOutputFromFMTable<Occ>>\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = getTableName(this.table);\n const error = await parseErrorResponse(response, response.url || `/${this.databaseName}/${tableName}`);\n return { data: undefined, error };\n }\n\n // Check for empty response (204 No Content)\n const text = await response.text();\n if (!text || text.trim() === \"\") {\n // For 204 No Content, check the fmodata.affected_rows header\n const affectedRows = response.headers.get(\"fmodata.affected_rows\");\n const updatedCount = affectedRows ? Number.parseInt(affectedRows, 10) : 1;\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n\n const rawResponse = JSON.parse(text);\n\n // Validate and transform input data using input validators (writeValidators)\n // This is needed for processResponse because it's called from batch operations\n // where the data hasn't been validated yet\n let _validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n try {\n _validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type\n } as any;\n }\n }\n\n // Handle based on return preference\n if (this.returnPreference === \"representation\") {\n // Return the full updated record\n return {\n data: rawResponse as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n // Return updated count (minimal)\n let updatedCount = 0;\n\n if (typeof rawResponse === \"number\") {\n updatedCount = rawResponse;\n } else if (rawResponse && typeof rawResponse === \"object\") {\n // Check if the response has a count property (fallback)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n updatedCount = (rawResponse as any).updatedCount || 0;\n }\n\n return {\n data: { updatedCount } as ReturnPreference extends \"minimal\"\n ? { updatedCount: number }\n : InferSchemaOutputFromFMTable<Occ>,\n error: undefined,\n };\n }\n}\n"],"names":["getTableIdHelper","updatedCount"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"webhook-builder.js","sources":["../../../src/client/webhook-builder.ts"],"sourcesContent":["import { type FMTable, getTableName } from \"../orm\";\nimport { type Column, isColumn } from \"../orm/column\";\nimport { FilterExpression } from \"../orm/operators\";\nimport type { ExecuteMethodOptions, ExecutionContext } from \"../types\";\nimport { formatSelectFields } from \"./builders/select-utils\";\n\nexport interface Webhook<TableName = string> {\n webhook: string;\n headers?: Record<string, string>;\n tableName: TableName;\n notifySchemaChanges?: boolean;\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n select?: string | Column<any, any, any>[];\n filter?: string | FilterExpression;\n}\n\n/**\n * Webhook information returned by the API\n */\nexport interface WebhookInfo {\n webHookID: number;\n tableName: string;\n url: string;\n headers?: Record<string, string>;\n notifySchemaChanges: boolean;\n select: string;\n filter: string;\n pendingOperations: unknown[];\n}\n\n/**\n * Response from listing all webhooks\n */\nexport interface WebhookListResponse {\n Status: string;\n WebHook: WebhookInfo[];\n}\n\n/**\n * Response from adding a webhook\n */\nexport interface WebhookAddResponse {\n webHookResult: {\n webHookID: number;\n };\n}\n\nexport class WebhookManager {\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n\n constructor(databaseName: string, context: ExecutionContext) {\n this.databaseName = databaseName;\n this.context = context;\n }\n\n /**\n * Adds a new webhook to the database.\n * @param webhook - The webhook configuration object\n * @param webhook.webhook - The webhook URL to call\n * @param webhook.tableName - The FMTable instance for the table to monitor\n * @param webhook.headers - Optional custom headers to include in webhook requests\n * @param webhook.notifySchemaChanges - Whether to notify on schema changes\n * @param webhook.select - Optional field selection (string or array of Column references)\n * @param webhook.filter - Optional filter (string or FilterExpression)\n * @returns Promise resolving to the created webhook data with ID\n * @example\n * ```ts\n * const result = await db.webhook.add({\n * webhook: \"https://example.com/webhook\",\n * tableName: contactsTable,\n * headers: { \"X-Custom-Header\": \"value\" },\n * });\n * // result.webHookResult.webHookID contains the new webhook ID\n * ```\n * @example\n * ```ts\n * // Using filter expressions and column arrays (same DX as query builder)\n * const result = await db.webhook.add({\n * webhook: \"https://example.com/webhook\",\n * tableName: contacts,\n * filter: eq(contacts.name, \"John\"),\n * select: [contacts.name, contacts.PrimaryKey],\n * });\n * ```\n */\n async add(webhook: Webhook<FMTable>, options?: ExecuteMethodOptions): Promise<WebhookAddResponse> {\n // Extract the string table name from the FMTable instance\n const tableName = getTableName(webhook.tableName);\n\n // Get useEntityIds setting (check options first, then context, default to false)\n const useEntityIds = options?.useEntityIds ?? this.context._getUseEntityIds?.() ?? false;\n\n // Transform filter if it's a FilterExpression\n let filter: string | undefined;\n if (webhook.filter !== undefined) {\n if (webhook.filter instanceof FilterExpression) {\n filter = webhook.filter.toODataFilter(useEntityIds);\n } else {\n filter = webhook.filter;\n }\n }\n\n // Transform select if it's an array of Columns\n let select: string | undefined;\n if (webhook.select !== undefined) {\n if (Array.isArray(webhook.select)) {\n // Extract field identifiers from columns or use strings as-is\n const fieldNames = webhook.select.map((item) => {\n if (isColumn(item)) {\n return item.getFieldIdentifier(useEntityIds);\n }\n return String(item);\n });\n // Use formatSelectFields to properly format the select string\n select = formatSelectFields(fieldNames, webhook.tableName, useEntityIds);\n } else {\n // Already a string, use as-is\n select = webhook.select;\n }\n }\n\n // Create request body with string table name and transformed filter/select\n const requestBody: {\n webhook: string;\n headers?: Record<string, string>;\n tableName: string;\n notifySchemaChanges?: boolean;\n select?: string;\n filter?: string;\n } = {\n webhook: webhook.webhook,\n tableName,\n };\n\n if (webhook.headers !== undefined) {\n requestBody.headers = webhook.headers;\n }\n if (webhook.notifySchemaChanges !== undefined) {\n requestBody.notifySchemaChanges = webhook.notifySchemaChanges;\n }\n if (select !== undefined) {\n requestBody.select = select;\n }\n if (filter !== undefined) {\n requestBody.filter = filter;\n }\n\n const result = await this.context._makeRequest<WebhookAddResponse>(`/${this.databaseName}/Webhook.Add`, {\n method: \"POST\",\n body: JSON.stringify(requestBody),\n ...options,\n });\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n /**\n * Deletes a webhook by ID.\n * @param webhookId - The ID of the webhook to delete\n * @returns Promise that resolves when the webhook is deleted\n * @example\n * ```ts\n * await db.webhook.remove(1);\n * ```\n */\n async remove(webhookId: number, options?: ExecuteMethodOptions): Promise<void> {\n const result = await this.context._makeRequest(`/${this.databaseName}/Webhook.Delete(${webhookId})`, {\n method: \"POST\",\n ...options,\n });\n\n if (result.error) {\n throw result.error;\n }\n }\n\n /**\n * Gets a webhook by ID.\n * @param webhookId - The ID of the webhook to retrieve\n * @returns Promise resolving to the webhook data\n * @example\n * ```ts\n * const webhook = await db.webhook.get(1);\n * // webhook.webHookID, webhook.tableName, webhook.url, etc.\n * ```\n */\n async get(webhookId: number, options?: ExecuteMethodOptions): Promise<WebhookInfo> {\n const result = await this.context._makeRequest<WebhookInfo>(\n `/${this.databaseName}/Webhook.Get(${webhookId})`,\n options,\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n /**\n * Lists all webhooks.\n * @returns Promise resolving to webhook list response with status and webhooks array\n * @example\n * ```ts\n * const result = await db.webhook.list();\n * // result.Status contains the status\n * // result.WebHook contains the array of webhooks\n * ```\n */\n async list(options?: ExecuteMethodOptions): Promise<WebhookListResponse> {\n const result = await this.context._makeRequest<WebhookListResponse>(\n `/${this.databaseName}/Webhook.GetAll`,\n options,\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n /**\n * Invokes a webhook by ID, optionally for specific row IDs.\n * @param webhookId - The ID of the webhook to invoke\n * @param options - Optional configuration\n * @param options.rowIDs - Array of row IDs to trigger the webhook for\n * @returns Promise resolving to the invocation result (type unknown until API behavior is confirmed)\n * @example\n * ```ts\n * // Invoke for all rows\n * await db.webhook.invoke(1);\n *\n * // Invoke for specific rows\n * await db.webhook.invoke(1, { rowIDs: [63, 61] });\n * ```\n */\n async invoke(\n webhookId: number,\n options?: { rowIDs?: number[] },\n executeOptions?: ExecuteMethodOptions,\n ): Promise<unknown> {\n const body: { rowIDs?: number[] } = {};\n if (options?.rowIDs !== undefined) {\n body.rowIDs = options.rowIDs;\n }\n\n const result = await this.context._makeRequest<unknown>(`/${this.databaseName}/Webhook.Invoke(${webhookId})`, {\n method: \"POST\",\n body: Object.keys(body).length > 0 ? JSON.stringify(body) : undefined,\n ...executeOptions,\n });\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"webhook-builder.js","sources":["../../../src/client/webhook-builder.ts"],"sourcesContent":["import { type FMTable, getTableName } from \"../orm\";\nimport { type Column, isColumn } from \"../orm/column\";\nimport { FilterExpression } from \"../orm/operators\";\nimport type { ExecuteMethodOptions, ExecutionContext } from \"../types\";\nimport { formatSelectFields } from \"./builders/select-utils\";\n\nexport interface Webhook<TableName = string> {\n webhook: string;\n headers?: Record<string, string>;\n tableName: TableName;\n notifySchemaChanges?: boolean;\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n select?: string | Column<any, any, any>[];\n filter?: string | FilterExpression;\n}\n\n/**\n * Webhook information returned by the API\n */\nexport interface WebhookInfo {\n webHookID: number;\n tableName: string;\n url: string;\n headers?: Record<string, string>;\n notifySchemaChanges: boolean;\n select: string;\n filter: string;\n pendingOperations: unknown[];\n}\n\n/**\n * Response from listing all webhooks\n */\nexport interface WebhookListResponse {\n Status: string;\n WebHook: WebhookInfo[];\n}\n\n/**\n * Response from adding a webhook\n */\nexport interface WebhookAddResponse {\n webHookResult: {\n webHookID: number;\n };\n}\n\nexport class WebhookManager {\n private readonly databaseName: string;\n private readonly context: ExecutionContext;\n\n constructor(databaseName: string, context: ExecutionContext) {\n this.databaseName = databaseName;\n this.context = context;\n }\n\n /**\n * Adds a new webhook to the database.\n * @param webhook - The webhook configuration object\n * @param webhook.webhook - The webhook URL to call\n * @param webhook.tableName - The FMTable instance for the table to monitor\n * @param webhook.headers - Optional custom headers to include in webhook requests\n * @param webhook.notifySchemaChanges - Whether to notify on schema changes\n * @param webhook.select - Optional field selection (string or array of Column references)\n * @param webhook.filter - Optional filter (string or FilterExpression)\n * @returns Promise resolving to the created webhook data with ID\n * @example\n * ```ts\n * const result = await db.webhook.add({\n * webhook: \"https://example.com/webhook\",\n * tableName: contactsTable,\n * headers: { \"X-Custom-Header\": \"value\" },\n * });\n * // result.webHookResult.webHookID contains the new webhook ID\n * ```\n * @example\n * ```ts\n * // Using filter expressions and column arrays (same DX as query builder)\n * const result = await db.webhook.add({\n * webhook: \"https://example.com/webhook\",\n * tableName: contacts,\n * filter: eq(contacts.name, \"John\"),\n * select: [contacts.name, contacts.PrimaryKey],\n * });\n * ```\n */\n async add(webhook: Webhook<FMTable>, options?: ExecuteMethodOptions): Promise<WebhookAddResponse> {\n // Extract the string table name from the FMTable instance\n const tableName = getTableName(webhook.tableName);\n\n // Get useEntityIds setting (check options first, then context, default to false)\n const useEntityIds = options?.useEntityIds ?? this.context._getUseEntityIds?.() ?? false;\n\n // Transform filter if it's a FilterExpression\n let filter: string | undefined;\n if (webhook.filter !== undefined) {\n if (webhook.filter instanceof FilterExpression) {\n filter = webhook.filter.toODataFilter(useEntityIds);\n } else {\n filter = webhook.filter;\n }\n }\n\n // Transform select if it's an array of Columns\n let select: string | undefined;\n if (webhook.select !== undefined) {\n if (Array.isArray(webhook.select)) {\n // Extract field identifiers from columns or use strings as-is\n const fieldNames = webhook.select.map((item) => {\n if (isColumn(item)) {\n return item.getFieldIdentifier(useEntityIds);\n }\n return String(item);\n });\n // Use formatSelectFields to properly format the select string\n select = formatSelectFields(fieldNames, webhook.tableName, useEntityIds);\n } else {\n // Already a string, use as-is\n select = webhook.select;\n }\n }\n\n // Create request body with string table name and transformed filter/select\n const requestBody: {\n webhook: string;\n headers?: Record<string, string>;\n tableName: string;\n notifySchemaChanges?: boolean;\n select?: string;\n filter?: string;\n } = {\n webhook: webhook.webhook,\n tableName,\n };\n\n if (webhook.headers !== undefined) {\n requestBody.headers = webhook.headers;\n }\n if (webhook.notifySchemaChanges !== undefined) {\n requestBody.notifySchemaChanges = webhook.notifySchemaChanges;\n }\n if (select !== undefined) {\n requestBody.select = select;\n }\n if (filter !== undefined) {\n requestBody.filter = filter;\n }\n\n const result = await this.context._makeRequest<WebhookAddResponse>(`/${this.databaseName}/Webhook.Add`, {\n method: \"POST\",\n body: JSON.stringify(requestBody),\n ...options,\n });\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n /**\n * Deletes a webhook by ID.\n * @param webhookId - The ID of the webhook to delete\n * @returns Promise that resolves when the webhook is deleted\n * @example\n * ```ts\n * await db.webhook.remove(1);\n * ```\n */\n async remove(webhookId: number, options?: ExecuteMethodOptions): Promise<void> {\n const result = await this.context._makeRequest(`/${this.databaseName}/Webhook.Delete(${webhookId})`, {\n method: \"POST\",\n ...options,\n });\n\n if (result.error) {\n throw result.error;\n }\n }\n\n /**\n * Gets a webhook by ID.\n * @param webhookId - The ID of the webhook to retrieve\n * @returns Promise resolving to the webhook data\n * @example\n * ```ts\n * const webhook = await db.webhook.get(1);\n * // webhook.webHookID, webhook.tableName, webhook.url, etc.\n * ```\n */\n async get(webhookId: number, options?: ExecuteMethodOptions): Promise<WebhookInfo> {\n const result = await this.context._makeRequest<WebhookInfo>(\n `/${this.databaseName}/Webhook.Get(${webhookId})`,\n options,\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n /**\n * Lists all webhooks.\n * @returns Promise resolving to webhook list response with status and webhooks array\n * @example\n * ```ts\n * const result = await db.webhook.list();\n * // result.Status contains the status\n * // result.WebHook contains the array of webhooks\n * ```\n */\n async list(options?: ExecuteMethodOptions): Promise<WebhookListResponse> {\n const result = await this.context._makeRequest<WebhookListResponse>(\n `/${this.databaseName}/Webhook.GetAll`,\n options,\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n /**\n * Invokes a webhook by ID, optionally for specific row IDs.\n * @param webhookId - The ID of the webhook to invoke\n * @param options - Optional configuration\n * @param options.rowIDs - Array of row IDs to trigger the webhook for\n * @returns Promise resolving to the invocation result (type unknown until API behavior is confirmed)\n * @example\n * ```ts\n * // Invoke for all rows\n * await db.webhook.invoke(1);\n *\n * // Invoke for specific rows\n * await db.webhook.invoke(1, { rowIDs: [63, 61] });\n * ```\n */\n async invoke(\n webhookId: number,\n options?: { rowIDs?: number[] },\n executeOptions?: ExecuteMethodOptions,\n ): Promise<unknown> {\n const body: { rowIDs?: number[] } = {};\n if (options?.rowIDs !== undefined) {\n body.rowIDs = options.rowIDs;\n }\n\n const result = await this.context._makeRequest<unknown>(`/${this.databaseName}/Webhook.Invoke(${webhookId})`, {\n method: \"POST\",\n body: Object.keys(body).length > 0 ? JSON.stringify(body) : undefined,\n ...executeOptions,\n });\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sources":["../../src/errors.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Base class for all fmodata errors\n */\nexport abstract class FMODataError extends Error {\n abstract readonly kind: string;\n readonly timestamp: Date;\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = this.constructor.name;\n this.timestamp = new Date();\n }\n}\n\n// ============================================\n// HTTP Errors (with status codes)\n// ============================================\n\nexport class HTTPError extends FMODataError {\n readonly kind = \"HTTPError\" as const;\n readonly url: string;\n readonly status: number;\n readonly statusText: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n readonly response?: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n constructor(url: string, status: number, statusText: string, response?: any) {\n super(`HTTP ${status} ${statusText} for ${url}`);\n this.url = url;\n this.status = status;\n this.statusText = statusText;\n this.response = response;\n }\n\n // Helper methods for common status checks\n is4xx(): boolean {\n return this.status >= 400 && this.status < 500;\n }\n\n is5xx(): boolean {\n return this.status >= 500 && this.status < 600;\n }\n\n isNotFound(): boolean {\n return this.status === 404;\n }\n\n isUnauthorized(): boolean {\n return this.status === 401;\n }\n\n isForbidden(): boolean {\n return this.status === 403;\n }\n}\n\n// ============================================\n// OData Specific Errors\n// ============================================\n\nexport class ODataError extends FMODataError {\n readonly kind = \"ODataError\" as const;\n readonly url: string;\n readonly code?: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n readonly details?: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n constructor(url: string, message: string, code?: string, details?: any) {\n super(`OData error: ${message}`);\n this.url = url;\n this.code = code;\n this.details = details;\n }\n}\n\nexport class SchemaLockedError extends FMODataError {\n readonly kind = \"SchemaLockedError\" as const;\n readonly url: string;\n readonly code: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n readonly details?: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n constructor(url: string, message: string, details?: any) {\n super(`OData error: ${message}`);\n this.url = url;\n this.code = \"303\";\n this.details = details;\n }\n}\n\n// ============================================\n// Validation Errors\n// ============================================\n\nexport class ValidationError extends FMODataError {\n readonly kind = \"ValidationError\" as const;\n readonly field?: string;\n readonly issues: readonly StandardSchemaV1.Issue[];\n readonly value?: unknown;\n\n constructor(\n message: string,\n issues: readonly StandardSchemaV1.Issue[],\n options?: {\n field?: string;\n value?: unknown;\n cause?: Error[\"cause\"];\n },\n ) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.field = options?.field;\n this.issues = issues;\n this.value = options?.value;\n }\n}\n\nexport class ResponseStructureError extends FMODataError {\n readonly kind = \"ResponseStructureError\" as const;\n readonly expected: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n readonly received: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n constructor(expected: string, received: any) {\n super(`Invalid response structure: expected ${expected}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\nexport class RecordCountMismatchError extends FMODataError {\n readonly kind = \"RecordCountMismatchError\" as const;\n readonly expected: number | \"one\" | \"at-most-one\";\n readonly received: number;\n\n constructor(expected: number | \"one\" | \"at-most-one\", received: number) {\n const expectedStr = typeof expected === \"number\" ? expected : expected;\n super(`Expected ${expectedStr} record(s), but received ${received}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\nexport class InvalidLocationHeaderError extends FMODataError {\n readonly kind = \"InvalidLocationHeaderError\" as const;\n readonly locationHeader?: string;\n\n constructor(message: string, locationHeader?: string) {\n super(message);\n this.locationHeader = locationHeader;\n }\n}\n\nexport class ResponseParseError extends FMODataError {\n readonly kind = \"ResponseParseError\" as const;\n readonly url: string;\n readonly rawText?: string;\n\n constructor(url: string, message: string, options?: { rawText?: string; cause?: Error }) {\n super(message, options?.cause ? { cause: options.cause } : undefined);\n this.url = url;\n this.rawText = options?.rawText;\n }\n}\n\nexport class BatchTruncatedError extends FMODataError {\n readonly kind = \"BatchTruncatedError\" as const;\n readonly operationIndex: number;\n readonly failedAtIndex: number;\n\n constructor(operationIndex: number, failedAtIndex: number) {\n super(`Operation ${operationIndex} was not executed because operation ${failedAtIndex} failed`);\n this.operationIndex = operationIndex;\n this.failedAtIndex = failedAtIndex;\n }\n}\n\n// ============================================\n// Type Guards\n// ============================================\n\nexport function isHTTPError(error: unknown): error is HTTPError {\n return error instanceof HTTPError;\n}\n\nexport function isValidationError(error: unknown): error is ValidationError {\n return error instanceof ValidationError;\n}\n\nexport function isODataError(error: unknown): error is ODataError {\n return error instanceof ODataError;\n}\n\nexport function isSchemaLockedError(error: unknown): error is SchemaLockedError {\n return error instanceof SchemaLockedError;\n}\n\nexport function isResponseStructureError(error: unknown): error is ResponseStructureError {\n return error instanceof ResponseStructureError;\n}\n\nexport function isRecordCountMismatchError(error: unknown): error is RecordCountMismatchError {\n return error instanceof RecordCountMismatchError;\n}\n\nexport function isResponseParseError(error: unknown): error is ResponseParseError {\n return error instanceof ResponseParseError;\n}\n\nexport function isBatchTruncatedError(error: unknown): error is BatchTruncatedError {\n return error instanceof BatchTruncatedError;\n}\n\nexport function isFMODataError(error: unknown): error is FMODataError {\n return error instanceof FMODataError;\n}\n\n// ============================================\n// Union type for all possible errors\n// ============================================\n\n// Re-export ffetch errors (they'll be imported from @fetchkit/ffetch)\nexport type {\n AbortError,\n CircuitOpenError,\n NetworkError,\n RetryLimitError,\n TimeoutError,\n} from \"@fetchkit/ffetch\";\n\nexport type FMODataErrorType =\n | import(\"@fetchkit/ffetch\").TimeoutError\n | import(\"@fetchkit/ffetch\").AbortError\n | import(\"@fetchkit/ffetch\").NetworkError\n | import(\"@fetchkit/ffetch\").RetryLimitError\n | import(\"@fetchkit/ffetch\").CircuitOpenError\n | HTTPError\n | ODataError\n | SchemaLockedError\n | ValidationError\n | ResponseStructureError\n | RecordCountMismatchError\n | InvalidLocationHeaderError\n | ResponseParseError\n | BatchTruncatedError;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sources":["../../src/errors.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Base class for all fmodata errors\n */\nexport abstract class FMODataError extends Error {\n abstract readonly kind: string;\n readonly timestamp: Date;\n\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = this.constructor.name;\n this.timestamp = new Date();\n }\n}\n\n// ============================================\n// HTTP Errors (with status codes)\n// ============================================\n\nexport class HTTPError extends FMODataError {\n readonly kind = \"HTTPError\" as const;\n readonly url: string;\n readonly status: number;\n readonly statusText: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n readonly response?: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n constructor(url: string, status: number, statusText: string, response?: any) {\n super(`HTTP ${status} ${statusText} for ${url}`);\n this.url = url;\n this.status = status;\n this.statusText = statusText;\n this.response = response;\n }\n\n // Helper methods for common status checks\n is4xx(): boolean {\n return this.status >= 400 && this.status < 500;\n }\n\n is5xx(): boolean {\n return this.status >= 500 && this.status < 600;\n }\n\n isNotFound(): boolean {\n return this.status === 404;\n }\n\n isUnauthorized(): boolean {\n return this.status === 401;\n }\n\n isForbidden(): boolean {\n return this.status === 403;\n }\n}\n\n// ============================================\n// OData Specific Errors\n// ============================================\n\nexport class ODataError extends FMODataError {\n readonly kind = \"ODataError\" as const;\n readonly url: string;\n readonly code?: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n readonly details?: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n constructor(url: string, message: string, code?: string, details?: any) {\n super(`OData error: ${message}`);\n this.url = url;\n this.code = code;\n this.details = details;\n }\n}\n\nexport class SchemaLockedError extends FMODataError {\n readonly kind = \"SchemaLockedError\" as const;\n readonly url: string;\n readonly code: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n readonly details?: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic error details from OData API\n constructor(url: string, message: string, details?: any) {\n super(`OData error: ${message}`);\n this.url = url;\n this.code = \"303\";\n this.details = details;\n }\n}\n\n// ============================================\n// Validation Errors\n// ============================================\n\nexport class ValidationError extends FMODataError {\n readonly kind = \"ValidationError\" as const;\n readonly field?: string;\n readonly issues: readonly StandardSchemaV1.Issue[];\n readonly value?: unknown;\n\n constructor(\n message: string,\n issues: readonly StandardSchemaV1.Issue[],\n options?: {\n field?: string;\n value?: unknown;\n cause?: Error[\"cause\"];\n },\n ) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.field = options?.field;\n this.issues = issues;\n this.value = options?.value;\n }\n}\n\nexport class ResponseStructureError extends FMODataError {\n readonly kind = \"ResponseStructureError\" as const;\n readonly expected: string;\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n readonly received: any;\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API\n constructor(expected: string, received: any) {\n super(`Invalid response structure: expected ${expected}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\nexport class RecordCountMismatchError extends FMODataError {\n readonly kind = \"RecordCountMismatchError\" as const;\n readonly expected: number | \"one\" | \"at-most-one\";\n readonly received: number;\n\n constructor(expected: number | \"one\" | \"at-most-one\", received: number) {\n const expectedStr = typeof expected === \"number\" ? expected : expected;\n super(`Expected ${expectedStr} record(s), but received ${received}`);\n this.expected = expected;\n this.received = received;\n }\n}\n\nexport class InvalidLocationHeaderError extends FMODataError {\n readonly kind = \"InvalidLocationHeaderError\" as const;\n readonly locationHeader?: string;\n\n constructor(message: string, locationHeader?: string) {\n super(message);\n this.locationHeader = locationHeader;\n }\n}\n\nexport class ResponseParseError extends FMODataError {\n readonly kind = \"ResponseParseError\" as const;\n readonly url: string;\n readonly rawText?: string;\n\n constructor(url: string, message: string, options?: { rawText?: string; cause?: Error }) {\n super(message, options?.cause ? { cause: options.cause } : undefined);\n this.url = url;\n this.rawText = options?.rawText;\n }\n}\n\nexport class BatchTruncatedError extends FMODataError {\n readonly kind = \"BatchTruncatedError\" as const;\n readonly operationIndex: number;\n readonly failedAtIndex: number;\n\n constructor(operationIndex: number, failedAtIndex: number) {\n super(`Operation ${operationIndex} was not executed because operation ${failedAtIndex} failed`);\n this.operationIndex = operationIndex;\n this.failedAtIndex = failedAtIndex;\n }\n}\n\n// ============================================\n// Type Guards\n// ============================================\n\nexport function isHTTPError(error: unknown): error is HTTPError {\n return error instanceof HTTPError;\n}\n\nexport function isValidationError(error: unknown): error is ValidationError {\n return error instanceof ValidationError;\n}\n\nexport function isODataError(error: unknown): error is ODataError {\n return error instanceof ODataError;\n}\n\nexport function isSchemaLockedError(error: unknown): error is SchemaLockedError {\n return error instanceof SchemaLockedError;\n}\n\nexport function isResponseStructureError(error: unknown): error is ResponseStructureError {\n return error instanceof ResponseStructureError;\n}\n\nexport function isRecordCountMismatchError(error: unknown): error is RecordCountMismatchError {\n return error instanceof RecordCountMismatchError;\n}\n\nexport function isResponseParseError(error: unknown): error is ResponseParseError {\n return error instanceof ResponseParseError;\n}\n\nexport function isBatchTruncatedError(error: unknown): error is BatchTruncatedError {\n return error instanceof BatchTruncatedError;\n}\n\nexport function isFMODataError(error: unknown): error is FMODataError {\n return error instanceof FMODataError;\n}\n\n// ============================================\n// Union type for all possible errors\n// ============================================\n\n// Re-export ffetch errors (they'll be imported from @fetchkit/ffetch)\nexport type {\n AbortError,\n CircuitOpenError,\n NetworkError,\n RetryLimitError,\n TimeoutError,\n} from \"@fetchkit/ffetch\";\n\nexport type FMODataErrorType =\n | import(\"@fetchkit/ffetch\").TimeoutError\n | import(\"@fetchkit/ffetch\").AbortError\n | import(\"@fetchkit/ffetch\").NetworkError\n | import(\"@fetchkit/ffetch\").RetryLimitError\n | import(\"@fetchkit/ffetch\").CircuitOpenError\n | HTTPError\n | ODataError\n | SchemaLockedError\n | ValidationError\n | ResponseStructureError\n | RecordCountMismatchError\n | InvalidLocationHeaderError\n | ResponseParseError\n | BatchTruncatedError;\n"],"names":[],"mappings":"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"}
@@ -1 +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(currentLogLevel: LogLevel, logLevel: LogLevel): 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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic log arguments from user code\n log?: ((level: Exclude<LogLevel, \"success\">, message: string, ...args: any[]) => void) | undefined;\n}\n\nexport type LogHandlerParams = Parameters<NonNullable<Logger[\"log\"]>> extends [LogLevel, ...infer Rest] ? Rest : 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 = (level: LogLevel, message: string, colorsEnabled: boolean): 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]:${TTY_COLORS.reset} ${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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic log arguments from user code\n const LogFunc = (level: LogLevel, message: string, args: any[] = []): 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) => [level, (...[message, ...args]: LogHandlerParams) => LogFunc(level, message, args)]),\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,iBAAiB,iBAA2B,UAA6B;AACvF,SAAO,OAAO,QAAQ,QAAQ,KAAK,OAAO,QAAQ,eAAe;AACnE;AAYA,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,CAAC,OAAiB,SAAiB,kBAAmC;AAC1F,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,aAAa,WAAW,KAAK,IAAI,OAAO;AAAA,EAAA;AAGxG,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;AAGjD,QAAM,UAAU,CAAC,OAAiB,SAAiB,OAAc,CAAA,MAAa;AAC5E,QAAI,EAAE,WAAW,iBAAiB,UAAU,KAAK,IAAI;AACnD;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,CAAC,OAAO,IAAI,CAAC,SAAY,OAAI,MAAwB,QAAQ,OAAO,SAAS,IAAI,CAAC,CAAC;AAAA,EAC3G;AAEO,SAAA;AAAA,IACL,GAAGA;AAAAA,IACH,IAAI,QAAQ;AACH,aAAA;AAAA,IAAA;AAAA,EAEX;AACF;AAEsB,aAAa;"}
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(currentLogLevel: LogLevel, logLevel: LogLevel): 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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic log arguments from user code\n log?: ((level: Exclude<LogLevel, \"success\">, message: string, ...args: any[]) => void) | undefined;\n}\n\nexport type LogHandlerParams = Parameters<NonNullable<Logger[\"log\"]>> extends [LogLevel, ...infer Rest] ? Rest : 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 = (level: LogLevel, message: string, colorsEnabled: boolean): 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]:${TTY_COLORS.reset} ${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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic log arguments from user code\n const LogFunc = (level: LogLevel, message: string, args: any[] = []): 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) => [level, (...[message, ...args]: LogHandlerParams) => LogFunc(level, message, args)]),\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;AAE3D,SAAS,iBAAiB,iBAA2B,UAA6B;AACvF,SAAO,OAAO,QAAQ,QAAQ,KAAK,OAAO,QAAQ,eAAe;AACnE;AAYA,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,CAAC,OAAiB,SAAiB,kBAAmC;AAC1F,QAAM,aAAY,oBAAI,KAAA,GAAO,YAAA;AAE7B,MAAI,eAAe;AACjB,WAAO,GAAG,WAAW,GAAG,GAAG,SAAS,GAAG,WAAW,KAAK,IACrD,YAAY,KAAK,CACnB,GAAG,MAAM,YAAA,CAAa,GAAG,WAAW,KAAK,IAAI,WAAW,MAAM,aAAa,WAAW,KAAK,IAAI,OAAO;AAAA,EACxG;AAEA,SAAO,GAAG,SAAS,IAAI,MAAM,aAAa,eAAe,OAAO;AAClE;AAQO,MAAM,eAAe,CAAC,YAAiD;AAC5E,QAAM,WAAU,mCAAS,cAAa;AACtC,QAAM,YAAW,mCAAS,UAAS;AAEnC,QAAM,iBAAgB,mCAAS,mBAAkB;AAGjD,QAAM,UAAU,CAAC,OAAiB,SAAiB,OAAc,CAAA,MAAa;AAC5E,QAAI,EAAE,WAAW,iBAAiB,UAAU,KAAK,IAAI;AACnD;AAAA,IACF;AAEA,UAAM,mBAAmB,cAAc,OAAO,SAAS,aAAa;AAEpE,QAAI,CAAC,WAAW,OAAO,QAAQ,QAAQ,YAAY;AACjD,UAAI,UAAU,SAAS;AACrB,gBAAQ,MAAM,kBAAkB,GAAG,IAAI;AAAA,MACzC,WAAW,UAAU,QAAQ;AAC3B,gBAAQ,KAAK,kBAAkB,GAAG,IAAI;AAAA,MACxC,OAAO;AACL,gBAAQ,IAAI,kBAAkB,GAAG,IAAI;AAAA,MACvC;AACA;AAAA,IACF;AAEA,YAAQ,IAAI,UAAU,YAAY,SAAS,OAAO,SAAS,GAAG,IAAI;AAAA,EACpE;AAEA,QAAMA,UAAS,OAAO;AAAA,IACpB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,CAAC,SAAY,OAAI,MAAwB,QAAQ,OAAO,SAAS,IAAI,CAAC,CAAC;AAAA,EAAA;AAG3G,SAAO;AAAA,IACL,GAAGA;AAAAA,IACH,IAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,EAAA;AAEJ;AAEsB,aAAA;"}
@@ -1 +1 @@
1
- {"version":3,"file":"column.js","sources":["../../../src/orm/column.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Column represents a type-safe reference to a table field.\n * Used in queries, filters, and operators to provide autocomplete and type checking.\n *\n * @template TOutput - The TypeScript type when reading from the database (output type)\n * @template TInput - The TypeScript type when writing to the database (input type, for filters)\n * @template TableName - The table name as a string literal type (for validation)\n * @template IsContainer - Whether this column represents a container field (cannot be selected)\n */\nexport class Column<\n // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\n TOutput = any,\n TInput = TOutput,\n TableName extends string = string,\n IsContainer extends boolean = false,\n> {\n readonly fieldName: string;\n readonly entityId?: `FMFID:${string}`;\n readonly tableName: TableName;\n readonly tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n readonly inputValidator?: StandardSchemaV1<TInput, any>;\n\n // Phantom types for TypeScript inference - never actually hold values\n readonly _phantomOutput!: TOutput;\n readonly _phantomInput!: TInput;\n readonly _isContainer!: IsContainer;\n\n constructor(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TableName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n }) {\n this.fieldName = config.fieldName;\n this.entityId = config.entityId;\n this.tableName = config.tableName;\n this.tableEntityId = config.tableEntityId;\n this.inputValidator = config.inputValidator;\n }\n\n /**\n * Get the field identifier (entity ID if available, otherwise field name).\n * Used when building OData queries.\n */\n getFieldIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.entityId) {\n return this.entityId;\n }\n return this.fieldName;\n }\n\n /**\n * Get the table identifier (entity ID if available, otherwise table name).\n * Used when building OData queries.\n */\n getTableIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.tableEntityId) {\n return this.tableEntityId;\n }\n return this.tableName;\n }\n\n /**\n * Check if this column is from a specific table.\n * Useful for validation in cross-table operations.\n */\n isFromTable(tableName: string): boolean {\n return this.tableName === tableName;\n }\n\n /**\n * Create a string representation for debugging.\n */\n toString(): string {\n return `${this.tableName}.${this.fieldName}`;\n }\n}\n\n/**\n * Type guard to check if a value is a Column instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type, generic constraint accepting any Column configuration\nexport function isColumn(value: any): value is Column<any, any, any, any> {\n return value instanceof Column;\n}\n\n/**\n * Create a Column with proper type inference from the inputValidator.\n * This helper ensures TypeScript can infer TInput from the validator's input type.\n * @internal\n */\nexport function createColumn<TOutput, TInput, TName extends string, IsContainer extends boolean = false>(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n}): Column<TOutput, TInput, TName, IsContainer> {\n return new Column(config) as Column<TOutput, TInput, TName, IsContainer>;\n}\n"],"names":[],"mappings":";;;AAWO,MAAM,OAMX;AAAA,EAaA,YAAY,QAOT;AAnBM;AACA;AACA;AACA;AAEA;AAAA;AAGA;AAAA;AACA;AACA;AAUP,SAAK,YAAY,OAAO;AACxB,SAAK,WAAW,OAAO;AACvB,SAAK,YAAY,OAAO;AACxB,SAAK,gBAAgB,OAAO;AAC5B,SAAK,iBAAiB,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/B,mBAAmB,cAAgC;AAC7C,QAAA,gBAAgB,KAAK,UAAU;AACjC,aAAO,KAAK;AAAA,IAAA;AAEd,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,mBAAmB,cAAgC;AAC7C,QAAA,gBAAgB,KAAK,eAAe;AACtC,aAAO,KAAK;AAAA,IAAA;AAEd,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,YAAY,WAA4B;AACtC,WAAO,KAAK,cAAc;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAM5B,WAAmB;AACjB,WAAO,GAAG,KAAK,SAAS,IAAI,KAAK,SAAS;AAAA,EAAA;AAE9C;AAMO,SAAS,SAAS,OAAiD;AACxE,SAAO,iBAAiB;AAC1B;"}
1
+ {"version":3,"file":"column.js","sources":["../../../src/orm/column.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Column represents a type-safe reference to a table field.\n * Used in queries, filters, and operators to provide autocomplete and type checking.\n *\n * @template TOutput - The TypeScript type when reading from the database (output type)\n * @template TInput - The TypeScript type when writing to the database (input type, for filters)\n * @template TableName - The table name as a string literal type (for validation)\n * @template IsContainer - Whether this column represents a container field (cannot be selected)\n */\nexport class Column<\n // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\n TOutput = any,\n TInput = TOutput,\n TableName extends string = string,\n IsContainer extends boolean = false,\n> {\n readonly fieldName: string;\n readonly entityId?: `FMFID:${string}`;\n readonly tableName: TableName;\n readonly tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n readonly inputValidator?: StandardSchemaV1<TInput, any>;\n\n // Phantom types for TypeScript inference - never actually hold values\n readonly _phantomOutput!: TOutput;\n readonly _phantomInput!: TInput;\n readonly _isContainer!: IsContainer;\n\n constructor(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TableName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n }) {\n this.fieldName = config.fieldName;\n this.entityId = config.entityId;\n this.tableName = config.tableName;\n this.tableEntityId = config.tableEntityId;\n this.inputValidator = config.inputValidator;\n }\n\n /**\n * Get the field identifier (entity ID if available, otherwise field name).\n * Used when building OData queries.\n */\n getFieldIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.entityId) {\n return this.entityId;\n }\n return this.fieldName;\n }\n\n /**\n * Get the table identifier (entity ID if available, otherwise table name).\n * Used when building OData queries.\n */\n getTableIdentifier(useEntityIds?: boolean): string {\n if (useEntityIds && this.tableEntityId) {\n return this.tableEntityId;\n }\n return this.tableName;\n }\n\n /**\n * Check if this column is from a specific table.\n * Useful for validation in cross-table operations.\n */\n isFromTable(tableName: string): boolean {\n return this.tableName === tableName;\n }\n\n /**\n * Create a string representation for debugging.\n */\n toString(): string {\n return `${this.tableName}.${this.fieldName}`;\n }\n}\n\n/**\n * Type guard to check if a value is a Column instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type, generic constraint accepting any Column configuration\nexport function isColumn(value: any): value is Column<any, any, any, any> {\n return value instanceof Column;\n}\n\n/**\n * Create a Column with proper type inference from the inputValidator.\n * This helper ensures TypeScript can infer TInput from the validator's input type.\n * @internal\n */\nexport function createColumn<TOutput, TInput, TName extends string, IsContainer extends boolean = false>(config: {\n fieldName: string;\n entityId?: `FMFID:${string}`;\n tableName: TName;\n tableEntityId?: `FMTID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n inputValidator?: StandardSchemaV1<TInput, any>;\n}): Column<TOutput, TInput, TName, IsContainer> {\n return new Column(config) as Column<TOutput, TInput, TName, IsContainer>;\n}\n"],"names":[],"mappings":";;;AAWO,MAAM,OAMX;AAAA,EAaA,YAAY,QAOT;AAnBM;AACA;AACA;AACA;AAEA;AAAA;AAGA;AAAA;AACA;AACA;AAUP,SAAK,YAAY,OAAO;AACxB,SAAK,WAAW,OAAO;AACvB,SAAK,YAAY,OAAO;AACxB,SAAK,gBAAgB,OAAO;AAC5B,SAAK,iBAAiB,OAAO;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,cAAgC;AACjD,QAAI,gBAAgB,KAAK,UAAU;AACjC,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,mBAAmB,cAAgC;AACjD,QAAI,gBAAgB,KAAK,eAAe;AACtC,aAAO,KAAK;AAAA,IACd;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,WAA4B;AACtC,WAAO,KAAK,cAAc;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAKA,WAAmB;AACjB,WAAO,GAAG,KAAK,SAAS,IAAI,KAAK,SAAS;AAAA,EAC5C;AACF;AAMO,SAAS,SAAS,OAAiD;AACxE,SAAO,iBAAiB;AAC1B;"}
@@ -1 +1 @@
1
- {"version":3,"file":"field-builders.js","sources":["../../../src/orm/field-builders.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Branded type for container field's database type.\n * This allows TypeScript to distinguish container fields from regular string fields\n * at the type level, enabling compile-time exclusion from select operations.\n */\nexport type ContainerDbType = string & { readonly __container: true };\n\n/**\n * FieldBuilder provides a fluent API for defining table fields with type-safe metadata.\n * Supports chaining methods to configure primary keys, nullability, read-only status, entity IDs, and validators.\n *\n * @template TOutput - The output type after applying outputValidator (what you get when reading)\n * @template TInput - The input type after applying inputValidator (what you pass when writing)\n * @template TDbType - The database type (what FileMaker stores/expects)\n * @template TReadOnly - Whether this field is read-only (for type-level exclusion from insert/update)\n */\n// biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\nexport class FieldBuilder<TOutput = any, TInput = TOutput, TDbType = TOutput, TReadOnly extends boolean = false> {\n private _primaryKey = false;\n private _notNull = false;\n private _readOnly = false;\n private _entityId?: `FMFID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n private _outputValidator?: StandardSchemaV1<any, TOutput>;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n private _inputValidator?: StandardSchemaV1<TInput, any>;\n private readonly _fieldType: string;\n private _comment?: string;\n\n constructor(fieldType: string) {\n this._fieldType = fieldType;\n }\n\n /**\n * Mark this field as the primary key for the table.\n * Primary keys are automatically read-only and non-nullable.\n */\n primaryKey(): FieldBuilder<NonNullable<TOutput>, NonNullable<TInput>, NonNullable<TDbType>, true> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._primaryKey = true;\n builder._notNull = true; // Primary keys are automatically non-nullable\n builder._readOnly = true; // Primary keys are automatically read-only\n return builder;\n }\n\n /**\n * Mark this field as non-nullable.\n * Updates the type to exclude null/undefined.\n */\n notNull(): FieldBuilder<NonNullable<TOutput>, NonNullable<TInput>, NonNullable<TDbType>, TReadOnly> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._notNull = true;\n return builder;\n }\n\n /**\n * Mark this field as read-only.\n * Read-only fields are excluded from insert and update operations.\n */\n readOnly(): FieldBuilder<TOutput, TInput, TDbType, true> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._readOnly = true;\n return builder;\n }\n\n /**\n * Assign a FileMaker field ID (FMFID) to this field.\n * When useEntityIds is enabled, this ID will be used in API requests instead of the field name.\n */\n entityId(id: `FMFID:${string}`): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {\n const builder = this._clone();\n builder._entityId = id;\n return builder;\n }\n\n /**\n * Set a validator for the output (reading from database).\n * The output validator transforms/validates data coming FROM the database in list or get operations.\n *\n * @example\n * numberField().readValidator(z.coerce.boolean())\n * // FileMaker returns 0/1, you get true/false\n */\n readValidator<O, VInput = TDbType>(\n validator: StandardSchemaV1<VInput, O>,\n ): FieldBuilder<O, TInput, TDbType, TReadOnly> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._outputValidator = validator;\n return builder;\n }\n\n /**\n * Set a validator for the input (writing to database).\n * The input validator transforms/validates data going TO the database in insert, update, and filter operations.\n *\n * @example\n * numberField().writeValidator(z.boolean().transform(v => v ? 1 : 0))\n * // You pass true/false, FileMaker gets 1/0\n */\n writeValidator<I>(validator: StandardSchemaV1<I, TDbType>): FieldBuilder<TOutput, I, TDbType, TReadOnly> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._inputValidator = validator;\n return builder;\n }\n\n /**\n * Add a comment to this field for metadata purposes.\n * This helps future developers understand the purpose of the field.\n *\n * @example\n * textField().comment(\"Account name of the user who last modified each record\")\n */\n comment(comment: string): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {\n const builder = this._clone();\n builder._comment = comment;\n return builder;\n }\n\n /**\n * Get the metadata configuration for this field.\n * @internal Used by fmTableOccurrence to extract field configuration\n */\n _getConfig() {\n return {\n fieldType: this._fieldType,\n primaryKey: this._primaryKey,\n notNull: this._notNull,\n readOnly: this._readOnly,\n entityId: this._entityId,\n outputValidator: this._outputValidator,\n inputValidator: this._inputValidator,\n comment: this._comment,\n };\n }\n\n /**\n * Clone this builder to allow immutable chaining.\n * @private\n */\n private _clone(): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {\n const builder = new FieldBuilder<TOutput, TInput, TDbType, TReadOnly>(this._fieldType);\n builder._primaryKey = this._primaryKey;\n builder._notNull = this._notNull;\n builder._readOnly = this._readOnly;\n builder._entityId = this._entityId;\n builder._outputValidator = this._outputValidator;\n builder._inputValidator = this._inputValidator;\n builder._comment = this._comment;\n return builder;\n }\n}\n\n/**\n * Create a text field (Edm.String in FileMaker OData).\n * By default, text fields are nullable.\n *\n * @example\n * textField() // string | null\n * textField().notNull() // string\n * textField().entityId(\"FMFID:1\") // with entity ID\n */\nexport function textField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"text\");\n}\n\n/**\n * Create a number field (Edm.Decimal in FileMaker OData).\n * By default, number fields are nullable.\n *\n * @example\n * numberField() // number | null\n * numberField().notNull() // number\n * numberField().outputValidator(z.coerce.boolean()) // transform to boolean on read\n */\nexport function numberField(): FieldBuilder<number | null, number | null, number | null, false> {\n return new FieldBuilder<number | null, number | null, number | null, false>(\"number\");\n}\n\n/**\n * Create a date field (Edm.Date in FileMaker OData).\n * By default, date fields are nullable and represented as ISO date strings (YYYY-MM-DD).\n *\n * @example\n * dateField() // string | null (ISO date format)\n * dateField().notNull() // string\n */\nexport function dateField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"date\");\n}\n\n/**\n * Create a time field (Edm.TimeOfDay in FileMaker OData).\n * By default, time fields are nullable and represented as ISO time strings (HH:mm:ss).\n *\n * @example\n * timeField() // string | null (ISO time format)\n * timeField().notNull() // string\n */\nexport function timeField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"time\");\n}\n\n/**\n * Create a timestamp field (Edm.DateTimeOffset in FileMaker OData).\n * By default, timestamp fields are nullable and represented as ISO 8601 strings.\n *\n * @example\n * timestampField() // string | null (ISO 8601 format)\n * timestampField().notNull() // string\n * timestampField().readOnly() // typical for CreationTimestamp\n */\nexport function timestampField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"timestamp\");\n}\n\n/**\n * Create a container field (Edm.Stream in FileMaker OData).\n * Container fields store binary data and are represented as base64 strings in the API.\n * By default, container fields are nullable.\n *\n * Note: Container fields cannot be selected via .select() - they can only be accessed\n * via .getSingleField() due to FileMaker OData API limitations.\n *\n * @example\n * containerField() // string | null (base64 encoded)\n * containerField().notNull() // string\n */\nexport function containerField(): FieldBuilder<string | null, string | null, ContainerDbType | null, false> {\n return new FieldBuilder<string | null, string | null, ContainerDbType | null, false>(\"container\");\n}\n\n/**\n * Create a calculated field (read-only field computed by FileMaker).\n * Calculated fields are automatically marked as read-only.\n *\n * @example\n * calcField() // string | null\n * calcField().notNull() // string\n */\nexport function calcField(): FieldBuilder<string | null, string | null, string | null, true> {\n const builder = new FieldBuilder<string | null, string | null, string | null, false>(\"calculated\");\n return builder.readOnly();\n}\n"],"names":[],"mappings":";;;AAmBO,MAAM,aAAoG;AAAA,EAY/G,YAAY,WAAmB;AAXvB,uCAAc;AACd,oCAAW;AACX,qCAAY;AACZ;AAEA;AAAA;AAEA;AAAA;AACS;AACT;AAGN,SAAK,aAAa;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpB,aAAkG;AAE1F,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,cAAc;AACtB,YAAQ,WAAW;AACnB,YAAQ,YAAY;AACb,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,UAAoG;AAE5F,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,WAAW;AACZ,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,WAAyD;AAEjD,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,YAAY;AACb,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,SAAS,IAA0E;AAC3E,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,YAAY;AACb,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWT,cACE,WAC6C;AAEvC,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,mBAAmB;AACpB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWT,eAAkB,WAAuF;AAEjG,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,kBAAkB;AACnB,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUT,QAAQ,SAAoE;AACpE,UAAA,UAAU,KAAK,OAAO;AAC5B,YAAQ,WAAW;AACZ,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT,aAAa;AACJ,WAAA;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,MACjB,SAAS,KAAK;AAAA,MACd,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,iBAAiB,KAAK;AAAA,MACtB,gBAAgB,KAAK;AAAA,MACrB,SAAS,KAAK;AAAA,IAChB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,SAA4D;AAClE,UAAM,UAAU,IAAI,aAAkD,KAAK,UAAU;AACrF,YAAQ,cAAc,KAAK;AAC3B,YAAQ,WAAW,KAAK;AACxB,YAAQ,YAAY,KAAK;AACzB,YAAQ,YAAY,KAAK;AACzB,YAAQ,mBAAmB,KAAK;AAChC,YAAQ,kBAAkB,KAAK;AAC/B,YAAQ,WAAW,KAAK;AACjB,WAAA;AAAA,EAAA;AAEX;AAWO,SAAS,YAA8E;AACrF,SAAA,IAAI,aAAiE,MAAM;AACpF;AAWO,SAAS,cAAgF;AACvF,SAAA,IAAI,aAAiE,QAAQ;AACtF;AAUO,SAAS,YAA8E;AACrF,SAAA,IAAI,aAAiE,MAAM;AACpF;AAUO,SAAS,YAA8E;AACrF,SAAA,IAAI,aAAiE,MAAM;AACpF;AAWO,SAAS,iBAAmF;AAC1F,SAAA,IAAI,aAAiE,WAAW;AACzF;AAcO,SAAS,iBAA4F;AACnG,SAAA,IAAI,aAA0E,WAAW;AAClG;AAUO,SAAS,YAA6E;AACrF,QAAA,UAAU,IAAI,aAAiE,YAAY;AACjG,SAAO,QAAQ,SAAS;AAC1B;"}
1
+ {"version":3,"file":"field-builders.js","sources":["../../../src/orm/field-builders.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Branded type for container field's database type.\n * This allows TypeScript to distinguish container fields from regular string fields\n * at the type level, enabling compile-time exclusion from select operations.\n */\nexport type ContainerDbType = string & { readonly __container: true };\n\n/**\n * FieldBuilder provides a fluent API for defining table fields with type-safe metadata.\n * Supports chaining methods to configure primary keys, nullability, read-only status, entity IDs, and validators.\n *\n * @template TOutput - The output type after applying outputValidator (what you get when reading)\n * @template TInput - The input type after applying inputValidator (what you pass when writing)\n * @template TDbType - The database type (what FileMaker stores/expects)\n * @template TReadOnly - Whether this field is read-only (for type-level exclusion from insert/update)\n */\n// biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility\nexport class FieldBuilder<TOutput = any, TInput = TOutput, TDbType = TOutput, TReadOnly extends boolean = false> {\n private _primaryKey = false;\n private _notNull = false;\n private _readOnly = false;\n private _entityId?: `FMFID:${string}`;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n private _outputValidator?: StandardSchemaV1<any, TOutput>;\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n private _inputValidator?: StandardSchemaV1<TInput, any>;\n private readonly _fieldType: string;\n private _comment?: string;\n\n constructor(fieldType: string) {\n this._fieldType = fieldType;\n }\n\n /**\n * Mark this field as the primary key for the table.\n * Primary keys are automatically read-only and non-nullable.\n */\n primaryKey(): FieldBuilder<NonNullable<TOutput>, NonNullable<TInput>, NonNullable<TDbType>, true> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._primaryKey = true;\n builder._notNull = true; // Primary keys are automatically non-nullable\n builder._readOnly = true; // Primary keys are automatically read-only\n return builder;\n }\n\n /**\n * Mark this field as non-nullable.\n * Updates the type to exclude null/undefined.\n */\n notNull(): FieldBuilder<NonNullable<TOutput>, NonNullable<TInput>, NonNullable<TDbType>, TReadOnly> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._notNull = true;\n return builder;\n }\n\n /**\n * Mark this field as read-only.\n * Read-only fields are excluded from insert and update operations.\n */\n readOnly(): FieldBuilder<TOutput, TInput, TDbType, true> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._readOnly = true;\n return builder;\n }\n\n /**\n * Assign a FileMaker field ID (FMFID) to this field.\n * When useEntityIds is enabled, this ID will be used in API requests instead of the field name.\n */\n entityId(id: `FMFID:${string}`): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {\n const builder = this._clone();\n builder._entityId = id;\n return builder;\n }\n\n /**\n * Set a validator for the output (reading from database).\n * The output validator transforms/validates data coming FROM the database in list or get operations.\n *\n * @example\n * numberField().readValidator(z.coerce.boolean())\n * // FileMaker returns 0/1, you get true/false\n */\n readValidator<O, VInput = TDbType>(\n validator: StandardSchemaV1<VInput, O>,\n ): FieldBuilder<O, TInput, TDbType, TReadOnly> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._outputValidator = validator;\n return builder;\n }\n\n /**\n * Set a validator for the input (writing to database).\n * The input validator transforms/validates data going TO the database in insert, update, and filter operations.\n *\n * @example\n * numberField().writeValidator(z.boolean().transform(v => v ? 1 : 0))\n * // You pass true/false, FileMaker gets 1/0\n */\n writeValidator<I>(validator: StandardSchemaV1<I, TDbType>): FieldBuilder<TOutput, I, TDbType, TReadOnly> {\n // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation\n const builder = this._clone() as any;\n builder._inputValidator = validator;\n return builder;\n }\n\n /**\n * Add a comment to this field for metadata purposes.\n * This helps future developers understand the purpose of the field.\n *\n * @example\n * textField().comment(\"Account name of the user who last modified each record\")\n */\n comment(comment: string): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {\n const builder = this._clone();\n builder._comment = comment;\n return builder;\n }\n\n /**\n * Get the metadata configuration for this field.\n * @internal Used by fmTableOccurrence to extract field configuration\n */\n _getConfig() {\n return {\n fieldType: this._fieldType,\n primaryKey: this._primaryKey,\n notNull: this._notNull,\n readOnly: this._readOnly,\n entityId: this._entityId,\n outputValidator: this._outputValidator,\n inputValidator: this._inputValidator,\n comment: this._comment,\n };\n }\n\n /**\n * Clone this builder to allow immutable chaining.\n * @private\n */\n private _clone(): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {\n const builder = new FieldBuilder<TOutput, TInput, TDbType, TReadOnly>(this._fieldType);\n builder._primaryKey = this._primaryKey;\n builder._notNull = this._notNull;\n builder._readOnly = this._readOnly;\n builder._entityId = this._entityId;\n builder._outputValidator = this._outputValidator;\n builder._inputValidator = this._inputValidator;\n builder._comment = this._comment;\n return builder;\n }\n}\n\n/**\n * Create a text field (Edm.String in FileMaker OData).\n * By default, text fields are nullable.\n *\n * @example\n * textField() // string | null\n * textField().notNull() // string\n * textField().entityId(\"FMFID:1\") // with entity ID\n */\nexport function textField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"text\");\n}\n\n/**\n * Create a number field (Edm.Decimal in FileMaker OData).\n * By default, number fields are nullable.\n *\n * @example\n * numberField() // number | null\n * numberField().notNull() // number\n * numberField().outputValidator(z.coerce.boolean()) // transform to boolean on read\n */\nexport function numberField(): FieldBuilder<number | null, number | null, number | null, false> {\n return new FieldBuilder<number | null, number | null, number | null, false>(\"number\");\n}\n\n/**\n * Create a date field (Edm.Date in FileMaker OData).\n * By default, date fields are nullable and represented as ISO date strings (YYYY-MM-DD).\n *\n * @example\n * dateField() // string | null (ISO date format)\n * dateField().notNull() // string\n */\nexport function dateField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"date\");\n}\n\n/**\n * Create a time field (Edm.TimeOfDay in FileMaker OData).\n * By default, time fields are nullable and represented as ISO time strings (HH:mm:ss).\n *\n * @example\n * timeField() // string | null (ISO time format)\n * timeField().notNull() // string\n */\nexport function timeField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"time\");\n}\n\n/**\n * Create a timestamp field (Edm.DateTimeOffset in FileMaker OData).\n * By default, timestamp fields are nullable and represented as ISO 8601 strings.\n *\n * @example\n * timestampField() // string | null (ISO 8601 format)\n * timestampField().notNull() // string\n * timestampField().readOnly() // typical for CreationTimestamp\n */\nexport function timestampField(): FieldBuilder<string | null, string | null, string | null, false> {\n return new FieldBuilder<string | null, string | null, string | null, false>(\"timestamp\");\n}\n\n/**\n * Create a container field (Edm.Stream in FileMaker OData).\n * Container fields store binary data and are represented as base64 strings in the API.\n * By default, container fields are nullable.\n *\n * Note: Container fields cannot be selected via .select() - they can only be accessed\n * via .getSingleField() due to FileMaker OData API limitations.\n *\n * @example\n * containerField() // string | null (base64 encoded)\n * containerField().notNull() // string\n */\nexport function containerField(): FieldBuilder<string | null, string | null, ContainerDbType | null, false> {\n return new FieldBuilder<string | null, string | null, ContainerDbType | null, false>(\"container\");\n}\n\n/**\n * Create a calculated field (read-only field computed by FileMaker).\n * Calculated fields are automatically marked as read-only.\n *\n * @example\n * calcField() // string | null\n * calcField().notNull() // string\n */\nexport function calcField(): FieldBuilder<string | null, string | null, string | null, true> {\n const builder = new FieldBuilder<string | null, string | null, string | null, false>(\"calculated\");\n return builder.readOnly();\n}\n"],"names":[],"mappings":";;;AAmBO,MAAM,aAAoG;AAAA,EAY/G,YAAY,WAAmB;AAXvB,uCAAc;AACd,oCAAW;AACX,qCAAY;AACZ;AAEA;AAAA;AAEA;AAAA;AACS;AACT;AAGN,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAkG;AAEhG,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,cAAc;AACtB,YAAQ,WAAW;AACnB,YAAQ,YAAY;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAoG;AAElG,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,WAAW;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAAyD;AAEvD,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,YAAY;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,IAA0E;AACjF,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,YAAY;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,cACE,WAC6C;AAE7C,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,mBAAmB;AAC3B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,eAAkB,WAAuF;AAEvG,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,kBAAkB;AAC1B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,SAAoE;AAC1E,UAAM,UAAU,KAAK,OAAA;AACrB,YAAQ,WAAW;AACnB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa;AACX,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB,YAAY,KAAK;AAAA,MACjB,SAAS,KAAK;AAAA,MACd,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,iBAAiB,KAAK;AAAA,MACtB,gBAAgB,KAAK;AAAA,MACrB,SAAS,KAAK;AAAA,IAAA;AAAA,EAElB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,SAA4D;AAClE,UAAM,UAAU,IAAI,aAAkD,KAAK,UAAU;AACrF,YAAQ,cAAc,KAAK;AAC3B,YAAQ,WAAW,KAAK;AACxB,YAAQ,YAAY,KAAK;AACzB,YAAQ,YAAY,KAAK;AACzB,YAAQ,mBAAmB,KAAK;AAChC,YAAQ,kBAAkB,KAAK;AAC/B,YAAQ,WAAW,KAAK;AACxB,WAAO;AAAA,EACT;AACF;AAWO,SAAS,YAA8E;AAC5F,SAAO,IAAI,aAAiE,MAAM;AACpF;AAWO,SAAS,cAAgF;AAC9F,SAAO,IAAI,aAAiE,QAAQ;AACtF;AAUO,SAAS,YAA8E;AAC5F,SAAO,IAAI,aAAiE,MAAM;AACpF;AAUO,SAAS,YAA8E;AAC5F,SAAO,IAAI,aAAiE,MAAM;AACpF;AAWO,SAAS,iBAAmF;AACjG,SAAO,IAAI,aAAiE,WAAW;AACzF;AAcO,SAAS,iBAA4F;AAC1G,SAAO,IAAI,aAA0E,WAAW;AAClG;AAUO,SAAS,YAA6E;AAC3F,QAAM,UAAU,IAAI,aAAiE,YAAY;AACjG,SAAO,QAAQ,SAAA;AACjB;"}
@@ -1 +1 @@
1
- {"version":3,"file":"operators.js","sources":["../../../src/orm/operators.ts"],"sourcesContent":["import { needsFieldQuoting } from \"../client/builders/select-utils\";\nimport 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 readonly operator: string;\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n readonly operands: (Column | any | FilterExpression)[];\n\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n constructor(operator: string, operands: (Column | any | FilterExpression)[]) {\n this.operator = operator;\n this.operands = operands;\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 let columnForValue: typeof left | typeof right | undefined;\n if (isColumn(left) && !isColumn(right)) {\n columnForValue = left;\n } else if (isColumn(right) && !isColumn(left)) {\n columnForValue = right;\n } else {\n columnForValue = undefined;\n }\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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).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 // biome-ignore lint/suspicious/noExplicitAny: Operand can be Column, FilterExpression, or any value type\n operand: any,\n useEntityIds?: boolean, // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\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) ? `\"${fieldIdentifier}\"` : 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 column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\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 column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\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>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): 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>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): 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>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): 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>(column: Column<TOutput, TInput>, values: NoInfer<TInput>[]): 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>(column: Column<TOutput, TInput>): 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>(column: Column<TOutput, TInput>): 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 // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n readonly column: Column<any, any, TableName>;\n readonly direction: \"asc\" | \"desc\";\n\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n constructor(column: Column<any, any, TableName>, direction: \"asc\" | \"desc\") {\n this.column = column;\n this.direction = direction;\n }\n}\n\n/**\n * Type guard to check if a value is an OrderByExpression instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type\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 */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function asc<TableName extends string>(column: Column<any, any, TableName>): 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 */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function desc<TableName extends string>(column: Column<any, any, TableName>): 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 { needsFieldQuoting } from \"../client/builders/select-utils\";\nimport 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 readonly operator: string;\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n readonly operands: (Column | any | FilterExpression)[];\n\n // biome-ignore lint/suspicious/noExplicitAny: Operands can be Column, FilterExpression, or any value type\n constructor(operator: string, operands: (Column | any | FilterExpression)[]) {\n this.operator = operator;\n this.operands = operands;\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 let columnForValue: typeof left | typeof right | undefined;\n if (isColumn(left) && !isColumn(right)) {\n columnForValue = left;\n } else if (isColumn(right) && !isColumn(left)) {\n columnForValue = right;\n } else {\n columnForValue = undefined;\n }\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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).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 // biome-ignore lint/suspicious/noExplicitAny: Dynamic array of values from user input\n const valuesStr = (values as any[]).map((v) => this._operandToString(v, useEntityIds, columnInstance)).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 // biome-ignore lint/suspicious/noExplicitAny: Operand can be Column, FilterExpression, or any value type\n operand: any,\n useEntityIds?: boolean, // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\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) ? `\"${fieldIdentifier}\"` : 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 column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\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 column1: Column<TOutput, TInput>,\n column2: Column<TOutput, TInput> | NoInfer<TInput>,\n): FilterExpression;\n// biome-ignore lint/suspicious/noExplicitAny: Implementation signature for overloads\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>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): 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>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): 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>(column: Column<TOutput, TInput>, value: NoInfer<TInput>): 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>(column: Column<TOutput, TInput>, values: NoInfer<TInput>[]): 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>(column: Column<TOutput, TInput>): 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>(column: Column<TOutput, TInput>): 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 // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n readonly column: Column<any, any, TableName>;\n readonly direction: \"asc\" | \"desc\";\n\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n constructor(column: Column<any, any, TableName>, direction: \"asc\" | \"desc\") {\n this.column = column;\n this.direction = direction;\n }\n}\n\n/**\n * Type guard to check if a value is an OrderByExpression instance.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type\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 */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function asc<TableName extends string>(column: Column<any, any, TableName>): 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 */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport function desc<TableName extends string>(column: Column<any, any, TableName>): OrderByExpression<TableName> {\n return new OrderByExpression(column, \"desc\");\n}\n"],"names":[],"mappings":"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"}