@proofkit/fmodata 0.1.0-alpha.19 → 0.1.0-alpha.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +198 -0
  2. package/dist/esm/client/builders/default-select.d.ts +4 -1
  3. package/dist/esm/client/builders/default-select.js +3 -2
  4. package/dist/esm/client/builders/default-select.js.map +1 -1
  5. package/dist/esm/client/builders/expand-builder.js.map +1 -1
  6. package/dist/esm/client/builders/query-string-builder.d.ts +1 -0
  7. package/dist/esm/client/builders/query-string-builder.js.map +1 -1
  8. package/dist/esm/client/builders/response-processor.d.ts +2 -0
  9. package/dist/esm/client/builders/response-processor.js +8 -3
  10. package/dist/esm/client/builders/response-processor.js.map +1 -1
  11. package/dist/esm/client/database.d.ts +27 -4
  12. package/dist/esm/client/database.js +17 -4
  13. package/dist/esm/client/database.js.map +1 -1
  14. package/dist/esm/client/delete-builder.d.ts +2 -0
  15. package/dist/esm/client/delete-builder.js +2 -0
  16. package/dist/esm/client/delete-builder.js.map +1 -1
  17. package/dist/esm/client/entity-set.d.ts +8 -7
  18. package/dist/esm/client/entity-set.js +21 -26
  19. package/dist/esm/client/entity-set.js.map +1 -1
  20. package/dist/esm/client/error-parser.js.map +1 -1
  21. package/dist/esm/client/filemaker-odata.d.ts +15 -2
  22. package/dist/esm/client/filemaker-odata.js +25 -2
  23. package/dist/esm/client/filemaker-odata.js.map +1 -1
  24. package/dist/esm/client/insert-builder.d.ts +2 -0
  25. package/dist/esm/client/insert-builder.js +2 -0
  26. package/dist/esm/client/insert-builder.js.map +1 -1
  27. package/dist/esm/client/query/query-builder.d.ts +26 -15
  28. package/dist/esm/client/query/query-builder.js +43 -9
  29. package/dist/esm/client/query/query-builder.js.map +1 -1
  30. package/dist/esm/client/query/response-processor.d.ts +1 -0
  31. package/dist/esm/client/query/types.d.ts +24 -3
  32. package/dist/esm/client/record-builder.d.ts +24 -13
  33. package/dist/esm/client/record-builder.js +50 -17
  34. package/dist/esm/client/record-builder.js.map +1 -1
  35. package/dist/esm/client/update-builder.d.ts +2 -0
  36. package/dist/esm/client/update-builder.js +2 -0
  37. package/dist/esm/client/update-builder.js.map +1 -1
  38. package/dist/esm/client/webhook-builder.d.ts +126 -0
  39. package/dist/esm/client/webhook-builder.js +197 -0
  40. package/dist/esm/client/webhook-builder.js.map +1 -0
  41. package/dist/esm/index.d.ts +1 -0
  42. package/dist/esm/orm/field-builders.d.ts +12 -2
  43. package/dist/esm/orm/field-builders.js +18 -2
  44. package/dist/esm/orm/field-builders.js.map +1 -1
  45. package/dist/esm/orm/table.d.ts +23 -10
  46. package/dist/esm/orm/table.js +17 -30
  47. package/dist/esm/orm/table.js.map +1 -1
  48. package/dist/esm/types.d.ts +32 -2
  49. package/dist/esm/types.js.map +1 -1
  50. package/dist/esm/validation.d.ts +5 -5
  51. package/dist/esm/validation.js +44 -13
  52. package/dist/esm/validation.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/client/builders/default-select.ts +12 -1
  55. package/src/client/builders/expand-builder.ts +1 -1
  56. package/src/client/builders/query-string-builder.ts +6 -0
  57. package/src/client/builders/response-processor.ts +10 -0
  58. package/src/client/database.ts +54 -12
  59. package/src/client/delete-builder.ts +5 -1
  60. package/src/client/entity-set.ts +72 -44
  61. package/src/client/error-parser.ts +3 -0
  62. package/src/client/filemaker-odata.ts +39 -6
  63. package/src/client/insert-builder.ts +4 -0
  64. package/src/client/query/query-builder.ts +198 -35
  65. package/src/client/query/response-processor.ts +15 -25
  66. package/src/client/query/types.ts +35 -6
  67. package/src/client/record-builder.ts +159 -32
  68. package/src/client/update-builder.ts +4 -1
  69. package/src/client/webhook-builder.ts +285 -0
  70. package/src/index.ts +6 -0
  71. package/src/orm/field-builders.ts +24 -2
  72. package/src/orm/table.ts +40 -48
  73. package/src/types.ts +62 -6
  74. package/src/validation.ts +58 -13
