@proofkit/fmodata 0.1.0-alpha.8 → 0.1.0-beta.23

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 +651 -449
  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 +110 -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 +28 -20
  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 +266 -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 -166
  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,161 @@
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
+ return builder.select(allColumns, systemColumns).top(1000) as QueryBuilder<
124
+ Occ,
125
+ keyof InferSchemaOutputFromFMTable<Occ>,
126
+ false,
127
+ false,
128
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
129
+ {},
130
+ DatabaseIncludeSpecialColumns,
131
+ typeof systemColumns
132
+ >;
133
+ }
134
+ if (typeof defaultSelectValue === "object") {
135
+ // defaultSelectValue is a select object (Record<string, Column>)
136
+ // Cast to the declared return type - runtime behavior handles the actual selection
137
+ return builder.select(defaultSelectValue as ExtractColumnsFromOcc<Occ>).top(1000) as QueryBuilder<
138
+ Occ,
139
+ keyof InferSchemaOutputFromFMTable<Occ>,
140
+ false,
141
+ false,
142
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
143
+ {},
144
+ DatabaseIncludeSpecialColumns
145
+ >;
191
146
  }
192
147
  // If defaultSelect is "all", no changes needed (current behavior)
193
148
  }
194
149
 
195
150
  // 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
151
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
152
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
153
+ (builder as any).navigation = {
154
+ relation: this.navigateRelation,
155
+ sourceTableName: this.navigateSourceTableName,
156
+ basePath: this.navigateBasePath,
157
+ // recordId is intentionally not set (undefined) to indicate navigation from EntitySet
158
+ };
201
159
  }
202
160
 
203
161
  // Apply default pagination limit of 1000 records to prevent stack overflow
@@ -207,191 +165,206 @@ export class EntitySet<
207
165
 
208
166
  get(
209
167
  id: string | number,
210
- ): RecordBuilder<
211
- InferSchemaType<Schema>,
212
- false,
213
- keyof InferSchemaType<Schema>,
214
- Occ
215
- > {
168
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
169
+ ): RecordBuilder<Occ, false, undefined, keyof InferSchemaOutputFromFMTable<Occ>, {}, DatabaseIncludeSpecialColumns> {
216
170
  const builder = new RecordBuilder<
217
- InferSchemaType<Schema>,
171
+ Occ,
218
172
  false,
219
- keyof InferSchemaType<Schema>,
220
- Occ
173
+ undefined,
174
+ keyof InferSchemaOutputFromFMTable<Occ>,
175
+ // biome-ignore lint/complexity/noBannedTypes: Empty object type represents no expands by default
176
+ {},
177
+ DatabaseIncludeSpecialColumns
221
178
  >({
222
179
  occurrence: this.occurrence,
223
- tableName: this.tableName,
224
180
  databaseName: this.databaseName,
225
181
  context: this.context,
226
182
  recordId: id,
227
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
183
+ databaseUseEntityIds: this.databaseUseEntityIds,
184
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
228
185
  });
186
+
187
+ // Apply defaultSelect if occurrence exists
188
+ if (this.occurrence) {
189
+ // FMTable - access via helper functions
190
+ const defaultSelectValue = getDefaultSelect(this.occurrence);
191
+ // Schema is stored directly as Partial<Record<keyof TFields, StandardSchemaV1>>
192
+ const _schema = getTableSchema(this.occurrence);
193
+
194
+ if (defaultSelectValue === "schema") {
195
+ // Use getTableColumns to get all columns and select them
196
+ // This is equivalent to select(getTableColumns(occurrence))
197
+ // Use ExtractColumnsFromOcc to preserve the properly-typed column types
198
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
199
+ const allColumns = getTableColumns(this.occurrence as any) as ExtractColumnsFromOcc<Occ>;
200
+
201
+ // Include special columns if enabled at database level
202
+ const systemColumns = this.databaseIncludeSpecialColumns ? { ROWID: true, ROWMODID: true } : undefined;
203
+
204
+ const selectedBuilder = builder.select(allColumns, systemColumns);
205
+ // Propagate navigation context if present
206
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
207
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
208
+ (selectedBuilder as any).navigation = {
209
+ relation: this.navigateRelation,
210
+ sourceTableName: this.navigateSourceTableName,
211
+ basePath: this.navigateBasePath,
212
+ };
213
+ }
214
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
215
+ return selectedBuilder as any;
216
+ }
217
+ if (typeof defaultSelectValue === "object" && defaultSelectValue !== null && !Array.isArray(defaultSelectValue)) {
218
+ // defaultSelectValue is a select object (Record<string, Column>)
219
+ // Use it directly with select()
220
+ // Use ExtractColumnsFromOcc to preserve the properly-typed column types
221
+ const selectedBuilder = builder.select(defaultSelectValue as ExtractColumnsFromOcc<Occ>);
222
+ // Propagate navigation context if present
223
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
224
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
225
+ (selectedBuilder as any).navigation = {
226
+ relation: this.navigateRelation,
227
+ sourceTableName: this.navigateSourceTableName,
228
+ basePath: this.navigateBasePath,
229
+ };
230
+ }
231
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
232
+ return selectedBuilder as any;
233
+ }
234
+ // If defaultSelect is "all", no changes needed (current behavior)
235
+ }
236
+
229
237
  // 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;
