@proofkit/fmodata 0.1.0-alpha.9 → 0.1.0-beta.24

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 (163) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +655 -453
  3. package/dist/esm/client/batch-builder.d.ts +10 -9
  4. package/dist/esm/client/batch-builder.js +119 -56
  5. package/dist/esm/client/batch-builder.js.map +1 -1
  6. package/dist/esm/client/batch-request.js +16 -21
  7. package/dist/esm/client/batch-request.js.map +1 -1
  8. package/dist/esm/client/builders/default-select.d.ts +10 -0
  9. package/dist/esm/client/builders/default-select.js +41 -0
  10. package/dist/esm/client/builders/default-select.js.map +1 -0
  11. package/dist/esm/client/builders/expand-builder.d.ts +45 -0
  12. package/dist/esm/client/builders/expand-builder.js +185 -0
  13. package/dist/esm/client/builders/expand-builder.js.map +1 -0
  14. package/dist/esm/client/builders/index.d.ts +9 -0
  15. package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
  16. package/dist/esm/client/builders/query-string-builder.js +21 -0
  17. package/dist/esm/client/builders/query-string-builder.js.map +1 -0
  18. package/dist/esm/client/builders/response-processor.d.ts +43 -0
  19. package/dist/esm/client/builders/response-processor.js +175 -0
  20. package/dist/esm/client/builders/response-processor.js.map +1 -0
  21. package/dist/esm/client/builders/select-mixin.d.ts +25 -0
  22. package/dist/esm/client/builders/select-mixin.js +28 -0
  23. package/dist/esm/client/builders/select-mixin.js.map +1 -0
  24. package/dist/esm/client/builders/select-utils.d.ts +18 -0
  25. package/dist/esm/client/builders/select-utils.js +30 -0
  26. package/dist/esm/client/builders/select-utils.js.map +1 -0
  27. package/dist/esm/client/builders/shared-types.d.ts +40 -0
  28. package/dist/esm/client/builders/table-utils.d.ts +35 -0
  29. package/dist/esm/client/builders/table-utils.js +44 -0
  30. package/dist/esm/client/builders/table-utils.js.map +1 -0
  31. package/dist/esm/client/database.d.ts +34 -22
  32. package/dist/esm/client/database.js +48 -84
  33. package/dist/esm/client/database.js.map +1 -1
  34. package/dist/esm/client/delete-builder.d.ts +25 -30
  35. package/dist/esm/client/delete-builder.js +45 -30
  36. package/dist/esm/client/delete-builder.js.map +1 -1
  37. package/dist/esm/client/entity-set.d.ts +35 -43
  38. package/dist/esm/client/entity-set.js +126 -52
  39. package/dist/esm/client/entity-set.js.map +1 -1
  40. package/dist/esm/client/error-parser.d.ts +12 -0
  41. package/dist/esm/client/error-parser.js +25 -0
  42. package/dist/esm/client/error-parser.js.map +1 -0
  43. package/dist/esm/client/filemaker-odata.d.ts +26 -7
  44. package/dist/esm/client/filemaker-odata.js +65 -42
  45. package/dist/esm/client/filemaker-odata.js.map +1 -1
  46. package/dist/esm/client/insert-builder.d.ts +19 -24
  47. package/dist/esm/client/insert-builder.js +94 -58
  48. package/dist/esm/client/insert-builder.js.map +1 -1
  49. package/dist/esm/client/query/expand-builder.d.ts +35 -0
  50. package/dist/esm/client/query/index.d.ts +4 -0
  51. package/dist/esm/client/query/query-builder.d.ts +132 -0
  52. package/dist/esm/client/query/query-builder.js +456 -0
  53. package/dist/esm/client/query/query-builder.js.map +1 -0
  54. package/dist/esm/client/query/response-processor.d.ts +25 -0
  55. package/dist/esm/client/query/types.d.ts +77 -0
  56. package/dist/esm/client/query/url-builder.d.ts +71 -0
  57. package/dist/esm/client/query/url-builder.js +100 -0
  58. package/dist/esm/client/query/url-builder.js.map +1 -0
  59. package/dist/esm/client/query-builder.d.ts +2 -115
  60. package/dist/esm/client/record-builder.d.ts +108 -36
  61. package/dist/esm/client/record-builder.js +284 -119
  62. package/dist/esm/client/record-builder.js.map +1 -1
  63. package/dist/esm/client/response-processor.d.ts +4 -9
  64. package/dist/esm/client/sanitize-json.d.ts +35 -0
  65. package/dist/esm/client/sanitize-json.js +27 -0
  66. package/dist/esm/client/sanitize-json.js.map +1 -0
  67. package/dist/esm/client/schema-manager.d.ts +5 -5
  68. package/dist/esm/client/schema-manager.js +45 -31
  69. package/dist/esm/client/schema-manager.js.map +1 -1
  70. package/dist/esm/client/update-builder.d.ts +34 -40
  71. package/dist/esm/client/update-builder.js +99 -58
  72. package/dist/esm/client/update-builder.js.map +1 -1
  73. package/dist/esm/client/webhook-builder.d.ts +126 -0
  74. package/dist/esm/client/webhook-builder.js +189 -0
  75. package/dist/esm/client/webhook-builder.js.map +1 -0
  76. package/dist/esm/errors.d.ts +19 -2
  77. package/dist/esm/errors.js +39 -4
  78. package/dist/esm/errors.js.map +1 -1
  79. package/dist/esm/index.d.ts +10 -8
  80. package/dist/esm/index.js +40 -10
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/logger.d.ts +47 -0
  83. package/dist/esm/logger.js +69 -0
  84. package/dist/esm/logger.js.map +1 -0
  85. package/dist/esm/logger.test.d.ts +1 -0
  86. package/dist/esm/orm/column.d.ts +62 -0
  87. package/dist/esm/orm/column.js +63 -0
  88. package/dist/esm/orm/column.js.map +1 -0
  89. package/dist/esm/orm/field-builders.d.ts +164 -0
  90. package/dist/esm/orm/field-builders.js +158 -0
  91. package/dist/esm/orm/field-builders.js.map +1 -0
  92. package/dist/esm/orm/index.d.ts +5 -0
  93. package/dist/esm/orm/operators.d.ts +173 -0
  94. package/dist/esm/orm/operators.js +260 -0
  95. package/dist/esm/orm/operators.js.map +1 -0
  96. package/dist/esm/orm/table.d.ts +355 -0
  97. package/dist/esm/orm/table.js +202 -0
  98. package/dist/esm/orm/table.js.map +1 -0
  99. package/dist/esm/transform.d.ts +20 -21
  100. package/dist/esm/transform.js +44 -45
  101. package/dist/esm/transform.js.map +1 -1
  102. package/dist/esm/types.d.ts +96 -30
  103. package/dist/esm/types.js +7 -0
  104. package/dist/esm/types.js.map +1 -0
  105. package/dist/esm/validation.d.ts +22 -12
  106. package/dist/esm/validation.js +132 -85
  107. package/dist/esm/validation.js.map +1 -1
  108. package/package.json +34 -29
  109. package/src/client/batch-builder.ts +153 -89
  110. package/src/client/batch-request.ts +25 -41
  111. package/src/client/builders/default-select.ts +75 -0
  112. package/src/client/builders/expand-builder.ts +246 -0
  113. package/src/client/builders/index.ts +11 -0
  114. package/src/client/builders/query-string-builder.ts +46 -0
  115. package/src/client/builders/response-processor.ts +279 -0
  116. package/src/client/builders/select-mixin.ts +65 -0
  117. package/src/client/builders/select-utils.ts +59 -0
  118. package/src/client/builders/shared-types.ts +45 -0
  119. package/src/client/builders/table-utils.ts +83 -0
  120. package/src/client/database.ts +89 -183
  121. package/src/client/delete-builder.ts +74 -84
  122. package/src/client/entity-set.ts +286 -293
  123. package/src/client/error-parser.ts +41 -0
  124. package/src/client/filemaker-odata.ts +98 -66
  125. package/src/client/insert-builder.ts +157 -118
  126. package/src/client/query/expand-builder.ts +160 -0
  127. package/src/client/query/index.ts +14 -0
  128. package/src/client/query/query-builder.ts +729 -0
  129. package/src/client/query/response-processor.ts +226 -0
  130. package/src/client/query/types.ts +126 -0
  131. package/src/client/query/url-builder.ts +151 -0
  132. package/src/client/query-builder.ts +10 -1455
  133. package/src/client/record-builder.ts +575 -240
  134. package/src/client/response-processor.ts +15 -42
  135. package/src/client/sanitize-json.ts +64 -0
  136. package/src/client/schema-manager.ts +61 -76
  137. package/src/client/update-builder.ts +161 -143
  138. package/src/client/webhook-builder.ts +265 -0
  139. package/src/errors.ts +49 -16
  140. package/src/index.ts +99 -54
  141. package/src/logger.test.ts +34 -0
  142. package/src/logger.ts +116 -0
  143. package/src/orm/column.ts +106 -0
  144. package/src/orm/field-builders.ts +250 -0
  145. package/src/orm/index.ts +61 -0
  146. package/src/orm/operators.ts +473 -0
  147. package/src/orm/table.ts +741 -0
  148. package/src/transform.ts +90 -70
  149. package/src/types.ts +154 -113
  150. package/src/validation.ts +200 -115
  151. package/dist/esm/client/base-table.d.ts +0 -125
  152. package/dist/esm/client/base-table.js +0 -57
  153. package/dist/esm/client/base-table.js.map +0 -1
  154. package/dist/esm/client/query-builder.js +0 -896
  155. package/dist/esm/client/query-builder.js.map +0 -1
  156. package/dist/esm/client/table-occurrence.d.ts +0 -72
  157. package/dist/esm/client/table-occurrence.js +0 -74
  158. package/dist/esm/client/table-occurrence.js.map +0 -1
  159. package/dist/esm/filter-types.d.ts +0 -76
  160. package/src/client/base-table.ts +0 -175
  161. package/src/client/query-builder.ts.bak +0 -1457
  162. package/src/client/table-occurrence.ts +0 -175
  163. package/src/filter-types.ts +0 -97