@@ -54,3 +54,6 @@ export async function parseErrorResponse(
54
54
  // Fall back to generic HTTPError
55
55
  return new HTTPError(url, response.status, response.statusText, errorBody);
56
56
  }
57
+
58
+
59
+
@@ -24,6 +24,7 @@ export class FMServerConnection implements ExecutionContext {
24
24
  private serverUrl: string;
25
25
  private auth: Auth;
26
26
  private useEntityIds: boolean = false;
27
+ private includeSpecialColumns: boolean = false;
27
28
  private logger: InternalLogger;
28
29
  constructor(config: {
29
30
  serverUrl: string;
@@ -63,6 +64,22 @@ export class FMServerConnection implements ExecutionContext {
63
64
  return this.useEntityIds;
64
65
  }
65
66
 
67
+ /**
68
+ * @internal
69
+ * Sets whether to include special columns (ROWID and ROWMODID) in requests
70
+ */
71
+ _setIncludeSpecialColumns(includeSpecialColumns: boolean): void {
72
+ this.includeSpecialColumns = includeSpecialColumns;
73
+ }
74
+
75
+ /**
76
+ * @internal
77
+ * Gets whether to include special columns (ROWID and ROWMODID) in requests
78
+ */
79
+ _getIncludeSpecialColumns(): boolean {
80
+ return this.includeSpecialColumns;
81
+ }
82
+
66
83
  /**
67
84
  * @internal
68
85
  * Gets the base URL for OData requests
@@ -84,7 +101,11 @@ export class FMServerConnection implements ExecutionContext {
84
101
  */
85
102
  async _makeRequest<T>(
86
103
  url: string,
87
- options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
104
+ options?: RequestInit &
105
+ FFetchOptions & {
106
+ useEntityIds?: boolean;
107
+ includeSpecialColumns?: boolean;
108
+ },
88
109
  ): Promise<Result<T>> {
89
110
  const logger = this._getLogger();
90
111
  const baseUrl = `${this.serverUrl}${"apiKey" in this.auth ? `/otto` : ""}/fmi/odata/v4`;
@@ -92,10 +113,21 @@ export class FMServerConnection implements ExecutionContext {
92
113
 
93
114
  // Use per-request override if provided, otherwise use the database-level setting
94
115
  const useEntityIds = options?.useEntityIds ?? this.useEntityIds;
116
+ const includeSpecialColumns =
117
+ options?.includeSpecialColumns ?? this.includeSpecialColumns;
95
118
 
96
119
  // Get includeODataAnnotations from options (it's passed through from execute options)
97
120
  const includeODataAnnotations = (options as any)?.includeODataAnnotations;
98
121
 
122
+ // Build Prefer header as comma-separated list when multiple preferences are set
123
+ const preferValues: string[] = [];
124
+ if (useEntityIds) {
125
+ preferValues.push("fmodata.entity-ids");
126
+ }
127
+ if (includeSpecialColumns) {
128
+ preferValues.push("fmodata.include-specialcolumns");
129
+ }
130
+
99
131
  const headers = {
100
132
  Authorization:
101
133
  "apiKey" in this.auth
@@ -103,7 +135,7 @@ export class FMServerConnection implements ExecutionContext {
103
135
  : `Basic ${btoa(`${this.auth.username}:${this.auth.password}`)}`,
104
136
  "Content-Type": "application/json",
105
137
  Accept: getAcceptHeader(includeODataAnnotations),
106
- ...(useEntityIds ? { Prefer: "fmodata.entity-ids" } : {}),
138
+ ...(preferValues.length > 0 ? { Prefer: preferValues.join(", ") } : {}),
107
139
  ...(options?.headers || {}),
108
140
  };
109
141
 
@@ -271,13 +303,14 @@ export class FMServerConnection implements ExecutionContext {
271
303
  }
272
304
  }
273
305
 
274
- database(
306
+ database<IncludeSpecialColumns extends boolean = false>(
275
307
  name: string,
276
308
  config?: {
277
309
  useEntityIds?: boolean;
310
+ includeSpecialColumns?: IncludeSpecialColumns;
278
311
  },
279
- ): Database {
280
- return new Database(name, this, config);
312
+ ): Database<IncludeSpecialColumns> {
313
+ return new Database<IncludeSpecialColumns>(name, this, config);
281
314
  }
282
315
 
283
316
  /**
@@ -287,7 +320,7 @@ export class FMServerConnection implements ExecutionContext {
287
320
  async listDatabaseNames(): Promise<string[]> {
288
321
  const result = await this._makeRequest<{
289
322
  value?: Array<{ name: string }>;
290
- }>("/");
323
+ }>("/$metadata", { headers: { Accept: "application/json" } });
291
324
  if (result.error) {
292
325
  throw result.error;
293
326
  }
@@ -52,6 +52,7 @@ export class InsertBuilder<
52
52
  private returnPreference: ReturnPreference;
53
53
 
54
54
  private databaseUseEntityIds: boolean;
55
+ private databaseIncludeSpecialColumns: boolean;
55
56
 
56
57
  constructor(config: {
57
58
  occurrence?: Occ;
@@ -60,6 +61,7 @@ export class InsertBuilder<
60
61
  data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;
61
62
  returnPreference?: ReturnPreference;
62
63
  databaseUseEntityIds?: boolean;
64
+ databaseIncludeSpecialColumns?: boolean;
63
65
  }) {
64
66
  this.table = config.occurrence;
65
67
  this.databaseName = config.databaseName;
@@ -68,6 +70,8 @@ export class InsertBuilder<
68
70
  this.returnPreference = (config.returnPreference ||
69
71
  "representation") as ReturnPreference;
70
72
  this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
73
+ this.databaseIncludeSpecialColumns =
74
+ config.databaseIncludeSpecialColumns ?? false;
71
75
  }
72
76
 
73
77
  /**
@@ -6,15 +6,13 @@ import type {
6
6
  Result,
7
7
  ExecuteOptions,
8
8
  ConditionallyWithODataAnnotations,
9
- ExtractSchemaFromOccurrence,
9
+ ConditionallyWithSpecialColumns,
10
+ NormalizeIncludeSpecialColumns,
10
11
  ExecuteMethodOptions,
11
12
  } from "../../types";
12
13
  import { RecordCountMismatchError } from "../../errors";
13
14
  import { type FFetchOptions } from "@fetchkit/ffetch";
14
- import {
15
- transformFieldNamesArray,
16
- transformOrderByField,
17
- } from "../../transform";
15
+ import { transformOrderByField } from "../../transform";
18
16
  import { safeJsonParse } from "../sanitize-json";
19
17
  import { parseErrorResponse } from "../error-parser";
20
18
  import { isColumn, type Column } from "../../orm/column";
@@ -28,7 +26,6 @@ import {
28
26
  type InferSchemaOutputFromFMTable,
29
27
  type ValidExpandTarget,
30
28
  type ExtractTableName,
31
- type ValidateNoContainerFields,
32
29
  getTableName,
33
30
  } from "../../orm/table";
34
31
  import {
@@ -37,14 +34,17 @@ import {
37
34
  type ExpandedRelations,
38
35
  resolveTableId,
39
36
  mergeExecuteOptions,
40
- formatSelectFields,
41
37
  processQueryResponse,
42
38
  processSelectWithRenames,
43
39
  buildSelectExpandQueryString,
44
40
  createODataRequest,
45
41
  } from "../builders/index";
46
42
  import { QueryUrlBuilder, type NavigationConfig } from "./url-builder";
47
- import type { TypeSafeOrderBy, QueryReturnType } from "./types";
43
+ import type {
44
+ TypeSafeOrderBy,
45
+ QueryReturnType,
46
+ SystemColumnsOption,
47
+ } from "./types";
48
48
  import { createLogger, InternalLogger } from "../../logger";
49
49
 
50
50
  // Re-export QueryReturnType for backward compatibility
@@ -70,6 +70,8 @@ export class QueryBuilder<
70
70
  SingleMode extends "exact" | "maybe" | false = false,
71
71
  IsCount extends boolean = false,
72
72
  Expands extends ExpandedRelations = {},
73
+ DatabaseIncludeSpecialColumns extends boolean = false,
74
+ SystemCols extends SystemColumnsOption | undefined = undefined,
73
75
  > implements
74
76
  ExecutableBuilder<
75
77
  QueryReturnType<
@@ -77,7 +79,8 @@ export class QueryBuilder<
77
79
  Selected,
78
80
  SingleMode,
79
81
  IsCount,
80
- Expands
82
+ Expands,
83
+ SystemCols
81
84
  >
82
85
  >
83
86
  {
@@ -92,10 +95,13 @@ export class QueryBuilder<
92
95
  private context: ExecutionContext;
93
96
  private navigation?: NavigationConfig;
94
97
  private databaseUseEntityIds: boolean;
98
+ private databaseIncludeSpecialColumns: boolean;
95
99
  private expandBuilder: ExpandBuilder;
96
100
  private urlBuilder: QueryUrlBuilder;
97
101
  // Mapping from field names to output keys (for renamed fields in select)
98
102
  private fieldMapping?: Record<string, string>;
103
+ // System columns requested via select() second argument
104
+ private systemColumns?: SystemColumnsOption;
99
105
  private logger: InternalLogger;
100
106
 
101
107
  constructor(config: {
@@ -103,12 +109,15 @@ export class QueryBuilder<
103
109
  databaseName: string;
104
110
  context: ExecutionContext;
105
111
  databaseUseEntityIds?: boolean;
112
+ databaseIncludeSpecialColumns?: boolean;
106
113
  }) {
107
114
  this.occurrence = config.occurrence;
108
115
  this.databaseName = config.databaseName;
109
116
  this.context = config.context;
110
117
  this.logger = config.context?._getLogger?.() ?? createLogger();
111
118
  this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
119
+ this.databaseIncludeSpecialColumns =
120
+ config.databaseIncludeSpecialColumns ?? false;
112
121
  this.expandBuilder = new ExpandBuilder(
113
122
  this.databaseUseEntityIds,
114
123
  this.logger,
@@ -121,12 +130,21 @@ export class QueryBuilder<
121
130
  }
122
131
 
123
132
  /**
124
- * Helper to merge database-level useEntityIds with per-request options
133
+ * Helper to merge database-level useEntityIds and includeSpecialColumns with per-request options
125
134
  */
126
135
  private mergeExecuteOptions(
127
136
  options?: RequestInit & FFetchOptions & ExecuteOptions,
128
- ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {
129
- return mergeExecuteOptions(options, this.databaseUseEntityIds);
137
+ ): RequestInit &
138
+ FFetchOptions & {
139
+ useEntityIds?: boolean;
140
+ includeSpecialColumns?: boolean;
141
+ } {
142
+ const merged = mergeExecuteOptions(options, this.databaseUseEntityIds);
143
+ return {
144
+ ...merged,
145
+ includeSpecialColumns:
146
+ options?.includeSpecialColumns ?? this.databaseIncludeSpecialColumns,
147
+ };
130
148
  }
131
149
 
132
150
  /**
@@ -159,24 +177,37 @@ export class QueryBuilder<
159
177
  | Record<string, Column<any, any, ExtractTableName<Occ>>> = Selected,
160
178
  NewSingle extends "exact" | "maybe" | false = SingleMode,
161
179
  NewCount extends boolean = IsCount,
180
+ NewSystemCols extends SystemColumnsOption | undefined = SystemCols,
162
181
  >(changes: {
163
182
  selectedFields?: NewSelected;
164
183
  singleMode?: NewSingle;
165
184
  isCountMode?: NewCount;
166
185
  queryOptions?: Partial<QueryOptions<InferSchemaOutputFromFMTable<Occ>>>;
167
186
  fieldMapping?: Record<string, string>;
168
- }): QueryBuilder<Occ, NewSelected, NewSingle, NewCount, Expands> {
187
+ systemColumns?: NewSystemCols;
188
+ }): QueryBuilder<
189
+ Occ,
190
+ NewSelected,
191
+ NewSingle,
192
+ NewCount,
193
+ Expands,
194
+ DatabaseIncludeSpecialColumns,
195
+ NewSystemCols
196
+ > {
169
197
  const newBuilder = new QueryBuilder<
170
198
  Occ,
171
199
  NewSelected,
172
200
  NewSingle,
173
201
  NewCount,
174
- Expands
202
+ Expands,
203
+ DatabaseIncludeSpecialColumns,
204
+ NewSystemCols
175
205
  >({
176
206
  occurrence: this.occurrence,
177
207
  databaseName: this.databaseName,
178
208
  context: this.context,
179
209
  databaseUseEntityIds: this.databaseUseEntityIds,
210
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
180
211
  });
181
212
  newBuilder.queryOptions = {
182
213
  ...this.queryOptions,
@@ -186,6 +217,10 @@ export class QueryBuilder<
186
217
  newBuilder.singleMode = (changes.singleMode ?? this.singleMode) as any;
187
218
  newBuilder.isCountMode = (changes.isCountMode ?? this.isCountMode) as any;
188
219
  newBuilder.fieldMapping = changes.fieldMapping ?? this.fieldMapping;
220
+ newBuilder.systemColumns =
221
+ changes.systemColumns !== undefined
222
+ ? changes.systemColumns
223
+ : this.systemColumns;
189
224
  // Copy navigation metadata
190
225
  newBuilder.navigation = this.navigation;
191
226
  newBuilder.urlBuilder = new QueryUrlBuilder(
@@ -207,7 +242,15 @@ export class QueryBuilder<
207
242
  * userEmail: users.email // renamed!
208
243
  * })
209
244
  *
245
+ * @example
246
+ * // Include system columns (ROWID, ROWMODID) when using select()
247
+ * db.from(users).list().select(
248
+ * { name: users.name },
249
+ * { ROWID: true, ROWMODID: true }
250
+ * )
251
+ *
210
252
  * @param fields - Object mapping output keys to column references (container fields excluded)
253
+ * @param systemColumns - Optional object to request system columns (ROWID, ROWMODID)
211
254
  * @returns QueryBuilder with updated selected fields
212
255
  */
213
256
  select<
@@ -215,7 +258,19 @@ export class QueryBuilder<
215
258
  string,
216
259
  Column<any, any, ExtractTableName<Occ>, false>
217
260
  >,
218
- >(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands> {
261
+ TSystemCols extends SystemColumnsOption = {},
262
+ >(
263
+ fields: TSelect,
264
+ systemColumns?: TSystemCols,
265
+ ): QueryBuilder<
266
+ Occ,
267
+ TSelect,
268
+ SingleMode,
269
+ IsCount,
270
+ Expands,
271
+ DatabaseIncludeSpecialColumns,
272
+ TSystemCols
273
+ > {
219
274
  const tableName = getTableName(this.occurrence);
220
275
  const { selectedFields, fieldMapping } = processSelectWithRenames(
221
276
  fields,
@@ -223,13 +278,23 @@ export class QueryBuilder<
223
278
  this.logger,
224
279
  );
225
280
 
281
+ // Add system columns to selectedFields if requested
282
+ const finalSelectedFields = [...selectedFields];
283
+ if (systemColumns?.ROWID) {
284
+ finalSelectedFields.push("ROWID");
285
+ }
286
+ if (systemColumns?.ROWMODID) {
287
+ finalSelectedFields.push("ROWMODID");
288
+ }
289
+
226
290
  return this.cloneWithChanges({
227
291
  selectedFields: fields as any,
228
292
  queryOptions: {
229
- select: selectedFields,
293
+ select: finalSelectedFields,
230
294
  },
231
295
  fieldMapping:
232
296
  Object.keys(fieldMapping).length > 0 ? fieldMapping : undefined,
297
+ systemColumns: systemColumns as any,
233
298
  });
234
299
  }
235
300
 
@@ -245,7 +310,15 @@ export class QueryBuilder<
245
310
  */
246
311
  where(
247
312
  expression: FilterExpression | string,
248
- ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {
313
+ ): QueryBuilder<
314
+ Occ,
315
+ Selected,
316
+ SingleMode,
317
+ IsCount,
318
+ Expands,
319
+ DatabaseIncludeSpecialColumns,
320
+ SystemCols
321
+ > {
249
322
  // Handle raw string filters (escape hatch)
250
323
  if (typeof expression === "string") {
251
324
  this.queryOptions.filter = expression;
@@ -295,7 +368,15 @@ export class QueryBuilder<
295
368
  | OrderByExpression<ExtractTableName<Occ>>
296
369
  >,
297
370
  ]
298
- ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {
371
+ ): QueryBuilder<
372
+ Occ,
373
+ Selected,
374
+ SingleMode,
375
+ IsCount,
376
+ Expands,
377
+ DatabaseIncludeSpecialColumns,
378
+ SystemCols
379
+ > {
299
380
  const tableName = getTableName(this.occurrence);
300
381
 
301
382
  // Handle variadic arguments (multiple fields)
@@ -440,14 +521,30 @@ export class QueryBuilder<
440
521
 
441
522
  top(
442
523
  count: number,
443
- ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {
524
+ ): QueryBuilder<
525
+ Occ,
526
+ Selected,
527
+ SingleMode,
528
+ IsCount,
529
+ Expands,
530
+ DatabaseIncludeSpecialColumns,
531
+ SystemCols
532
+ > {
444
533
  this.queryOptions.top = count;
445
534
  return this;
446
535
  }
447
536
 
448
537
  skip(
449
538
  count: number,
450
- ): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands> {
539
+ ): QueryBuilder<
540
+ Occ,
541
+ Selected,
542
+ SingleMode,
543
+ IsCount,
544
+ Expands,
545
+ DatabaseIncludeSpecialColumns,
546
+ SystemCols
547
+ > {
451
548
  this.queryOptions.skip = count;
452
549
  return this;
453
550
  }
@@ -483,7 +580,9 @@ export class QueryBuilder<
483
580
  selected: TSelected;
484
581
  nested: TNestedExpands;
485
582
  };
486
- }
583
+ },
584
+ DatabaseIncludeSpecialColumns,
585
+ SystemCols
487
586
  > {
488
587
  // Use ExpandBuilder.processExpand to handle the expand logic
489
588
  type TargetBuilder = QueryBuilder<
@@ -491,7 +590,8 @@ export class QueryBuilder<
491
590
  keyof InferSchemaOutputFromFMTable<TargetTable>,
492
591
  false,
493
592
  false,
494
- {}
593
+ {},
594
+ DatabaseIncludeSpecialColumns
495
595
  >;
496
596
  const expandConfig = this.expandBuilder.processExpand<
497
597
  TargetTable,
@@ -501,11 +601,20 @@ export class QueryBuilder<
501
601
  this.occurrence,
502
602
  callback as ((builder: TargetBuilder) => TargetBuilder) | undefined,
503
603
  () =>
504
- new QueryBuilder<TargetTable>({
604
+ new QueryBuilder<
605
+ TargetTable,
606
+ any,
607
+ any,
608
+ any,
609
+ any,
610
+ DatabaseIncludeSpecialColumns,
611
+ undefined
612
+ >({
505
613
  occurrence: targetTable,
506
614
  databaseName: this.databaseName,
507
615
  context: this.context,
508
616
  databaseUseEntityIds: this.databaseUseEntityIds,
617
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
509
618
  }),
510
619
  );
511
620
 
@@ -513,15 +622,39 @@ export class QueryBuilder<
513
622
  return this as any;
514
623
  }
515
624
 
516
- single(): QueryBuilder<Occ, Selected, "exact", IsCount, Expands> {
625
+ single(): QueryBuilder<
626
+ Occ,
627
+ Selected,
628
+ "exact",
629
+ IsCount,
630
+ Expands,
631
+ DatabaseIncludeSpecialColumns,
632
+ SystemCols
633
+ > {
517
634
  return this.cloneWithChanges({ singleMode: "exact" as const });
518
635
  }
519
636
 
520
- maybeSingle(): QueryBuilder<Occ, Selected, "maybe", IsCount, Expands> {
637
+ maybeSingle(): QueryBuilder<
638
+ Occ,
639
+ Selected,
640
+ "maybe",
641
+ IsCount,
642
+ Expands,
643
+ DatabaseIncludeSpecialColumns,
644
+ SystemCols
645
+ > {
521
646
  return this.cloneWithChanges({ singleMode: "maybe" as const });
522
647
  }
523
648
 
524
- count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands> {
649
+ count(): QueryBuilder<
650
+ Occ,
651
+ Selected,
652
+ SingleMode,
653
+ true,
654
+ Expands,
655
+ DatabaseIncludeSpecialColumns,
656
+ SystemCols
657
+ > {
525
658
  return this.cloneWithChanges({
526
659
  isCountMode: true as const,
527
660
  queryOptions: { count: true },
@@ -531,7 +664,7 @@ export class QueryBuilder<
531
664
  /**
532
665
  * Builds the OData query string from current query options and expand configs.
533
666
  */
534
- private buildQueryString(): string {
667
+ private buildQueryString(includeSpecialColumns?: boolean): string {
535
668
  // Build query without expand and select (we'll add them manually if using entity IDs)
536
669
  const queryOptionsWithoutExpandAndSelect = { ...this.queryOptions };
537
670
  const originalSelect = queryOptionsWithoutExpandAndSelect.select;
@@ -547,12 +680,17 @@ export class QueryBuilder<
547
680
  : [String(originalSelect)]
548
681
  : undefined;
549
682
 
683
+ // Use merged includeSpecialColumns if provided, otherwise use database-level default
684
+ const finalIncludeSpecialColumns =
685
+ includeSpecialColumns ?? this.databaseIncludeSpecialColumns;
686
+
550
687
  const selectExpandString = buildSelectExpandQueryString({
551
688
  selectedFields: selectArray,
552
689
  expandConfigs: this.expandConfigs,
553
690
  table: this.occurrence,
554
691
  useEntityIds: this.databaseUseEntityIds,
555
692
  logger: this.logger,
693
+ includeSpecialColumns: finalIncludeSpecialColumns,
556
694
  });
557
695
 
558
696
  // Append select/expand to existing query string
@@ -573,19 +711,35 @@ export class QueryBuilder<
573
711
  ): Promise<
574
712
  Result<
575
713
  ConditionallyWithODataAnnotations<
576
- QueryReturnType<
577
- InferSchemaOutputFromFMTable<Occ>,
578
- Selected,
579
- SingleMode,
580
- IsCount,
581
- Expands
714
+ ConditionallyWithSpecialColumns<
715
+ QueryReturnType<
716
+ InferSchemaOutputFromFMTable<Occ>,
717
+ Selected,
718
+ SingleMode,
719
+ IsCount,
720
+ Expands,
721
+ SystemCols
722
+ >,
723
+ // Use the merged value: if explicitly provided in options, use that; otherwise use database default
724
+ NormalizeIncludeSpecialColumns<
725
+ EO["includeSpecialColumns"],
726
+ DatabaseIncludeSpecialColumns
727
+ >,
728
+ // Check if select was applied: if Selected is Record (object select) or a subset of keys, select was applied
729
+ Selected extends Record<string, Column<any, any, any>>
730
+ ? true
731
+ : Selected extends keyof InferSchemaOutputFromFMTable<Occ>
732
+ ? false
733
+ : true
582
734
  >,
583
735
  EO["includeODataAnnotations"] extends true ? true : false
584
736
  >
585
737
  >
586
738
  > {
587
739
  const mergedOptions = this.mergeExecuteOptions(options);
588
- const queryString = this.buildQueryString();
740
+ const queryString = this.buildQueryString(
741
+ mergedOptions.includeSpecialColumns,
742
+ );
589
743
 
590
744
  // Handle $count endpoint
591
745
  if (this.isCountMode) {
@@ -618,6 +772,9 @@ export class QueryBuilder<
618
772
  return { data: undefined, error: result.error };
619
773
  }
620
774
 
775
+ // Check if select was applied (runtime check)
776
+ const hasSelect = this.queryOptions.select !== undefined;
777
+
621
778
  return processQueryResponse(result.data, {
622
779
  occurrence: this.occurrence,
623
780
  singleMode: this.singleMode,
@@ -625,6 +782,7 @@ export class QueryBuilder<
625
782
  expandConfigs: this.expandConfigs,
626
783
  skipValidation: options?.skipValidation,
627
784
  useEntityIds: mergedOptions.useEntityIds,
785
+ includeSpecialColumns: mergedOptions.includeSpecialColumns,
628
786
  fieldMapping: this.fieldMapping,
629
787
  logger: this.logger,
630
788
  });
@@ -667,7 +825,8 @@ export class QueryBuilder<
667
825
  Selected,
668
826
  SingleMode,
669
827
  IsCount,
670
- Expands
828
+ Expands,
829
+ SystemCols
671
830
  >
672
831
  >
673
832
  > {
@@ -728,6 +887,9 @@ export class QueryBuilder<
728
887
  }
729
888
 
730
889
  const mergedOptions = this.mergeExecuteOptions(options);
890
+ // Check if select was applied (runtime check)
891
+ const hasSelect = this.queryOptions.select !== undefined;
892
+
731
893
  return processQueryResponse(rawData, {
732
894
  occurrence: this.occurrence,
733
895
  singleMode: this.singleMode,
@@ -735,6 +897,7 @@ export class QueryBuilder<
735
897
  expandConfigs: this.expandConfigs,
736
898
  skipValidation: options?.skipValidation,
737
899
  useEntityIds: mergedOptions.useEntityIds,
900
+ includeSpecialColumns: mergedOptions.includeSpecialColumns,
738
901
  fieldMapping: this.fieldMapping,
739
902
  logger: this.logger,
740
903
  });