@proofkit/fmodata 0.1.0-alpha.13 → 0.1.0-alpha.15

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 (143) hide show
  1. package/README.md +489 -334
  2. package/dist/esm/client/batch-builder.d.ts +7 -5
  3. package/dist/esm/client/batch-builder.js +84 -25
  4. package/dist/esm/client/batch-builder.js.map +1 -1
  5. package/dist/esm/client/builders/default-select.d.ts +7 -0
  6. package/dist/esm/client/builders/default-select.js +42 -0
  7. package/dist/esm/client/builders/default-select.js.map +1 -0
  8. package/dist/esm/client/builders/expand-builder.d.ts +43 -0
  9. package/dist/esm/client/builders/expand-builder.js +173 -0
  10. package/dist/esm/client/builders/expand-builder.js.map +1 -0
  11. package/dist/esm/client/builders/index.d.ts +8 -0
  12. package/dist/esm/client/builders/query-string-builder.d.ts +15 -0
  13. package/dist/esm/client/builders/query-string-builder.js +25 -0
  14. package/dist/esm/client/builders/query-string-builder.js.map +1 -0
  15. package/dist/esm/client/builders/response-processor.d.ts +39 -0
  16. package/dist/esm/client/builders/response-processor.js +170 -0
  17. package/dist/esm/client/builders/response-processor.js.map +1 -0
  18. package/dist/esm/client/builders/select-mixin.d.ts +31 -0
  19. package/dist/esm/client/builders/select-mixin.js +30 -0
  20. package/dist/esm/client/builders/select-mixin.js.map +1 -0
  21. package/dist/esm/client/builders/select-utils.d.ts +8 -0
  22. package/dist/esm/client/builders/select-utils.js +15 -0
  23. package/dist/esm/client/builders/select-utils.js.map +1 -0
  24. package/dist/esm/client/builders/shared-types.d.ts +39 -0
  25. package/dist/esm/client/builders/table-utils.d.ts +35 -0
  26. package/dist/esm/client/builders/table-utils.js +45 -0
  27. package/dist/esm/client/builders/table-utils.js.map +1 -0
  28. package/dist/esm/client/database.d.ts +3 -22
  29. package/dist/esm/client/database.js +14 -76
  30. package/dist/esm/client/database.js.map +1 -1
  31. package/dist/esm/client/delete-builder.d.ts +12 -19
  32. package/dist/esm/client/delete-builder.js +26 -26
  33. package/dist/esm/client/delete-builder.js.map +1 -1
  34. package/dist/esm/client/entity-set.d.ts +32 -32
  35. package/dist/esm/client/entity-set.js +92 -69
  36. package/dist/esm/client/entity-set.js.map +1 -1
  37. package/dist/esm/client/error-parser.d.ts +12 -0
  38. package/dist/esm/client/error-parser.js +30 -0
  39. package/dist/esm/client/error-parser.js.map +1 -0
  40. package/dist/esm/client/filemaker-odata.d.ts +2 -4
  41. package/dist/esm/client/filemaker-odata.js +1 -5
  42. package/dist/esm/client/filemaker-odata.js.map +1 -1
  43. package/dist/esm/client/insert-builder.d.ts +9 -12
  44. package/dist/esm/client/insert-builder.js +70 -24
  45. package/dist/esm/client/insert-builder.js.map +1 -1
  46. package/dist/esm/client/query/expand-builder.d.ts +35 -0
  47. package/dist/esm/client/query/index.d.ts +3 -0
  48. package/dist/esm/client/query/query-builder.d.ts +133 -0
  49. package/dist/esm/client/query/query-builder.js +505 -0
  50. package/dist/esm/client/query/query-builder.js.map +1 -0
  51. package/dist/esm/client/query/response-processor.d.ts +22 -0
  52. package/dist/esm/client/query/types.d.ts +52 -0
  53. package/dist/esm/client/query/url-builder.d.ts +71 -0
  54. package/dist/esm/client/query/url-builder.js +107 -0
  55. package/dist/esm/client/query/url-builder.js.map +1 -0
  56. package/dist/esm/client/query-builder.d.ts +1 -111
  57. package/dist/esm/client/record-builder.d.ts +56 -64
  58. package/dist/esm/client/record-builder.js +158 -297
  59. package/dist/esm/client/record-builder.js.map +1 -1
  60. package/dist/esm/client/response-processor.d.ts +3 -3
  61. package/dist/esm/client/update-builder.d.ts +17 -25
  62. package/dist/esm/client/update-builder.js +56 -30
  63. package/dist/esm/client/update-builder.js.map +1 -1
  64. package/dist/esm/errors.d.ts +8 -1
  65. package/dist/esm/errors.js +17 -0
  66. package/dist/esm/errors.js.map +1 -1
  67. package/dist/esm/index.d.ts +3 -7
  68. package/dist/esm/index.js +37 -8
  69. package/dist/esm/index.js.map +1 -1
  70. package/dist/esm/orm/column.d.ts +45 -0
  71. package/dist/esm/orm/column.js +59 -0
  72. package/dist/esm/orm/column.js.map +1 -0
  73. package/dist/esm/orm/field-builders.d.ts +154 -0
  74. package/dist/esm/orm/field-builders.js +152 -0
  75. package/dist/esm/orm/field-builders.js.map +1 -0
  76. package/dist/esm/orm/index.d.ts +4 -0
  77. package/dist/esm/orm/operators.d.ts +175 -0
  78. package/dist/esm/orm/operators.js +221 -0
  79. package/dist/esm/orm/operators.js.map +1 -0
  80. package/dist/esm/orm/table.d.ts +341 -0
  81. package/dist/esm/orm/table.js +211 -0
  82. package/dist/esm/orm/table.js.map +1 -0
  83. package/dist/esm/transform.d.ts +20 -21
  84. package/dist/esm/transform.js +34 -34
  85. package/dist/esm/transform.js.map +1 -1
  86. package/dist/esm/types.d.ts +73 -12
  87. package/dist/esm/types.js.map +1 -1
  88. package/dist/esm/validation.d.ts +14 -4
  89. package/dist/esm/validation.js +45 -1
  90. package/dist/esm/validation.js.map +1 -1
  91. package/package.json +22 -17
  92. package/src/client/batch-builder.ts +102 -33
  93. package/src/client/builders/default-select.ts +69 -0
  94. package/src/client/builders/expand-builder.ts +236 -0
  95. package/src/client/builders/index.ts +11 -0
  96. package/src/client/builders/query-string-builder.ts +41 -0
  97. package/src/client/builders/response-processor.ts +273 -0
  98. package/src/client/builders/select-mixin.ts +74 -0
  99. package/src/client/builders/select-utils.ts +34 -0
  100. package/src/client/builders/shared-types.ts +41 -0
  101. package/src/client/builders/table-utils.ts +87 -0
  102. package/src/client/database.ts +19 -160
  103. package/src/client/delete-builder.ts +48 -52
  104. package/src/client/entity-set.ts +227 -302
  105. package/src/client/error-parser.ts +59 -0
  106. package/src/client/filemaker-odata.ts +3 -14
  107. package/src/client/insert-builder.ts +126 -44
  108. package/src/client/query/expand-builder.ts +164 -0
  109. package/src/client/query/index.ts +13 -0
  110. package/src/client/query/query-builder.ts +826 -0
  111. package/src/client/query/response-processor.ts +244 -0
  112. package/src/client/query/types.ts +102 -0
  113. package/src/client/query/url-builder.ts +179 -0
  114. package/src/client/query-builder.ts +8 -1454
  115. package/src/client/record-builder.ts +336 -586
  116. package/src/client/response-processor.ts +4 -5
  117. package/src/client/update-builder.ts +113 -75
  118. package/src/errors.ts +22 -1
  119. package/src/index.ts +58 -5
  120. package/src/orm/column.ts +78 -0
  121. package/src/orm/field-builders.ts +296 -0
  122. package/src/orm/index.ts +60 -0
  123. package/src/orm/operators.ts +428 -0
  124. package/src/orm/table.ts +759 -0
  125. package/src/transform.ts +62 -48
  126. package/src/types.ts +88 -63
  127. package/src/validation.ts +76 -4
  128. package/LICENSE.md +0 -21
  129. package/dist/esm/client/base-table.d.ts +0 -128
  130. package/dist/esm/client/base-table.js +0 -57
  131. package/dist/esm/client/base-table.js.map +0 -1
  132. package/dist/esm/client/build-occurrences.d.ts +0 -74
  133. package/dist/esm/client/build-occurrences.js +0 -31
  134. package/dist/esm/client/build-occurrences.js.map +0 -1
  135. package/dist/esm/client/query-builder.js +0 -900
  136. package/dist/esm/client/query-builder.js.map +0 -1
  137. package/dist/esm/client/table-occurrence.d.ts +0 -86
  138. package/dist/esm/client/table-occurrence.js +0 -58
  139. package/dist/esm/client/table-occurrence.js.map +0 -1
  140. package/src/client/base-table.ts +0 -178
  141. package/src/client/build-occurrences.ts +0 -155
  142. package/src/client/query-builder.ts.bak +0 -1457
  143. package/src/client/table-occurrence.ts +0 -156