@@ -1,203 +1,181 @@
1
+ import { createLogger, type InternalLogger } from "../logger";
2
+ import type { FieldBuilder } from "../orm/field-builders";
1
3
  import type {
2
- ExecutionContext,
3
- InferSchemaType,
4
- WithSystemFields,
5
- InsertData,
6
- UpdateData,
7
- } from "../types";
8
- import type { StandardSchemaV1 } from "@standard-schema/spec";
9
- import type { BaseTable } from "./base-table";
10
- import type { TableOccurrence } from "./table-occurrence";
11
- import { QueryBuilder } from "./query-builder";
12
- import { RecordBuilder } from "./record-builder";
13
- import { InsertBuilder } from "./insert-builder";
4
+ ColumnMap,
5
+ FMTable,
6
+ InferSchemaOutputFromFMTable,
7
+ InsertDataFromFMTable,
8
+ UpdateDataFromFMTable,
9
+ ValidExpandTarget,
10
+ } from "../orm/table";
11
+ import { FMTable as FMTableClass, getDefaultSelect, getTableColumns, getTableName, getTableSchema } from "../orm/table";
12
+ import type { ExecutionContext } from "../types";
13
+ import type { Database } from "./database";
14
14
  import { DeleteBuilder } from "./delete-builder";
15
+ import { InsertBuilder } from "./insert-builder";
16
+ import { QueryBuilder } from "./query/index";
17
+ import { RecordBuilder } from "./record-builder";
15
18
  import { UpdateBuilder } from "./update-builder";
