Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
reviews:
# Enable automated review for pull requests
auto_review:
enabled: true
enabled: false
base_branches:
- ".*" # Matches all branches using regex
review_status: false
poem: false

path_filters:
- "!apps/demo/**" # exclude the demo app from reivews
high_level_summary: false
path_filters:
- "!apps/demo/**" # exclude the demo app from reivews
33 changes: 32 additions & 1 deletion packages/fmodata/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,7 @@ const result = await db.webhook.add({
tableName: contactsTable,
headers: {
"X-Custom-Header": "value",
"Authorization": "Bearer token",
Authorization: "Bearer token",
},
notifySchemaChanges: true, // Notify when schema changes
});
Expand Down Expand Up @@ -1442,6 +1442,37 @@ const users = fmTableOccurrence(
);
```

### Special Columns (ROWID and ROWMODID)

FileMaker provides special columns `ROWID` and `ROWMODID` that uniquely identify records and track modifications. These can be included in query responses when enabled.

Enable special columns at the database level:

```typescript
const db = connection.database("MyDatabase", {
includeSpecialColumns: true,
});

const result = await db.from(users).list().execute();
// result.data[0] will have ROWID and ROWMODID properties
```

Override at the request level:

```typescript
// Enable for this request only
const result = await db.from(users).list().execute({
includeSpecialColumns: true,
});