@@ -0,0 +1,41 @@
1
+ import type { QueryOptions } from "odata-query";
2
+ import type { ExecutionContext } from "../../types";
3
+ import type { FMTable } from "../../orm/table";
4
+
5
+ /**
6
+ * Expand configuration used by both QueryBuilder and RecordBuilder
7
+ */
8
+ export type ExpandConfig = {
9
+ relation: string;
10
+ options?: Partial<QueryOptions<any>>;
11
+ targetTable?: FMTable<any, any>;
12
+ };
13
+
14
+ /**
15
+ * Type to represent expanded relations in return types
16
+ */
17
+ export type ExpandedRelations = Record<string, { schema: any; selected: any }>;
18
+
19
+ /**
20
+ * Navigation context shared between builders
21
+ */
22
+ export interface NavigationContext {
23
+ isNavigate?: boolean;
24
+ navigateRecordId?: string | number;
25
+ navigateRelation?: string;
26
+ navigateSourceTableName?: string;
27
+ navigateBaseRelation?: string;
28
+ navigateBasePath?: string;
29
+ }
30
+
31
+ /**
32
+ * Common builder configuration
33
+ */
34
+ export interface BuilderConfig<Occ extends FMTable<any, any> | undefined> {
35
+ occurrence?: Occ;
36
+ tableName: string;
37
+ databaseName: string;
38
+ context: ExecutionContext;
39
+ databaseUseEntityIds?: boolean;
40
+ }
41
+
@@ -0,0 +1,87 @@
1
+ import type { ExecutionContext } from "../../types";
2
+ import { getAcceptHeader } from "../../types";
3
+ import type { FMTable } from "../../orm/table";
4
+ import {
5
+ getTableName,
6
+ getTableId as getTableIdHelper,
7
+ isUsingEntityIds,
8
+ } from "../../orm/table";
9
+ import type { FFetchOptions } from "@fetchkit/ffetch";
10
+ import type { ExecuteOptions } from "../../types";
11
+
12
+ /**
13
+ * Resolves table identifier based on entity ID settings.
14
+ * Used by both QueryBuilder and RecordBuilder.
15
+ */
16
+ export function resolveTableId(
17
+ table: FMTable<any, any> | undefined,
18
+ fallbackTableName: string,
19
+ context: ExecutionContext,
20
+ useEntityIdsOverride?: boolean,
21
+ ): string {
22
+ if (!table) {
23
+ return fallbackTableName;
24
+ }
25
+
26
+ const contextDefault = context._getUseEntityIds?.() ?? false;
27
+ const shouldUseIds = useEntityIdsOverride ?? contextDefault;
28
+
29
+ if (shouldUseIds) {
30
+ if (!isUsingEntityIds(table)) {
31
+ throw new Error(
32
+ `useEntityIds is true but table "${getTableName(table)}" does not have entity IDs configured`,
33
+ );
34
+ }
35
+ return getTableIdHelper(table);
36
+ }
37
+
38
+ return getTableName(table);
39
+ }
40
+
41
+ /**
42
+ * Merges database-level useEntityIds with per-request options.
43
+ */
44
+ export function mergeEntityIdOptions<T extends Record<string, any>>(
45
+ options: T | undefined,
46
+ databaseDefault: boolean,
47
+ ): T & { useEntityIds?: boolean } {
48
+ return {
49
+ ...options,
50
+ useEntityIds: (options as any)?.useEntityIds ?? databaseDefault,
51
+ } as T & { useEntityIds?: boolean };
52
+ }
53
+
54
+ /**
55
+ * Type-safe helper for merging execute options with entity ID settings
56
+ */
57
+ export function mergeExecuteOptions(
58
+ options: (RequestInit & FFetchOptions & ExecuteOptions) | undefined,
59
+ databaseUseEntityIds: boolean,
60
+ ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {
61
+ return mergeEntityIdOptions(options, databaseUseEntityIds);
62
+ }
63
+
64
+ /**
65
+ * Creates an OData Request object with proper headers.
66
+ * Used by both QueryBuilder and RecordBuilder to eliminate duplication.
67
+ *
68
+ * @param baseUrl - Base URL for the request
69
+ * @param config - Request configuration with method and url
70
+ * @param options - Optional execution options
71
+ * @returns Request object ready to use
72
+ */
73
+ export function createODataRequest(
74
+ baseUrl: string,
75
+ config: { method: string; url: string },
76
+ options?: { includeODataAnnotations?: boolean },
77
+ ): Request {
78
+ const fullUrl = `${baseUrl}${config.url}`;
79
+
80
+ return new Request(fullUrl, {
81
+ method: config.method,
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ Accept: getAcceptHeader(options?.includeODataAnnotations),
85
+ },
86
+ });
87
+ }
@@ -1,50 +1,11 @@
1
1
  import type { StandardSchemaV1 } from "@standard-schema/spec";
