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