238
+ if (this.isNavigateFromEntitySet && this.navigateRelation && this.navigateSourceTableName) {
239
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
240
+ (builder as any).navigation = {
241
+ relation: this.navigateRelation,
242
+ sourceTableName: this.navigateSourceTableName,
243
+ basePath: this.navigateBasePath,
244
+ };
234
245
  }
235
- return builder;
246
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
247
+ return builder as any;
236
248
  }
237
249
 
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">;
250
+ // Overload: when returnFullRecord is false
251
+ insert(data: InsertDataFromFMTable<Occ>, options: { returnFullRecord: false }): InsertBuilder<Occ, "minimal">;
247
252
 
248
253
  // 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">;
254
+ insert(data: InsertDataFromFMTable<Occ>, options?: { returnFullRecord?: true }): InsertBuilder<Occ, "representation">;
257
255
 
258
256
  // Implementation
259
257
  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>>,
258
+ data: InsertDataFromFMTable<Occ>,
265
259
  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>({
260
+ ): InsertBuilder<Occ, "minimal" | "representation"> {
261
+ const returnPreference = options?.returnFullRecord === false ? "minimal" : "representation";
262
+
263
+ return new InsertBuilder<Occ, typeof returnPreference>({
270
264
  occurrence: this.occurrence,
271
- tableName: this.tableName,
272
265
  databaseName: this.databaseName,
273
266
  context: this.context,
274
- data: data as Partial<InferSchemaType<Schema>>,
275
- returnPreference: returnPref as any,
276
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
267
+ // biome-ignore lint/suspicious/noExplicitAny: Input type is validated/transformed at runtime
268
+ data: data as any,
269
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
270
+ returnPreference: returnPreference as any,
271
+ databaseUseEntityIds: this.databaseUseEntityIds,
272
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
277
273
  });
278
274
  }
279
275
 
280
276
  // 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
- >;
277
+ update(data: UpdateDataFromFMTable<Occ>, options: { returnFullRecord: true }): UpdateBuilder<Occ, "representation">;
278
+
279
+ // Overload: when returnFullRecord is false or omitted (default)
280
+ update(data: UpdateDataFromFMTable<Occ>, options?: { returnFullRecord?: false }): UpdateBuilder<Occ, "minimal">;
315
281
 
316
282
  // Implementation
317
283
  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>>,
284
+ data: UpdateDataFromFMTable<Occ>,
323
285
  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
- >({
286
+ ): UpdateBuilder<Occ, "minimal" | "representation"> {
287
+ const returnPreference = options?.returnFullRecord === true ? "representation" : "minimal";
288
+
289
+ return new UpdateBuilder<Occ, typeof returnPreference>({
344
290
  occurrence: this.occurrence,
345
- tableName: this.tableName,
346
291
  databaseName: this.databaseName,
347
292
  context: this.context,
348
- data: data as Partial<InferSchemaType<Schema>>,
349
- returnPreference: returnPref as any,
350
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
293
+ // biome-ignore lint/suspicious/noExplicitAny: Input type is validated/transformed at runtime
294
+ data: data as any,
295
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic type parameter
296
+ returnPreference: returnPreference as any,
297
+ databaseUseEntityIds: this.databaseUseEntityIds,
298
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
351
299
  });
352
300
  }
353
301
 
354
- delete(): DeleteBuilder<InferSchemaType<Schema>> {
355
- return new DeleteBuilder<InferSchemaType<Schema>>({
302
+ delete(): DeleteBuilder<Occ> {
303
+ return new DeleteBuilder<Occ>({
356
304
  occurrence: this.occurrence,
357
- tableName: this.tableName,
358
305
  databaseName: this.databaseName,
359
306
  context: this.context,
360
- databaseUseEntityIds: this.database?.isUsingEntityIds() ?? false,
361
- });
307
+ databaseUseEntityIds: this.databaseUseEntityIds,
308
+ databaseIncludeSpecialColumns: this.databaseIncludeSpecialColumns,
309
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for complex generic return type
310
+ }) as any;
362
311
  }
363
312
 
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
313
  // 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,
314
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
315
+ navigate<TargetTable extends FMTable<any, any>>(
316
+ targetTable: ValidExpandTarget<Occ, TargetTable>,
317
+ // biome-ignore lint/suspicious/noExplicitAny: Required for conditional type inference
318
+ ): EntitySet<TargetTable extends FMTable<any, any> ? TargetTable : never, DatabaseIncludeSpecialColumns> {
319
+ // Check if it's an FMTable object or a string
320
+ let relationName: string;
321
+
322
+ // FMTable object - extract name and validate
323
+ relationName = getTableName(targetTable);
324
+
325
+ // Runtime validation: Check if relation name is in navigationPaths
326
+ if (this.occurrence && FMTableClass.Symbol.NavigationPaths in this.occurrence) {
327
+ // biome-ignore lint/suspicious/noExplicitAny: Symbol property access for internal property
328
+ const navigationPaths = (this.occurrence as any)[FMTableClass.Symbol.NavigationPaths] as readonly string[];
329
+ if (navigationPaths && !navigationPaths.includes(relationName)) {
330
+ this.logger.warn(
331
+ `Cannot navigate to "${relationName}". Valid navigation paths: ${navigationPaths.length > 0 ? navigationPaths.join(", ") : "none"}`,
332
+ );
333
+ }
334
+ }
335
+
336
+ // Create EntitySet with target table
337
+ // biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any FMTable configuration
338
+ const entitySet = new EntitySet<any, DatabaseIncludeSpecialColumns>({
339
+ occurrence: targetTable,
387
340
  databaseName: this.databaseName,
388
341
  context: this.context,
342
+ database: this.database,
389
343
  });
390
344
  // Store the navigation info in the EntitySet
391
- // We'll need to pass this through when creating QueryBuilders
345
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
392
346
  (entitySet as any).isNavigateFromEntitySet = true;
347
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
393
348
  (entitySet as any).navigateRelation = relationName;
394
- (entitySet as any).navigateSourceTableName = this.tableName;
349
+
350
+ // Build the full base path for chained navigations
351
+ if (this.isNavigateFromEntitySet && this.navigateBasePath) {
352
+ // Already have a base path from previous navigation - extend it with current relation
353
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
354
+ (entitySet as any).navigateBasePath = `${this.navigateBasePath}/${this.navigateRelation}`;
355
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
356
+ (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;
357
+ } else if (this.isNavigateFromEntitySet && this.navigateRelation) {
358
+ // First chained navigation - create base path from source/relation
359
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
360
+ (entitySet as any).navigateBasePath = `${this.navigateSourceTableName}/${this.navigateRelation}`;
361
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
362
+ (entitySet as any).navigateSourceTableName = this.navigateSourceTableName;
363
+ } else {
364
+ // Initial navigation - source is just the table name
365
+ // biome-ignore lint/suspicious/noExplicitAny: Mutation of readonly properties for builder pattern
366
+ (entitySet as any).navigateSourceTableName = getTableName(this.occurrence);
367
+ }
395
368
  return entitySet;
396
369
  }
397
370
  }