@proofkit/fmodata 0.1.0-alpha.4 → 0.1.0-alpha.6
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.
- package/README.md +357 -28
- package/dist/esm/client/base-table.d.ts +122 -5
- package/dist/esm/client/base-table.js +46 -5
- package/dist/esm/client/base-table.js.map +1 -1
- package/dist/esm/client/database.d.ts +20 -3
- package/dist/esm/client/database.js +62 -13
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.js +24 -27
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +9 -6
- package/dist/esm/client/entity-set.js +5 -1
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +17 -4
- package/dist/esm/client/filemaker-odata.js +90 -27
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.js +45 -34
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +7 -2
- package/dist/esm/client/query-builder.js +273 -202
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +2 -2
- package/dist/esm/client/record-builder.js +50 -40
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/table-occurrence.d.ts +66 -2
- package/dist/esm/client/table-occurrence.js +36 -1
- package/dist/esm/client/table-occurrence.js.map +1 -1
- package/dist/esm/client/update-builder.js +39 -35
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +60 -0
- package/dist/esm/errors.js +122 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +5 -2
- package/dist/esm/index.js +25 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/transform.d.ts +56 -0
- package/dist/esm/transform.js +107 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.d.ts +21 -5
- package/dist/esm/validation.d.ts +6 -3
- package/dist/esm/validation.js +104 -33
- package/dist/esm/validation.js.map +1 -1
- package/package.json +10 -1
- package/src/client/base-table.ts +155 -8
- package/src/client/database.ts +116 -13
- package/src/client/delete-builder.ts +42 -43
- package/src/client/entity-set.ts +21 -11
- package/src/client/filemaker-odata.ts +132 -34
- package/src/client/insert-builder.ts +69 -37
- package/src/client/query-builder.ts +345 -233
- package/src/client/record-builder.ts +84 -59
- package/src/client/table-occurrence.ts +118 -4
- package/src/client/update-builder.ts +77 -49
- package/src/errors.ts +185 -0
- package/src/index.ts +30 -1
- package/src/transform.ts +236 -0
- package/src/types.ts +112 -34
- package/src/validation.ts +120 -36
|
@@ -8,10 +8,12 @@ import type {
|
|
|
8
8
|
} from "../types";
|
|
9
9
|
import type { TableOccurrence } from "./table-occurrence";
|
|
10
10
|
import type { BaseTable } from "./base-table";
|
|
11
|
+
import { transformTableName, transformResponseFields } from "../transform";
|
|
11
12
|
import { QueryBuilder } from "./query-builder";
|
|
12
13
|
import { validateSingleResponse } from "../validation";
|
|
13
14
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
14
|
-
import {
|
|
15
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
16
|
+
// import type { z } from "zod/v4";
|
|
15
17
|
|
|
16
18
|
// Helper type to extract schema from a TableOccurrence
|
|
17
19
|
type ExtractSchemaFromOccurrence<O> =
|
|
@@ -105,7 +107,7 @@ export class RecordBuilder<
|
|
|
105
107
|
): QueryBuilder<
|
|
106
108
|
ExtractSchemaFromOccurrence<
|
|
107
109
|
FindNavigationTarget<Occ, RelationName>
|
|
108
|
-
> extends Record<string,
|
|
110
|
+
> extends Record<string, StandardSchemaV1>
|
|
109
111
|
? InferSchemaType<
|
|
110
112
|
ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>
|
|
111
113
|
>
|
|
@@ -127,9 +129,14 @@ export class RecordBuilder<
|
|
|
127
129
|
context: this.context,
|
|
128
130
|
});
|
|
129
131
|
// Store the navigation info - we'll use it in execute
|
|
132
|
+
// Transform relation name to FMTID if using entity IDs
|
|
133
|
+
const relationId = targetOccurrence
|
|
134
|
+
? transformTableName(targetOccurrence)
|
|
135
|
+
: relationName;
|
|
136
|
+
|
|
130
137
|
(builder as any).isNavigate = true;
|
|
131
138
|
(builder as any).navigateRecordId = this.recordId;
|
|
132
|
-
(builder as any).navigateRelation =
|
|
139
|
+
(builder as any).navigateRelation = relationId;
|
|
133
140
|
|
|
134
141
|
// If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path
|
|
135
142
|
if (
|
|
@@ -142,7 +149,11 @@ export class RecordBuilder<
|
|
|
142
149
|
(builder as any).navigateBaseRelation = this.navigateRelation;
|
|
143
150
|
} else {
|
|
144
151
|
// Normal record navigation: /tableName('recordId')/relation
|
|
145
|
-
|
|
152
|
+
// Transform source table name to FMTID if using entity IDs
|
|
153
|
+
const sourceTableId = this.occurrence
|
|
154
|
+
? transformTableName(this.occurrence)
|
|
155
|
+
: this.tableName;
|
|
156
|
+
(builder as any).navigateSourceTableName = sourceTableId;
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
return builder;
|
|
@@ -153,63 +164,74 @@ export class RecordBuilder<
|
|
|
153
164
|
): Promise<
|
|
154
165
|
Result<IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata>
|
|
155
166
|
> {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
const
|
|
167
|
+
let url: string;
|
|
168
|
+
|
|
169
|
+
// Build the base URL depending on whether this came from a navigated EntitySet
|
|
170
|
+
if (
|
|
171
|
+
this.isNavigateFromEntitySet &&
|
|
172
|
+
this.navigateSourceTableName &&
|
|
173
|
+
this.navigateRelation
|
|
174
|
+
) {
|
|
175
|
+
// From navigated EntitySet: /sourceTable/relation('recordId')
|
|
176
|
+
url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;
|
|
177
|
+
} else {
|
|
178
|
+
// Normal record: /tableName('recordId') - use FMTID if configured
|
|
179
|
+
const tableId = this.occurrence
|
|
180
|
+
? transformTableName(this.occurrence)
|
|
181
|
+
: this.tableName;
|
|
182
|
+
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this.operation === "getSingleField" && this.operationParam) {
|
|
186
|
+
url += `/${this.operationParam}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const result = await this.context._makeRequest(url, options);
|
|
190
|
+
|
|
191
|
+
if (result.error) {
|
|
192
|
+
return { data: undefined, error: result.error };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let response = result.data;
|
|
196
|
+
|
|
197
|
+
// Handle single field operation
|
|
198
|
+
if (this.operation === "getSingleField") {
|
|
199
|
+
// Single field returns a JSON object with @context and value
|
|
200
|
+
const fieldResponse = response as ODataFieldResponse<T>;
|
|
201
|
+
return { data: fieldResponse.value as any, error: undefined };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Transform response field IDs back to names if using entity IDs
|
|
205
|
+
if (this.occurrence?.baseTable) {
|
|
206
|
+
response = transformResponseFields(
|
|
190
207
|
response,
|
|
191
|
-
|
|
192
|
-
undefined, // No
|
|
193
|
-
undefined, // No expand configs
|
|
194
|
-
"exact", // Expect exactly one record
|
|
208
|
+
this.occurrence.baseTable,
|
|
209
|
+
undefined, // No expand configs for simple get
|
|
195
210
|
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Get schema from occurrence if available
|
|
214
|
+
const schema = this.occurrence?.baseTable?.schema;
|
|
215
|
+
|
|
216
|
+
// Validate the single record response
|
|
217
|
+
const validation = await validateSingleResponse<any>(
|
|
218
|
+
response,
|
|
219
|
+
schema,
|
|
220
|
+
undefined, // No selected fields for record.get()
|
|
221
|
+
undefined, // No expand configs
|
|
222
|
+
"exact", // Expect exactly one record
|
|
223
|
+
);
|
|
196
224
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Handle null response
|
|
202
|
-
if (validation.data === null) {
|
|
203
|
-
return { data: null as any, error: undefined };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return { data: validation.data, error: undefined };
|
|
207
|
-
} catch (error) {
|
|
208
|
-
return {
|
|
209
|
-
data: undefined,
|
|
210
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
211
|
-
};
|
|
225
|
+
if (!validation.valid) {
|
|
226
|
+
return { data: undefined, error: validation.error };
|
|
212
227
|
}
|
|
228
|
+
|
|
229
|
+
// Handle null response
|
|
230
|
+
if (validation.data === null) {
|
|
231
|
+
return { data: null as any, error: undefined };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return { data: validation.data, error: undefined };
|
|
213
235
|
}
|
|
214
236
|
|
|
215
237
|
getRequestConfig(): { method: string; url: string; body?: any } {
|
|
@@ -224,8 +246,11 @@ export class RecordBuilder<
|
|
|
224
246
|
// From navigated EntitySet: /sourceTable/relation('recordId')
|
|
225
247
|
url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;
|
|
226
248
|
} else {
|
|
227
|
-
// Normal record: /tableName('recordId')
|
|
228
|
-
|
|
249
|
+
// Normal record: /tableName('recordId') - use FMTID if configured
|
|
250
|
+
const tableId = this.occurrence
|
|
251
|
+
? transformTableName(this.occurrence)
|
|
252
|
+
: this.tableName;
|
|
253
|
+
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
229
254
|
}
|
|
230
255
|
|
|
231
256
|
if (this.operation === "getSingleField" && this.operationParam) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseTable } from "./base-table";
|
|
1
|
+
import { BaseTable, BaseTableWithIds } from "./base-table";
|
|
2
2
|
|
|
3
3
|
// Helper type to extract schema from BaseTable
|
|
4
4
|
type ExtractSchema<BT> =
|
|
@@ -48,10 +48,9 @@ export class TableOccurrence<
|
|
|
48
48
|
> {
|
|
49
49
|
public readonly name: Name;
|
|
50
50
|
public readonly baseTable: BT;
|
|
51
|
-
|
|
51
|
+
protected _navigationConfig: Nav;
|
|
52
52
|
public readonly navigation: ResolveNavigation<Nav>;
|
|
53
53
|
public readonly defaultSelect: DefSelect;
|
|
54
|
-
|
|
55
54
|
constructor(config: {
|
|
56
55
|
readonly name: Name;
|
|
57
56
|
readonly baseTable: BT;
|
|
@@ -62,7 +61,6 @@ export class TableOccurrence<
|
|
|
62
61
|
this.baseTable = config.baseTable;
|
|
63
62
|
this._navigationConfig = (config.navigation ?? {}) as Nav;
|
|
64
63
|
this.defaultSelect = (config.defaultSelect ?? "schema") as DefSelect;
|
|
65
|
-
|
|
66
64
|
// Create navigation getters that lazily resolve functions
|
|
67
65
|
this.navigation = createNavigationGetters(this._navigationConfig);
|
|
68
66
|
}
|
|
@@ -81,6 +79,32 @@ export class TableOccurrence<
|
|
|
81
79
|
defaultSelect: this.defaultSelect,
|
|
82
80
|
});
|
|
83
81
|
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.
|
|
85
|
+
* @returns The FMTID string or the table name
|
|
86
|
+
*/
|
|
87
|
+
getTableId(): string {
|
|
88
|
+
// Check if fmtId exists (only on TableOccurrenceWithIds)
|
|
89
|
+
return "fmtId" in this && (this as any).fmtId
|
|
90
|
+
? (this as any).fmtId
|
|
91
|
+
: this.name;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the table occurrence name.
|
|
96
|
+
* @returns The table name
|
|
97
|
+
*/
|
|
98
|
+
getTableName(): string {
|
|
99
|
+
return this.name;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns true if this TableOccurrence is using FileMaker table occurrence IDs.
|
|
104
|
+
*/
|
|
105
|
+
isUsingTableId(): boolean {
|
|
106
|
+
return "fmtId" in this && this.fmtId !== undefined;
|
|
107
|
+
}
|
|
84
108
|
}
|
|
85
109
|
|
|
86
110
|
// Helper function to create TableOccurrence with proper type inference
|
|
@@ -98,3 +122,93 @@ export function createTableOccurrence<
|
|
|
98
122
|
}): TableOccurrence<BT, Name, {}, DefSelect> {
|
|
99
123
|
return new TableOccurrence(config);
|
|
100
124
|
}
|
|
125
|
+
|
|
126
|
+
// Helper type to validate that all navigation values are TableOccurrenceWithIds
|
|
127
|
+
type ValidateNavWithIds<Nav> =
|
|
128
|
+
Nav extends Record<
|
|
129
|
+
string,
|
|
130
|
+
| TableOccurrenceWithIds<any, any, any, any>
|
|
131
|
+
| (() => TableOccurrenceWithIds<any, any, any, any>)
|
|
132
|
+
>
|
|
133
|
+
? Nav
|
|
134
|
+
: "Error: All navigation table occurrences must be TableOccurrenceWithIds when using TableOccurrenceWithIds";
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* TableOccurrenceWithIds extends TableOccurrence to require:
|
|
138
|
+
* 1. A BaseTableWithIds (which has fmfIds defined)
|
|
139
|
+
* 2. A required fmtId for this table occurrence
|
|
140
|
+
* 3. All navigation relationships must also be TableOccurrenceWithIds
|
|
141
|
+
*
|
|
142
|
+
* This ensures bidirectional type-level enforcement: if you use FileMaker IDs,
|
|
143
|
+
* they must be defined on both the BaseTable (fmfIds) and TableOccurrence (fmtId),
|
|
144
|
+
* and all related table occurrences in navigation must also have IDs.
|
|
145
|
+
*
|
|
146
|
+
* @template BT - Must be a BaseTableWithIds (enforced at type level)
|
|
147
|
+
* @template Name - The name of this table occurrence
|
|
148
|
+
* @template Nav - Navigation relationships (must all be TableOccurrenceWithIds)
|
|
149
|
+
* @template DefSelect - Default select behavior
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* const usersBaseWithIds = new BaseTableWithIds({
|
|
154
|
+
* schema: { id: z.string(), name: z.string() },
|
|
155
|
+
* idField: "id",
|
|
156
|
+
* fmfIds: { id: "FMFID:1", name: "FMFID:2" },
|
|
157
|
+
* });
|
|
158
|
+
*
|
|
159
|
+
* const usersTO = new TableOccurrenceWithIds({
|
|
160
|
+
* name: "users",
|
|
161
|
+
* baseTable: usersBaseWithIds,
|
|
162
|
+
* fmtId: "FMTID:100",
|
|
163
|
+
* navigation: {
|
|
164
|
+
* contacts: () => contactsTO, // Must also be TableOccurrenceWithIds
|
|
165
|
+
* },
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export class TableOccurrenceWithIds<
|
|
170
|
+
BT extends BaseTableWithIds<any, any, any, any> = any,
|
|
171
|
+
Name extends string = string,
|
|
172
|
+
Nav extends Record<
|
|
173
|
+
string,
|
|
174
|
+
| TableOccurrence<any, any, any, any>
|
|
175
|
+
| (() => TableOccurrence<any, any, any, any>)
|
|
176
|
+
> = {},
|
|
177
|
+
DefSelect extends
|
|
178
|
+
| "all"
|
|
179
|
+
| "schema"
|
|
180
|
+
| readonly (keyof ExtractSchema<BT>)[] = "schema",
|
|
181
|
+
> extends TableOccurrence<BT, Name, Nav, DefSelect> {
|
|
182
|
+
public readonly fmtId: `FMTID:${string}`;
|
|
183
|
+
|
|
184
|
+
constructor(config: {
|
|
185
|
+
readonly name: Name;
|
|
186
|
+
readonly baseTable: BT;
|
|
187
|
+
readonly fmtId: `FMTID:${string}`;
|
|
188
|
+
readonly navigation?: ValidateNavWithIds<Nav>;
|
|
189
|
+
readonly defaultSelect?: DefSelect;
|
|
190
|
+
}) {
|
|
191
|
+
super({
|
|
192
|
+
...config,
|
|
193
|
+
navigation: config.navigation as Nav,
|
|
194
|
+
});
|
|
195
|
+
this.fmtId = config.fmtId;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Helper function to create TableOccurrenceWithIds with proper type inference
|
|
200
|
+
export function createTableOccurrenceWithIds<
|
|
201
|
+
const Name extends string,
|
|
202
|
+
BT extends BaseTableWithIds<any, any, any, any>,
|
|
203
|
+
DefSelect extends
|
|
204
|
+
| "all"
|
|
205
|
+
| "schema"
|
|
206
|
+
| readonly (keyof ExtractSchema<BT>)[] = "schema",
|
|
207
|
+
>(config: {
|
|
208
|
+
name: Name;
|
|
209
|
+
baseTable: BT;
|
|
210
|
+
fmtId: `FMTID:${string}`;
|
|
211
|
+
defaultSelect?: DefSelect;
|
|
212
|
+
}): TableOccurrenceWithIds<BT, Name, {}, DefSelect> {
|
|
213
|
+
return new TableOccurrenceWithIds(config);
|
|
214
|
+
}
|
|
@@ -8,6 +8,10 @@ import type { TableOccurrence } from "./table-occurrence";
|
|
|
8
8
|
import type { BaseTable } from "./base-table";
|
|
9
9
|
import { QueryBuilder } from "./query-builder";
|
|
10
10
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
11
|
+
import {
|
|
12
|
+
transformFieldNamesToIds,
|
|
13
|
+
transformTableName,
|
|
14
|
+
} from "../transform";
|
|
11
15
|
|
|
12
16
|
/**
|
|
13
17
|
* Initial update builder returned from EntitySet.update(data)
|
|
@@ -134,79 +138,103 @@ export class ExecutableUpdateBuilder<
|
|
|
134
138
|
async execute(
|
|
135
139
|
options?: RequestInit & FFetchOptions,
|
|
136
140
|
): Promise<Result<{ updatedCount: number }>> {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
// Transform table name to FMTID if using entity IDs
|
|
142
|
+
const tableId = this.occurrence
|
|
143
|
+
? transformTableName(this.occurrence)
|
|
144
|
+
: this.tableName;
|
|
145
|
+
|
|
146
|
+
// Transform field names to FMFIDs if using entity IDs
|
|
147
|
+
const transformedData = this.occurrence?.baseTable
|
|
148
|
+
? transformFieldNamesToIds(this.data, this.occurrence.baseTable)
|
|
149
|
+
: this.data;
|
|
150
|
+
|
|
151
|
+
let url: string;
|
|
152
|
+
|
|
153
|
+
if (this.mode === "byId") {
|
|
154
|
+
// Update single record by ID: PATCH /{database}/{table}('id')
|
|
155
|
+
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
156
|
+
} else {
|
|
157
|
+
// Update by filter: PATCH /{database}/{table}?$filter=...
|
|
158
|
+
if (!this.queryBuilder) {
|
|
159
|
+
throw new Error("Query builder is required for filter-based update");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Get the query string from the configured QueryBuilder
|
|
163
|
+
const queryString = this.queryBuilder.getQueryString();
|
|
164
|
+
// The query string will have the tableId already transformed by QueryBuilder
|
|
165
|
+
// Remove the leading "/" and table name from the query string as we'll build our own URL
|
|
166
|
+
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
167
|
+
? queryString.slice(`/${tableId}`.length)
|
|
168
|
+
: queryString.startsWith(`/${this.tableName}`)
|
|
153
169
|
? queryString.slice(`/${this.tableName}`.length)
|
|
154
170
|
: queryString;
|
|
155
171
|
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
173
|
+
}
|
|
158
174
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
} else if (response && typeof response === "object") {
|
|
175
|
-
// Check if the response has a count property (fallback)
|
|
176
|
-
updatedCount = (response as any).updatedCount || 0;
|
|
177
|
-
}
|
|
175
|
+
// Make PATCH request with JSON body
|
|
176
|
+
const result = await this.context._makeRequest(url, {
|
|
177
|
+
method: "PATCH",
|
|
178
|
+
headers: {
|
|
179
|
+
"Content-Type": "application/json",
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify(transformedData),
|
|
182
|
+
...options,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (result.error) {
|
|
186
|
+
return { data: undefined, error: result.error };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const response = result.data;
|
|
178
190
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
191
|
+
// Both byId and byFilter return affected row count
|
|
192
|
+
let updatedCount = 0;
|
|
193
|
+
|
|
194
|
+
if (typeof response === "number") {
|
|
195
|
+
updatedCount = response;
|
|
196
|
+
} else if (response && typeof response === "object") {
|
|
197
|
+
// Check if the response has a count property (fallback)
|
|
198
|
+
updatedCount = (response as any).updatedCount || 0;
|
|
185
199
|
}
|
|
200
|
+
|
|
201
|
+
return { data: { updatedCount }, error: undefined };
|
|
186
202
|
}
|
|
187
203
|
|
|
188
204
|
getRequestConfig(): { method: string; url: string; body?: any } {
|
|
205
|
+
// Transform table name to FMTID if using entity IDs
|
|
206
|
+
const tableId = this.occurrence
|
|
207
|
+
? transformTableName(this.occurrence)
|
|
208
|
+
: this.tableName;
|
|
209
|
+
|
|
210
|
+
// Transform field names to FMFIDs if using entity IDs
|
|
211
|
+
const transformedData = this.occurrence?.baseTable
|
|
212
|
+
? transformFieldNamesToIds(this.data, this.occurrence.baseTable)
|
|
213
|
+
: this.data;
|
|
214
|
+
|
|
189
215
|
let url: string;
|
|
190
216
|
|
|
191
217
|
if (this.mode === "byId") {
|
|
192
|
-
url = `/${this.databaseName}/${
|
|
218
|
+
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
193
219
|
} else {
|
|
194
220
|
if (!this.queryBuilder) {
|
|
195
221
|
throw new Error("Query builder is required for filter-based update");
|
|
196
222
|
}
|
|
197
223
|
|
|
198
224
|
const queryString = this.queryBuilder.getQueryString();
|
|
199
|
-
const queryParams = queryString.startsWith(`/${
|
|
200
|
-
? queryString.slice(`/${
|
|
201
|
-
: queryString
|
|
225
|
+
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
226
|
+
? queryString.slice(`/${tableId}`.length)
|
|
227
|
+
: queryString.startsWith(`/${this.tableName}`)
|
|
228
|
+
? queryString.slice(`/${this.tableName}`.length)
|
|
229
|
+
: queryString;
|
|
202
230
|
|
|
203
|
-
url = `/${this.databaseName}/${
|
|
231
|
+
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
204
232
|
}
|
|
205
233
|
|
|
206
234
|
return {
|
|
207
235
|
method: "PATCH",
|
|
208
236
|
url,
|
|
209
|
-
body: JSON.stringify(
|
|
237
|
+
body: JSON.stringify(transformedData),
|
|
210
238
|
};
|
|
211
239
|
}
|
|
212
240
|
}
|