@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.
- package/README.md +4 -4
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/batch-request.js.map +1 -1
- package/dist/esm/client/builders/default-select.js +1 -1
- package/dist/esm/client/builders/default-select.js.map +1 -1
- package/dist/esm/client/builders/expand-builder.js +3 -1
- package/dist/esm/client/builders/expand-builder.js.map +1 -1
- package/dist/esm/client/builders/query-string-builder.js.map +1 -1
- package/dist/esm/client/builders/response-processor.js.map +1 -1
- package/dist/esm/client/builders/select-mixin.js.map +1 -1
- package/dist/esm/client/builders/select-utils.js.map +1 -1
- package/dist/esm/client/builders/table-utils.js.map +1 -1
- package/dist/esm/client/database.d.ts +11 -1
- package/dist/esm/client/database.js +13 -0
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.js +18 -2
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +2 -0
- package/dist/esm/client/filemaker-odata.js +3 -0
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/query-builder.js.map +1 -1
- package/dist/esm/client/query/url-builder.js.map +1 -1
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/sanitize-json.js.map +1 -1
- package/dist/esm/client/schema-manager.js.map +1 -1
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.js.map +1 -1
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/logger.js.map +1 -1
- package/dist/esm/orm/column.js.map +1 -1
- package/dist/esm/orm/field-builders.js.map +1 -1
- package/dist/esm/orm/operators.js.map +1 -1
- package/dist/esm/orm/table.js.map +1 -1
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/validation.js.map +1 -1
- package/package.json +16 -19
- package/src/client/builders/default-select.ts +3 -1
- package/src/client/builders/expand-builder.ts +3 -1
- package/src/client/database.ts +17 -1
- package/src/client/entity-set.ts +22 -2
- package/src/client/filemaker-odata.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sources":["../../../src/orm/table.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { Column } from \"./column\";\nimport type { ContainerDbType, FieldBuilder, FieldBuilder as FieldBuilderType } from \"./field-builders\";\n// import { z } from \"zod/v4\";\n\n/**\n * Extract the output type from a FieldBuilder.\n * This is what you get when reading from the database.\n *\n * This type extracts the TOutput type parameter, which is set by readValidator()\n * and represents the transformed/validated output type.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\nexport type InferFieldOutput<F> = F extends FieldBuilder<infer TOutput, any, any, any> ? TOutput : never;\n\n/**\n * Extract the input type from a FieldBuilder.\n * This is what you pass when writing to the database.\n *\n * This type extracts the TInput type parameter, which is set by writeValidator()\n * and represents the transformed/validated input type.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\ntype InferFieldInput<F> = F extends FieldBuilder<any, infer TInput, any, any> ? TInput : never;\n\n/**\n * Build a schema type from field builders (output/read types).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype InferSchemaFromFields<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in keyof TFields]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Build an input schema type from field builders (input/write types).\n * Used for insert and update operations.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype InferInputSchemaFromFields<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in keyof TFields]: InferFieldInput<TFields[K]>;\n};\n\n/**\n * Check if a field is a container field by inspecting its TDbType.\n * Container fields have a branded TDbType that extends ContainerDbType.\n */\ntype IsContainerField<F> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n F extends FieldBuilder<any, any, infer TDbType, any>\n ? NonNullable<TDbType> extends ContainerDbType\n ? true\n : false\n : false;\n\n/**\n * Extract only selectable (non-container) field keys from a fields record.\n * Container fields are excluded because they cannot be selected via $select in FileMaker OData.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype SelectableFieldKeys<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in keyof TFields]: IsContainerField<TFields[K]> extends true ? never : K;\n}[keyof TFields];\n\n/**\n * Build a schema type excluding container fields (for query return types).\n * This is used to ensure container fields don't appear in the return type\n * when using defaultSelect: \"schema\" or \"all\".\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype _InferSelectableSchemaFromFields<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in SelectableFieldKeys<TFields>]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Internal Symbols for table properties (hidden from IDE autocomplete).\n * These are used to store internal configuration that shouldn't be visible\n * when users access table columns.\n * @internal - Not exported from public API, only accessible via FMTable.Symbol\n */\nconst FMTableName = Symbol.for(\"fmodata:FMTableName\");\nconst FMTableEntityId = Symbol.for(\"fmodata:FMTableEntityId\");\nconst FMTableSchema = Symbol.for(\"fmodata:FMTableSchema\");\nconst FMTableFields = Symbol.for(\"fmodata:FMTableFields\");\nconst FMTableNavigationPaths = Symbol.for(\"fmodata:FMTableNavigationPaths\");\nconst FMTableDefaultSelect = Symbol.for(\"fmodata:FMTableDefaultSelect\");\nconst FMTableBaseTableConfig = Symbol.for(\"fmodata:FMTableBaseTableConfig\");\nconst FMTableUseEntityIds = Symbol.for(\"fmodata:FMTableUseEntityIds\");\nconst FMTableComment = Symbol.for(\"fmodata:FMTableComment\");\n\n/**\n * Base table class with Symbol-based internal properties.\n * This follows the Drizzle ORM pattern where internal configuration\n * is stored via Symbols, keeping it hidden from IDE autocomplete.\n */\nexport class FMTable<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration, default allows untyped tables\n TFields extends Record<string, FieldBuilder<any, any, any, any>> = any,\n TName extends string = string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> {\n /**\n * Internal Symbols for accessing table metadata.\n * @internal - Not intended for public use. Access table properties via columns instead.\n */\n static readonly Symbol = {\n Name: FMTableName,\n EntityId: FMTableEntityId,\n UseEntityIds: FMTableUseEntityIds,\n Schema: FMTableSchema,\n Fields: FMTableFields,\n NavigationPaths: FMTableNavigationPaths,\n DefaultSelect: FMTableDefaultSelect,\n BaseTableConfig: FMTableBaseTableConfig,\n Comment: FMTableComment,\n };\n\n /** @internal */\n [FMTableName]: TName;\n\n /** @internal */\n [FMTableEntityId]?: `FMTID:${string}`;\n\n /** @internal */\n [FMTableUseEntityIds]?: boolean;\n\n /** @internal */\n [FMTableComment]?: string;\n\n /** @internal */\n [FMTableSchema]: Partial<Record<keyof TFields, StandardSchemaV1>>;\n\n /** @internal */\n [FMTableFields]: TFields;\n\n /** @internal */\n [FMTableNavigationPaths]: TNavigationPaths;\n\n /** @internal */\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n [FMTableDefaultSelect]: \"all\" | \"schema\" | Record<string, Column<any, any, TName>>;\n\n /** @internal */\n [FMTableBaseTableConfig]: {\n schema: Partial<Record<keyof TFields, StandardSchemaV1>>;\n inputSchema?: Partial<Record<keyof TFields, StandardSchemaV1>>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n\n constructor(config: {\n name: TName;\n entityId?: `FMTID:${string}`;\n useEntityIds?: boolean;\n comment?: string;\n schema: Partial<Record<keyof TFields, StandardSchemaV1>>;\n fields: TFields;\n navigationPaths: TNavigationPaths;\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n defaultSelect: \"all\" | \"schema\" | Record<string, Column<any, any, TName>>;\n baseTableConfig: {\n schema: Partial<Record<keyof TFields, StandardSchemaV1>>;\n inputSchema?: Partial<Record<keyof TFields, StandardSchemaV1>>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n }) {\n this[FMTableName] = config.name;\n this[FMTableEntityId] = config.entityId;\n this[FMTableUseEntityIds] = config.useEntityIds;\n this[FMTableComment] = config.comment;\n this[FMTableSchema] = config.schema;\n this[FMTableFields] = config.fields;\n this[FMTableNavigationPaths] = config.navigationPaths;\n this[FMTableDefaultSelect] = config.defaultSelect;\n this[FMTableBaseTableConfig] = config.baseTableConfig;\n }\n}\n\n/**\n * Type helper to extract the column map from fields.\n * Table name is baked into each column type for validation.\n * Container fields are marked with IsContainer=true.\n * Columns include both output type (for reading) and input type (for writing/filtering).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\nexport type ColumnMap<TFields extends Record<string, FieldBuilder<any, any, any, any>>, TName extends string> = {\n [K in keyof TFields]: Column<\n InferFieldOutput<TFields[K]>,\n InferFieldInput<TFields[K]>,\n TName,\n IsContainerField<TFields[K]>\n >;\n};\n\n/**\n * Extract only selectable (non-container) columns from a table.\n * This is used to prevent selecting container fields in queries.\n */\nexport type SelectableColumnMap<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = {\n [K in SelectableFieldKeys<TFields>]: Column<InferFieldOutput<TFields[K]>, InferFieldInput<TFields[K]>, TName, false>;\n};\n\n/**\n * Validates that a select object doesn't contain container field columns.\n * Returns never if any container fields are found, otherwise returns the original type.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport type ValidateNoContainerFields<TSelect extends Record<string, Column<any, any, any, any>>> = {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true> ? never : TSelect[K];\n} extends TSelect\n ? TSelect\n : {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true>\n ? \"❌ Container fields cannot be selected. Use .getSingleField() instead.\"\n : TSelect[K];\n };\n\n/**\n * Extract the keys from a defaultSelect function's return type.\n * Used to infer which fields are selected by default for type narrowing.\n */\ntype _ExtractDefaultSelectKeys<\n TDefaultSelect,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = TDefaultSelect extends (columns: ColumnMap<TFields, TName>) => infer R\n ? keyof R\n : TDefaultSelect extends \"schema\"\n ? keyof TFields\n : keyof TFields; // \"all\" defaults to all keys\n\n/**\n * Complete table type with both metadata (via Symbols) and column accessors.\n * This is the return type of fmTableOccurrence - users see columns directly,\n * but internal config is hidden via Symbols.\n */\nexport type FMTableWithColumns<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> = FMTable<TFields, TName, TNavigationPaths> & ColumnMap<TFields, TName>;\n\n/**\n * Options for fmTableOccurrence function.\n * Provides autocomplete-friendly typing while preserving inference for navigationPaths.\n */\nexport interface FMTableOccurrenceOptions<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> {\n /** The entity ID (FMTID) for this table occurrence */\n entityId?: `FMTID:${string}`;\n\n /** The comment for this table */\n comment?: string;\n\n /**\n * Default select behavior:\n * - \"all\": Select all fields (including related tables)\n * - \"schema\": Select only schema-defined fields (default)\n * - function: Custom selection from columns\n */\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n defaultSelect?: \"all\" | \"schema\" | ((columns: ColumnMap<TFields, TName>) => Record<string, Column<any, any, TName>>);\n\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: readonly string[];\n\n /** Whether to use entity IDs (FMTID/FMFID) instead of names in queries */\n useEntityIds?: boolean;\n}\n\n/**\n * Create a table occurrence with field builders.\n * This is the main API for defining tables in the new ORM style.\n *\n * @example\n * const users = fmTableOccurrence(\"users\", {\n * id: textField().primaryKey().entityId(\"FMFID:1\"),\n * name: textField().notNull().entityId(\"FMFID:6\"),\n * active: numberField()\n * .outputValidator(z.coerce.boolean())\n * .inputValidator(z.boolean().transform(v => v ? 1 : 0))\n * .entityId(\"FMFID:7\"),\n * }, {\n * entityId: \"FMTID:100\",\n * defaultSelect: \"schema\",\n * navigationPaths: [\"contacts\"],\n * });\n *\n * // Access columns\n * users.id // Column<string, \"id\">\n * users.name // Column<string, \"name\">\n *\n * // Use in queries\n * db.from(users).select(\"id\", \"name\").where(eq(users.active, true))\n */\nexport function fmTableOccurrence<\n const TName extends string,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n const TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n const TNavPaths extends readonly string[] = readonly [],\n>(\n name: TName,\n fields: TFields,\n options?: FMTableOccurrenceOptions<TFields, TName> & {\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: TNavPaths;\n },\n): FMTableWithColumns<TFields, TName, TNavPaths> {\n // Extract configuration from field builders\n const fieldConfigs = Object.entries(fields).map(([fieldName, builder]) => ({\n fieldName,\n // biome-ignore lint/suspicious/noExplicitAny: Internal property access for builder pattern\n config: (builder as any)._getConfig(),\n }));\n\n // Find primary key field\n const primaryKeyField = fieldConfigs.find((f) => f.config.primaryKey);\n const idField = primaryKeyField?.fieldName;\n\n // Collect required fields (notNull fields)\n const required = fieldConfigs.filter((f) => f.config.notNull).map((f) => f.fieldName);\n\n // Collect read-only fields\n const readOnly = fieldConfigs.filter((f) => f.config.readOnly).map((f) => f.fieldName);\n\n // Collect container fields (cannot be selected via $select)\n const containerFields = fieldConfigs.filter((f) => f.config.fieldType === \"container\").map((f) => f.fieldName);\n\n // Collect entity IDs\n const fmfIds: Record<string, `FMFID:${string}`> = {};\n for (const { fieldName, config } of fieldConfigs) {\n if (config.entityId) {\n fmfIds[fieldName] = config.entityId;\n }\n }\n\n // Build Zod schema from field builders (output/read validators)\n const outputSchema: Partial<Record<keyof TFields, StandardSchemaV1>> = {};\n // Build input schema from field builders (input/write validators)\n const inputSchema: Record<string, StandardSchemaV1> = {};\n\n for (const { fieldName, config } of fieldConfigs) {\n // Use outputValidator if provided\n if (config.outputValidator) {\n outputSchema[fieldName as keyof TFields] = config.outputValidator;\n }\n\n // Store inputValidator if provided (for write operations)\n if (config.inputValidator) {\n inputSchema[fieldName] = config.inputValidator;\n }\n }\n\n // Build BaseTable-compatible config\n const baseTableConfig = {\n schema: outputSchema as Partial<Record<keyof TFields, StandardSchemaV1>>,\n inputSchema:\n Object.keys(inputSchema).length > 0\n ? (inputSchema as Partial<Record<keyof TFields, StandardSchemaV1>>)\n : undefined,\n idField: idField as keyof TFields | undefined,\n required: required as readonly (keyof TFields)[],\n readOnly: readOnly as readonly (keyof TFields)[],\n containerFields: containerFields as readonly (keyof TFields)[],\n fmfIds: (Object.keys(fmfIds).length > 0 ? fmfIds : undefined) as\n | Record<keyof TFields, `FMFID:${string}`>\n | undefined,\n };\n\n // Create column instances\n const columns = {} as ColumnMap<TFields, TName>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n // biome-ignore lint/suspicious/noExplicitAny: Internal property access for builder pattern\n const config = (builder as any)._getConfig();\n // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: config.entityId,\n tableName: name,\n tableEntityId: options?.entityId,\n inputValidator: config.inputValidator,\n });\n }\n\n // Resolve defaultSelect: if it's a function, call it with columns; otherwise use as-is\n const defaultSelectOption = options?.defaultSelect ?? \"schema\";\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n const resolvedDefaultSelect: \"all\" | \"schema\" | Record<string, Column<any, any, TName>> =\n typeof defaultSelectOption === \"function\"\n ? defaultSelectOption(columns as ColumnMap<TFields, TName>)\n : defaultSelectOption;\n\n // Create the FMTable instance with Symbol-based internal properties\n const navigationPaths = (options?.navigationPaths ?? []) as TNavPaths;\n const table = new FMTable<TFields, TName, TNavPaths>({\n name,\n entityId: options?.entityId,\n useEntityIds: options?.useEntityIds,\n comment: options?.comment,\n schema: outputSchema,\n fields,\n navigationPaths,\n defaultSelect: resolvedDefaultSelect,\n baseTableConfig,\n });\n\n // Assign columns to the table instance (making them accessible directly)\n Object.assign(table, columns);\n\n return table as FMTableWithColumns<TFields, TName, TNavPaths>;\n}\n\n// /**\n// * Type guard to check if a value is a TableOccurrence or FMTable.\n// * Supports both Symbol-based (new) and underscore-prefixed (legacy) formats.\n// */\n// function isTableOccurrence(value: any): value is TableOccurrence {\n// if (!value || typeof value !== \"object\") {\n// return false;\n// }\n\n// // Check for Symbol-based format (new FMTable class)\n// if (\n// FMTableName in value &&\n// FMTableSchema in value &&\n// FMTableFields in value\n// ) {\n// return typeof value[FMTableName] === \"string\";\n// }\n\n// // Check for underscore-prefixed format (legacy interface)\n// if (\"_name\" in value && \"_schema\" in value && \"_fields\" in value) {\n// return typeof value._name === \"string\";\n// }\n\n// return false;\n// }\n\n/**\n * Helper to extract the schema type from a TableOccurrence or FMTable.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\nexport type InferTableSchema<T> = T extends FMTable<infer TFields, any> ? InferSchemaFromFields<TFields> : never;\n\n/**\n * Extract the schema type from an FMTable instance.\n * This is used to infer the schema from table objects passed to db.from(), expand(), etc.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type InferSchemaOutputFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any> ? InferSchemaFromFields<TFields> : never;\n\n/**\n * Extract the input schema type from an FMTable instance.\n * This is used for insert and update operations where we need write types.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type InferInputSchemaFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any> ? InferInputSchemaFromFields<TFields> : never;\n\n/**\n * Helper type to check if a FieldBuilder's input type excludes null and undefined.\n * This checks the TInput type parameter, which preserves nullability from notNull().\n */\ntype FieldInputExcludesNullish<F> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n F extends FieldBuilder<any, infer TInput, any>\n ? null extends TInput\n ? false\n : undefined extends TInput\n ? false\n : true\n : false;\n\n/**\n * Check if a FieldBuilder is readOnly at the type level\n */\ntype IsFieldReadOnly<F> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n F extends FieldBuilderType<any, any, any, infer ReadOnly> ? (ReadOnly extends true ? true : false) : false;\n\n/**\n * Compute insert data type from FMTable, making notNull fields required.\n * Fields are required if their FieldBuilder's TInput type excludes null/undefined.\n * All other fields are optional (can be omitted).\n * readOnly fields are excluded (including primaryKey/idField since they're automatically readOnly).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type InsertDataFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? K\n : never]: InferFieldInput<TFields[K]>;\n } & {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? never\n : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Compute update data type from FMTable.\n * All fields are optional, but readOnly fields are excluded (including primaryKey/idField).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type UpdateDataFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true ? never : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Extract the table name type from an FMTable.\n * This is a workaround since we can't directly index Symbols in types.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration, required for type inference with infer\nexport type ExtractTableName<T extends FMTable<any, any>> = T extends FMTable<any, infer Name> ? Name : never;\n\n/**\n * Validates that a target table's name matches one of the source table's navigationPaths.\n * Used to ensure type-safe expand/navigate operations.\n */\nexport type ValidExpandTarget<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n SourceTable extends FMTable<any, any, any> | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n TargetTable extends FMTable<any, any, any>,\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n> = SourceTable extends FMTable<any, any, infer SourceNavPaths>\n ? ExtractTableName<TargetTable> extends SourceNavPaths[number]\n ? TargetTable\n : never\n : TargetTable;\n\n// ============================================================================\n// Helper Functions for Accessing FMTable Internal Properties\n// ============================================================================\n\n/**\n * Get the table name from an FMTable instance.\n * @param table - FMTable instance\n * @returns The table name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableName<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableName];\n}\n\n/**\n * Get the entity ID (FMTID) from an FMTable instance.\n * @param table - FMTable instance\n * @returns The entity ID or undefined if not using entity IDs\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableEntityId<T extends FMTable<any, any>>(table: T): string | undefined {\n return table[FMTableEntityId];\n}\n\n/**\n * Get the schema validator from an FMTable instance.\n * @param table - FMTable instance\n * @returns The StandardSchemaV1 validator record (partial - only fields with validators)\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableSchema<T extends FMTable<any, any>>(\n table: T,\n): Partial<Record<keyof T[typeof FMTableFields], StandardSchemaV1>> {\n return table[FMTableSchema];\n}\n\n/**\n * Get the fields from an FMTable instance.\n * @param table - FMTable instance\n * @returns The fields record\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableFields<T extends FMTable<any, any>>(table: T) {\n return table[FMTableFields];\n}\n\n/**\n * Get the navigation paths from an FMTable instance.\n * @param table - FMTable instance\n * @returns Array of navigation path names\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getNavigationPaths<T extends FMTable<any, any>>(table: T): readonly string[] {\n return table[FMTableNavigationPaths];\n}\n\n/**\n * Get the default select configuration from an FMTable instance.\n * @param table - FMTable instance\n * @returns Default select configuration\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getDefaultSelect<T extends FMTable<any, any>>(table: T) {\n return table[FMTableDefaultSelect];\n}\n\n/**\n * Get the base table configuration from an FMTable instance.\n * This provides access to schema, idField, required fields, readOnly fields, and field IDs.\n * @param table - FMTable instance\n * @returns Base table configuration object\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getBaseTableConfig<T extends FMTable<any, any>>(table: T) {\n return table[FMTableBaseTableConfig];\n}\n\n/**\n * Check if an FMTable instance is using entity IDs (both FMTID and FMFIDs).\n * @param table - FMTable instance\n * @returns True if using entity IDs, false otherwise\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function isUsingEntityIds<T extends FMTable<any, any>>(table: T): boolean {\n return table[FMTableEntityId] !== undefined && table[FMTableBaseTableConfig].fmfIds !== undefined;\n}\n\n/**\n * Get the field ID (FMFID) for a given field name, or the field name itself if not using IDs.\n * @param table - FMTable instance\n * @param fieldName - Field name to get the ID for\n * @returns The FMFID string or the original field name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getFieldId<T extends FMTable<any, any>>(table: T, fieldName: string): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds && fieldName in config.fmfIds) {\n const fieldId = config.fmfIds[fieldName];\n if (fieldId) {\n return fieldId;\n }\n }\n return fieldName;\n}\n\n/**\n * Get the field name for a given field ID (FMFID), or the ID itself if not found.\n * @param table - FMTable instance\n * @param fieldId - The FMFID to get the field name for\n * @returns The field name or the original ID\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getFieldName<T extends FMTable<any, any>>(table: T, fieldId: string): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds) {\n for (const [fieldName, fmfId] of Object.entries(config.fmfIds)) {\n if (fmfId === fieldId) {\n return fieldName;\n }\n }\n }\n return fieldId;\n}\n/**\n * Get the table ID (FMTID or name) from an FMTable instance.\n * Returns the FMTID if available, otherwise returns the table name.\n * @param table - FMTable instance\n * @returns The FMTID string or the table name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableId<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableEntityId] ?? table[FMTableName];\n}\n\n/**\n * Get the comment from an FMTable instance.\n * @param table - FMTable instance\n * @returns The comment string or undefined if not set\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableComment<T extends FMTable<any, any>>(table: T): string | undefined {\n return table[FMTableComment];\n}\n\n/**\n * Get all columns from a table as an object.\n * Useful for selecting all fields except some using destructuring.\n *\n * @example\n * const { password, ...cols } = getTableColumns(users)\n * db.from(users).list().select(cols)\n *\n * @param table - FMTable instance\n * @returns Object with all columns from the table\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableColumns<T extends FMTable<any, any>>(\n table: T,\n): ColumnMap<T[typeof FMTableFields], ExtractTableName<T>> {\n const fields = table[FMTableFields];\n const tableName = table[FMTableName];\n const tableEntityId = table[FMTableEntityId];\n const baseConfig = table[FMTableBaseTableConfig];\n\n const columns = {} as ColumnMap<T[typeof FMTableFields], ExtractTableName<T>>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n // biome-ignore lint/suspicious/noExplicitAny: Internal property access for builder pattern\n const config = (builder as any)._getConfig();\n // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: baseConfig.fmfIds?.[fieldName],\n tableName,\n tableEntityId,\n inputValidator: config.inputValidator,\n });\n }\n\n return columns;\n}\n"],"names":["_a"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"table.js","sources":["../../../src/orm/table.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { Column } from \"./column\";\nimport type { ContainerDbType, FieldBuilder, FieldBuilder as FieldBuilderType } from \"./field-builders\";\n// import { z } from \"zod/v4\";\n\n/**\n * Extract the output type from a FieldBuilder.\n * This is what you get when reading from the database.\n *\n * This type extracts the TOutput type parameter, which is set by readValidator()\n * and represents the transformed/validated output type.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\nexport type InferFieldOutput<F> = F extends FieldBuilder<infer TOutput, any, any, any> ? TOutput : never;\n\n/**\n * Extract the input type from a FieldBuilder.\n * This is what you pass when writing to the database.\n *\n * This type extracts the TInput type parameter, which is set by writeValidator()\n * and represents the transformed/validated input type.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\ntype InferFieldInput<F> = F extends FieldBuilder<any, infer TInput, any, any> ? TInput : never;\n\n/**\n * Build a schema type from field builders (output/read types).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype InferSchemaFromFields<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in keyof TFields]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Build an input schema type from field builders (input/write types).\n * Used for insert and update operations.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype InferInputSchemaFromFields<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in keyof TFields]: InferFieldInput<TFields[K]>;\n};\n\n/**\n * Check if a field is a container field by inspecting its TDbType.\n * Container fields have a branded TDbType that extends ContainerDbType.\n */\ntype IsContainerField<F> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n F extends FieldBuilder<any, any, infer TDbType, any>\n ? NonNullable<TDbType> extends ContainerDbType\n ? true\n : false\n : false;\n\n/**\n * Extract only selectable (non-container) field keys from a fields record.\n * Container fields are excluded because they cannot be selected via $select in FileMaker OData.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype SelectableFieldKeys<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in keyof TFields]: IsContainerField<TFields[K]> extends true ? never : K;\n}[keyof TFields];\n\n/**\n * Build a schema type excluding container fields (for query return types).\n * This is used to ensure container fields don't appear in the return type\n * when using defaultSelect: \"schema\" or \"all\".\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\ntype _InferSelectableSchemaFromFields<TFields extends Record<string, FieldBuilder<any, any, any, any>>> = {\n [K in SelectableFieldKeys<TFields>]: InferFieldOutput<TFields[K]>;\n};\n\n/**\n * Internal Symbols for table properties (hidden from IDE autocomplete).\n * These are used to store internal configuration that shouldn't be visible\n * when users access table columns.\n * @internal - Not exported from public API, only accessible via FMTable.Symbol\n */\nconst FMTableName = Symbol.for(\"fmodata:FMTableName\");\nconst FMTableEntityId = Symbol.for(\"fmodata:FMTableEntityId\");\nconst FMTableSchema = Symbol.for(\"fmodata:FMTableSchema\");\nconst FMTableFields = Symbol.for(\"fmodata:FMTableFields\");\nconst FMTableNavigationPaths = Symbol.for(\"fmodata:FMTableNavigationPaths\");\nconst FMTableDefaultSelect = Symbol.for(\"fmodata:FMTableDefaultSelect\");\nconst FMTableBaseTableConfig = Symbol.for(\"fmodata:FMTableBaseTableConfig\");\nconst FMTableUseEntityIds = Symbol.for(\"fmodata:FMTableUseEntityIds\");\nconst FMTableComment = Symbol.for(\"fmodata:FMTableComment\");\n\n/**\n * Base table class with Symbol-based internal properties.\n * This follows the Drizzle ORM pattern where internal configuration\n * is stored via Symbols, keeping it hidden from IDE autocomplete.\n */\nexport class FMTable<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration, default allows untyped tables\n TFields extends Record<string, FieldBuilder<any, any, any, any>> = any,\n TName extends string = string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> {\n /**\n * Internal Symbols for accessing table metadata.\n * @internal - Not intended for public use. Access table properties via columns instead.\n */\n static readonly Symbol = {\n Name: FMTableName,\n EntityId: FMTableEntityId,\n UseEntityIds: FMTableUseEntityIds,\n Schema: FMTableSchema,\n Fields: FMTableFields,\n NavigationPaths: FMTableNavigationPaths,\n DefaultSelect: FMTableDefaultSelect,\n BaseTableConfig: FMTableBaseTableConfig,\n Comment: FMTableComment,\n };\n\n /** @internal */\n [FMTableName]: TName;\n\n /** @internal */\n [FMTableEntityId]?: `FMTID:${string}`;\n\n /** @internal */\n [FMTableUseEntityIds]?: boolean;\n\n /** @internal */\n [FMTableComment]?: string;\n\n /** @internal */\n [FMTableSchema]: Partial<Record<keyof TFields, StandardSchemaV1>>;\n\n /** @internal */\n [FMTableFields]: TFields;\n\n /** @internal */\n [FMTableNavigationPaths]: TNavigationPaths;\n\n /** @internal */\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n [FMTableDefaultSelect]: \"all\" | \"schema\" | Record<string, Column<any, any, TName>>;\n\n /** @internal */\n [FMTableBaseTableConfig]: {\n schema: Partial<Record<keyof TFields, StandardSchemaV1>>;\n inputSchema?: Partial<Record<keyof TFields, StandardSchemaV1>>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n\n constructor(config: {\n name: TName;\n entityId?: `FMTID:${string}`;\n useEntityIds?: boolean;\n comment?: string;\n schema: Partial<Record<keyof TFields, StandardSchemaV1>>;\n fields: TFields;\n navigationPaths: TNavigationPaths;\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n defaultSelect: \"all\" | \"schema\" | Record<string, Column<any, any, TName>>;\n baseTableConfig: {\n schema: Partial<Record<keyof TFields, StandardSchemaV1>>;\n inputSchema?: Partial<Record<keyof TFields, StandardSchemaV1>>;\n idField?: keyof TFields;\n required: readonly (keyof TFields)[];\n readOnly: readonly (keyof TFields)[];\n containerFields: readonly (keyof TFields)[];\n fmfIds?: Record<keyof TFields, `FMFID:${string}`>;\n };\n }) {\n this[FMTableName] = config.name;\n this[FMTableEntityId] = config.entityId;\n this[FMTableUseEntityIds] = config.useEntityIds;\n this[FMTableComment] = config.comment;\n this[FMTableSchema] = config.schema;\n this[FMTableFields] = config.fields;\n this[FMTableNavigationPaths] = config.navigationPaths;\n this[FMTableDefaultSelect] = config.defaultSelect;\n this[FMTableBaseTableConfig] = config.baseTableConfig;\n }\n}\n\n/**\n * Type helper to extract the column map from fields.\n * Table name is baked into each column type for validation.\n * Container fields are marked with IsContainer=true.\n * Columns include both output type (for reading) and input type (for writing/filtering).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\nexport type ColumnMap<TFields extends Record<string, FieldBuilder<any, any, any, any>>, TName extends string> = {\n [K in keyof TFields]: Column<\n InferFieldOutput<TFields[K]>,\n InferFieldInput<TFields[K]>,\n TName,\n IsContainerField<TFields[K]>\n >;\n};\n\n/**\n * Extract only selectable (non-container) columns from a table.\n * This is used to prevent selecting container fields in queries.\n */\nexport type SelectableColumnMap<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = {\n [K in SelectableFieldKeys<TFields>]: Column<InferFieldOutput<TFields[K]>, InferFieldInput<TFields[K]>, TName, false>;\n};\n\n/**\n * Validates that a select object doesn't contain container field columns.\n * Returns never if any container fields are found, otherwise returns the original type.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\nexport type ValidateNoContainerFields<TSelect extends Record<string, Column<any, any, any, any>>> = {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true> ? never : TSelect[K];\n} extends TSelect\n ? TSelect\n : {\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n [K in keyof TSelect]: TSelect[K] extends Column<any, any, any, true>\n ? \"❌ Container fields cannot be selected. Use .getSingleField() instead.\"\n : TSelect[K];\n };\n\n/**\n * Extract the keys from a defaultSelect function's return type.\n * Used to infer which fields are selected by default for type narrowing.\n */\ntype _ExtractDefaultSelectKeys<\n TDefaultSelect,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> = TDefaultSelect extends (columns: ColumnMap<TFields, TName>) => infer R\n ? keyof R\n : TDefaultSelect extends \"schema\"\n ? keyof TFields\n : keyof TFields; // \"all\" defaults to all keys\n\n/**\n * Complete table type with both metadata (via Symbols) and column accessors.\n * This is the return type of fmTableOccurrence - users see columns directly,\n * but internal config is hidden via Symbols.\n */\nexport type FMTableWithColumns<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n TNavigationPaths extends readonly string[] = readonly string[],\n> = FMTable<TFields, TName, TNavigationPaths> & ColumnMap<TFields, TName>;\n\n/**\n * Options for fmTableOccurrence function.\n * Provides autocomplete-friendly typing while preserving inference for navigationPaths.\n */\nexport interface FMTableOccurrenceOptions<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n TName extends string,\n> {\n /** The entity ID (FMTID) for this table occurrence */\n entityId?: `FMTID:${string}`;\n\n /** The comment for this table */\n comment?: string;\n\n /**\n * Default select behavior:\n * - \"all\": Select all fields (including related tables)\n * - \"schema\": Select only schema-defined fields (default)\n * - function: Custom selection from columns\n */\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n defaultSelect?: \"all\" | \"schema\" | ((columns: ColumnMap<TFields, TName>) => Record<string, Column<any, any, TName>>);\n\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: readonly string[];\n\n /** Whether to use entity IDs (FMTID/FMFID) instead of names in queries */\n useEntityIds?: boolean;\n}\n\n/**\n * Create a table occurrence with field builders.\n * This is the main API for defining tables in the new ORM style.\n *\n * @example\n * const users = fmTableOccurrence(\"users\", {\n * id: textField().primaryKey().entityId(\"FMFID:1\"),\n * name: textField().notNull().entityId(\"FMFID:6\"),\n * active: numberField()\n * .outputValidator(z.coerce.boolean())\n * .inputValidator(z.boolean().transform(v => v ? 1 : 0))\n * .entityId(\"FMFID:7\"),\n * }, {\n * entityId: \"FMTID:100\",\n * defaultSelect: \"schema\",\n * navigationPaths: [\"contacts\"],\n * });\n *\n * // Access columns\n * users.id // Column<string, \"id\">\n * users.name // Column<string, \"name\">\n *\n * // Use in queries\n * db.from(users).select(\"id\", \"name\").where(eq(users.active, true))\n */\nexport function fmTableOccurrence<\n const TName extends string,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration\n const TFields extends Record<string, FieldBuilder<any, any, any, any>>,\n const TNavPaths extends readonly string[] = readonly [],\n>(\n name: TName,\n fields: TFields,\n options?: FMTableOccurrenceOptions<TFields, TName> & {\n /** Navigation paths available from this table (for expand operations) */\n navigationPaths?: TNavPaths;\n },\n): FMTableWithColumns<TFields, TName, TNavPaths> {\n // Extract configuration from field builders\n const fieldConfigs = Object.entries(fields).map(([fieldName, builder]) => ({\n fieldName,\n // biome-ignore lint/suspicious/noExplicitAny: Internal property access for builder pattern\n config: (builder as any)._getConfig(),\n }));\n\n // Find primary key field\n const primaryKeyField = fieldConfigs.find((f) => f.config.primaryKey);\n const idField = primaryKeyField?.fieldName;\n\n // Collect required fields (notNull fields)\n const required = fieldConfigs.filter((f) => f.config.notNull).map((f) => f.fieldName);\n\n // Collect read-only fields\n const readOnly = fieldConfigs.filter((f) => f.config.readOnly).map((f) => f.fieldName);\n\n // Collect container fields (cannot be selected via $select)\n const containerFields = fieldConfigs.filter((f) => f.config.fieldType === \"container\").map((f) => f.fieldName);\n\n // Collect entity IDs\n const fmfIds: Record<string, `FMFID:${string}`> = {};\n for (const { fieldName, config } of fieldConfigs) {\n if (config.entityId) {\n fmfIds[fieldName] = config.entityId;\n }\n }\n\n // Build Zod schema from field builders (output/read validators)\n const outputSchema: Partial<Record<keyof TFields, StandardSchemaV1>> = {};\n // Build input schema from field builders (input/write validators)\n const inputSchema: Record<string, StandardSchemaV1> = {};\n\n for (const { fieldName, config } of fieldConfigs) {\n // Use outputValidator if provided\n if (config.outputValidator) {\n outputSchema[fieldName as keyof TFields] = config.outputValidator;\n }\n\n // Store inputValidator if provided (for write operations)\n if (config.inputValidator) {\n inputSchema[fieldName] = config.inputValidator;\n }\n }\n\n // Build BaseTable-compatible config\n const baseTableConfig = {\n schema: outputSchema as Partial<Record<keyof TFields, StandardSchemaV1>>,\n inputSchema:\n Object.keys(inputSchema).length > 0\n ? (inputSchema as Partial<Record<keyof TFields, StandardSchemaV1>>)\n : undefined,\n idField: idField as keyof TFields | undefined,\n required: required as readonly (keyof TFields)[],\n readOnly: readOnly as readonly (keyof TFields)[],\n containerFields: containerFields as readonly (keyof TFields)[],\n fmfIds: (Object.keys(fmfIds).length > 0 ? fmfIds : undefined) as\n | Record<keyof TFields, `FMFID:${string}`>\n | undefined,\n };\n\n // Create column instances\n const columns = {} as ColumnMap<TFields, TName>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n // biome-ignore lint/suspicious/noExplicitAny: Internal property access for builder pattern\n const config = (builder as any)._getConfig();\n // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: config.entityId,\n tableName: name,\n tableEntityId: options?.entityId,\n inputValidator: config.inputValidator,\n });\n }\n\n // Resolve defaultSelect: if it's a function, call it with columns; otherwise use as-is\n const defaultSelectOption = options?.defaultSelect ?? \"schema\";\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any Column configuration\n const resolvedDefaultSelect: \"all\" | \"schema\" | Record<string, Column<any, any, TName>> =\n typeof defaultSelectOption === \"function\"\n ? defaultSelectOption(columns as ColumnMap<TFields, TName>)\n : defaultSelectOption;\n\n // Create the FMTable instance with Symbol-based internal properties\n const navigationPaths = (options?.navigationPaths ?? []) as TNavPaths;\n const table = new FMTable<TFields, TName, TNavPaths>({\n name,\n entityId: options?.entityId,\n useEntityIds: options?.useEntityIds,\n comment: options?.comment,\n schema: outputSchema,\n fields,\n navigationPaths,\n defaultSelect: resolvedDefaultSelect,\n baseTableConfig,\n });\n\n // Assign columns to the table instance (making them accessible directly)\n Object.assign(table, columns);\n\n return table as FMTableWithColumns<TFields, TName, TNavPaths>;\n}\n\n// /**\n// * Type guard to check if a value is a TableOccurrence or FMTable.\n// * Supports both Symbol-based (new) and underscore-prefixed (legacy) formats.\n// */\n// function isTableOccurrence(value: any): value is TableOccurrence {\n// if (!value || typeof value !== \"object\") {\n// return false;\n// }\n\n// // Check for Symbol-based format (new FMTable class)\n// if (\n// FMTableName in value &&\n// FMTableSchema in value &&\n// FMTableFields in value\n// ) {\n// return typeof value[FMTableName] === \"string\";\n// }\n\n// // Check for underscore-prefixed format (legacy interface)\n// if (\"_name\" in value && \"_schema\" in value && \"_fields\" in value) {\n// return typeof value._name === \"string\";\n// }\n\n// return false;\n// }\n\n/**\n * Helper to extract the schema type from a TableOccurrence or FMTable.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\nexport type InferTableSchema<T> = T extends FMTable<infer TFields, any> ? InferSchemaFromFields<TFields> : never;\n\n/**\n * Extract the schema type from an FMTable instance.\n * This is used to infer the schema from table objects passed to db.from(), expand(), etc.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type InferSchemaOutputFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any> ? InferSchemaFromFields<TFields> : never;\n\n/**\n * Extract the input schema type from an FMTable instance.\n * This is used for insert and update operations where we need write types.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type InferInputSchemaFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any> ? InferInputSchemaFromFields<TFields> : never;\n\n/**\n * Helper type to check if a FieldBuilder's input type excludes null and undefined.\n * This checks the TInput type parameter, which preserves nullability from notNull().\n */\ntype FieldInputExcludesNullish<F> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n F extends FieldBuilder<any, infer TInput, any>\n ? null extends TInput\n ? false\n : undefined extends TInput\n ? false\n : true\n : false;\n\n/**\n * Check if a FieldBuilder is readOnly at the type level\n */\ntype IsFieldReadOnly<F> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n F extends FieldBuilderType<any, any, any, infer ReadOnly> ? (ReadOnly extends true ? true : false) : false;\n\n/**\n * Compute insert data type from FMTable, making notNull fields required.\n * Fields are required if their FieldBuilder's TInput type excludes null/undefined.\n * All other fields are optional (can be omitted).\n * readOnly fields are excluded (including primaryKey/idField since they're automatically readOnly).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type InsertDataFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? K\n : never]: InferFieldInput<TFields[K]>;\n } & {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true\n ? never\n : FieldInputExcludesNullish<TFields[K]> extends true\n ? never\n : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Compute update data type from FMTable.\n * All fields are optional, but readOnly fields are excluded (including primaryKey/idField).\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport type UpdateDataFromFMTable<T extends FMTable<any, any>> =\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n T extends FMTable<infer TFields, any>\n ? {\n [K in keyof TFields as IsFieldReadOnly<TFields[K]> extends true ? never : K]?: InferFieldInput<TFields[K]>;\n }\n : never;\n\n/**\n * Extract the table name type from an FMTable.\n * This is a workaround since we can't directly index Symbols in types.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration, required for type inference with infer\nexport type ExtractTableName<T extends FMTable<any, any>> = T extends FMTable<any, infer Name> ? Name : never;\n\n/**\n * Validates that a target table's name matches one of the source table's navigationPaths.\n * Used to ensure type-safe expand/navigate operations.\n */\nexport type ValidExpandTarget<\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n SourceTable extends FMTable<any, any, any> | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n TargetTable extends FMTable<any, any, any>,\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n> = SourceTable extends FMTable<any, any, infer SourceNavPaths>\n ? ExtractTableName<TargetTable> extends SourceNavPaths[number]\n ? TargetTable\n : never\n : TargetTable;\n\n// ============================================================================\n// Helper Functions for Accessing FMTable Internal Properties\n// ============================================================================\n\n/**\n * Get the table name from an FMTable instance.\n * @param table - FMTable instance\n * @returns The table name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableName<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableName];\n}\n\n/**\n * Get the entity ID (FMTID) from an FMTable instance.\n * @param table - FMTable instance\n * @returns The entity ID or undefined if not using entity IDs\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableEntityId<T extends FMTable<any, any>>(table: T): string | undefined {\n return table[FMTableEntityId];\n}\n\n/**\n * Get the schema validator from an FMTable instance.\n * @param table - FMTable instance\n * @returns The StandardSchemaV1 validator record (partial - only fields with validators)\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableSchema<T extends FMTable<any, any>>(\n table: T,\n): Partial<Record<keyof T[typeof FMTableFields], StandardSchemaV1>> {\n return table[FMTableSchema];\n}\n\n/**\n * Get the fields from an FMTable instance.\n * @param table - FMTable instance\n * @returns The fields record\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableFields<T extends FMTable<any, any>>(table: T) {\n return table[FMTableFields];\n}\n\n/**\n * Get the navigation paths from an FMTable instance.\n * @param table - FMTable instance\n * @returns Array of navigation path names\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getNavigationPaths<T extends FMTable<any, any>>(table: T): readonly string[] {\n return table[FMTableNavigationPaths];\n}\n\n/**\n * Get the default select configuration from an FMTable instance.\n * @param table - FMTable instance\n * @returns Default select configuration\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getDefaultSelect<T extends FMTable<any, any>>(table: T) {\n return table[FMTableDefaultSelect];\n}\n\n/**\n * Get the base table configuration from an FMTable instance.\n * This provides access to schema, idField, required fields, readOnly fields, and field IDs.\n * @param table - FMTable instance\n * @returns Base table configuration object\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getBaseTableConfig<T extends FMTable<any, any>>(table: T) {\n return table[FMTableBaseTableConfig];\n}\n\n/**\n * Check if an FMTable instance is using entity IDs (both FMTID and FMFIDs).\n * @param table - FMTable instance\n * @returns True if using entity IDs, false otherwise\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function isUsingEntityIds<T extends FMTable<any, any>>(table: T): boolean {\n return table[FMTableEntityId] !== undefined && table[FMTableBaseTableConfig].fmfIds !== undefined;\n}\n\n/**\n * Get the field ID (FMFID) for a given field name, or the field name itself if not using IDs.\n * @param table - FMTable instance\n * @param fieldName - Field name to get the ID for\n * @returns The FMFID string or the original field name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getFieldId<T extends FMTable<any, any>>(table: T, fieldName: string): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds && fieldName in config.fmfIds) {\n const fieldId = config.fmfIds[fieldName];\n if (fieldId) {\n return fieldId;\n }\n }\n return fieldName;\n}\n\n/**\n * Get the field name for a given field ID (FMFID), or the ID itself if not found.\n * @param table - FMTable instance\n * @param fieldId - The FMFID to get the field name for\n * @returns The field name or the original ID\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getFieldName<T extends FMTable<any, any>>(table: T, fieldId: string): string {\n const config = table[FMTableBaseTableConfig];\n if (config.fmfIds) {\n for (const [fieldName, fmfId] of Object.entries(config.fmfIds)) {\n if (fmfId === fieldId) {\n return fieldName;\n }\n }\n }\n return fieldId;\n}\n/**\n * Get the table ID (FMTID or name) from an FMTable instance.\n * Returns the FMTID if available, otherwise returns the table name.\n * @param table - FMTable instance\n * @returns The FMTID string or the table name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableId<T extends FMTable<any, any>>(table: T): string {\n return table[FMTableEntityId] ?? table[FMTableName];\n}\n\n/**\n * Get the comment from an FMTable instance.\n * @param table - FMTable instance\n * @returns The comment string or undefined if not set\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableComment<T extends FMTable<any, any>>(table: T): string | undefined {\n return table[FMTableComment];\n}\n\n/**\n * Get all columns from a table as an object.\n * Useful for selecting all fields except some using destructuring.\n *\n * @example\n * const { password, ...cols } = getTableColumns(users)\n * db.from(users).list().select(cols)\n *\n * @param table - FMTable instance\n * @returns Object with all columns from the table\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableColumns<T extends FMTable<any, any>>(\n table: T,\n): ColumnMap<T[typeof FMTableFields], ExtractTableName<T>> {\n const fields = table[FMTableFields];\n const tableName = table[FMTableName];\n const tableEntityId = table[FMTableEntityId];\n const baseConfig = table[FMTableBaseTableConfig];\n\n const columns = {} as ColumnMap<T[typeof FMTableFields], ExtractTableName<T>>;\n for (const [fieldName, builder] of Object.entries(fields)) {\n // biome-ignore lint/suspicious/noExplicitAny: Internal property access for builder pattern\n const config = (builder as any)._getConfig();\n // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern\n (columns as any)[fieldName] = new Column({\n fieldName: String(fieldName),\n entityId: baseConfig.fmfIds?.[fieldName],\n tableName,\n tableEntityId,\n inputValidator: config.inputValidator,\n });\n }\n\n return columns;\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.js","sources":["../../src/transform.ts"],"sourcesContent":["import type { FMTable } from \"./orm/table\";\nimport { getBaseTableConfig, getFieldId, getFieldName, getTableId, getTableName, isUsingEntityIds } from \"./orm/table\";\n\nconst WHITESPACE_SPLIT_REGEX = /\\s+/;\n\n/**\n * Transforms field names to FileMaker field IDs (FMFID) in an object\n * @param data - Object with field names as keys\n * @param table - FMTable instance to get field IDs from\n * @returns Object with FMFID keys instead of field names\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport function transformFieldNamesToIds<T extends Record<string, any>>(\n data: T,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n): Record<string, any> {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformed: Record<string, any> = {};\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldId = getFieldId(table, fieldName);\n transformed[fieldId] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms FileMaker field IDs (FMFID) to field names in an object\n * @param data - Object with FMFID keys\n * @param table - FMTable instance to get field names from\n * @returns Object with field names as keys instead of FMFIDs\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport function transformFieldIdsToNames<T extends Record<string, any>>(\n data: T,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n): Record<string, any> {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformed: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Check if this is an OData metadata field (starts with @)\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n const fieldName = getFieldName(table, key);\n transformed[fieldName] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms a field name to FMFID or returns the field name if not using IDs\n * @param fieldName - The field name to transform\n * @param table - FMTable instance to get field ID from\n * @returns The FMFID or field name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformFieldName(fieldName: string, table: FMTable<any, any>): string {\n return getFieldId(table, fieldName);\n}\n\n/**\n * Transforms a table name to FMTID or returns the name if not using IDs\n * @param table - FMTable instance to get table ID from\n * @returns The FMTID or table name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformTableName(table: FMTable<any, any>): string {\n return getTableId(table);\n}\n\n/**\n * Gets both table name and ID from a table\n * @param table - FMTable instance\n * @returns Object with name (always present) and id (may be undefined if not using IDs)\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableIdentifiers(table: FMTable<any, any>): { name: string; id: string | undefined } {\n return {\n name: getTableName(table),\n id: isUsingEntityIds(table) ? getTableId(table) : undefined,\n };\n}\n\n/**\n * Transforms response data by converting field IDs back to field names recursively.\n * Handles both single records and arrays of records, as well as nested expand relationships.\n *\n * @param data - Response data from FileMaker (can be single record, array, or wrapped in value property)\n * @param table - FMTable instance for the main table\n * @param expandConfigs - Configuration for expanded relations (optional)\n * @returns Transformed data with field names instead of IDs\n */\nexport function transformResponseFields(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response data transformation\n data: any,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n expandConfigs?: Array<{\n relation: string;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>;\n }>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response data transformation\n): any {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // Handle null/undefined\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle OData list response with value array\n if (data.value && Array.isArray(data.value)) {\n return {\n ...data,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n value: data.value.map((record: any) => transformSingleRecord(record, table, expandConfigs)),\n };\n }\n\n // Handle array of records\n if (Array.isArray(data)) {\n return data.map((record) => transformSingleRecord(record, table, expandConfigs));\n }\n\n // Handle single record\n return transformSingleRecord(data, table, expandConfigs);\n}\n\n/**\n * Transforms a single record, converting field IDs to names and handling nested expands\n */\nfunction transformSingleRecord(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n record: any,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n expandConfigs?: Array<{\n relation: string;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>;\n }>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n): any {\n if (!record || typeof record !== \"object\") {\n return record;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformed: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(record)) {\n // Preserve OData metadata fields\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n // Check if this is an expanded relation (by relation name)\n let expandConfig = expandConfigs?.find((ec) => ec.relation === key);\n\n // If not found by relation name, check if this key is a FMTID\n // (FileMaker returns expanded relations with FMTID keys when using entity IDs)\n if (!expandConfig && key.startsWith(\"FMTID:\")) {\n expandConfig = expandConfigs?.find(\n (ec) => ec.table && isUsingEntityIds(ec.table) && getTableId(ec.table) === key,\n );\n }\n\n if (expandConfig?.table) {\n // Transform the expanded relation data recursively\n // Use the relation name (not the FMTID) as the key\n const relationKey = expandConfig.relation;\n\n if (Array.isArray(value)) {\n if (!expandConfig.table) {\n transformed[relationKey] = value;\n continue;\n }\n const nestedTable = expandConfig.table;\n transformed[relationKey] = value.map((nestedRecord) =>\n transformSingleRecord(\n nestedRecord,\n nestedTable,\n undefined, // Don't pass nested expand configs for now\n ),\n );\n } else if (value && typeof value === \"object\") {\n if (!expandConfig.table) {\n transformed[relationKey] = value;\n continue;\n }\n transformed[relationKey] = transformSingleRecord(value, expandConfig.table, undefined);\n } else {\n transformed[relationKey] = value;\n }\n continue;\n }\n\n // Transform field ID to field name\n const fieldName = getFieldName(table, key);\n transformed[fieldName] = value;\n }\n\n return transformed;\n}\n\n/**\n * Transforms an array of field names to FMFIDs\n * @param fieldNames - Array of field names\n * @param table - FMTable instance to get field IDs from\n * @returns Array of FMFIDs or field names\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformFieldNamesArray(fieldNames: string[], table: FMTable<any, any>): string[] {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return fieldNames;\n }\n\n return fieldNames.map((fieldName) => getFieldId(table, fieldName));\n}\n\n/**\n * Transforms a field name in an orderBy string (e.g., \"name desc\" -> \"FMFID:1 desc\")\n * @param orderByString - The orderBy string (field name with optional asc/desc)\n * @param table - FMTable instance to get field ID from\n * @returns Transformed orderBy string with FMFID\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformOrderByField(orderByString: string, table: FMTable<any, any> | undefined): string {\n if (!table) {\n return orderByString;\n }\n const config = getBaseTableConfig(table);\n if (!config?.fmfIds) {\n return orderByString;\n }\n\n // Parse the orderBy string to extract field name and direction\n const parts = orderByString.trim().split(WHITESPACE_SPLIT_REGEX);\n const fieldName = parts[0];\n if (!fieldName) {\n return orderByString;\n }\n const direction = parts[1]; // \"asc\" or \"desc\" or undefined\n\n const fieldId = getFieldId(table, fieldName);\n return direction ? `${fieldId} ${direction}` : fieldId;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transform.js","sources":["../../src/transform.ts"],"sourcesContent":["import type { FMTable } from \"./orm/table\";\nimport { getBaseTableConfig, getFieldId, getFieldName, getTableId, getTableName, isUsingEntityIds } from \"./orm/table\";\n\nconst WHITESPACE_SPLIT_REGEX = /\\s+/;\n\n/**\n * Transforms field names to FileMaker field IDs (FMFID) in an object\n * @param data - Object with field names as keys\n * @param table - FMTable instance to get field IDs from\n * @returns Object with FMFID keys instead of field names\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport function transformFieldNamesToIds<T extends Record<string, any>>(\n data: T,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n): Record<string, any> {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformed: Record<string, any> = {};\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldId = getFieldId(table, fieldName);\n transformed[fieldId] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms FileMaker field IDs (FMFID) to field names in an object\n * @param data - Object with FMFID keys\n * @param table - FMTable instance to get field names from\n * @returns Object with field names as keys instead of FMFIDs\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport function transformFieldIdsToNames<T extends Record<string, any>>(\n data: T,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n): Record<string, any> {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformed: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Check if this is an OData metadata field (starts with @)\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n const fieldName = getFieldName(table, key);\n transformed[fieldName] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms a field name to FMFID or returns the field name if not using IDs\n * @param fieldName - The field name to transform\n * @param table - FMTable instance to get field ID from\n * @returns The FMFID or field name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformFieldName(fieldName: string, table: FMTable<any, any>): string {\n return getFieldId(table, fieldName);\n}\n\n/**\n * Transforms a table name to FMTID or returns the name if not using IDs\n * @param table - FMTable instance to get table ID from\n * @returns The FMTID or table name\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformTableName(table: FMTable<any, any>): string {\n return getTableId(table);\n}\n\n/**\n * Gets both table name and ID from a table\n * @param table - FMTable instance\n * @returns Object with name (always present) and id (may be undefined if not using IDs)\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function getTableIdentifiers(table: FMTable<any, any>): { name: string; id: string | undefined } {\n return {\n name: getTableName(table),\n id: isUsingEntityIds(table) ? getTableId(table) : undefined,\n };\n}\n\n/**\n * Transforms response data by converting field IDs back to field names recursively.\n * Handles both single records and arrays of records, as well as nested expand relationships.\n *\n * @param data - Response data from FileMaker (can be single record, array, or wrapped in value property)\n * @param table - FMTable instance for the main table\n * @param expandConfigs - Configuration for expanded relations (optional)\n * @returns Transformed data with field names instead of IDs\n */\nexport function transformResponseFields(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response data transformation\n data: any,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n expandConfigs?: Array<{\n relation: string;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>;\n }>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response data transformation\n): any {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // Handle null/undefined\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle OData list response with value array\n if (data.value && Array.isArray(data.value)) {\n return {\n ...data,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n value: data.value.map((record: any) => transformSingleRecord(record, table, expandConfigs)),\n };\n }\n\n // Handle array of records\n if (Array.isArray(data)) {\n return data.map((record) => transformSingleRecord(record, table, expandConfigs));\n }\n\n // Handle single record\n return transformSingleRecord(data, table, expandConfigs);\n}\n\n/**\n * Transforms a single record, converting field IDs to names and handling nested expands\n */\nfunction transformSingleRecord(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n record: any,\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table: FMTable<any, any>,\n expandConfigs?: Array<{\n relation: string;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>;\n }>,\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record transformation\n): any {\n if (!record || typeof record !== \"object\") {\n return record;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformed: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(record)) {\n // Preserve OData metadata fields\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n // Check if this is an expanded relation (by relation name)\n let expandConfig = expandConfigs?.find((ec) => ec.relation === key);\n\n // If not found by relation name, check if this key is a FMTID\n // (FileMaker returns expanded relations with FMTID keys when using entity IDs)\n if (!expandConfig && key.startsWith(\"FMTID:\")) {\n expandConfig = expandConfigs?.find(\n (ec) => ec.table && isUsingEntityIds(ec.table) && getTableId(ec.table) === key,\n );\n }\n\n if (expandConfig?.table) {\n // Transform the expanded relation data recursively\n // Use the relation name (not the FMTID) as the key\n const relationKey = expandConfig.relation;\n\n if (Array.isArray(value)) {\n if (!expandConfig.table) {\n transformed[relationKey] = value;\n continue;\n }\n const nestedTable = expandConfig.table;\n transformed[relationKey] = value.map((nestedRecord) =>\n transformSingleRecord(\n nestedRecord,\n nestedTable,\n undefined, // Don't pass nested expand configs for now\n ),\n );\n } else if (value && typeof value === \"object\") {\n if (!expandConfig.table) {\n transformed[relationKey] = value;\n continue;\n }\n transformed[relationKey] = transformSingleRecord(value, expandConfig.table, undefined);\n } else {\n transformed[relationKey] = value;\n }\n continue;\n }\n\n // Transform field ID to field name\n const fieldName = getFieldName(table, key);\n transformed[fieldName] = value;\n }\n\n return transformed;\n}\n\n/**\n * Transforms an array of field names to FMFIDs\n * @param fieldNames - Array of field names\n * @param table - FMTable instance to get field IDs from\n * @returns Array of FMFIDs or field names\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformFieldNamesArray(fieldNames: string[], table: FMTable<any, any>): string[] {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return fieldNames;\n }\n\n return fieldNames.map((fieldName) => getFieldId(table, fieldName));\n}\n\n/**\n * Transforms a field name in an orderBy string (e.g., \"name desc\" -> \"FMFID:1 desc\")\n * @param orderByString - The orderBy string (field name with optional asc/desc)\n * @param table - FMTable instance to get field ID from\n * @returns Transformed orderBy string with FMFID\n */\n// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\nexport function transformOrderByField(orderByString: string, table: FMTable<any, any> | undefined): string {\n if (!table) {\n return orderByString;\n }\n const config = getBaseTableConfig(table);\n if (!config?.fmfIds) {\n return orderByString;\n }\n\n // Parse the orderBy string to extract field name and direction\n const parts = orderByString.trim().split(WHITESPACE_SPLIT_REGEX);\n const fieldName = parts[0];\n if (!fieldName) {\n return orderByString;\n }\n const direction = parts[1]; // \"asc\" or \"desc\" or undefined\n\n const fieldId = getFieldId(table, fieldName);\n return direction ? `${fieldId} ${direction}` : fieldId;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/esm/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type { FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { InternalLogger } from \"./logger\";\n\nexport type Auth = { username: string; password: string } | { apiKey: string };\n\nexport interface ExecutableBuilder<T> {\n execute(): Promise<Result<T>>;\n // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value\n getRequestConfig(): { method: string; url: string; body?: any };\n\n /**\n * Convert this builder to a native Request object for batch processing.\n * @param baseUrl - The base URL for the OData service\n * @param options - Optional execution options (e.g., includeODataAnnotations)\n * @returns A native Request object\n */\n toRequest(baseUrl: string, options?: ExecuteOptions): Request;\n\n /**\n * Process a raw Response object into a typed Result.\n * This allows builders to apply their own validation and transformation logic.\n * @param response - The native Response object from the batch operation\n * @param options - Optional execution options (e.g., skipValidation, includeODataAnnotations)\n * @returns A typed Result with the builder's expected return type\n */\n processResponse(response: Response, options?: ExecuteOptions): Promise<Result<T>>;\n}\n\nexport interface ExecutionContext {\n _makeRequest<T>(\n url: string,\n options?: RequestInit &\n FFetchOptions & {\n useEntityIds?: boolean;\n includeSpecialColumns?: boolean;\n },\n ): Promise<Result<T>>;\n _setUseEntityIds?(useEntityIds: boolean): void;\n _getUseEntityIds?(): boolean;\n _setIncludeSpecialColumns?(includeSpecialColumns: boolean): void;\n _getIncludeSpecialColumns?(): boolean;\n _getBaseUrl?(): string;\n _getLogger?(): InternalLogger;\n}\n\nexport type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n [K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output> ? Output : never;\n};\n\nexport type WithSpecialColumns<T> =\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n T extends Record<string, any>\n ? T & {\n ROWID: number;\n ROWMODID: number;\n }\n : never;\n\n// Helper type to exclude special columns from a union of keys\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any key type\nexport type ExcludeSystemFields<T extends keyof any> = Exclude<T, \"ROWID\" | \"ROWMODID\">;\n\n// OData record metadata fields (present on each record)\nexport interface ODataRecordMetadata {\n \"@id\": string;\n \"@editLink\": string;\n}\n\n// OData response wrapper (top-level, internal use only)\nexport interface ODataListResponse<T> {\n \"@context\": string;\n value: (T & ODataRecordMetadata)[];\n}\n\nexport type ODataSingleResponse<T> = T &\n ODataRecordMetadata & {\n \"@context\": string;\n };\n\n// OData response for single field values\nexport interface ODataFieldResponse<T> {\n \"@context\": string;\n value: T;\n}\n\n// Result pattern for execute responses\nexport type Result<T, E = import(\"./errors\").FMODataErrorType> =\n | { data: T; error: undefined }\n | { data: undefined; error: E };\n\n// Batch operation result types\nexport interface BatchItemResult<T> {\n data: T | undefined;\n error: import(\"./errors\").FMODataErrorType | undefined;\n status: number; // HTTP status code (0 for truncated)\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any tuple type\nexport interface BatchResult<T extends readonly any[]> {\n results: { [K in keyof T]: BatchItemResult<T[K]> };\n successCount: number;\n errorCount: number;\n truncated: boolean;\n firstErrorIndex: number | null;\n}\n\n// Make specific keys required, rest optional\nexport type MakeFieldsRequired<T, Keys extends keyof T> = Partial<T> & Required<Pick<T, Keys>>;\n\n// Extract keys from schema where validator doesn't allow null/undefined (auto-required fields)\nexport type AutoRequiredKeys<Schema extends Record<string, StandardSchemaV1>> = {\n [K in keyof Schema]: Extract<StandardSchemaV1.InferOutput<Schema[K]>, null | undefined> extends never ? K : never;\n}[keyof Schema];\n\n// Helper type to compute excluded fields (readOnly fields + idField)\nexport type ExcludedFields<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any key type\n IdField extends keyof any | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any array type\n ReadOnly extends readonly any[],\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any key type\n> = IdField extends keyof any ? IdField | ReadOnly[number] : ReadOnly[number];\n\n// Helper type for InsertData computation\ntype _ComputeInsertData<\n Schema extends Record<string, StandardSchemaV1>,\n IdField extends keyof Schema | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any array type\n Required extends readonly any[],\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any array type\n ReadOnly extends readonly any[],\n> = [Required[number]] extends [keyof InferSchemaType<Schema>]\n ? Required extends readonly (keyof InferSchemaType<Schema>)[]\n ? MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema> | Required[number], ExcludedFields<IdField, ReadOnly>>\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >;\n\nexport interface ExecuteOptions {\n includeODataAnnotations?: boolean;\n skipValidation?: boolean;\n /**\n * Overrides the default behavior of the database to use entity IDs (rather than field names) in THIS REQUEST ONLY\n */\n useEntityIds?: boolean;\n /**\n * Overrides the default behavior of the database to include special columns (ROWID and ROWMODID) in THIS REQUEST ONLY.\n * Note: Special columns are only included when there is no $select query.\n */\n includeSpecialColumns?: boolean;\n}\n\n/**\n * Type for the fetchHandler callback function.\n * This is a convenience type export that matches the fetchHandler signature in FFetchOptions.\n *\n * @example\n * ```typescript\n * import type { FetchHandler } from '@proofkit/fmodata';\n *\n * const myFetchHandler: FetchHandler = (input, init) => {\n * console.log('Custom fetch:', input);\n * return fetch(input, init);\n * };\n *\n * await query.execute({\n * fetchHandler: myFetchHandler\n * });\n * ```\n */\nexport type FetchHandler = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Combined type for execute() method options.\n *\n * Uses FFetchOptions from @fetchkit/ffetch to ensure proper type inference.\n * FFetchOptions is re-exported in the package to ensure type availability in consuming packages.\n */\nexport type ExecuteMethodOptions<EO extends ExecuteOptions = ExecuteOptions> = RequestInit &\n FFetchOptions &\n ExecuteOptions &\n EO;\n\n/**\n * Get the Accept header value based on includeODataAnnotations option\n * @param includeODataAnnotations - Whether to include OData annotations\n * @returns Accept header value\n */\nexport function getAcceptHeader(includeODataAnnotations?: boolean): string {\n return includeODataAnnotations === true ? \"application/json\" : \"application/json;odata.metadata=none\";\n}\n\nexport type ConditionallyWithODataAnnotations<\n T,\n IncludeODataAnnotations extends boolean,\n> = IncludeODataAnnotations extends true\n ? T & {\n \"@id\": string;\n \"@editLink\": string;\n }\n : T;\n\n/**\n * Normalizes includeSpecialColumns with a database-level default.\n * Uses distributive conditional types to handle unions correctly.\n * @template IncludeSpecialColumns - The includeSpecialColumns value from execute options\n * @template DatabaseDefault - The database-level includeSpecialColumns setting (defaults to false)\n */\nexport type NormalizeIncludeSpecialColumns<\n IncludeSpecialColumns extends boolean | undefined,\n DatabaseDefault extends boolean = false,\n> = [IncludeSpecialColumns] extends [true] ? true : [IncludeSpecialColumns] extends [false] ? false : DatabaseDefault; // When undefined, use database-level default\n\n/**\n * Conditionally adds ROWID and ROWMODID special columns to a type.\n * Special columns are only included when:\n * - includeSpecialColumns is true AND\n * - hasSelect is false (no $select query was applied) AND\n * - T is an object type (not a primitive like string or number)\n *\n * Handles both single objects and arrays of objects.\n */\nexport type ConditionallyWithSpecialColumns<\n T,\n IncludeSpecialColumns extends boolean,\n HasSelect extends boolean,\n> = IncludeSpecialColumns extends true\n ? HasSelect extends false\n ? // Handle array types\n T extends readonly (infer U)[]\n ? // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n U extends Record<string, any>\n ? (U & {\n ROWID: number;\n ROWMODID: number;\n })[]\n : T\n : // Handle single object types\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n T extends Record<string, any>\n ? T & {\n ROWID: number;\n ROWMODID: number;\n }\n : T // Don't add special columns to primitives (e.g., single field queries)\n : T\n : T;\n\n// Helper type to extract schema from a FMTable\nexport type ExtractSchemaFromOccurrence<Occ> = Occ extends {\n baseTable: { schema: infer S };\n}\n ? S extends Record<string, StandardSchemaV1>\n ? S\n : Record<string, StandardSchemaV1>\n : Record<string, StandardSchemaV1>;\n\nexport interface GenericFieldMetadata {\n $Nullable?: boolean;\n \"@Index\"?: boolean;\n \"@Calculation\"?: boolean;\n \"@Summary\"?: boolean;\n \"@Global\"?: boolean;\n \"@Org.OData.Core.V1.Permissions\"?: \"Org.OData.Core.V1.Permission@Read\";\n}\n\nexport type StringFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.String\";\n $DefaultValue?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n $MaxLength?: number;\n};\n\nexport type DecimalFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Decimal\";\n \"@AutoGenerated\"?: boolean;\n};\n\nexport type DateFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURDATE\" | \"CURRENT_DATE\";\n};\n\nexport type TimeOfDayFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.TimeOfDay\";\n $DefaultValue?: \"CURTIME\" | \"CURRENT_TIME\";\n};\n\nexport type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURTIMESTAMP\" | \"CURRENT_TIMESTAMP\";\n \"@VersionId\"?: boolean;\n};\n\nexport interface StreamFieldMetadata {\n $Type: \"Edm.Stream\";\n $Nullable?: boolean;\n \"@EnclosedPath\": string;\n \"@ExternalOpenPath\": string;\n \"@ExternalSecurePath\"?: string;\n}\n\nexport type FieldMetadata =\n | StringFieldMetadata\n | DecimalFieldMetadata\n | DateFieldMetadata\n | TimeOfDayFieldMetadata\n | DateTimeOffsetFieldMetadata\n | StreamFieldMetadata;\n\nexport type EntityType = {\n $Kind: \"EntityType\";\n $Key: string[];\n} & Record<string, FieldMetadata>;\n\nexport interface EntitySet {\n $Kind: \"EntitySet\";\n $Type: string;\n}\n\nexport type Metadata = Record<string, EntityType | EntitySet>;\n"],"names":[],"mappings":"AAsMO,SAAS,gBAAgB,yBAA2C;AAClE,SAAA,4BAA4B,OAAO,qBAAqB;AACjE;"}
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import type { FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { InternalLogger } from \"./logger\";\n\nexport type Auth = { username: string; password: string } | { apiKey: string };\n\nexport interface ExecutableBuilder<T> {\n execute(): Promise<Result<T>>;\n // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value\n getRequestConfig(): { method: string; url: string; body?: any };\n\n /**\n * Convert this builder to a native Request object for batch processing.\n * @param baseUrl - The base URL for the OData service\n * @param options - Optional execution options (e.g., includeODataAnnotations)\n * @returns A native Request object\n */\n toRequest(baseUrl: string, options?: ExecuteOptions): Request;\n\n /**\n * Process a raw Response object into a typed Result.\n * This allows builders to apply their own validation and transformation logic.\n * @param response - The native Response object from the batch operation\n * @param options - Optional execution options (e.g., skipValidation, includeODataAnnotations)\n * @returns A typed Result with the builder's expected return type\n */\n processResponse(response: Response, options?: ExecuteOptions): Promise<Result<T>>;\n}\n\nexport interface ExecutionContext {\n _makeRequest<T>(\n url: string,\n options?: RequestInit &\n FFetchOptions & {\n useEntityIds?: boolean;\n includeSpecialColumns?: boolean;\n },\n ): Promise<Result<T>>;\n _setUseEntityIds?(useEntityIds: boolean): void;\n _getUseEntityIds?(): boolean;\n _setIncludeSpecialColumns?(includeSpecialColumns: boolean): void;\n _getIncludeSpecialColumns?(): boolean;\n _getBaseUrl?(): string;\n _getLogger?(): InternalLogger;\n}\n\nexport type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {\n // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer\n [K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output> ? Output : never;\n};\n\nexport type WithSpecialColumns<T> =\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n T extends Record<string, any>\n ? T & {\n ROWID: number;\n ROWMODID: number;\n }\n : never;\n\n// Helper type to exclude special columns from a union of keys\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any key type\nexport type ExcludeSystemFields<T extends keyof any> = Exclude<T, \"ROWID\" | \"ROWMODID\">;\n\n// OData record metadata fields (present on each record)\nexport interface ODataRecordMetadata {\n \"@id\": string;\n \"@editLink\": string;\n}\n\n// OData response wrapper (top-level, internal use only)\nexport interface ODataListResponse<T> {\n \"@context\": string;\n value: (T & ODataRecordMetadata)[];\n}\n\nexport type ODataSingleResponse<T> = T &\n ODataRecordMetadata & {\n \"@context\": string;\n };\n\n// OData response for single field values\nexport interface ODataFieldResponse<T> {\n \"@context\": string;\n value: T;\n}\n\n// Result pattern for execute responses\nexport type Result<T, E = import(\"./errors\").FMODataErrorType> =\n | { data: T; error: undefined }\n | { data: undefined; error: E };\n\n// Batch operation result types\nexport interface BatchItemResult<T> {\n data: T | undefined;\n error: import(\"./errors\").FMODataErrorType | undefined;\n status: number; // HTTP status code (0 for truncated)\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any tuple type\nexport interface BatchResult<T extends readonly any[]> {\n results: { [K in keyof T]: BatchItemResult<T[K]> };\n successCount: number;\n errorCount: number;\n truncated: boolean;\n firstErrorIndex: number | null;\n}\n\n// Make specific keys required, rest optional\nexport type MakeFieldsRequired<T, Keys extends keyof T> = Partial<T> & Required<Pick<T, Keys>>;\n\n// Extract keys from schema where validator doesn't allow null/undefined (auto-required fields)\nexport type AutoRequiredKeys<Schema extends Record<string, StandardSchemaV1>> = {\n [K in keyof Schema]: Extract<StandardSchemaV1.InferOutput<Schema[K]>, null | undefined> extends never ? K : never;\n}[keyof Schema];\n\n// Helper type to compute excluded fields (readOnly fields + idField)\nexport type ExcludedFields<\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any key type\n IdField extends keyof any | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any array type\n ReadOnly extends readonly any[],\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any key type\n> = IdField extends keyof any ? IdField | ReadOnly[number] : ReadOnly[number];\n\n// Helper type for InsertData computation\ntype _ComputeInsertData<\n Schema extends Record<string, StandardSchemaV1>,\n IdField extends keyof Schema | undefined,\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any array type\n Required extends readonly any[],\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any array type\n ReadOnly extends readonly any[],\n> = [Required[number]] extends [keyof InferSchemaType<Schema>]\n ? Required extends readonly (keyof InferSchemaType<Schema>)[]\n ? MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema> | Required[number], ExcludedFields<IdField, ReadOnly>>\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >;\n\nexport interface ExecuteOptions {\n includeODataAnnotations?: boolean;\n skipValidation?: boolean;\n /**\n * Overrides the default behavior of the database to use entity IDs (rather than field names) in THIS REQUEST ONLY\n */\n useEntityIds?: boolean;\n /**\n * Overrides the default behavior of the database to include special columns (ROWID and ROWMODID) in THIS REQUEST ONLY.\n * Note: Special columns are only included when there is no $select query.\n */\n includeSpecialColumns?: boolean;\n}\n\n/**\n * Type for the fetchHandler callback function.\n * This is a convenience type export that matches the fetchHandler signature in FFetchOptions.\n *\n * @example\n * ```typescript\n * import type { FetchHandler } from '@proofkit/fmodata';\n *\n * const myFetchHandler: FetchHandler = (input, init) => {\n * console.log('Custom fetch:', input);\n * return fetch(input, init);\n * };\n *\n * await query.execute({\n * fetchHandler: myFetchHandler\n * });\n * ```\n */\nexport type FetchHandler = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Combined type for execute() method options.\n *\n * Uses FFetchOptions from @fetchkit/ffetch to ensure proper type inference.\n * FFetchOptions is re-exported in the package to ensure type availability in consuming packages.\n */\nexport type ExecuteMethodOptions<EO extends ExecuteOptions = ExecuteOptions> = RequestInit &\n FFetchOptions &\n ExecuteOptions &\n EO;\n\n/**\n * Get the Accept header value based on includeODataAnnotations option\n * @param includeODataAnnotations - Whether to include OData annotations\n * @returns Accept header value\n */\nexport function getAcceptHeader(includeODataAnnotations?: boolean): string {\n return includeODataAnnotations === true ? \"application/json\" : \"application/json;odata.metadata=none\";\n}\n\nexport type ConditionallyWithODataAnnotations<\n T,\n IncludeODataAnnotations extends boolean,\n> = IncludeODataAnnotations extends true\n ? T & {\n \"@id\": string;\n \"@editLink\": string;\n }\n : T;\n\n/**\n * Normalizes includeSpecialColumns with a database-level default.\n * Uses distributive conditional types to handle unions correctly.\n * @template IncludeSpecialColumns - The includeSpecialColumns value from execute options\n * @template DatabaseDefault - The database-level includeSpecialColumns setting (defaults to false)\n */\nexport type NormalizeIncludeSpecialColumns<\n IncludeSpecialColumns extends boolean | undefined,\n DatabaseDefault extends boolean = false,\n> = [IncludeSpecialColumns] extends [true] ? true : [IncludeSpecialColumns] extends [false] ? false : DatabaseDefault; // When undefined, use database-level default\n\n/**\n * Conditionally adds ROWID and ROWMODID special columns to a type.\n * Special columns are only included when:\n * - includeSpecialColumns is true AND\n * - hasSelect is false (no $select query was applied) AND\n * - T is an object type (not a primitive like string or number)\n *\n * Handles both single objects and arrays of objects.\n */\nexport type ConditionallyWithSpecialColumns<\n T,\n IncludeSpecialColumns extends boolean,\n HasSelect extends boolean,\n> = IncludeSpecialColumns extends true\n ? HasSelect extends false\n ? // Handle array types\n T extends readonly (infer U)[]\n ? // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n U extends Record<string, any>\n ? (U & {\n ROWID: number;\n ROWMODID: number;\n })[]\n : T\n : // Handle single object types\n // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\n T extends Record<string, any>\n ? T & {\n ROWID: number;\n ROWMODID: number;\n }\n : T // Don't add special columns to primitives (e.g., single field queries)\n : T\n : T;\n\n// Helper type to extract schema from a FMTable\nexport type ExtractSchemaFromOccurrence<Occ> = Occ extends {\n baseTable: { schema: infer S };\n}\n ? S extends Record<string, StandardSchemaV1>\n ? S\n : Record<string, StandardSchemaV1>\n : Record<string, StandardSchemaV1>;\n\nexport interface GenericFieldMetadata {\n $Nullable?: boolean;\n \"@Index\"?: boolean;\n \"@Calculation\"?: boolean;\n \"@Summary\"?: boolean;\n \"@Global\"?: boolean;\n \"@Org.OData.Core.V1.Permissions\"?: \"Org.OData.Core.V1.Permission@Read\";\n}\n\nexport type StringFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.String\";\n $DefaultValue?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n $MaxLength?: number;\n};\n\nexport type DecimalFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Decimal\";\n \"@AutoGenerated\"?: boolean;\n};\n\nexport type DateFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURDATE\" | \"CURRENT_DATE\";\n};\n\nexport type TimeOfDayFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.TimeOfDay\";\n $DefaultValue?: \"CURTIME\" | \"CURRENT_TIME\";\n};\n\nexport type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURTIMESTAMP\" | \"CURRENT_TIMESTAMP\";\n \"@VersionId\"?: boolean;\n};\n\nexport interface StreamFieldMetadata {\n $Type: \"Edm.Stream\";\n $Nullable?: boolean;\n \"@EnclosedPath\": string;\n \"@ExternalOpenPath\": string;\n \"@ExternalSecurePath\"?: string;\n}\n\nexport type FieldMetadata =\n | StringFieldMetadata\n | DecimalFieldMetadata\n | DateFieldMetadata\n | TimeOfDayFieldMetadata\n | DateTimeOffsetFieldMetadata\n | StreamFieldMetadata;\n\nexport type EntityType = {\n $Kind: \"EntityType\";\n $Key: string[];\n} & Record<string, FieldMetadata>;\n\nexport interface EntitySet {\n $Kind: \"EntitySet\";\n $Type: string;\n}\n\nexport type Metadata = Record<string, EntityType | EntitySet>;\n"],"names":[],"mappings":"AAsMO,SAAS,gBAAgB,yBAA2C;AACzE,SAAO,4BAA4B,OAAO,qBAAqB;AACjE;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sources":["../../src/validation.ts"],"sourcesContent":["/** biome-ignore-all lint/style/useCollapsedElseIf: easier to read */\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { RecordCountMismatchError, ResponseStructureError, ValidationError } from \"./errors\";\nimport type { FMTable } from \"./orm/table\";\nimport type { ODataRecordMetadata } from \"./types\";\n\n/**\n * Validates and transforms input data for insert/update operations.\n * Applies input validators (writeValidators) to transform user input to database format.\n * Fields without input validators are passed through unchanged.\n *\n * @param data - The input data to validate and transform\n * @param inputSchema - Optional schema containing input validators for each field\n * @returns Transformed data ready to send to the server\n * @throws ValidationError if any field fails validation\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateAndTransformInput<T extends Record<string, any>>(\n data: Partial<T>,\n inputSchema?: Partial<Record<string, StandardSchemaV1>>,\n): Promise<Partial<T>> {\n // If no input schema, return data as-is\n if (!inputSchema) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformedData: Record<string, any> = { ...data };\n\n // Process each field that has an input validator\n for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) {\n // Skip if no schema for this field\n if (!fieldSchema) {\n continue;\n }\n\n // Only process fields that are present in the input data\n if (fieldName in data) {\n const inputValue = data[fieldName];\n\n try {\n // Run the input validator to transform the value\n let result = fieldSchema[\"~standard\"].validate(inputValue);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // Check for validation errors\n if (result.issues) {\n throw new ValidationError(`Input validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: inputValue,\n cause: result.issues,\n });\n }\n\n // Store the transformed value\n transformedData[fieldName] = result.value;\n } catch (error) {\n // If it's already a ValidationError, re-throw it\n if (error instanceof ValidationError) {\n throw error;\n }\n\n // Otherwise, wrap the error\n throw new ValidationError(`Input validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: inputValue,\n cause: error,\n });\n }\n }\n }\n\n // Fields without input validators are already in transformedData (passed through)\n return transformedData as Partial<T>;\n}\n\n// Type for expand validation configuration\nexport interface ExpandValidationConfig {\n relation: string;\n targetSchema?: Partial<Record<string, StandardSchemaV1>>;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n targetTable?: FMTable<any, any>;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>; // For transformation\n selectedFields?: string[];\n nestedExpands?: ExpandValidationConfig[];\n}\n\n/**\n * Validates a single record against a schema, only validating selected fields.\n * Also validates expanded relations if expandConfigs are provided.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateRecord<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record validation\n record: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n includeSpecialColumns?: boolean,\n): Promise<{ valid: true; data: T & ODataRecordMetadata } | { valid: false; error: ValidationError }> {\n // Extract OData metadata fields (don't validate them - include if present)\n const { \"@id\": id, \"@editLink\": editLink, ...rest } = record;\n\n // Only include metadata fields if they actually exist and have values\n const metadata: Partial<ODataRecordMetadata> = {};\n if (id) {\n metadata[\"@id\"] = id;\n }\n if (editLink) {\n metadata[\"@editLink\"] = editLink;\n }\n\n // If no schema, just return the data with metadata\n // Exclude special columns if includeSpecialColumns is false\n if (!schema) {\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n const specialColumns: { ROWID?: number; ROWMODID?: number } = {};\n if (includeSpecialColumns) {\n if (ROWID !== undefined) {\n specialColumns.ROWID = ROWID;\n }\n if (ROWMODID !== undefined) {\n specialColumns.ROWMODID = ROWMODID;\n }\n }\n return {\n valid: true,\n data: {\n ...restWithoutSystemFields,\n ...specialColumns,\n ...metadata,\n } as T & ODataRecordMetadata,\n };\n }\n\n // Extract FileMaker special columns - preserve them if includeSpecialColumns is enabled\n // Note: Special columns are excluded when using single() method (per OData spec behavior)\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n const specialColumns: { ROWID?: number; ROWMODID?: number } = {};\n // Only include special columns if explicitly enabled (they're excluded for single() by design)\n if (includeSpecialColumns) {\n if (ROWID !== undefined) {\n specialColumns.ROWID = ROWID;\n }\n if (ROWMODID !== undefined) {\n specialColumns.ROWMODID = ROWMODID;\n }\n }\n\n // If selected fields are specified, validate only those fields\n if (selectedFields && selectedFields.length > 0) {\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field validation\n const validatedRecord: Record<string, any> = {};\n\n for (const field of selectedFields) {\n const fieldName = String(field);\n const fieldSchema = schema[fieldName];\n\n if (fieldSchema) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: input,\n cause: result.issues,\n }),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws directly, wrap it\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: input,\n cause: originalError,\n }),\n };\n }\n } else {\n // For fields not in schema (like when explicitly selecting ROWID/ROWMODID)\n // Check if it's a special column that was destructured earlier\n if (fieldName === \"ROWID\" || fieldName === \"ROWMODID\") {\n // Use the destructured value since it was removed from rest\n if (fieldName === \"ROWID\" && ROWID !== undefined) {\n validatedRecord[fieldName] = ROWID;\n } else if (fieldName === \"ROWMODID\" && ROWMODID !== undefined) {\n validatedRecord[fieldName] = ROWMODID;\n }\n } else {\n // For other fields not in schema, include them from the original response\n validatedRecord[fieldName] = rest[fieldName];\n }\n }\n }\n\n // Validate expanded relations\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) ||\n expandConfig.selectedFields?.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic expanded items validation\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n // Return only the validated fields plus metadata and special columns\n // Do NOT merge restWithoutSystemFields as that would overwrite validated values\n return {\n valid: true,\n data: { ...validatedRecord, ...specialColumns, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Validate all fields in schema, but exclude ROWID/ROWMODID by default (unless includeSpecialColumns is enabled)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field validation\n const validatedRecord: Record<string, any> = { ...restWithoutSystemFields };\n\n for (const [fieldName, fieldSchema] of Object.entries(schema)) {\n // Skip if no schema for this field\n if (!fieldSchema) {\n continue;\n }\n\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: input,\n cause: result.issues,\n }),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws an error directly, catch and wrap it\n // This preserves the original error instance for instanceof checks\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: input,\n cause: originalError,\n }),\n };\n }\n }\n\n // Validate expanded relations even when not using selected fields\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) ||\n expandConfig.selectedFields?.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic expanded items validation\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n return {\n valid: true,\n data: { ...validatedRecord, ...specialColumns, ...metadata } as T & ODataRecordMetadata,\n };\n}\n\n/**\n * Validates a list response against a schema.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateListResponse<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response validation\n response: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n includeSpecialColumns?: boolean,\n): Promise<\n { valid: true; data: (T & ODataRecordMetadata)[] } | { valid: false; error: ResponseStructureError | ValidationError }\n> {\n // Check if response has the expected structure\n if (!response || typeof response !== \"object\") {\n return {\n valid: false,\n error: new ResponseStructureError(\"an object\", response),\n };\n }\n\n // Extract @context (for internal validation, but we won't return it)\n const { \"@context\": context, value, ..._rest } = response;\n\n if (!Array.isArray(value)) {\n return {\n valid: false,\n error: new ResponseStructureError(\"'value' property to be an array\", value),\n };\n }\n\n // Validate each record in the array\n const validatedRecords: (T & ODataRecordMetadata)[] = [];\n\n for (const record of value) {\n const validation = await validateRecord<T>(record, schema, selectedFields, expandConfigs, includeSpecialColumns);\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n validatedRecords.push(validation.data);\n }\n\n return {\n valid: true,\n data: validatedRecords,\n };\n}\n\n/**\n * Validates a single record response against a schema.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateSingleResponse<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response validation\n response: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n mode: \"exact\" | \"maybe\" = \"maybe\",\n includeSpecialColumns?: boolean,\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata) | null }\n | { valid: false; error: RecordCountMismatchError | ValidationError }\n> {\n // Check for multiple records (error in both modes)\n if (response.value && Array.isArray(response.value) && response.value.length > 1) {\n return {\n valid: false,\n error: new RecordCountMismatchError(mode === \"exact\" ? \"one\" : \"at-most-one\", response.value.length),\n };\n }\n\n // Handle empty responses\n if (!response || (response.value && response.value.length === 0)) {\n if (mode === \"exact\") {\n return {\n valid: false,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n // mode === \"maybe\" - return null for empty\n return {\n valid: true,\n data: null,\n };\n }\n\n // Single record validation\n const record = response.value?.[0] ?? response;\n const validation = await validateRecord<T>(record, schema, selectedFields, expandConfigs, includeSpecialColumns);\n\n if (!validation.valid) {\n return validation as { valid: false; error: ValidationError };\n }\n\n return {\n valid: true,\n data: validation.data,\n };\n}\n"],"names":["ROWID","ROWMODID","restWithoutSystemFields","specialColumns","validatedRecord"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validation.js","sources":["../../src/validation.ts"],"sourcesContent":["/** biome-ignore-all lint/style/useCollapsedElseIf: easier to read */\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { RecordCountMismatchError, ResponseStructureError, ValidationError } from \"./errors\";\nimport type { FMTable } from \"./orm/table\";\nimport type { ODataRecordMetadata } from \"./types\";\n\n/**\n * Validates and transforms input data for insert/update operations.\n * Applies input validators (writeValidators) to transform user input to database format.\n * Fields without input validators are passed through unchanged.\n *\n * @param data - The input data to validate and transform\n * @param inputSchema - Optional schema containing input validators for each field\n * @returns Transformed data ready to send to the server\n * @throws ValidationError if any field fails validation\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateAndTransformInput<T extends Record<string, any>>(\n data: Partial<T>,\n inputSchema?: Partial<Record<string, StandardSchemaV1>>,\n): Promise<Partial<T>> {\n // If no input schema, return data as-is\n if (!inputSchema) {\n return data;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field transformation\n const transformedData: Record<string, any> = { ...data };\n\n // Process each field that has an input validator\n for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) {\n // Skip if no schema for this field\n if (!fieldSchema) {\n continue;\n }\n\n // Only process fields that are present in the input data\n if (fieldName in data) {\n const inputValue = data[fieldName];\n\n try {\n // Run the input validator to transform the value\n let result = fieldSchema[\"~standard\"].validate(inputValue);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // Check for validation errors\n if (result.issues) {\n throw new ValidationError(`Input validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: inputValue,\n cause: result.issues,\n });\n }\n\n // Store the transformed value\n transformedData[fieldName] = result.value;\n } catch (error) {\n // If it's already a ValidationError, re-throw it\n if (error instanceof ValidationError) {\n throw error;\n }\n\n // Otherwise, wrap the error\n throw new ValidationError(`Input validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: inputValue,\n cause: error,\n });\n }\n }\n }\n\n // Fields without input validators are already in transformedData (passed through)\n return transformedData as Partial<T>;\n}\n\n// Type for expand validation configuration\nexport interface ExpandValidationConfig {\n relation: string;\n targetSchema?: Partial<Record<string, StandardSchemaV1>>;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n targetTable?: FMTable<any, any>;\n // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration\n table?: FMTable<any, any>; // For transformation\n selectedFields?: string[];\n nestedExpands?: ExpandValidationConfig[];\n}\n\n/**\n * Validates a single record against a schema, only validating selected fields.\n * Also validates expanded relations if expandConfigs are provided.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateRecord<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic record validation\n record: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n includeSpecialColumns?: boolean,\n): Promise<{ valid: true; data: T & ODataRecordMetadata } | { valid: false; error: ValidationError }> {\n // Extract OData metadata fields (don't validate them - include if present)\n const { \"@id\": id, \"@editLink\": editLink, ...rest } = record;\n\n // Only include metadata fields if they actually exist and have values\n const metadata: Partial<ODataRecordMetadata> = {};\n if (id) {\n metadata[\"@id\"] = id;\n }\n if (editLink) {\n metadata[\"@editLink\"] = editLink;\n }\n\n // If no schema, just return the data with metadata\n // Exclude special columns if includeSpecialColumns is false\n if (!schema) {\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n const specialColumns: { ROWID?: number; ROWMODID?: number } = {};\n if (includeSpecialColumns) {\n if (ROWID !== undefined) {\n specialColumns.ROWID = ROWID;\n }\n if (ROWMODID !== undefined) {\n specialColumns.ROWMODID = ROWMODID;\n }\n }\n return {\n valid: true,\n data: {\n ...restWithoutSystemFields,\n ...specialColumns,\n ...metadata,\n } as T & ODataRecordMetadata,\n };\n }\n\n // Extract FileMaker special columns - preserve them if includeSpecialColumns is enabled\n // Note: Special columns are excluded when using single() method (per OData spec behavior)\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n const specialColumns: { ROWID?: number; ROWMODID?: number } = {};\n // Only include special columns if explicitly enabled (they're excluded for single() by design)\n if (includeSpecialColumns) {\n if (ROWID !== undefined) {\n specialColumns.ROWID = ROWID;\n }\n if (ROWMODID !== undefined) {\n specialColumns.ROWMODID = ROWMODID;\n }\n }\n\n // If selected fields are specified, validate only those fields\n if (selectedFields && selectedFields.length > 0) {\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field validation\n const validatedRecord: Record<string, any> = {};\n\n for (const field of selectedFields) {\n const fieldName = String(field);\n const fieldSchema = schema[fieldName];\n\n if (fieldSchema) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: input,\n cause: result.issues,\n }),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws directly, wrap it\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: input,\n cause: originalError,\n }),\n };\n }\n } else {\n // For fields not in schema (like when explicitly selecting ROWID/ROWMODID)\n // Check if it's a special column that was destructured earlier\n if (fieldName === \"ROWID\" || fieldName === \"ROWMODID\") {\n // Use the destructured value since it was removed from rest\n if (fieldName === \"ROWID\" && ROWID !== undefined) {\n validatedRecord[fieldName] = ROWID;\n } else if (fieldName === \"ROWMODID\" && ROWMODID !== undefined) {\n validatedRecord[fieldName] = ROWMODID;\n }\n } else {\n // For other fields not in schema, include them from the original response\n validatedRecord[fieldName] = rest[fieldName];\n }\n }\n }\n\n // Validate expanded relations\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) ||\n expandConfig.selectedFields?.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic expanded items validation\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n // Return only the validated fields plus metadata and special columns\n // Do NOT merge restWithoutSystemFields as that would overwrite validated values\n return {\n valid: true,\n data: { ...validatedRecord, ...specialColumns, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Validate all fields in schema, but exclude ROWID/ROWMODID by default (unless includeSpecialColumns is enabled)\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic field validation\n const validatedRecord: Record<string, any> = { ...restWithoutSystemFields };\n\n for (const [fieldName, fieldSchema] of Object.entries(schema)) {\n // Skip if no schema for this field\n if (!fieldSchema) {\n continue;\n }\n\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, result.issues, {\n field: fieldName,\n value: input,\n cause: result.issues,\n }),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws an error directly, catch and wrap it\n // This preserves the original error instance for instanceof checks\n return {\n valid: false,\n error: new ValidationError(`Validation failed for field '${fieldName}'`, [], {\n field: fieldName,\n value: input,\n cause: originalError,\n }),\n };\n }\n }\n\n // Validate expanded relations even when not using selected fields\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage.toLowerCase().includes(expandConfig.relation.toLowerCase()) ||\n expandConfig.selectedFields?.some((field) => errorMessage.toLowerCase().includes(field.toLowerCase()));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic expanded items validation\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n includeSpecialColumns,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n return {\n valid: true,\n data: { ...validatedRecord, ...specialColumns, ...metadata } as T & ODataRecordMetadata,\n };\n}\n\n/**\n * Validates a list response against a schema.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateListResponse<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response validation\n response: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n includeSpecialColumns?: boolean,\n): Promise<\n { valid: true; data: (T & ODataRecordMetadata)[] } | { valid: false; error: ResponseStructureError | ValidationError }\n> {\n // Check if response has the expected structure\n if (!response || typeof response !== \"object\") {\n return {\n valid: false,\n error: new ResponseStructureError(\"an object\", response),\n };\n }\n\n // Extract @context (for internal validation, but we won't return it)\n const { \"@context\": context, value, ..._rest } = response;\n\n if (!Array.isArray(value)) {\n return {\n valid: false,\n error: new ResponseStructureError(\"'value' property to be an array\", value),\n };\n }\n\n // Validate each record in the array\n const validatedRecords: (T & ODataRecordMetadata)[] = [];\n\n for (const record of value) {\n const validation = await validateRecord<T>(record, schema, selectedFields, expandConfigs, includeSpecialColumns);\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n validatedRecords.push(validation.data);\n }\n\n return {\n valid: true,\n data: validatedRecords,\n };\n}\n\n/**\n * Validates a single record response against a schema.\n */\n// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape\nexport async function validateSingleResponse<T extends Record<string, any>>(\n // biome-ignore lint/suspicious/noExplicitAny: Dynamic response validation\n response: any,\n schema: Partial<Record<string, StandardSchemaV1>> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n mode: \"exact\" | \"maybe\" = \"maybe\",\n includeSpecialColumns?: boolean,\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata) | null }\n | { valid: false; error: RecordCountMismatchError | ValidationError }\n> {\n // Check for multiple records (error in both modes)\n if (response.value && Array.isArray(response.value) && response.value.length > 1) {\n return {\n valid: false,\n error: new RecordCountMismatchError(mode === \"exact\" ? \"one\" : \"at-most-one\", response.value.length),\n };\n }\n\n // Handle empty responses\n if (!response || (response.value && response.value.length === 0)) {\n if (mode === \"exact\") {\n return {\n valid: false,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n // mode === \"maybe\" - return null for empty\n return {\n valid: true,\n data: null,\n };\n }\n\n // Single record validation\n const record = response.value?.[0] ?? response;\n const validation = await validateRecord<T>(record, schema, selectedFields, expandConfigs, includeSpecialColumns);\n\n if (!validation.valid) {\n return validation as { valid: false; error: ValidationError };\n }\n\n return {\n valid: true,\n data: validation.data,\n };\n}\n"],"names":["ROWID","ROWMODID","restWithoutSystemFields","specialColumns","validatedRecord"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proofkit/fmodata",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.25",
|
|
4
4
|
"description": "FileMaker OData API client",
|
|
5
5
|
"repository": "git@github.com:proofgeist/proofkit.git",
|
|
6
6
|
"author": "Eric <37158449+eluce2@users.noreply.github.com>",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@fetchkit/ffetch": "^4.2.0",
|
|
23
|
-
"dotenv": "^16.
|
|
24
|
-
"es-toolkit": "^1.
|
|
23
|
+
"dotenv": "^16.6.1",
|
|
24
|
+
"es-toolkit": "^1.43.0",
|
|
25
25
|
"neverthrow": "^8.2.0",
|
|
26
|
-
"odata-query": "^8.0.
|
|
26
|
+
"odata-query": "^8.0.7"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"zod": ">=4.0.0"
|
|
@@ -34,17 +34,17 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@standard-schema/spec": "^1.
|
|
38
|
-
"@tanstack/vite-config": "^0.2.
|
|
39
|
-
"@types/node": "^22.
|
|
40
|
-
"fast-xml-parser": "^5.3.
|
|
41
|
-
"publint": "^0.3.
|
|
42
|
-
"tsx": "^4.
|
|
37
|
+
"@standard-schema/spec": "^1.1.0",
|
|
38
|
+
"@tanstack/vite-config": "^0.2.1",
|
|
39
|
+
"@types/node": "^22.19.5",
|
|
40
|
+
"fast-xml-parser": "^5.3.3",
|
|
41
|
+
"publint": "^0.3.16",
|
|
42
|
+
"tsx": "^4.21.0",
|
|
43
43
|
"typescript": "^5.9.3",
|
|
44
|
-
"vite": "^6.
|
|
44
|
+
"vite": "^6.4.1",
|
|
45
45
|
"vite-plugin-dts": "^4.5.4",
|
|
46
|
-
"vitest": "^4.0.
|
|
47
|
-
"zod": "^4.
|
|
46
|
+
"vitest": "^4.0.17",
|
|
47
|
+
"zod": "^4.3.5"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=18.0.0"
|
|
@@ -64,20 +64,17 @@
|
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsc && vite build && publint --strict",
|
|
66
66
|
"build:watch": "tsc && vite build --watch",
|
|
67
|
-
"check-format": "biome format --check .",
|
|
68
|
-
"format": "biome format --write .",
|
|
69
67
|
"lint": "biome check . --write",
|
|
70
68
|
"lint:summary": "biome check . --reporter=summary",
|
|
71
|
-
"lint:fix": "pnpm lint --fix",
|
|
72
69
|
"dev": "tsc --watch",
|
|
73
70
|
"test": "vitest run --typecheck",
|
|
74
|
-
"
|
|
71
|
+
"typecheck": "tsc --noEmit",
|
|
75
72
|
"test:typecheck": "vitest run --typecheck",
|
|
76
73
|
"test:watch": "vitest --typecheck",
|
|
77
74
|
"test:build": "pnpm build && TEST_BUILD=true vitest run --typecheck",
|
|
78
75
|
"test:watch:build": "TEST_BUILD=true vitest --typecheck",
|
|
79
|
-
"test:e2e": "
|
|
80
|
-
"capture": "
|
|
76
|
+
"test:e2e": "doppler run -- vitest run tests/e2e",
|
|
77
|
+
"capture": "doppler run -- tsx scripts/capture-responses.ts",
|
|
81
78
|
"knip": "knip",
|
|
82
79
|
"pub:alpha": "bun run scripts/publish-alpha.ts",
|
|
83
80
|
"global:link": "pnpm link --global"
|
|
@@ -47,7 +47,9 @@ export function getDefaultSelectFields(
|
|
|
47
47
|
fields.push("ROWID", "ROWMODID");
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
// Return undefined (meaning "all") when schema has no fields with validators,
|
|
51
|
+
// rather than an empty array which would generate an empty $select=
|
|
52
|
+
return fields.length > 0 ? fields : undefined;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
if (Array.isArray(defaultSelect)) {
|
|
@@ -214,7 +214,9 @@ export class ExpandBuilder {
|
|
|
214
214
|
if (opts.select) {
|
|
215
215
|
const selectArray = Array.isArray(opts.select) ? opts.select.map(String) : [String(opts.select)];
|
|
216
216
|
const selectFields = formatSelectFields(selectArray, config.targetTable, this.useEntityIds);
|
|
217
|
-
|
|
217
|
+
if (selectFields) {
|
|
218
|
+
parts.push(`$select=${selectFields}`);
|
|
219
|
+
}
|
|
218
220
|
}
|
|
219
221
|
|
|
220
222
|
if (opts.filter) {
|
package/src/client/database.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { FFetchOptions } from "@fetchkit/ffetch";
|
|
1
2
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
3
|
import { FMTable } from "../orm/table";
|
|
3
|
-
import type { ExecutableBuilder, ExecutionContext, Metadata } from "../types";
|
|
4
|
+
import type { ExecutableBuilder, ExecutionContext, Metadata, Result } from "../types";
|
|
4
5
|
import { BatchBuilder } from "./batch-builder";
|
|
5
6
|
import { EntitySet } from "./entity-set";
|
|
6
7
|
import { SchemaManager } from "./schema-manager";
|
|
@@ -53,6 +54,13 @@ export class Database<IncludeSpecialColumns extends boolean = false> {
|
|
|
53
54
|
this._includeSpecialColumns = (config?.includeSpecialColumns ?? false) as IncludeSpecialColumns;
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/**
|
|
58
|
+
* @internal Used by adapter packages to access the database filename.
|
|
59
|
+
*/
|
|
60
|
+
get _getDatabaseName(): string {
|
|
61
|
+
return this.databaseName;
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
/**
|
|
57
65
|
* @internal Used by EntitySet to access database configuration
|
|
58
66
|
*/
|
|
@@ -67,6 +75,14 @@ export class Database<IncludeSpecialColumns extends boolean = false> {
|
|
|
67
75
|
return this._includeSpecialColumns;
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @internal Used by adapter packages for raw OData requests.
|
|
80
|
+
* Delegates to the connection's _makeRequest with the database name prepended.
|
|
81
|
+
*/
|
|
82
|
+
_makeRequest<T>(path: string, options?: RequestInit & FFetchOptions): Promise<Result<T>> {
|
|
83
|
+
return this.context._makeRequest<T>(`/${this.databaseName}${path}`, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
70
86
|
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
|
|
71
87
|
from<T extends FMTable<any, any>>(table: T): EntitySet<T, IncludeSpecialColumns> {
|
|
72
88
|
// Only override database-level useEntityIds if table explicitly sets it
|