16
- import { Database } from "./database";
17
-
18
- // Helper type to extract navigation relation names from an occurrence
19
- type ExtractNavigationNames<
20
- O extends TableOccurrence<any, any, any, any> | undefined,
21
- > =
22
- O extends TableOccurrence<any, any, infer Nav, any>
23
- ? Nav extends Record<string, any>
24
- ? keyof Nav & string
25
- : never
26
- : never;
27
19
 
28
- // Helper type to extract schema from a TableOccurrence
29
- type ExtractSchemaFromOccurrence<O> =
30
- O extends TableOccurrence<infer BT, any, any, any>
31
- ? BT extends BaseTable<infer S, any, any, any>
32
- ? S
33
- : never
34
- : never;
20
+ // Helper type to extract defaultSelect from an FMTable
21
+ // Since TypeScript can't extract Symbol-indexed properties at the type level,
22
+ // we simplify to return keyof InferSchemaFromFMTable<O> when O is an FMTable.
23
+ // The actual defaultSelect logic is handled at runtime.
24
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
25
+ type _ExtractDefaultSelect<O> = O extends FMTable<any, any> ? keyof InferSchemaOutputFromFMTable<O> : never;
35
26
 
36
- // Helper type to extract defaultSelect from a TableOccurrence
37
- type ExtractDefaultSelect<O> =
38
- O extends TableOccurrence<infer BT, any, any, infer DefSelect>
39
- ? BT extends BaseTable<infer S, any, any, any>
40
- ? DefSelect extends "all"
41
- ? keyof S
42
- : DefSelect extends "schema"
43
- ? keyof S
44
- : DefSelect extends readonly (infer K)[]
45
- ? K & keyof S
46
- : keyof S
27
+ /**
28
+ * Helper type to extract properly-typed columns from an FMTable.
29
+ * This preserves the specific column types instead of widening to `any`.
30
+ */
31
+ type ExtractColumnsFromOcc<T> =
32
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
33
+ T extends FMTable<infer TFields, infer TName, any>
34
+ ? // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FieldBuilder configuration
35
+ TFields extends Record<string, FieldBuilder<any, any, any, any>>
36
+ ? ColumnMap<TFields, TName>
47
37
  : never
48
38
  : never;
49
39
 
