@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,47 +1,43 @@
1
- import type {
2
- ExecutionContext,
3
- ExecutableBuilder,
4
- Result,
5
- WithSystemFields,
6
- ExecuteOptions,
7
- } from "../types";
8
- import type { TableOccurrence } from "./table-occurrence";
1
+ import type { FFetchOptions } from "@fetchkit/ffetch";
2
+ import type { FMTable } from "../orm/table";
3
+ import { getTableId as getTableIdHelper, getTableName, isUsingEntityIds } from "../orm/table";
4
+ import type { ExecutableBuilder, ExecuteMethodOptions, ExecuteOptions, ExecutionContext, Result } from "../types";
5
+ import { getAcceptHeader } from "../types";
6
+ import { parseErrorResponse } from "./error-parser";
9
7
  import { QueryBuilder } from "./query-builder";
10
- import { type FFetchOptions } from "@fetchkit/ffetch";
11
- import { getTableIdentifiers } from "../transform";
12
8
 
13
9
  /**
14
10
  * Initial delete builder returned from EntitySet.delete()
15
11
  * Requires calling .byId() or .where() before .execute() is available
16
12
  */
17
- export class DeleteBuilder<T extends Record<string, any>> {
18
- private tableName: string;
19
- private databaseName: string;
20
- private context: ExecutionContext;
21
- private occurrence?: TableOccurrence<any, any, any, any>;
22
- private databaseUseEntityIds: boolean;
13
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
14
+ export class DeleteBuilder<Occ extends FMTable<any, any>> {
15
+ private readonly databaseName: string;
16
+ private readonly context: ExecutionContext;
17
+ private readonly table: Occ;
18
+ private readonly databaseUseEntityIds: boolean;
19
+ private readonly databaseIncludeSpecialColumns: boolean;
23
20
 
24
21
  constructor(config: {
25
- occurrence?: TableOccurrence<any, any, any, any>;
26
- tableName: string;
22
+ occurrence: Occ;
27
23
  databaseName: string;
28
24
  context: ExecutionContext;
29
25
  databaseUseEntityIds?: boolean;
26
+ databaseIncludeSpecialColumns?: boolean;
30
27
  }) {
31
- this.occurrence = config.occurrence;
32
- this.tableName = config.tableName;
28
+ this.table = config.occurrence;
33
29
  this.databaseName = config.databaseName;
34
30
  this.context = config.context;
35
31
  this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
32
+ this.databaseIncludeSpecialColumns = config.databaseIncludeSpecialColumns ?? false;
36
33
  }
37
34
 
38
35
  /**
39
36
  * Delete a single record by ID
40
37
  */
41
- byId(id: string | number): ExecutableDeleteBuilder<T> {
42
- return new ExecutableDeleteBuilder<T>({
43
- occurrence: this.occurrence,
44
- tableName: this.tableName,
38
+ byId(id: string | number): ExecutableDeleteBuilder<Occ> {
39
+ return new ExecutableDeleteBuilder<Occ>({
40
+ occurrence: this.table,
45
41
  databaseName: this.databaseName,
46
42
  context: this.context,
47
43
  mode: "byId",
@@ -54,21 +50,10 @@ export class DeleteBuilder<T extends Record<string, any>> {
54
50
  * Delete records matching a filter query
55
51
  * @param fn Callback that receives a QueryBuilder for building the filter
56
52
  */
57
- where(
58
- fn: (
59
- q: QueryBuilder<WithSystemFields<T>>,
60
- ) => QueryBuilder<WithSystemFields<T>>,
61
- ): ExecutableDeleteBuilder<T> {
53
+ where(fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>): ExecutableDeleteBuilder<Occ> {
62
54
  // Create a QueryBuilder for the user to configure
63
- const queryBuilder = new QueryBuilder<
64
- WithSystemFields<T>,
65
- keyof WithSystemFields<T>,
66
- false,
67
- false,
68
- undefined
69
- >({
70
- occurrence: undefined,
71
- tableName: this.tableName,
55
+ const queryBuilder = new QueryBuilder<Occ>({
56
+ occurrence: this.table,
72
57
  databaseName: this.databaseName,
73
58
  context: this.context,
74
59
  });
@@ -76,9 +61,8 @@ export class DeleteBuilder<T extends Record<string, any>> {
76
61
  // Let the user configure it
77
62
  const configuredBuilder = fn(queryBuilder);
78
63
 
79
- return new ExecutableDeleteBuilder<T>({
80
- occurrence: this.occurrence,
81
- tableName: this.tableName,
64
+ return new ExecutableDeleteBuilder<Occ>({
65
+ occurrence: this.table,
82
66
  databaseName: this.databaseName,
83
67
  context: this.context,
84
68
  mode: "byFilter",
@@ -92,30 +76,28 @@ export class DeleteBuilder<T extends Record<string, any>> {
92
76
  * Executable delete builder - has execute() method
93
77
  * Returned after calling .byId() or .where()
94
78
  */
95
- export class ExecutableDeleteBuilder<T extends Record<string, any>>
79
+ // biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
80
+ export class ExecutableDeleteBuilder<Occ extends FMTable<any, any>>
96
81
  implements ExecutableBuilder<{ deletedCount: number }>
97
82
  {
98
- private tableName: string;
99
- private databaseName: string;
100
- private context: ExecutionContext;
101
- private occurrence?: TableOccurrence<any, any, any, any>;
102
- private mode: "byId" | "byFilter";
103
- private recordId?: string | number;
104
- private queryBuilder?: QueryBuilder<any>;
105
- private databaseUseEntityIds: boolean;
83
+ private readonly databaseName: string;
84
+ private readonly context: ExecutionContext;
85
+ private readonly table: Occ;
86
+ private readonly mode: "byId" | "byFilter";
87
+ private readonly recordId?: string | number;
88
+ private readonly queryBuilder?: QueryBuilder<Occ>;
89
+ private readonly databaseUseEntityIds: boolean;
106
90
 
107
91
  constructor(config: {
108
- occurrence?: TableOccurrence<any, any, any, any>;
109
- tableName: string;
92
+ occurrence: Occ;
110
93
  databaseName: string;
111
94
  context: ExecutionContext;
112
95
  mode: "byId" | "byFilter";
113
96
  recordId?: string | number;
114
- queryBuilder?: QueryBuilder<any>;
97
+ queryBuilder?: QueryBuilder<Occ>;
115
98
  databaseUseEntityIds?: boolean;
116
99
  }) {
117
- this.occurrence = config.occurrence;
118
- this.tableName = config.tableName;
100
+ this.table = config.occurrence;
119
101
  this.databaseName = config.databaseName;
120
102
  this.context = config.context;
121
103
  this.mode = config.mode;
@@ -142,29 +124,22 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
142
124
  * @param useEntityIds - Optional override for entity ID usage
143
125
  */
144
126
  private getTableId(useEntityIds?: boolean): string {
145
- if (!this.occurrence) {
146
- return this.tableName;
147
- }
148
-
149
127
  const contextDefault = this.context._getUseEntityIds?.() ?? false;
150
128
  const shouldUseIds = useEntityIds ?? contextDefault;
151
129
 
152
130
  if (shouldUseIds) {
153
- const identifiers = getTableIdentifiers(this.occurrence);
154
- if (!identifiers.id) {
131
+ if (!isUsingEntityIds(this.table)) {
155
132
  throw new Error(
156
- `useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined`,
133
+ `useEntityIds is true but table "${getTableName(this.table)}" does not have entity IDs configured`,
157
134
  );
158
135
  }
159
- return identifiers.id;
136
+ return getTableIdHelper(this.table);
160
137
  }
161
138
 
162
- return this.occurrence.getTableName();
139
+ return getTableName(this.table);
163
140
  }
164
141
 
165
- async execute(
166
- options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
167
- ): Promise<Result<{ deletedCount: number }>> {
142
+ async execute(options?: ExecuteMethodOptions<ExecuteOptions>): Promise<Result<{ deletedCount: number }>> {
168
143
  // Merge database-level useEntityIds with per-request options
169
144
  const mergedOptions = this.mergeExecuteOptions(options);
170
145
 
@@ -185,11 +160,15 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
185
160
  // Get the query string from the configured QueryBuilder
186
161
  const queryString = this.queryBuilder.getQueryString();
187
162
  // Remove the leading "/" and table name from the query string as we'll build our own URL
188
- const queryParams = queryString.startsWith(`/${tableId}`)
189
- ? queryString.slice(`/${tableId}`.length)
190
- : queryString.startsWith(`/${this.tableName}`)
191
- ? queryString.slice(`/${this.tableName}`.length)
192
- : queryString;
163
+ const tableName = getTableName(this.table);
164
+ let queryParams: string;
165
+ if (queryString.startsWith(`/${tableId}`)) {
166
+ queryParams = queryString.slice(`/${tableId}`.length);
167
+ } else if (queryString.startsWith(`/${tableName}`)) {
168
+ queryParams = queryString.slice(`/${tableName}`.length);
169
+ } else {
170
+ queryParams = queryString;
171
+ }
193
172
 
194
173
  url = `/${this.databaseName}/${tableId}${queryParams}`;
195
174
  }
@@ -215,12 +194,14 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
215
194
  deletedCount = response;
216
195
  } else if (response && typeof response === "object") {
217
196
  // Check if the response has a count property (fallback)
197
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API
218
198
  deletedCount = (response as any).deletedCount || 0;
219
199
  }
220
200
 
221
201
  return { data: { deletedCount }, error: undefined };
222
202
  }
223
203
 
204
+ // biome-ignore lint/suspicious/noExplicitAny: Request body can be any JSON-serializable value
224
205
  getRequestConfig(): { method: string; url: string; body?: any } {
225
206
  // For batch operations, use database-level setting (no per-request override available here)
226
207
  const tableId = this.getTableId(this.databaseUseEntityIds);
@@ -235,11 +216,15 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
235
216
  }
236
217
 
237
218
  const queryString = this.queryBuilder.getQueryString();
238
- const queryParams = queryString.startsWith(`/${tableId}`)
239
- ? queryString.slice(`/${tableId}`.length)
240
- : queryString.startsWith(`/${this.tableName}`)
241
- ? queryString.slice(`/${this.tableName}`.length)
242
- : queryString;
219
+ const tableName = getTableName(this.table);
220
+ let queryParams: string;
221
+ if (queryString.startsWith(`/${tableId}`)) {
222
+ queryParams = queryString.slice(`/${tableId}`.length);
223
+ } else if (queryString.startsWith(`/${tableName}`)) {
224
+ queryParams = queryString.slice(`/${tableName}`.length);
225
+ } else {
226
+ queryParams = queryString;
227
+ }
243
228
 
244
229
  url = `/${this.databaseName}/${tableId}${queryParams}`;
245
230
  }
@@ -250,28 +235,32 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
250
235
  };
251
236
  }
252
237
 
253
- toRequest(baseUrl: string): Request {
238
+ toRequest(baseUrl: string, options?: ExecuteOptions): Request {
254
239
  const config = this.getRequestConfig();
255
240
  const fullUrl = `${baseUrl}${config.url}`;
256
241
 
257
242
  return new Request(fullUrl, {
258
243
  method: config.method,
259
244
  headers: {
260
- Accept: "application/json",
245
+ Accept: getAcceptHeader(options?.includeODataAnnotations),
261
246
  },
262
247
  });
263
248
  }
264
249
 
265
- async processResponse(
266
- response: Response,
267
- options?: ExecuteOptions,
268
- ): Promise<Result<{ deletedCount: number }>> {
250
+ async processResponse(response: Response, _options?: ExecuteOptions): Promise<Result<{ deletedCount: number }>> {
251
+ // Check for error responses (important for batch operations)
252
+ if (!response.ok) {
253
+ const tableName = getTableName(this.table);
254
+ const error = await parseErrorResponse(response, response.url || `/${this.databaseName}/${tableName}`);
255
+ return { data: undefined, error };
256
+ }
257
+
269
258
  // Check for empty response (204 No Content)
270
259
  const text = await response.text();
271
260
  if (!text || text.trim() === "") {
272
261
  // For 204 No Content, check the fmodata.affected_rows header
273
262
  const affectedRows = response.headers.get("fmodata.affected_rows");
274
- const deletedCount = affectedRows ? parseInt(affectedRows, 10) : 1;
263
+ const deletedCount = affectedRows ? Number.parseInt(affectedRows, 10) : 1;
275
264
  return { data: { deletedCount }, error: undefined };
276
265
  }
277
266
 
@@ -286,6 +275,7 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
286
275
  deletedCount = rawResponse;
287
276
  } else if (rawResponse && typeof rawResponse === "object") {
288
277
  // Check if the response has a count property (fallback)
278
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic response type from OData API
289
279
  deletedCount = (rawResponse as any).deletedCount || 0;
290
280
  }
291
281