// Disable for this request
const result = await db.from(users).list().execute({
includeSpecialColumns: false,
});
```

**Important:** Special columns are only included when no `$select` query is applied (per OData specification). When using `.select()`, special columns are excluded even if `includeSpecialColumns` is enabled.

### Error Handling

All operations return a `Result` type with either `data` or `error`. The library provides rich error types that help you handle different error scenarios appropriately.
Expand Down
13 changes: 12 additions & 1 deletion packages/fmodata/src/client/builders/default-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ function getContainerFieldNames(table: FMTable<any, any>): string[] {
* Gets default select fields from a table definition.
* Returns undefined if defaultSelect is "all".
* Automatically filters out container fields since they cannot be selected via $select.
*
* @param table - The table occurrence
* @param includeSpecialColumns - If true, includes ROWID and ROWMODID when defaultSelect is "schema"
*/
export function getDefaultSelectFields(
table: FMTable<any, any> | undefined,
includeSpecialColumns?: boolean,
): string[] | undefined {
if (!table) return undefined;

Expand All @@ -33,7 +37,14 @@ export function getDefaultSelectFields(
const baseTableConfig = getBaseTableConfig(table);
const allFields = Object.keys(baseTableConfig.schema);
// Filter out container fields
return [...new Set(allFields.filter((f) => !containerFields.includes(f)))];
const fields = [...new Set(allFields.filter((f) => !containerFields.includes(f)))];

// Add special columns if requested
if (includeSpecialColumns) {
fields.push("ROWID", "ROWMODID");
}

return fields;
}

if (Array.isArray(defaultSelect)) {
Expand Down
6 changes: 6 additions & 0 deletions packages/fmodata/src/client/builders/query-string-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ export function buildSelectExpandQueryString(config: {
table?: FMTable<any, any>;
useEntityIds: boolean;
logger: InternalLogger;
includeSpecialColumns?: boolean;
}): string {
const parts: string[] = [];
const expandBuilder = new ExpandBuilder(config.useEntityIds, config.logger);

// Build $select
if (config.selectedFields && config.selectedFields.length > 0) {
// Important: do NOT implicitly add system columns (ROWID/ROWMODID) here.
// - `includeSpecialColumns` controls the Prefer header + response parsing, but should not
// mutate/expand an explicit `$select` (e.g. when the user calls `.select({ ... })`).
// - If system columns are desired with `.select()`, they must be explicitly included via
// the `systemColumns` argument, which will already have added them to `selectedFields`.
const selectString = formatSelectFields(
config.selectedFields,
config.table,
Expand Down
10 changes: 10 additions & 0 deletions packages/fmodata/src/client/builders/response-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface ProcessResponseConfig {
expandValidationConfigs?: ExpandValidationConfig[];
skipValidation?: boolean;
useEntityIds?: boolean;
includeSpecialColumns?: boolean;
// Mapping from field names to output keys (for renamed fields in select)
fieldMapping?: Record<string, string>;
}
Expand All @@ -37,6 +38,7 @@ export async function processODataResponse<T>(
expandValidationConfigs,
skipValidation,
useEntityIds,
includeSpecialColumns,
fieldMapping,
} = config;

Expand Down Expand Up @@ -67,13 +69,17 @@ export async function processODataResponse<T>(
}

// Validation path
// Note: Special columns are excluded when using QueryBuilder.single() method,
// but included for RecordBuilder.get() method (both use singleMode: "exact")
// The exclusion is handled in QueryBuilder's processQueryResponse, not here
if (singleMode !== false) {
const validation = await validateSingleResponse<any>(
response,
schema,
selectedFields as any,
expandValidationConfigs,
singleMode,
includeSpecialColumns,
);

if (!validation.valid) {
Expand All @@ -96,6 +102,7 @@ export async function processODataResponse<T>(
schema,
selectedFields as any,
expandValidationConfigs,
includeSpecialColumns,
);

if (!validation.valid) {
Expand Down Expand Up @@ -223,6 +230,7 @@ export async function processQueryResponse<T>(
expandConfigs: ExpandConfig[];
skipValidation?: boolean;
useEntityIds?: boolean;
includeSpecialColumns?: boolean;
// Mapping from field names to output keys (for renamed fields in select)
fieldMapping?: Record<string, string>;
logger: InternalLogger;
Expand All @@ -235,6 +243,7 @@ export async function processQueryResponse<T>(
expandConfigs,
skipValidation,
useEntityIds,
includeSpecialColumns,
fieldMapping,
logger,
} = config;
Expand All @@ -258,6 +267,7 @@ export async function processQueryResponse<T>(
expandValidationConfigs,
skipValidation,
useEntityIds,
includeSpecialColumns,
});

// Rename fields if field mapping is provided (for renamed fields in select)
Expand Down
16 changes: 13 additions & 3 deletions packages/fmodata/src/client/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { SchemaManager } from "./schema-manager";
import { FMTable } from "../orm/table";
import { WebhookManager } from "./webhook-builder";

export class Database {
export class Database<IncludeSpecialColumns extends boolean = false> {
private _useEntityIds: boolean = false;
private _includeSpecialColumns: IncludeSpecialColumns;
public readonly schema: SchemaManager;
public readonly webhook: WebhookManager;

Expand All @@ -21,15 +22,24 @@ export class Database {
* If set to false but some occurrences do not use entity IDs, an error will be thrown
*/
useEntityIds?: boolean;
/**
* Whether to include special columns (ROWID and ROWMODID) in responses.
* Note: Special columns are only included when there is no $select query.
*/
includeSpecialColumns?: IncludeSpecialColumns;
},
) {
// Initialize schema manager
this.schema = new SchemaManager(this.databaseName, this.context);
this.webhook = new WebhookManager(this.databaseName, this.context);
this._useEntityIds = config?.useEntityIds ?? false;
this._includeSpecialColumns = (config?.includeSpecialColumns ??
false) as IncludeSpecialColumns;
}

from<T extends FMTable<any, any>>(table: T): EntitySet<T> {
from<T extends FMTable<any, any>>(
table: T,
): EntitySet<T, IncludeSpecialColumns> {
// Only override database-level useEntityIds if table explicitly sets it
// (not if it's undefined, which would override the database setting)
if (
Expand All @@ -40,7 +50,7 @@ export class Database {
this._useEntityIds = tableUseEntityIds;
}
}
return new EntitySet<T>({
return new EntitySet<T, IncludeSpecialColumns>({
occurrence: table as T,
databaseName: this.databaseName,
context: this.context,
Expand Down
6 changes: 5 additions & 1 deletion packages/fmodata/src/client/delete-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {
ExecutionContext,
ExecutableBuilder,
Result,
WithSystemFields,
WithSpecialColumns,
ExecuteOptions,
ExecuteMethodOptions,
} from "../types";
Expand All @@ -26,17 +26,21 @@ export class DeleteBuilder<Occ extends FMTable<any, any>> {
private context: ExecutionContext;
private table: Occ;
private databaseUseEntityIds: boolean;
private databaseIncludeSpecialColumns: boolean;

constructor(config: {
occurrence: Occ;
databaseName: string;
context: ExecutionContext;
databaseUseEntityIds?: boolean;
databaseIncludeSpecialColumns?: boolean;
}) {
this.table = config.occurrence;
this.databaseName = config.databaseName;
this.context = config.context;
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
this.databaseIncludeSpecialColumns =
config.databaseIncludeSpecialColumns ?? false;
}

/**
Expand Down
Loading
Loading