50
- // Helper type to resolve a navigation item (handles both direct and lazy-loaded)
51
- type ResolveNavigationItem<T> = T extends () => infer R ? R : T;
52
-
53
- // Helper type to find target occurrence by relation name
54
- type FindNavigationTarget<
55
- O extends TableOccurrence<any, any, any, any> | undefined,
56
- Name extends string,
57
- > =
58
- O extends TableOccurrence<any, any, infer Nav, any>
59
- ? Nav extends Record<string, any>
60
- ? Name extends keyof Nav
61
- ? ResolveNavigationItem<Nav[Name]>
62
- : TableOccurrence<
63
- BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
64
- any,
65
- any,
66
- any
67
- >
68
- : TableOccurrence<
69
- BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
70
- any,
71
- any,
72
- any
73
- >
74
- : TableOccurrence<
75
- BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
76
- any,
77
- any,
78
- any
79
- >;
80
-
81
- // Helper type to get the inferred schema type from a target occurrence
82
- type GetTargetSchemaType<
83
- O extends TableOccurrence<any, any, any, any> | undefined,
84
- Rel extends string,
85
- > = [FindNavigationTarget<O, Rel>] extends [
86
- TableOccurrence<infer BT, any, any, any>,
87
- ]
88
- ? [BT] extends [BaseTable<infer S, any, any, any>]
89
- ? [S] extends [Record<string, StandardSchemaV1>]
90
- ? InferSchemaType<S>
91
- : Record<string, any>
92
- : Record<string, any>
93
- : Record<string, any>;
94
-
95
- export class EntitySet<
96
- Schema extends Record<string, StandardSchemaV1> = any,
97
- Occ extends TableOccurrence<any, any, any, any> | undefined = undefined,
98
- > {
99
- private occurrence?: Occ;
100
- private tableName: string;
101
- private databaseName: string;
102
- private context: ExecutionContext;
103
- private database: Database<any>; // Database instance for accessing occurrences
104
- private isNavigateFromEntitySet?: boolean;
105
- private navigateRelation?: string;
106
- private navigateSourceTableName?: string;
40
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
41
+ export class EntitySet<Occ extends FMTable<any, any>, DatabaseIncludeSpecialColumns extends boolean = false> {
42
+ private readonly occurrence: Occ;
43
+ private readonly databaseName: string;
44
+ private readonly context: ExecutionContext;
45
+ private readonly database: Database<DatabaseIncludeSpecialColumns>; // Database instance for accessing occurrences
46
+ private readonly isNavigateFromEntitySet?: boolean;
47
+ private readonly navigateRelation?: string;
48
+ private readonly navigateSourceTableName?: string;
49
+ private readonly navigateBasePath?: string; // Full base path for chained navigations
50
+ private readonly databaseUseEntityIds: boolean;
51
+ private readonly databaseIncludeSpecialColumns: DatabaseIncludeSpecialColumns;
52
+ private readonly logger: InternalLogger;
107
53
 
108
54
  constructor(config: {
109
- occurrence?: Occ;
110
- tableName: string;
55
+ occurrence: Occ;
111
56
  databaseName: string;
112
57
  context: ExecutionContext;
58
+ // biome-ignore lint/suspicious/noExplicitAny: Database type is optional and can be any Database instance
113
59
  database?: any;
114
60
  }) {
115
61
  this.occurrence = config.occurrence;
116
- this.tableName = config.tableName;
117
62
  this.databaseName = config.databaseName;
118
63
  this.context = config.context;
119
64
  this.database = config.database;
65
+ // Get useEntityIds from database if available, otherwise default to false
66
+ this.databaseUseEntityIds = config.database?._getUseEntityIds ?? false;
67
+ // Get includeSpecialColumns from database if available, otherwise default to false
68
+ this.databaseIncludeSpecialColumns = (config.database?._getIncludeSpecialColumns ??
69
+ false) as DatabaseIncludeSpecialColumns;
70
+ this.logger = config.context?._getLogger?.() ?? createLogger();
120
71
  }
121
72
 
122
- // Type-only method to help TypeScript infer the schema from occurrence
123
- static create<
124
- OccurrenceSchema extends Record<string, StandardSchemaV1>,
125
- Occ extends
126
- | TableOccurrence<
127
- BaseTable<OccurrenceSchema, any, any, any>,
128
- any,
129
- any,
130
- any
131
- >
132
- | undefined = undefined,
133
- >(config: {
134
- occurrence?: Occ;
135
- tableName: string;
73
+ // Type-only method to help TypeScript infer the schema from table
74
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
75
+ static create<Occ extends FMTable<any, any>, DatabaseIncludeSpecialColumns extends boolean = false>(config: {
76
+ occurrence: Occ;
136
77
  databaseName: string;
137
78
  context: ExecutionContext;
138
- database: Database<any>;
139
- }): EntitySet<OccurrenceSchema, Occ> {
140
- return new EntitySet<OccurrenceSchema, Occ>({
79
+ database: Database<DatabaseIncludeSpecialColumns>;
80
+ }): EntitySet<Occ, DatabaseIncludeSpecialColumns> {
81
+ return new EntitySet<Occ, DatabaseIncludeSpecialColumns>({
141
82
  occurrence: config.occurrence,
142
- tableName: config.tableName,
143
83
  databaseName: config.databaseName,
144
84
  context: config.context,
145
85
  database: config.database,
146
86
  });
147
87
  }
148
88
 
149
- list(): QueryBuilder<
150
- InferSchemaType<Schema>,
151
- Occ extends TableOccurrence<any, any, any, any>
152
- ? ExtractDefaultSelect<Occ>
153
- : keyof InferSchemaType<Schema>,
154
- false,
155
- false,
156
- Occ
157
- > {
89
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
90
+ list(): QueryBuilder<Occ, keyof InferSchemaOutputFromFMTable<Occ>, false, false, {}, DatabaseIncludeSpecialColumns> {
158
91
  const builder = new QueryBuilder<
159
- InferSchemaType<Schema>,
160
- Occ extends TableOccurrence<any, any, any, any>
161
- ? ExtractDefaultSelect<Occ>
162
- : keyof InferSchemaType<Schema>,
92
+ Occ,
93
+ keyof InferSchemaOutputFromFMTable<Occ>,
163
94
  false,
164
95
  false,
165
- Occ
96
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
97
+ {},
98
+ DatabaseIncludeSpecialColumns
166
99
  >({
167
100
  occurrence: this.occurrence as Occ,
168
- tableName: this.tableName,
169
101
  databaseName: this.databaseName,
170
102
  context: this.context,
171
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
103
+ databaseUseEntityIds: this.databaseUseEntityIds,
104
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
172
105
  });
173
106
 
174
107
  // Apply defaultSelect if occurrence exists and select hasn't been called
175
108
  if (this.occurrence) {
176
- const defaultSelect = this.occurrence.defaultSelect;
177
-
178
- if (defaultSelect === "schema") {
179
- // Extract field names from schema
180
- const schema = this.occurrence.baseTable.schema;
181
- const fields = Object.keys(schema) as (keyof InferSchemaType<Schema>)[];
182
- // Deduplicate fields (same as select method)
183
- const uniqueFields = [...new Set(fields)];
184
- return builder.select(...uniqueFields).top(1000);
185
- } else if (Array.isArray(defaultSelect)) {
186
- // Use the provided field names, deduplicated
187
- const uniqueFields = [
188
- ...new Set(defaultSelect),
189
- ] as (keyof InferSchemaType<Schema>)[];
190
- return builder.select(...uniqueFields).top(1000);
109
+ // FMTable - access via helper functions
110
+ const defaultSelectValue = getDefaultSelect(this.occurrence);
111
+ // Schema is stored directly as Partial<Record<keyof TFields, StandardSchemaV1>>
112
+ const _schema = getTableSchema(this.occurrence);
113
+
114
+ if (defaultSelectValue === "schema") {
115
+ // Use getTableColumns to get all columns and select them
116
+ // This is equivalent to select(getTableColumns(occurrence))
117
+ // Cast to the declared return type - runtime behavior handles the actual selection
118
+ const allColumns = getTableColumns(this.occurrence) as ExtractColumnsFromOcc<Occ>;
119
+
120
+ // Include special columns if enabled at database level
121
+ const systemColumns = this.databaseIncludeSpecialColumns ? { ROWID: true, ROWMODID: true } : undefined;
122
+
123
+ const selectedBuilder = builder.select(allColumns, systemColumns).top(1000);
124
+ // Propagate navigation context if present
125
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
126
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
127
+ (selectedBuilder as any).navigation = {
128
+ relation: this.navigateRelation,
129
+ sourceTableName: this.navigateSourceTableName,
130
+ basePath: this.navigateBasePath,
131
+ };
132
+ }
133
+ return selectedBuilder as QueryBuilder<
134
+ Occ,
135
+ keyof InferSchemaOutputFromFMTable<Occ>,
136
+ false,
137
+ false,
138
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
139
+ {},
140
+ DatabaseIncludeSpecialColumns,
141
+ typeof systemColumns
142
+ >;
143
+ }
144
+ if (typeof defaultSelectValue === "object") {
145
+ // defaultSelectValue is a select object (Record<string, Column>)
146
+ // Cast to the declared return type - runtime behavior handles the actual selection
147
+ const selectedBuilder = builder.select(defaultSelectValue as ExtractColumnsFromOcc<Occ>).top(1000);
148
+ // Propagate navigation context if present
149
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
150
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
151
+ (selectedBuilder as any).navigation = {
152
+ relation: this.navigateRelation,
153
+ sourceTableName: this.navigateSourceTableName,
154
+ basePath: this.navigateBasePath,
155
+ };
156
+ }
157
+ return selectedBuilder as QueryBuilder<
158
+ Occ,
159
+ keyof InferSchemaOutputFromFMTable<Occ>,
160
+ false,
161
+ false,
162
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
163
+ {},
164
+ DatabaseIncludeSpecialColumns
165
+ >;
191
166
  }
192
167
  // If defaultSelect is "all", no changes needed (current behavior)
193
168
  }
194
169
 
195
170
  // Propagate navigation context if present
196
- if (this.isNavigateFromEntitySet) {
197
- (builder as any).isNavigate = true;
198
- (builder as any).navigateRelation = this.navigateRelation;
199
- (builder as any).navigateSourceTableName = this.navigateSourceTableName;
200
- // navigateRecordId is intentionally not set (undefined) to indicate navigation from EntitySet
171
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
172
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
173
+ (builder as any).navigation = {
174
+ relation: this.navigateRelation,
175
+ sourceTableName: this.navigateSourceTableName,
176
+ basePath: this.navigateBasePath,
177
+ // recordId is intentionally not set (undefined) to indicate navigation from EntitySet
178
+ };
201
179
  }
202
180
 
203
181
  // Apply default pagination limit of 1000 records to prevent stack overflow
@@ -207,191 +185,206 @@ export class EntitySet<
207
185
 
208
186
  get(
209
187
  id: string | number,
210
- ): RecordBuilder<
211
- InferSchemaType<Schema>,
212
- false,
213
- keyof InferSchemaType<Schema>,
214
- Occ
215
- > {
188
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
189
+ ): RecordBuilder<Occ, false, undefined, keyof InferSchemaOutputFromFMTable<Occ>, {}, DatabaseIncludeSpecialColumns> {
216
190
  const builder = new RecordBuilder<
217
- InferSchemaType<Schema>,
191
+ Occ,
218
192
  false,
219
- keyof InferSchemaType<Schema>,
220
- Occ
193
+ undefined,
194
+ keyof InferSchemaOutputFromFMTable<Occ>,
195
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
196
+ {},
197
+ DatabaseIncludeSpecialColumns
221
198
  >({
222
199
  occurrence: this.occurrence,
223
- tableName: this.tableName,
224
200
  databaseName: this.databaseName,
225
201
  context: this.context,
226
202
  recordId: id,
227
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
203
+ databaseUseEntityIds: this.databaseUseEntityIds,
204
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
228
205
  });
206
+
207
+ // Apply defaultSelect if occurrence exists
208
+ if (this.occurrence) {
209
+ // FMTable - access via helper functions
210
+ const defaultSelectValue = getDefaultSelect(this.occurrence);
211
+ // Schema is stored directly as Partial<Record<keyof TFields, StandardSchemaV1>>
212
+ const _schema = getTableSchema(this.occurrence);
213
+
214
+ if (defaultSelectValue === "schema") {
215
+ // Use getTableColumns to get all columns and select them
216
+ // This is equivalent to select(getTableColumns(occurrence))
217
+ // Use ExtractColumnsFromOcc to preserve the properly-typed column types
218
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
219
+ const allColumns = getTableColumns(this.occurrence as any) as ExtractColumnsFromOcc<Occ>;
220
+
221
+ // Include special columns if enabled at database level
222
+ const systemColumns = this.databaseIncludeSpecialColumns ? { ROWID: true, ROWMODID: true } : undefined;
223
+
224
+ const selectedBuilder = builder.select(allColumns, systemColumns);
225
+ // Propagate navigation context if present
226
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
227
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
228
+ (selectedBuilder as any).navigation = {
229
+ relation: this.navigateRelation,
230
+ sourceTableName: this.navigateSourceTableName,
231
+ basePath: this.navigateBasePath,
232
+ };
233
+ }
234
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
235
+ return selectedBuilder as any;
236
+ }
237
+ if (typeof defaultSelectValue === "object" && defaultSelectValue !== null && !Array.isArray(defaultSelectValue)) {
238
+ // defaultSelectValue is a select object (Record<string, Column>)
239
+ // Use it directly with select()
240
+ // Use ExtractColumnsFromOcc to preserve the properly-typed column types
241
+ const selectedBuilder = builder.select(defaultSelectValue as ExtractColumnsFromOcc<Occ>);
242
+ // Propagate navigation context if present
243
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
244
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
245
+ (selectedBuilder as any).navigation = {
246
+ relation: this.navigateRelation,
247
+ sourceTableName: this.navigateSourceTableName,
248
+ basePath: this.navigateBasePath,
249
+ };
250
+ }
251
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
252
+ return selectedBuilder as any;
253
+ }
254
+ // If defaultSelect is "all", no changes needed (current behavior)
255
+ }
256
+
229
257
  // Propagate navigation context if present
230
- if (this.isNavigateFromEntitySet) {
231
- (builder as any).isNavigateFromEntitySet = true;
232
- (builder as any).navigateRelation = this.navigateRelation;
233
- (builder as any).navigateSourceTableName = this.navigateSourceTableName;
258
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
259
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
260
+ (builder as any).navigation = {
261
+ relation: this.navigateRelation,
262
+ sourceTableName: this.navigateSourceTableName,
263
+ basePath: this.navigateBasePath,
264
+ };
234
265
  }
235
- return builder;
266
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
267
+ return builder as any;
236
268
  }
237
269
 
238
- // Overload: when returnFullRecord is explicitly false
239
- insert(
240
- data: Occ extends TableOccurrence<infer BT, any, any, any>
241
- ? BT extends BaseTable<any, any, any, any>
242
- ? InsertData<BT>
243
- : Partial<InferSchemaType<Schema>>
244
- : Partial<InferSchemaType<Schema>>,
245
- options: { returnFullRecord: false },
246
- ): InsertBuilder<InferSchemaType<Schema>, Occ, "minimal">;
270
+ // Overload: when returnFullRecord is false
271
+ insert(data: InsertDataFromFMTable<Occ>, options: { returnFullRecord: false }): InsertBuilder<Occ, "minimal">;
247
272
 
248
273
  // Overload: when returnFullRecord is true or omitted (default)
249
- insert(
250
- data: Occ extends TableOccurrence<infer BT, any, any, any>
251
- ? BT extends BaseTable<any, any, any, any>
252
- ? InsertData<BT>
253
- : Partial<InferSchemaType<Schema>>
254
- : Partial<InferSchemaType<Schema>>,
255
- options?: { returnFullRecord?: true },
256
- ): InsertBuilder<InferSchemaType<Schema>, Occ, "representation">;
274
+ insert(data: InsertDataFromFMTable<Occ>, options?: { returnFullRecord?: true }): InsertBuilder<Occ, "representation">;
257
275
 
258
276
  // Implementation
259
277
  insert(
260
- data: Occ extends TableOccurrence<infer BT, any, any, any>
261
- ? BT extends BaseTable<any, any, any, any>
262
- ? InsertData<BT>
263
- : Partial<InferSchemaType<Schema>>
264
- : Partial<InferSchemaType<Schema>>,
278
+ data: InsertDataFromFMTable<Occ>,
265
279
  options?: { returnFullRecord?: boolean },
266
- ): InsertBuilder<InferSchemaType<Schema>, Occ, "minimal" | "representation"> {
267
- const returnPref =
268
- options?.returnFullRecord === false ? "minimal" : "representation";
269
- return new InsertBuilder<InferSchemaType<Schema>, Occ, typeof returnPref>({
280
+ ): InsertBuilder<Occ, "minimal" | "representation"> {
281
+ const returnPreference = options?.returnFullRecord === false ? "minimal" : "representation";
282
+
283
+ return new InsertBuilder<Occ, typeof returnPreference>({
270
284
  occurrence: this.occurrence,
271
- tableName: this.tableName,
272
285
  databaseName: this.databaseName,
273
286
  context: this.context,
274
- data: data as Partial<InferSchemaType<Schema>>,
275
- returnPreference: returnPref as any,
276
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
287
+ // biome-ignore lint/suspicious/noExplicitAny: Input type is validated/transformed at runtime
288
+ data: data as any,
289
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
290
+ returnPreference: returnPreference as any,
291
+ databaseUseEntityIds: this.databaseUseEntityIds,
292
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
277
293
  });
278
294
  }
279
295
 
280
296
  // Overload: when returnFullRecord is explicitly true
281
- update(
282
- data: Occ extends TableOccurrence<infer BT, any, any, any>
283
- ? BT extends BaseTable<any, any, any, any>
284
- ? UpdateData<BT>
285
- : Partial<InferSchemaType<Schema>>
286
- : Partial<InferSchemaType<Schema>>,
287
- options: { returnFullRecord: true },
288
- ): UpdateBuilder<
289
- InferSchemaType<Schema>,
290
- Occ extends TableOccurrence<infer BT, any, any, any>
291
- ? BT extends BaseTable<any, any, any, any>
292
- ? BT
293
- : BaseTable<Schema, any, any, any>
294
- : BaseTable<Schema, any, any, any>,
295
- "representation"
296
- >;
297
-
298
- // Overload: when returnFullRecord is false or omitted (default returns count)
299
- update(
300
- data: Occ extends TableOccurrence<infer BT, any, any, any>
301
- ? BT extends BaseTable<any, any, any, any>
302
- ? UpdateData<BT>
303
- : Partial<InferSchemaType<Schema>>
304
- : Partial<InferSchemaType<Schema>>,
305
- options?: { returnFullRecord?: false },
306
- ): UpdateBuilder<
307
- InferSchemaType<Schema>,
308
- Occ extends TableOccurrence<infer BT, any, any, any>
309
- ? BT extends BaseTable<any, any, any, any>
310
- ? BT
311
- : BaseTable<Schema, any, any, any>
312
- : BaseTable<Schema, any, any, any>,
313
- "minimal"
314
- >;
297
+ update(data: UpdateDataFromFMTable<Occ>, options: { returnFullRecord: true }): UpdateBuilder<Occ, "representation">;
298
+
299
+ // Overload: when returnFullRecord is false or omitted (default)
300
+ update(data: UpdateDataFromFMTable<Occ>, options?: { returnFullRecord?: false }): UpdateBuilder<Occ, "minimal">;
315
301
 
316
302
  // Implementation
317
303
  update(
318
- data: Occ extends TableOccurrence<infer BT, any, any, any>
319
- ? BT extends BaseTable<any, any, any, any>
320
- ? UpdateData<BT>
321
- : Partial<InferSchemaType<Schema>>
322
- : Partial<InferSchemaType<Schema>>,
304
+ data: UpdateDataFromFMTable<Occ>,
323
305
  options?: { returnFullRecord?: boolean },
324
- ): UpdateBuilder<
325
- InferSchemaType<Schema>,
326
- Occ extends TableOccurrence<infer BT, any, any, any>
327
- ? BT extends BaseTable<any, any, any, any>
328
- ? BT
329
- : BaseTable<Schema, any, any, any>
330
- : BaseTable<Schema, any, any, any>,
331
- "minimal" | "representation"
332
- > {
333
- const returnPref =
334
- options?.returnFullRecord === true ? "representation" : "minimal";
335
- return new UpdateBuilder<
336
- InferSchemaType<Schema>,
337
- Occ extends TableOccurrence<infer BT, any, any, any>
338
- ? BT extends BaseTable<any, any, any, any>
339
- ? BT
340
- : BaseTable<Schema, any, any, any>
341
- : BaseTable<Schema, any, any, any>,
342
- typeof returnPref
343
- >({
306
+ ): UpdateBuilder<Occ, "minimal" | "representation"> {
307
+ const returnPreference = options?.returnFullRecord === true ? "representation" : "minimal";
308
+
309
+ return new UpdateBuilder<Occ, typeof returnPreference>({
344
310
  occurrence: this.occurrence,
345
- tableName: this.tableName,
346
311
  databaseName: this.databaseName,
347
312
  context: this.context,
348
- data: data as Partial<InferSchemaType<Schema>>,
349
- returnPreference: returnPref as any,
350
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
313
+ // biome-ignore lint/suspicious/noExplicitAny: Input type is validated/transformed at runtime
314
+ data: data as any,
315
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
316
+ returnPreference: returnPreference as any,
317
+ databaseUseEntityIds: this.databaseUseEntityIds,
318
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
351
319
  });
352
320
  }
353
321
 
354
- delete(): DeleteBuilder<InferSchemaType<Schema>> {
355
- return new DeleteBuilder<InferSchemaType<Schema>>({
322
+ delete(): DeleteBuilder<Occ> {
323
+ return new DeleteBuilder<Occ>({
356
324
  occurrence: this.occurrence,
357
- tableName: this.tableName,
358
325
  databaseName: this.databaseName,
359
326
  context: this.context,
360
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
361
- });
327
+ databaseUseEntityIds: this.databaseUseEntityIds,
328
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
329
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
330
+ }) as any;
362
331
  }
363
332
 
364
- // Overload for valid relation names - returns typed EntitySet
365
- navigate<RelationName extends ExtractNavigationNames<Occ>>(
366
- relationName: RelationName,
367
- ): EntitySet<
368
- ExtractSchemaFromOccurrence<
369
- FindNavigationTarget<Occ, RelationName>
370
- > extends Record<string, StandardSchemaV1>
371
- ? ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>
372
- : Record<string, StandardSchemaV1>,
373
- FindNavigationTarget<Occ, RelationName>
374
- >;
375
- // Overload for arbitrary strings - returns generic EntitySet
376
- navigate(
377
- relationName: string,
378
- ): EntitySet<Record<string, StandardSchemaV1>, undefined>;
379
333
  // Implementation
380
- navigate(relationName: string): EntitySet<any, any> {
381
- // Use the target occurrence if available, otherwise allow untyped navigation
382
- // (useful when types might be incomplete)
383
- const targetOccurrence = this.occurrence?.navigation[relationName];
384
- const entitySet = new EntitySet<any, any>({
385
- occurrence: targetOccurrence,
386
- tableName: targetOccurrence?.name ?? relationName,
334
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
335
+ navigate<TargetTable extends FMTable<any, any>>(
336
+ targetTable: ValidExpandTarget<Occ, TargetTable>,
337
+ // biome-ignore lint/suspicious/noExplicitAny: Required for conditional type inference
338
+ ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never, DatabaseIncludeSpecialColumns> {
339
+ // Check if it's an FMTable object or a string
340
+ let relationName: string;
341
+
342
+ // FMTable object - extract name and validate
343
+ relationName = getTableName(targetTable);
344
+
345
+ // Runtime validation: Check if relation name is in navigationPaths
346
+ if (this.occurrence && FMTableClass.Symbol.NavigationPaths in this.occurrence) {
347
+ // biome-ignore lint/suspicious/noExplicitAny: Symbol property access for internal property
348
+ const navigationPaths = (this.occurrence as any)[FMTableClass.Symbol.NavigationPaths] as readonly string[];
349
+ if (navigationPaths && !navigationPaths.includes(relationName)) {
350
+ this.logger.warn(
351
+ `Cannot navigate to "${relationName}". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(", ") : "none"}`,
352
+ );
353
+ }
354
+ }
355
+
356
+ // Create EntitySet with target table
357
+ // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FMTable configuration
358
+ const entitySet = new EntitySet<any, DatabaseIncludeSpecialColumns>({
359
+ occurrence: targetTable,
387
360
  databaseName: this.databaseName,
388
361
  context: this.context,
362
+ database: this.database,
389
363
  });
390
364
  // Store the navigation info in the EntitySet
391
- // We'll need to pass this through when creating QueryBuilders
365
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
392
366
  (entitySet as any).isNavigateFromEntitySet = true;
367
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
393
368
  (entitySet as any).navigateRelation = relationName;
394
- (entitySet as any).navigateSourceTableName = this.tableName;
369
+
370
+ // Build the full base path for chained navigations
371
+ if (this.isNavigateFromEntitySet && this.navigateBasePath) {
372
+ // Already have a base path from previous navigation - extend it with current relation
373
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
374
+ (entitySet as any).navigateBasePath = `${this.navigateBasePath}/${this.navigateRelation}`;
375
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
376
+ (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;
377
+ } else if (this.isNavigateFromEntitySet && this.navigateRelation) {
378
+ // First chained navigation - create base path from source/relation
379
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
380
+ (entitySet as any).navigateBasePath = `${this.navigateSourceTableName}/${this.navigateRelation}`;
381
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
382
+ (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;
383
+ } else {
384
+ // Initial navigation - source is just the table name
385
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
386
+ (entitySet as any).navigateSourceTableName = getTableName(this.occurrence);
387
+ }
395
388
  return entitySet;
396
389
  }
397
390
  }