2
2
  import type { ExecutionContext, ExecutableBuilder, Metadata } from "../types";
3
- import type { BaseTable } from "./base-table";
4
- import type { TableOccurrence } from "./table-occurrence";
5
3
  import { EntitySet } from "./entity-set";
6
4
  import { BatchBuilder } from "./batch-builder";
7
5
  import { SchemaManager } from "./schema-manager";
6
+ import { FMTable } from "../orm/table";
8
7
 
9
- // Helper type to extract schema from a TableOccurrence
10
- type ExtractSchemaFromOccurrence<O> =
11
- O extends TableOccurrence<infer BT, any, any, any>
12
- ? BT extends BaseTable<infer S, any, any, any>
13
- ? S
14
- : never
15
- : never;
16
-
17
- // Helper type to find an occurrence by name in the occurrences tuple
18
- type FindOccurrenceByName<
19
- Occurrences extends readonly TableOccurrence<any, any, any, any>[],
20
- Name extends string,
21
- > = Occurrences extends readonly [
22
- infer First,
23
- ...infer Rest extends readonly TableOccurrence<any, any, any, any>[],
24
- ]
25
- ? First extends TableOccurrence<any, any, any, any>
26
- ? First["name"] extends Name
27
- ? First
28
- : FindOccurrenceByName<Rest, Name>
29
- : never
30
- : never;
31
-
32
- // Helper type to extract all occurrence names from the tuple
33
- type ExtractOccurrenceNames<
34
- Occurrences extends readonly TableOccurrence<any, any, any, any>[],
35
- > = Occurrences extends readonly []
36
- ? string // If no occurrences, allow any string
37
- : Occurrences[number]["name"]; // Otherwise, extract union of names
38
-
39
- export class Database<
40
- Occurrences extends readonly TableOccurrence<
41
- any,
42
- any,
43
- any,
44
- any
45
- >[] = readonly [],
46
- > {
47
- private occurrenceMap: Map<string, TableOccurrence<any, any, any, any>>;
8
+ export class Database {
48
9
  private _useEntityIds: boolean = false;
49
10
  public readonly schema: SchemaManager;
50
11
 
@@ -52,7 +13,6 @@ export class Database<
52
13
  private readonly databaseName: string,
53
14
  private readonly context: ExecutionContext,
54
15
  config?: {
55
- occurrences?: Occurrences | undefined;
56
16
  /**
57
17
  * Whether to use entity IDs instead of field names in the actual requests to the server
58
18
  * Defaults to true if all occurrences use entity IDs, false otherwise
@@ -61,129 +21,28 @@ export class Database<
61
21
  useEntityIds?: boolean;
62
22
  },
63
23
  ) {
64
- this.occurrenceMap = new Map();
65
- if (config?.occurrences) {
66
- // Validate consistency: either all occurrences use entity IDs or none do
67
- const occurrencesWithIds: string[] = [];
68
- const occurrencesWithoutIds: string[] = [];
69
-
70
- for (const occ of config.occurrences) {
71
- this.occurrenceMap.set(occ.name, occ);
72
-
73
- const hasTableId = occ.isUsingTableId();
74
- const hasFieldIds = occ.baseTable.isUsingFieldIds();
75
-
76
- // An occurrence uses entity IDs if it has both fmtId and fmfIds
77
- if (hasTableId && hasFieldIds) {
78
- occurrencesWithIds.push(occ.name);
79
- } else if (!hasTableId && !hasFieldIds) {
80
- occurrencesWithoutIds.push(occ.name);
81
- } else {
82
- // Partial entity ID usage (only one of fmtId or fmfIds) - this is an error
83
- throw new Error(
84
- `TableOccurrence "${occ.name}" has inconsistent entity ID configuration. ` +
85
- `Both fmtId (${hasTableId ? "present" : "missing"}) and fmfIds (${hasFieldIds ? "present" : "missing"}) must be defined together.`,
86
- );
87
- }
88
- }
89
-
90
- // Determine default value: true if all occurrences use entity IDs, false otherwise
91
- const allOccurrencesUseEntityIds =
92
- occurrencesWithIds.length > 0 && occurrencesWithoutIds.length === 0;
93
- const hasMixedUsage =
94
- occurrencesWithIds.length > 0 && occurrencesWithoutIds.length > 0;
95
-
96
- // Handle explicit useEntityIds config
97
- if (config.useEntityIds !== undefined) {
98
- if (config.useEntityIds === false) {
99
- // If explicitly set to false, allow mixed usage and use false
100
- this._useEntityIds = false;
101
- } else if (config.useEntityIds === true) {
102
- // If explicitly set to true, validate that all occurrences use entity IDs
103
- if (hasMixedUsage || occurrencesWithoutIds.length > 0) {
104
- throw new Error(
105
- `useEntityIds is set to true but some occurrences do not use entity IDs. ` +
106
- `Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
107
- `Either set useEntityIds to false or configure all occurrences with entity IDs.`,
108
- );
109
- }
110
- this._useEntityIds = true;
111
- }
112
- } else {
113
- // Default: true if all occurrences use entity IDs, false otherwise
114
- // But throw error if there's mixed usage when using defaults
115
- if (hasMixedUsage) {
116
- throw new Error(
117
- `Cannot mix TableOccurrence instances with and without entity IDs in the same database. ` +
118
- `Occurrences with entity IDs: [${occurrencesWithIds.join(", ")}]. ` +
119
- `Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
120
- `Either all table occurrences must use entity IDs (fmtId + fmfIds), none should, or explicitly set useEntityIds to false.`,
121
- );
122
- }
123
- this._useEntityIds = allOccurrencesUseEntityIds;
124
- }
125
- } else {
126
- // No occurrences provided, use explicit config or default to false
127
- this._useEntityIds = config?.useEntityIds ?? false;
128
- }
129
-
130
- // Inform the execution context whether to use entity IDs
131
- if (this.context._setUseEntityIds) {
132
- this.context._setUseEntityIds(this._useEntityIds);
133
- }
134
-
135
24
  // Initialize schema manager
136
25
  this.schema = new SchemaManager(this.databaseName, this.context);
26
+ this._useEntityIds = config?.useEntityIds ?? false;
137
27
  }
138
28
 
139
- /**
140
- * Returns true if any table occurrence in this database is using entity IDs.
141
- */
142
- isUsingEntityIds(): boolean {
143
- return this._useEntityIds;
144
- }
145
-
146
- /**
147
- * Gets a table occurrence by name.
148
- * @internal
149
- */
150
- getOccurrence(name: string): TableOccurrence<any, any, any, any> | undefined {
151
- return this.occurrenceMap.get(name);
152
- }
153
-
154
- from<Name extends ExtractOccurrenceNames<Occurrences> | (string & {})>(
155
- name: Name,
156
- ): Occurrences extends readonly []
157
- ? EntitySet<Record<string, StandardSchemaV1>, undefined>
158
- : Name extends ExtractOccurrenceNames<Occurrences>
159
- ? EntitySet<
160
- ExtractSchemaFromOccurrence<FindOccurrenceByName<Occurrences, Name>>,
161
- FindOccurrenceByName<Occurrences, Name>
162
- >
163
- : EntitySet<Record<string, StandardSchemaV1>, undefined> {
164
- const occurrence = this.occurrenceMap.get(name as string);
165
-
166
- if (occurrence) {
167
- // Use EntitySet.create to preserve types better
168
- type OccType = FindOccurrenceByName<Occurrences, Name>;
169
- type SchemaType = ExtractSchemaFromOccurrence<OccType>;
170
-
171
- return EntitySet.create<SchemaType, OccType>({
172
- occurrence: occurrence as OccType,
173
- tableName: name as string,
174
- databaseName: this.databaseName,
175
- context: this.context,
176
- database: this,
177
- }) as any;
178
- } else {
179
- // Return untyped EntitySet for dynamic table access
180
- return new EntitySet<Record<string, StandardSchemaV1>, undefined>({
181
- tableName: name as string,
182
- databaseName: this.databaseName,
183
- context: this.context,
184
- database: this,
185
- }) as any;
29
+ from<T extends FMTable<any, any>>(table: T): EntitySet<T> {
30
+ // Only override database-level useEntityIds if table explicitly sets it
31
+ // (not if it's undefined, which would override the database setting)
32
+ if (
33
+ Object.prototype.hasOwnProperty.call(table, FMTable.Symbol.UseEntityIds)
34
+ ) {
35
+ const tableUseEntityIds = (table as any)[FMTable.Symbol.UseEntityIds];
36
+ if (typeof tableUseEntityIds === "boolean") {
37
+ this._useEntityIds = tableUseEntityIds;
38
+ }
186
39
  }
40
+ return new EntitySet<T>({
41
+ occurrence: table as T,
42
+ databaseName: this.databaseName,
43
+ context: this.context,
44
+ database: this,
45
+ });
187
46
  }
188
47
 
189
48
  /**
@@ -4,33 +4,36 @@ import type {
4
4
  Result,
5
5
  WithSystemFields,
6
6
  ExecuteOptions,
7
+ ExecuteMethodOptions,
7
8
  } from "../types";
8
9
  import { getAcceptHeader } from "../types";
9
- import type { TableOccurrence } from "./table-occurrence";
10
+ import type { FMTable, InferSchemaOutputFromFMTable } from "../orm/table";
11
+ import {
12
+ getTableName,
13
+ getTableId as getTableIdHelper,
14
+ isUsingEntityIds,
15
+ } from "../orm/table";
10
16
  import { QueryBuilder } from "./query-builder";
11
17
  import { type FFetchOptions } from "@fetchkit/ffetch";
12
- import { getTableIdentifiers } from "../transform";
18
+ import { parseErrorResponse } from "./error-parser";
13
19
 
14
20
  /**
15
21
  * Initial delete builder returned from EntitySet.delete()
16
22
  * Requires calling .byId() or .where() before .execute() is available
17
23
  */
18
- export class DeleteBuilder<T extends Record<string, any>> {
19
- private tableName: string;
24
+ export class DeleteBuilder<Occ extends FMTable<any, any>> {
20
25
  private databaseName: string;
21
26
  private context: ExecutionContext;
22
- private occurrence?: TableOccurrence<any, any, any, any>;
27
+ private table: Occ;
23
28
  private databaseUseEntityIds: boolean;
24
29
 
25
30
  constructor(config: {
26
- occurrence?: TableOccurrence<any, any, any, any>;
27
- tableName: string;
31
+ occurrence: Occ;
28
32
  databaseName: string;
29
33
  context: ExecutionContext;
30
34
  databaseUseEntityIds?: boolean;
31
35
  }) {
32
- this.occurrence = config.occurrence;
33
- this.tableName = config.tableName;
36
+ this.table = config.occurrence;
34
37
  this.databaseName = config.databaseName;
35
38
  this.context = config.context;
36
39
  this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
@@ -39,10 +42,9 @@ export class DeleteBuilder<T extends Record<string, any>> {
39
42
  /**
40
43
  * Delete a single record by ID
41
44
  */
42
- byId(id: string | number): ExecutableDeleteBuilder<T> {
43
- return new ExecutableDeleteBuilder<T>({
44
- occurrence: this.occurrence,
45
- tableName: this.tableName,
45
+ byId(id: string | number): ExecutableDeleteBuilder<Occ> {
46
+ return new ExecutableDeleteBuilder<Occ>({
47
+ occurrence: this.table,
46
48
  databaseName: this.databaseName,
47
49
  context: this.context,
48
50
  mode: "byId",
@@ -56,20 +58,11 @@ export class DeleteBuilder<T extends Record<string, any>> {
56
58
  * @param fn Callback that receives a QueryBuilder for building the filter
57
59
  */
58
60
  where(
59
- fn: (
60
- q: QueryBuilder<WithSystemFields<T>>,
61
- ) => QueryBuilder<WithSystemFields<T>>,
62
- ): ExecutableDeleteBuilder<T> {
61
+ fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>,
62
+ ): ExecutableDeleteBuilder<Occ> {
63
63
  // Create a QueryBuilder for the user to configure
64
- const queryBuilder = new QueryBuilder<
65
- WithSystemFields<T>,
66
- keyof WithSystemFields<T>,
67
- false,
68
- false,
69
- undefined
70
- >({
71
- occurrence: undefined,
72
- tableName: this.tableName,
64
+ const queryBuilder = new QueryBuilder<Occ>({
65
+ occurrence: this.table,
73
66
  databaseName: this.databaseName,
74
67
  context: this.context,
75
68
  });
@@ -77,9 +70,8 @@ export class DeleteBuilder<T extends Record<string, any>> {
77
70
  // Let the user configure it
78
71
  const configuredBuilder = fn(queryBuilder);
79
72
 
80
- return new ExecutableDeleteBuilder<T>({
81
- occurrence: this.occurrence,
82
- tableName: this.tableName,
73
+ return new ExecutableDeleteBuilder<Occ>({
74
+ occurrence: this.table,
83
75
  databaseName: this.databaseName,
84
76
  context: this.context,
85
77
  mode: "byFilter",
@@ -93,30 +85,27 @@ export class DeleteBuilder<T extends Record<string, any>> {
93
85
  * Executable delete builder - has execute() method
94
86
  * Returned after calling .byId() or .where()
95
87
  */
96
- export class ExecutableDeleteBuilder<T extends Record<string, any>>
88
+ export class ExecutableDeleteBuilder<Occ extends FMTable<any, any>>
97
89
  implements ExecutableBuilder<{ deletedCount: number }>
98
90
  {
99
- private tableName: string;
100
91
  private databaseName: string;
101
92
  private context: ExecutionContext;
102
- private occurrence?: TableOccurrence<any, any, any, any>;
93
+ private table: Occ;
103
94
  private mode: "byId" | "byFilter";
104
95
  private recordId?: string | number;
105
- private queryBuilder?: QueryBuilder<any>;
96
+ private queryBuilder?: QueryBuilder<Occ>;
106
97
  private databaseUseEntityIds: boolean;
107
98
 
108
99
  constructor(config: {
109
- occurrence?: TableOccurrence<any, any, any, any>;
110
- tableName: string;
100
+ occurrence: Occ;
111
101
  databaseName: string;
112
102
  context: ExecutionContext;
113
103
  mode: "byId" | "byFilter";
114
104
  recordId?: string | number;
115
- queryBuilder?: QueryBuilder<any>;
105
+ queryBuilder?: QueryBuilder<Occ>;
116
106
  databaseUseEntityIds?: boolean;
117
107
  }) {
118
- this.occurrence = config.occurrence;
119
- this.tableName = config.tableName;
108
+ this.table = config.occurrence;
120
109
  this.databaseName = config.databaseName;
121
110
  this.context = config.context;
122
111
  this.mode = config.mode;
@@ -143,28 +132,23 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
143
132
  * @param useEntityIds - Optional override for entity ID usage
144
133
  */
145
134
  private getTableId(useEntityIds?: boolean): string {
146
- if (!this.occurrence) {
147
- return this.tableName;
148
- }
149
-
150
135
  const contextDefault = this.context._getUseEntityIds?.() ?? false;
151
136
  const shouldUseIds = useEntityIds ?? contextDefault;
152
137
 
153
138
  if (shouldUseIds) {
154
- const identifiers = getTableIdentifiers(this.occurrence);
155
- if (!identifiers.id) {
139
+ if (!isUsingEntityIds(this.table)) {
156
140
  throw new Error(
157
- `useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined`,
141
+ `useEntityIds is true but table "${getTableName(this.table)}" does not have entity IDs configured`,
158
142
  );
159
143
  }
160
- return identifiers.id;
144
+ return getTableIdHelper(this.table);
161
145
  }
162
146
 
163
- return this.occurrence.getTableName();
147
+ return getTableName(this.table);
164
148
  }
165
149
 
166
150
  async execute(
167
- options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },
151
+ options?: ExecuteMethodOptions<ExecuteOptions>,
168
152
  ): Promise<Result<{ deletedCount: number }>> {
169
153
  // Merge database-level useEntityIds with per-request options
170
154
  const mergedOptions = this.mergeExecuteOptions(options);
@@ -186,10 +170,11 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
186
170
  // Get the query string from the configured QueryBuilder
187
171
  const queryString = this.queryBuilder.getQueryString();
188
172
  // Remove the leading "/" and table name from the query string as we'll build our own URL
173
+ const tableName = getTableName(this.table);
189
174
  const queryParams = queryString.startsWith(`/${tableId}`)
190
175
  ? queryString.slice(`/${tableId}`.length)
191
- : queryString.startsWith(`/${this.tableName}`)
192
- ? queryString.slice(`/${this.tableName}`.length)
176
+ : queryString.startsWith(`/${tableName}`)
177
+ ? queryString.slice(`/${tableName}`.length)
193
178
  : queryString;
194
179
 
195
180
  url = `/${this.databaseName}/${tableId}${queryParams}`;
@@ -236,10 +221,11 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
236
221
  }
237
222
 
238
223
  const queryString = this.queryBuilder.getQueryString();
224
+ const tableName = getTableName(this.table);
239
225
  const queryParams = queryString.startsWith(`/${tableId}`)
240
226
  ? queryString.slice(`/${tableId}`.length)
241
- : queryString.startsWith(`/${this.tableName}`)
242
- ? queryString.slice(`/${this.tableName}`.length)
227
+ : queryString.startsWith(`/${tableName}`)
228
+ ? queryString.slice(`/${tableName}`.length)
243
229
  : queryString;
244
230
 
245
231
  url = `/${this.databaseName}/${tableId}${queryParams}`;
@@ -267,6 +253,16 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
267
253
  response: Response,
268
254
  options?: ExecuteOptions,
269
255
  ): Promise<Result<{ deletedCount: number }>> {
256
+ // Check for error responses (important for batch operations)
257
+ if (!response.ok) {
258
+ const tableName = getTableName(this.table);
259
+ const error = await parseErrorResponse(
260
+ response,
261
+ response.url || `/${this.databaseName}/${tableName}`,
262
+ );
263
+ return { data: undefined, error };
264
+ }
265
+
270
266
  // Check for empty response (204 No Content)
271
267
  const text = await response.text();
272
268
  if (!text || text.trim() === "") {