@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.
- package/README.md +489 -334
- package/dist/esm/client/batch-builder.d.ts +7 -5
- package/dist/esm/client/batch-builder.js +84 -25
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +7 -0
- package/dist/esm/client/builders/default-select.js +42 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +43 -0
- package/dist/esm/client/builders/expand-builder.js +173 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +8 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +15 -0
- package/dist/esm/client/builders/query-string-builder.js +25 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +39 -0
- package/dist/esm/client/builders/response-processor.js +170 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +31 -0
- package/dist/esm/client/builders/select-mixin.js +30 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +8 -0
- package/dist/esm/client/builders/select-utils.js +15 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +39 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +45 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +3 -22
- package/dist/esm/client/database.js +14 -76
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +12 -19
- package/dist/esm/client/delete-builder.js +26 -26
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +32 -32
- package/dist/esm/client/entity-set.js +92 -69
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.d.ts +12 -0
- package/dist/esm/client/error-parser.js +30 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +2 -4
- package/dist/esm/client/filemaker-odata.js +1 -5
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +9 -12
- package/dist/esm/client/insert-builder.js +70 -24
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/expand-builder.d.ts +35 -0
- package/dist/esm/client/query/index.d.ts +3 -0
- package/dist/esm/client/query/query-builder.d.ts +133 -0
- package/dist/esm/client/query/query-builder.js +505 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +22 -0
- package/dist/esm/client/query/types.d.ts +52 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +107 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +1 -111
- package/dist/esm/client/record-builder.d.ts +56 -64
- package/dist/esm/client/record-builder.js +158 -297
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +3 -3
- package/dist/esm/client/update-builder.d.ts +17 -25
- package/dist/esm/client/update-builder.js +56 -30
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +8 -1
- package/dist/esm/errors.js +17 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +3 -7
- package/dist/esm/index.js +37 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/orm/column.d.ts +45 -0
- package/dist/esm/orm/column.js +59 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +154 -0
- package/dist/esm/orm/field-builders.js +152 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +4 -0
- package/dist/esm/orm/operators.d.ts +175 -0
- package/dist/esm/orm/operators.js +221 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +341 -0
- package/dist/esm/orm/table.js +211 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +34 -34
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +73 -12
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/validation.d.ts +14 -4
- package/dist/esm/validation.js +45 -1
- package/dist/esm/validation.js.map +1 -1
- package/package.json +22 -17
- package/src/client/batch-builder.ts +102 -33
- package/src/client/builders/default-select.ts +69 -0
- package/src/client/builders/expand-builder.ts +236 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +41 -0
- package/src/client/builders/response-processor.ts +273 -0
- package/src/client/builders/select-mixin.ts +74 -0
- package/src/client/builders/select-utils.ts +34 -0
- package/src/client/builders/shared-types.ts +41 -0
- package/src/client/builders/table-utils.ts +87 -0
- package/src/client/database.ts +19 -160
- package/src/client/delete-builder.ts +48 -52
- package/src/client/entity-set.ts +227 -302
- package/src/client/error-parser.ts +59 -0
- package/src/client/filemaker-odata.ts +3 -14
- package/src/client/insert-builder.ts +126 -44
- package/src/client/query/expand-builder.ts +164 -0
- package/src/client/query/index.ts +13 -0
- package/src/client/query/query-builder.ts +826 -0
- package/src/client/query/response-processor.ts +244 -0
- package/src/client/query/types.ts +102 -0
- package/src/client/query/url-builder.ts +179 -0
- package/src/client/query-builder.ts +8 -1454
- package/src/client/record-builder.ts +336 -586
- package/src/client/response-processor.ts +4 -5
- package/src/client/update-builder.ts +113 -75
- package/src/errors.ts +22 -1
- package/src/index.ts +58 -5
- package/src/orm/column.ts +78 -0
- package/src/orm/field-builders.ts +296 -0
- package/src/orm/index.ts +60 -0
- package/src/orm/operators.ts +428 -0
- package/src/orm/table.ts +759 -0
- package/src/transform.ts +62 -48
- package/src/types.ts +88 -63
- package/src/validation.ts +76 -4
- package/LICENSE.md +0 -21
- package/dist/esm/client/base-table.d.ts +0 -128
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/build-occurrences.d.ts +0 -74
- package/dist/esm/client/build-occurrences.js +0 -31
- package/dist/esm/client/build-occurrences.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -900
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -86
- package/dist/esm/client/table-occurrence.js +0 -58
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/src/client/base-table.ts +0 -178
- package/src/client/build-occurrences.ts +0 -155
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -156
|
@@ -2,21 +2,21 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
import { getAcceptHeader } from "../types.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { isUsingEntityIds, getTableName, getTableId, getBaseTableConfig } from "../orm/table.js";
|
|
6
|
+
import { validateAndTransformInput, validateSingleResponse } from "../validation.js";
|
|
7
|
+
import { transformFieldNamesToIds, transformResponseFields } from "../transform.js";
|
|
7
8
|
import { InvalidLocationHeaderError } from "../errors.js";
|
|
8
9
|
import { safeJsonParse } from "./sanitize-json.js";
|
|
10
|
+
import { parseErrorResponse } from "./error-parser.js";
|
|
9
11
|
class InsertBuilder {
|
|
10
12
|
constructor(config) {
|
|
11
|
-
__publicField(this, "
|
|
12
|
-
__publicField(this, "tableName");
|
|
13
|
+
__publicField(this, "table");
|
|
13
14
|
__publicField(this, "databaseName");
|
|
14
15
|
__publicField(this, "context");
|
|
15
16
|
__publicField(this, "data");
|
|
16
17
|
__publicField(this, "returnPreference");
|
|
17
18
|
__publicField(this, "databaseUseEntityIds");
|
|
18
|
-
this.
|
|
19
|
-
this.tableName = config.tableName;
|
|
19
|
+
this.table = config.occurrence;
|
|
20
20
|
this.databaseName = config.databaseName;
|
|
21
21
|
this.context = config.context;
|
|
22
22
|
this.data = config.data;
|
|
@@ -67,29 +67,40 @@ class InsertBuilder {
|
|
|
67
67
|
*/
|
|
68
68
|
getTableId(useEntityIds) {
|
|
69
69
|
var _a, _b;
|
|
70
|
-
if (!this.
|
|
71
|
-
|
|
70
|
+
if (!this.table) {
|
|
71
|
+
throw new Error("Table occurrence is required");
|
|
72
72
|
}
|
|
73
73
|
const contextDefault = ((_b = (_a = this.context)._getUseEntityIds) == null ? void 0 : _b.call(_a)) ?? false;
|
|
74
74
|
const shouldUseIds = useEntityIds ?? contextDefault;
|
|
75
75
|
if (shouldUseIds) {
|
|
76
|
-
|
|
77
|
-
if (!identifiers.id) {
|
|
76
|
+
if (!isUsingEntityIds(this.table)) {
|
|
78
77
|
throw new Error(
|
|
79
|
-
`useEntityIds is true but
|
|
78
|
+
`useEntityIds is true but table "${getTableName(this.table)}" does not have entity IDs configured`
|
|
80
79
|
);
|
|
81
80
|
}
|
|
82
|
-
return
|
|
81
|
+
return getTableId(this.table);
|
|
83
82
|
}
|
|
84
|
-
return this.
|
|
83
|
+
return getTableName(this.table);
|
|
85
84
|
}
|
|
86
85
|
async execute(options) {
|
|
87
|
-
var _a, _b, _c, _d;
|
|
88
86
|
const mergedOptions = this.mergeExecuteOptions(options);
|
|
89
87
|
const tableId = this.getTableId(mergedOptions.useEntityIds);
|
|
90
88
|
const url = `/${this.databaseName}/${tableId}`;
|
|
89
|
+
let validatedData = this.data;
|
|
90
|
+
if (this.table) {
|
|
91
|
+
const baseTableConfig = getBaseTableConfig(this.table);
|
|
92
|
+
const inputSchema = baseTableConfig.inputSchema;
|
|
93
|
+
try {
|
|
94
|
+
validatedData = await validateAndTransformInput(this.data, inputSchema);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
data: void 0,
|
|
98
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
91
102
|
const shouldUseIds = mergedOptions.useEntityIds ?? false;
|
|
92
|
-
const transformedData =
|
|
103
|
+
const transformedData = this.table && shouldUseIds ? transformFieldNamesToIds(validatedData, this.table) : validatedData;
|
|
93
104
|
const preferHeader = this.returnPreference === "minimal" ? "return=minimal" : "return=representation";
|
|
94
105
|
const result = await this.context._makeRequest(url, {
|
|
95
106
|
method: "POST",
|
|
@@ -115,15 +126,23 @@ class InsertBuilder {
|
|
|
115
126
|
return { data: { ROWID: rowid }, error: void 0 };
|
|
116
127
|
}
|
|
117
128
|
let response = result.data;
|
|
118
|
-
if (
|
|
129
|
+
if (this.table && shouldUseIds) {
|
|
119
130
|
response = transformResponseFields(
|
|
120
131
|
response,
|
|
121
|
-
this.
|
|
132
|
+
this.table,
|
|
122
133
|
void 0
|
|
123
134
|
// No expand configs for insert
|
|
124
135
|
);
|
|
125
136
|
}
|
|
126
|
-
|
|
137
|
+
let schema;
|
|
138
|
+
if (this.table) {
|
|
139
|
+
const baseTableConfig = getBaseTableConfig(this.table);
|
|
140
|
+
const containerFields = baseTableConfig.containerFields || [];
|
|
141
|
+
schema = { ...baseTableConfig.schema };
|
|
142
|
+
for (const containerField of containerFields) {
|
|
143
|
+
delete schema[containerField];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
127
146
|
const validation = await validateSingleResponse(
|
|
128
147
|
response,
|
|
129
148
|
schema,
|
|
@@ -146,9 +165,8 @@ class InsertBuilder {
|
|
|
146
165
|
return { data: validation.data, error: void 0 };
|
|
147
166
|
}
|
|
148
167
|
getRequestConfig() {
|
|
149
|
-
var _a;
|
|
150
168
|
const tableId = this.getTableId(this.databaseUseEntityIds);
|
|
151
|
-
const transformedData =
|
|
169
|
+
const transformedData = this.table && this.databaseUseEntityIds ? transformFieldNamesToIds(this.data, this.table) : this.data;
|
|
152
170
|
return {
|
|
153
171
|
method: "POST",
|
|
154
172
|
url: `/${this.databaseName}/${tableId}`,
|
|
@@ -170,7 +188,14 @@ class InsertBuilder {
|
|
|
170
188
|
});
|
|
171
189
|
}
|
|
172
190
|
async processResponse(response, options) {
|
|
173
|
-
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
const tableName = this.table ? getTableName(this.table) : "unknown";
|
|
193
|
+
const error = await parseErrorResponse(
|
|
194
|
+
response,
|
|
195
|
+
response.url || `/${this.databaseName}/${tableName}`
|
|
196
|
+
);
|
|
197
|
+
return { data: void 0, error };
|
|
198
|
+
}
|
|
174
199
|
if (response.status === 204) {
|
|
175
200
|
if (this.returnPreference === "minimal") {
|
|
176
201
|
const locationHeader = response.headers.get("Location") || response.headers.get("location");
|
|
@@ -211,17 +236,38 @@ class InsertBuilder {
|
|
|
211
236
|
}
|
|
212
237
|
};
|
|
213
238
|
}
|
|
239
|
+
let validatedData = this.data;
|
|
240
|
+
if (this.table) {
|
|
241
|
+
const baseTableConfig = getBaseTableConfig(this.table);
|
|
242
|
+
const inputSchema = baseTableConfig.inputSchema;
|
|
243
|
+
try {
|
|
244
|
+
validatedData = await validateAndTransformInput(this.data, inputSchema);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return {
|
|
247
|
+
data: void 0,
|
|
248
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
214
252
|
const shouldUseIds = (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds;
|
|
215
253
|
let transformedResponse = rawResponse;
|
|
216
|
-
if (
|
|
254
|
+
if (this.table && shouldUseIds) {
|
|
217
255
|
transformedResponse = transformResponseFields(
|
|
218
256
|
rawResponse,
|
|
219
|
-
this.
|
|
257
|
+
this.table,
|
|
220
258
|
void 0
|
|
221
259
|
// No expand configs for insert
|
|
222
260
|
);
|
|
223
261
|
}
|
|
224
|
-
|
|
262
|
+
let schema;
|
|
263
|
+
if (this.table) {
|
|
264
|
+
const baseTableConfig = getBaseTableConfig(this.table);
|
|
265
|
+
const containerFields = baseTableConfig.containerFields || [];
|
|
266
|
+
schema = { ...baseTableConfig.schema };
|
|
267
|
+
for (const containerField of containerFields) {
|
|
268
|
+
delete schema[containerField];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
225
271
|
const validation = await validateSingleResponse(
|
|
226
272
|
transformedResponse,
|
|
227
273
|
schema,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert-builder.js","sources":["../../../src/client/insert-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n InferSchemaType,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport { validateSingleResponse } from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformTableName,\n transformResponseFields,\n getTableIdentifiers,\n} from \"../transform\";\nimport { InvalidLocationHeaderError } from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\n\nexport type InsertOptions = {\n return?: \"minimal\" | \"representation\";\n};\n\nexport class InsertBuilder<\n T extends Record<string, any>,\n Occ extends TableOccurrence<any, any, any, any> | undefined = undefined,\n ReturnPreference extends \"minimal\" | \"representation\" = \"representation\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\" ? { ROWID: number } : T\n >\n{\n private occurrence?: Occ;\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private data: Partial<T>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<T>;\n returnPreference?: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = (config.returnPreference ||\n \"representation\") as ReturnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n\n /**\n * Parse ROWID from Location header\n * Expected formats:\n * - contacts(ROWID=4583)\n * - contacts('some-uuid')\n */\n private parseLocationHeader(locationHeader: string | undefined): number {\n if (!locationHeader) {\n throw new InvalidLocationHeaderError(\n \"Location header is required but was not provided\",\n );\n }\n\n // Try to match ROWID=number pattern\n const rowidMatch = locationHeader.match(/ROWID=(\\d+)/);\n if (rowidMatch && rowidMatch[1]) {\n return parseInt(rowidMatch[1], 10);\n }\n\n // Try to extract value from parentheses and parse as number\n const parenMatch = locationHeader.match(/\\(['\"]?([^'\"]+)['\"]?\\)/);\n if (parenMatch && parenMatch[1]) {\n const value = parenMatch[1];\n const numValue = parseInt(value, 10);\n if (!isNaN(numValue)) {\n return numValue;\n }\n }\n\n throw new InvalidLocationHeaderError(\n `Could not extract ROWID from Location header: ${locationHeader}`,\n locationHeader,\n );\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableId(useEntityIds?: boolean): string {\n if (!this.occurrence) {\n return this.tableName;\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n const identifiers = getTableIdentifiers(this.occurrence);\n if (!identifiers.id) {\n throw new Error(\n `useEntityIds is true but TableOccurrence \"${identifiers.name}\" does not have an fmtId defined`\n );\n }\n return identifiers.id;\n }\n\n return this.occurrence.getTableName();\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : ConditionallyWithODataAnnotations<\n T,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n \n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n const url = `/${this.databaseName}/${tableId}`;\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n \n const transformedData = this.occurrence?.baseTable && shouldUseIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n // Make POST request with JSON body\n const result = await this.context._makeRequest<any>(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Prefer: preferHeader,\n ...((mergedOptions as any)?.headers || {}),\n },\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Handle return=minimal case\n if (this.returnPreference === \"minimal\") {\n // The response should be empty (204 No Content)\n // _makeRequest will return { _location: string } when there's a Location header\n const responseData = result.data as any;\n\n if (!responseData || !responseData._location) {\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n const rowid = this.parseLocationHeader(responseData._location);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n\n let response = result.data;\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n if (this.occurrence?.baseTable && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.occurrence.baseTable,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<T>(\n response,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData = this.occurrence?.baseTable && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.occurrence.baseTable)\n : this.data;\n\n return {\n method: \"POST\",\n url: `/${this.databaseName}/${tableId}`,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n Prefer: preferHeader,\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<ReturnPreference extends \"minimal\" ? { ROWID: number } : T>\n > {\n // Handle 204 No Content (common in batch/changeset operations)\n // FileMaker uses return=minimal for changeset operations regardless of Prefer header\n if (response.status === 204) {\n // Check for Location header (for return=minimal)\n if (this.returnPreference === \"minimal\") {\n const locationHeader =\n response.headers.get(\"Location\") || response.headers.get(\"location\");\n if (locationHeader) {\n const rowid = this.parseLocationHeader(locationHeader);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n // For 204 responses without return=minimal, FileMaker doesn't return the created entity\n // This is valid OData behavior for changeset operations\n // We return a success indicator but no actual data\n return {\n data: {} as any,\n error: undefined,\n };\n }\n\n // If we expected return=minimal but got a body, that's unexpected\n if (this.returnPreference === \"minimal\") {\n throw new InvalidLocationHeaderError(\n \"Expected 204 No Content for return=minimal, but received response with body\",\n );\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n let rawResponse;\n try {\n rawResponse = await safeJsonParse(response);\n } catch (err) {\n // If parsing fails with 204, handle it gracefully\n if (response.status === 204) {\n return {\n data: {} as any,\n error: undefined,\n };\n }\n return {\n data: undefined,\n error: {\n name: \"ResponseParseError\",\n message: `Failed to parse response JSON: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;\n \n let transformedResponse = rawResponse;\n if (this.occurrence?.baseTable && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.occurrence.baseTable,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<T>(\n transformedResponse,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"insert-builder.js","sources":["../../../src/client/insert-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n InferSchemaType,\n ExecuteOptions,\n ConditionallyWithODataAnnotations,\n ExecuteMethodOptions,\n} from \"../types\";\nimport { getAcceptHeader } from \"../types\";\nimport type { FMTable } from \"../orm/table\";\nimport {\n getBaseTableConfig,\n getTableName,\n getTableId as getTableIdHelper,\n isUsingEntityIds,\n} from \"../orm/table\";\nimport {\n validateSingleResponse,\n validateAndTransformInput,\n} from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n transformFieldNamesToIds,\n transformResponseFields,\n} from \"../transform\";\nimport { InvalidLocationHeaderError } from \"../errors\";\nimport { safeJsonParse } from \"./sanitize-json\";\nimport { parseErrorResponse } from \"./error-parser\";\n\nexport type InsertOptions = {\n return?: \"minimal\" | \"representation\";\n};\n\nimport type { InferSchemaOutputFromFMTable } from \"../orm/table\";\n\nexport class InsertBuilder<\n Occ extends FMTable<any, any> | undefined = undefined,\n ReturnPreference extends \"minimal\" | \"representation\" = \"representation\",\n> implements\n ExecutableBuilder<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n{\n private table?: Occ;\n private databaseName: string;\n private context: ExecutionContext;\n private data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n private returnPreference: ReturnPreference;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n databaseName: string;\n context: ExecutionContext;\n data: Partial<InferSchemaOutputFromFMTable<NonNullable<Occ>>>;\n returnPreference?: ReturnPreference;\n databaseUseEntityIds?: boolean;\n }) {\n this.table = config.occurrence;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.data = config.data;\n this.returnPreference = (config.returnPreference ||\n \"representation\") as ReturnPreference;\n this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;\n }\n\n /**\n * Helper to merge database-level useEntityIds with per-request options\n */\n private mergeExecuteOptions(\n options?: RequestInit & FFetchOptions & ExecuteOptions,\n ): RequestInit & FFetchOptions & { useEntityIds?: boolean } {\n // If useEntityIds is not set in options, use the database-level setting\n return {\n ...options,\n useEntityIds: options?.useEntityIds ?? this.databaseUseEntityIds,\n };\n }\n\n /**\n * Parse ROWID from Location header\n * Expected formats:\n * - contacts(ROWID=4583)\n * - contacts('some-uuid')\n */\n private parseLocationHeader(locationHeader: string | undefined): number {\n if (!locationHeader) {\n throw new InvalidLocationHeaderError(\n \"Location header is required but was not provided\",\n );\n }\n\n // Try to match ROWID=number pattern\n const rowidMatch = locationHeader.match(/ROWID=(\\d+)/);\n if (rowidMatch && rowidMatch[1]) {\n return parseInt(rowidMatch[1], 10);\n }\n\n // Try to extract value from parentheses and parse as number\n const parenMatch = locationHeader.match(/\\(['\"]?([^'\"]+)['\"]?\\)/);\n if (parenMatch && parenMatch[1]) {\n const value = parenMatch[1];\n const numValue = parseInt(value, 10);\n if (!isNaN(numValue)) {\n return numValue;\n }\n }\n\n throw new InvalidLocationHeaderError(\n `Could not extract ROWID from Location header: ${locationHeader}`,\n locationHeader,\n );\n }\n\n /**\n * Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name\n * @param useEntityIds - Optional override for entity ID usage\n */\n private getTableId(useEntityIds?: boolean): string {\n if (!this.table) {\n throw new Error(\"Table occurrence is required\");\n }\n\n const contextDefault = this.context._getUseEntityIds?.() ?? false;\n const shouldUseIds = useEntityIds ?? contextDefault;\n\n if (shouldUseIds) {\n if (!isUsingEntityIds(this.table)) {\n throw new Error(\n `useEntityIds is true but table \"${getTableName(this.table)}\" does not have entity IDs configured`,\n );\n }\n return getTableIdHelper(this.table);\n }\n\n return getTableName(this.table);\n }\n\n async execute<EO extends ExecuteOptions>(\n options?: ExecuteMethodOptions<EO>,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : ConditionallyWithODataAnnotations<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>,\n EO[\"includeODataAnnotations\"] extends true ? true : false\n >\n >\n > {\n // Merge database-level useEntityIds with per-request options\n const mergedOptions = this.mergeExecuteOptions(options);\n\n // Get table identifier with override support\n const tableId = this.getTableId(mergedOptions.useEntityIds);\n const url = `/${this.databaseName}/${tableId}`;\n\n // Validate and transform input data using input validators (writeValidators)\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n // If validation fails, return error immediately\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n } as any;\n }\n }\n\n // Transform field names to FMFIDs if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = mergedOptions.useEntityIds ?? false;\n\n const transformedData =\n this.table && shouldUseIds\n ? transformFieldNamesToIds(validatedData, this.table)\n : validatedData;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n // Make POST request with JSON body\n const result = await this.context._makeRequest<any>(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Prefer: preferHeader,\n ...((mergedOptions as any)?.headers || {}),\n },\n body: JSON.stringify(transformedData),\n ...mergedOptions,\n });\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n // Handle return=minimal case\n if (this.returnPreference === \"minimal\") {\n // The response should be empty (204 No Content)\n // _makeRequest will return { _location: string } when there's a Location header\n const responseData = result.data as any;\n\n if (!responseData || !responseData._location) {\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n const rowid = this.parseLocationHeader(responseData._location);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n\n let response = result.data;\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n if (this.table && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >(\n response,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n // For batch operations, use database-level setting (no per-request override available here)\n // Note: Input validation happens in execute() and processResponse() for batch operations\n const tableId = this.getTableId(this.databaseUseEntityIds);\n\n // Transform field names to FMFIDs if using entity IDs\n const transformedData =\n this.table && this.databaseUseEntityIds\n ? transformFieldNamesToIds(this.data, this.table)\n : this.data;\n\n return {\n method: \"POST\",\n url: `/${this.databaseName}/${tableId}`,\n body: JSON.stringify(transformedData),\n };\n }\n\n toRequest(baseUrl: string, options?: ExecuteOptions): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n // Set Prefer header based on return preference\n const preferHeader =\n this.returnPreference === \"minimal\"\n ? \"return=minimal\"\n : \"return=representation\";\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: getAcceptHeader(options?.includeODataAnnotations),\n Prefer: preferHeader,\n },\n body: config.body,\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<\n ReturnPreference extends \"minimal\"\n ? { ROWID: number }\n : InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >\n > {\n // Check for error responses (important for batch operations)\n if (!response.ok) {\n const tableName = this.table ? getTableName(this.table) : \"unknown\";\n const error = await parseErrorResponse(\n response,\n response.url || `/${this.databaseName}/${tableName}`,\n );\n return { data: undefined, error };\n }\n\n // Handle 204 No Content (common in batch/changeset operations)\n // FileMaker uses return=minimal for changeset operations regardless of Prefer header\n if (response.status === 204) {\n // Check for Location header (for return=minimal)\n if (this.returnPreference === \"minimal\") {\n const locationHeader =\n response.headers.get(\"Location\") || response.headers.get(\"location\");\n if (locationHeader) {\n const rowid = this.parseLocationHeader(locationHeader);\n return { data: { ROWID: rowid } as any, error: undefined };\n }\n throw new InvalidLocationHeaderError(\n \"Location header is required when using return=minimal but was not found in response\",\n );\n }\n\n // For 204 responses without return=minimal, FileMaker doesn't return the created entity\n // This is valid OData behavior for changeset operations\n // We return a success indicator but no actual data\n return {\n data: {} as any,\n error: undefined,\n };\n }\n\n // If we expected return=minimal but got a body, that's unexpected\n if (this.returnPreference === \"minimal\") {\n throw new InvalidLocationHeaderError(\n \"Expected 204 No Content for return=minimal, but received response with body\",\n );\n }\n\n // Use safeJsonParse to handle FileMaker's invalid JSON with unquoted ? values\n let rawResponse;\n try {\n rawResponse = await safeJsonParse(response);\n } catch (err) {\n // If parsing fails with 204, handle it gracefully\n if (response.status === 204) {\n return {\n data: {} as any,\n error: undefined,\n };\n }\n return {\n data: undefined,\n error: {\n name: \"ResponseParseError\",\n message: `Failed to parse response JSON: ${err instanceof Error ? err.message : \"Unknown error\"}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n // Validate and transform input data using input validators (writeValidators)\n // This is needed for processResponse because it's called from batch operations\n // where the data hasn't been validated yet\n let validatedData = this.data;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const inputSchema = baseTableConfig.inputSchema;\n try {\n validatedData = await validateAndTransformInput(this.data, inputSchema);\n } catch (error) {\n return {\n data: undefined,\n error: error instanceof Error ? error : new Error(String(error)),\n } as any;\n }\n }\n\n // Transform response field IDs back to names if using entity IDs\n // Only transform if useEntityIds resolves to true (respects per-request override)\n const shouldUseIds = options?.useEntityIds ?? this.databaseUseEntityIds;\n\n let transformedResponse = rawResponse;\n if (this.table && shouldUseIds) {\n transformedResponse = transformResponseFields(\n rawResponse,\n this.table,\n undefined, // No expand configs for insert\n );\n }\n\n // Get schema from table if available, excluding container fields\n let schema: Record<string, any> | undefined;\n if (this.table) {\n const baseTableConfig = getBaseTableConfig(this.table);\n const containerFields = baseTableConfig.containerFields || [];\n\n // Filter out container fields from schema\n schema = { ...baseTableConfig.schema };\n for (const containerField of containerFields) {\n delete schema[containerField as string];\n }\n }\n\n // Validate the response (FileMaker returns the created record)\n const validation = await validateSingleResponse<\n InferSchemaOutputFromFMTable<NonNullable<Occ>>\n >(\n transformedResponse,\n schema,\n undefined, // No selected fields for insert\n undefined, // No expand configs\n \"exact\", // Expect exactly one record\n );\n\n if (!validation.valid) {\n return { data: undefined, error: validation.error };\n }\n\n // Handle null response (shouldn't happen for insert, but handle it)\n if (validation.data === null) {\n return {\n data: undefined,\n error: new Error(\"Insert operation returned null response\"),\n };\n }\n\n return { data: validation.data as any, error: undefined };\n }\n}\n"],"names":["getTableIdHelper"],"mappings":";;;;;;;;;;AAqCO,MAAM,cASb;AAAA,EASE,YAAY,QAOT;AAfK;AACA;AACA;AACA;AACA;AAEA;AAUN,SAAK,QAAQ,OAAO;AACpB,SAAK,eAAe,OAAO;AAC3B,SAAK,UAAU,OAAO;AACtB,SAAK,OAAO,OAAO;AACd,SAAA,mBAAoB,OAAO,oBAC9B;AACG,SAAA,uBAAuB,OAAO,wBAAwB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMrD,oBACN,SAC0D;AAEnD,WAAA;AAAA,MACL,GAAG;AAAA,MACH,eAAc,mCAAS,iBAAgB,KAAK;AAAA,IAC9C;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASM,oBAAoB,gBAA4C;AACtE,QAAI,CAAC,gBAAgB;AACnB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAII,UAAA,aAAa,eAAe,MAAM,aAAa;AACjD,QAAA,cAAc,WAAW,CAAC,GAAG;AAC/B,aAAO,SAAS,WAAW,CAAC,GAAG,EAAE;AAAA,IAAA;AAI7B,UAAA,aAAa,eAAe,MAAM,wBAAwB;AAC5D,QAAA,cAAc,WAAW,CAAC,GAAG;AACzB,YAAA,QAAQ,WAAW,CAAC;AACpB,YAAA,WAAW,SAAS,OAAO,EAAE;AAC/B,UAAA,CAAC,MAAM,QAAQ,GAAG;AACb,eAAA;AAAA,MAAA;AAAA,IACT;AAGF,UAAM,IAAI;AAAA,MACR,iDAAiD,cAAc;AAAA,MAC/D;AAAA,IACF;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOM,WAAW,cAAgC;;AAC7C,QAAA,CAAC,KAAK,OAAO;AACT,YAAA,IAAI,MAAM,8BAA8B;AAAA,IAAA;AAGhD,UAAM,mBAAiB,gBAAK,SAAQ,qBAAb,gCAAqC;AAC5D,UAAM,eAAe,gBAAgB;AAErC,QAAI,cAAc;AAChB,UAAI,CAAC,iBAAiB,KAAK,KAAK,GAAG;AACjC,cAAM,IAAI;AAAA,UACR,mCAAmC,aAAa,KAAK,KAAK,CAAC;AAAA,QAC7D;AAAA,MAAA;AAEK,aAAAA,WAAiB,KAAK,KAAK;AAAA,IAAA;AAG7B,WAAA,aAAa,KAAK,KAAK;AAAA,EAAA;AAAA,EAGhC,MAAM,QACJ,SAUA;AAEM,UAAA,gBAAgB,KAAK,oBAAoB,OAAO;AAGtD,UAAM,UAAU,KAAK,WAAW,cAAc,YAAY;AAC1D,UAAM,MAAM,IAAI,KAAK,YAAY,IAAI,OAAO;AAG5C,QAAI,gBAAgB,KAAK;AACzB,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AACrD,YAAM,cAAc,gBAAgB;AAEhC,UAAA;AACF,wBAAgB,MAAM,0BAA0B,KAAK,MAAM,WAAW;AAAA,eAC/D,OAAO;AAEP,eAAA;AAAA,UACL,MAAM;AAAA,UACN,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QACjE;AAAA,MAAA;AAAA,IACF;AAKI,UAAA,eAAe,cAAc,gBAAgB;AAE7C,UAAA,kBACJ,KAAK,SAAS,eACV,yBAAyB,eAAe,KAAK,KAAK,IAClD;AAGN,UAAM,eACJ,KAAK,qBAAqB,YACtB,mBACA;AAGN,UAAM,SAAS,MAAM,KAAK,QAAQ,aAAkB,KAAK;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,QACR,IAAK,+CAAuB,YAAW,CAAA;AAAA,MACzC;AAAA,MACA,MAAM,KAAK,UAAU,eAAe;AAAA,MACpC,GAAG;AAAA,IAAA,CACJ;AAED,QAAI,OAAO,OAAO;AAChB,aAAO,EAAE,MAAM,QAAW,OAAO,OAAO,MAAM;AAAA,IAAA;AAI5C,QAAA,KAAK,qBAAqB,WAAW;AAGvC,YAAM,eAAe,OAAO;AAE5B,UAAI,CAAC,gBAAgB,CAAC,aAAa,WAAW;AAC5C,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MAAA;AAGF,YAAM,QAAQ,KAAK,oBAAoB,aAAa,SAAS;AAC7D,aAAO,EAAE,MAAM,EAAE,OAAO,MAAM,GAAU,OAAO,OAAU;AAAA,IAAA;AAG3D,QAAI,WAAW,OAAO;AAIlB,QAAA,KAAK,SAAS,cAAc;AACnB,iBAAA;AAAA,QACT;AAAA,QACA,KAAK;AAAA,QACL;AAAA;AAAA,MACF;AAAA,IAAA;AAIE,QAAA;AACJ,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AAC/C,YAAA,kBAAkB,gBAAgB,mBAAmB,CAAC;AAGnD,eAAA,EAAE,GAAG,gBAAgB,OAAO;AACrC,iBAAW,kBAAkB,iBAAiB;AAC5C,eAAO,OAAO,cAAwB;AAAA,MAAA;AAAA,IACxC;AAIF,UAAM,aAAa,MAAM;AAAA,MAGvB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEI,QAAA,CAAC,WAAW,OAAO;AACrB,aAAO,EAAE,MAAM,QAAW,OAAO,WAAW,MAAM;AAAA,IAAA;AAIhD,QAAA,WAAW,SAAS,MAAM;AACrB,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO,IAAI,MAAM,yCAAyC;AAAA,MAC5D;AAAA,IAAA;AAGF,WAAO,EAAE,MAAM,WAAW,MAAa,OAAO,OAAU;AAAA,EAAA;AAAA,EAG1D,mBAAgE;AAG9D,UAAM,UAAU,KAAK,WAAW,KAAK,oBAAoB;AAGnD,UAAA,kBACJ,KAAK,SAAS,KAAK,uBACf,yBAAyB,KAAK,MAAM,KAAK,KAAK,IAC9C,KAAK;AAEJ,WAAA;AAAA,MACL,QAAQ;AAAA,MACR,KAAK,IAAI,KAAK,YAAY,IAAI,OAAO;AAAA,MACrC,MAAM,KAAK,UAAU,eAAe;AAAA,IACtC;AAAA,EAAA;AAAA,EAGF,UAAU,SAAiB,SAAmC;AACtD,UAAA,SAAS,KAAK,iBAAiB;AACrC,UAAM,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG;AAGvC,UAAM,eACJ,KAAK,qBAAqB,YACtB,mBACA;AAEC,WAAA,IAAI,QAAQ,SAAS;AAAA,MAC1B,QAAQ,OAAO;AAAA,MACf,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ,gBAAgB,mCAAS,uBAAuB;AAAA,QACxD,QAAQ;AAAA,MACV;AAAA,MACA,MAAM,OAAO;AAAA,IAAA,CACd;AAAA,EAAA;AAAA,EAGH,MAAM,gBACJ,UACA,SAOA;AAEI,QAAA,CAAC,SAAS,IAAI;AAChB,YAAM,YAAY,KAAK,QAAQ,aAAa,KAAK,KAAK,IAAI;AAC1D,YAAM,QAAQ,MAAM;AAAA,QAClB;AAAA,QACA,SAAS,OAAO,IAAI,KAAK,YAAY,IAAI,SAAS;AAAA,MACpD;AACO,aAAA,EAAE,MAAM,QAAW,MAAM;AAAA,IAAA;AAK9B,QAAA,SAAS,WAAW,KAAK;AAEvB,UAAA,KAAK,qBAAqB,WAAW;AACjC,cAAA,iBACJ,SAAS,QAAQ,IAAI,UAAU,KAAK,SAAS,QAAQ,IAAI,UAAU;AACrE,YAAI,gBAAgB;AACZ,gBAAA,QAAQ,KAAK,oBAAoB,cAAc;AACrD,iBAAO,EAAE,MAAM,EAAE,OAAO,MAAM,GAAU,OAAO,OAAU;AAAA,QAAA;AAE3D,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MAAA;AAMK,aAAA;AAAA,QACL,MAAM,CAAC;AAAA,QACP,OAAO;AAAA,MACT;AAAA,IAAA;AAIE,QAAA,KAAK,qBAAqB,WAAW;AACvC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAIE,QAAA;AACA,QAAA;AACY,oBAAA,MAAM,cAAc,QAAQ;AAAA,aACnC,KAAK;AAER,UAAA,SAAS,WAAW,KAAK;AACpB,eAAA;AAAA,UACL,MAAM,CAAC;AAAA,UACP,OAAO;AAAA,QACT;AAAA,MAAA;AAEK,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS,kCAAkC,eAAe,QAAQ,IAAI,UAAU,eAAe;AAAA,UAC/F,+BAAe,KAAK;AAAA,QAAA;AAAA,MAExB;AAAA,IAAA;AAMF,QAAI,gBAAgB,KAAK;AACzB,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AACrD,YAAM,cAAc,gBAAgB;AAChC,UAAA;AACF,wBAAgB,MAAM,0BAA0B,KAAK,MAAM,WAAW;AAAA,eAC/D,OAAO;AACP,eAAA;AAAA,UACL,MAAM;AAAA,UACN,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QACjE;AAAA,MAAA;AAAA,IACF;AAKI,UAAA,gBAAe,mCAAS,iBAAgB,KAAK;AAEnD,QAAI,sBAAsB;AACtB,QAAA,KAAK,SAAS,cAAc;AACR,4BAAA;AAAA,QACpB;AAAA,QACA,KAAK;AAAA,QACL;AAAA;AAAA,MACF;AAAA,IAAA;AAIE,QAAA;AACJ,QAAI,KAAK,OAAO;AACR,YAAA,kBAAkB,mBAAmB,KAAK,KAAK;AAC/C,YAAA,kBAAkB,gBAAgB,mBAAmB,CAAC;AAGnD,eAAA,EAAE,GAAG,gBAAgB,OAAO;AACrC,iBAAW,kBAAkB,iBAAiB;AAC5C,eAAO,OAAO,cAAwB;AAAA,MAAA;AAAA,IACxC;AAIF,UAAM,aAAa,MAAM;AAAA,MAGvB;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,MACA;AAAA;AAAA,IACF;AAEI,QAAA,CAAC,WAAW,OAAO;AACrB,aAAO,EAAE,MAAM,QAAW,OAAO,WAAW,MAAM;AAAA,IAAA;AAIhD,QAAA,WAAW,SAAS,MAAM;AACrB,aAAA;AAAA,QACL,MAAM;AAAA,QACN,OAAO,IAAI,MAAM,yCAAyC;AAAA,MAC5D;AAAA,IAAA;AAGF,WAAO,EAAE,MAAM,WAAW,MAAa,OAAO,OAAU;AAAA,EAAA;AAE5D;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { QueryOptions } from 'odata-query';
|
|
2
|
+
import { FMTable } from '../../orm/table.js';
|
|
3
|
+
import { ExpandValidationConfig } from '../../validation.js';
|
|
4
|
+
/**
|
|
5
|
+
* Internal type for expand configuration
|
|
6
|
+
*/
|
|
7
|
+
export type ExpandConfig = {
|
|
8
|
+
relation: string;
|
|
9
|
+
options?: Partial<QueryOptions<any>>;
|
|
10
|
+
targetTable?: FMTable<any, any>;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Builds OData expand query strings and validation configs.
|
|
14
|
+
* Handles nested expands recursively and transforms relation names to FMTIDs
|
|
15
|
+
* when using entity IDs.
|
|
16
|
+
*/
|
|
17
|
+
export declare class ExpandBuilder {
|
|
18
|
+
private useEntityIds;
|
|
19
|
+
constructor(useEntityIds: boolean);
|
|
20
|
+
/**
|
|
21
|
+
* Builds OData expand query string from expand configurations.
|
|
22
|
+
* Handles nested expands recursively.
|
|
23
|
+
* Transforms relation names to FMTIDs if using entity IDs.
|
|
24
|
+
*/
|
|
25
|
+
buildExpandString(configs: ExpandConfig[]): string;
|
|
26
|
+
/**
|
|
27
|
+
* Builds a single expand string with its options.
|
|
28
|
+
*/
|
|
29
|
+
private buildSingleExpand;
|
|
30
|
+
/**
|
|
31
|
+
* Builds expand validation configs from internal expand configurations.
|
|
32
|
+
* These are used to validate expanded navigation properties.
|
|
33
|
+
*/
|
|
34
|
+
buildValidationConfigs(configs: ExpandConfig[]): ExpandValidationConfig[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ExecuteOptions, ConditionallyWithODataAnnotations, ExtractSchemaFromOccurrence, ExecuteMethodOptions } from '../../types.js';
|
|
2
|
+
import { Filter } from '../../filter-types.js';
|
|
3
|
+
import { Column } from '../../orm/column.js';
|
|
4
|
+
import { FilterExpression, OrderByExpression } from '../../orm/operators.js';
|
|
5
|
+
import { FMTable, InferSchemaOutputFromFMTable, ValidExpandTarget, ExtractTableName } from '../../orm/table.js';
|
|
6
|
+
import { ExpandedRelations } from '../builders/index.js';
|
|
7
|
+
import { TypeSafeOrderBy, QueryReturnType } from './types.js';
|
|
8
|
+
export type { QueryReturnType };
|
|
9
|
+
export type { TypeSafeOrderBy, ExpandedRelations };
|
|
10
|
+
export declare class QueryBuilder<Occ extends FMTable<any, any>, Selected extends keyof InferSchemaOutputFromFMTable<Occ> | Record<string, Column<any, ExtractTableName<Occ>>> = keyof InferSchemaOutputFromFMTable<Occ>, SingleMode extends "exact" | "maybe" | false = false, IsCount extends boolean = false, Expands extends ExpandedRelations = {}> implements ExecutableBuilder<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>> {
|
|
11
|
+
private queryOptions;
|
|
12
|
+
private expandConfigs;
|
|
13
|
+
private singleMode;
|
|
14
|
+
private isCountMode;
|
|
15
|
+
private occurrence;
|
|
16
|
+
private databaseName;
|
|
17
|
+
private context;
|
|
18
|
+
private navigation?;
|
|
19
|
+
private databaseUseEntityIds;
|
|
20
|
+
private expandBuilder;
|
|
21
|
+
private urlBuilder;
|
|
22
|
+
private fieldMapping?;
|
|
23
|
+
constructor(config: {
|
|
24
|
+
occurrence: Occ;
|
|
25
|
+
databaseName: string;
|
|
26
|
+
context: ExecutionContext;
|
|
27
|
+
databaseUseEntityIds?: boolean;
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Helper to merge database-level useEntityIds with per-request options
|
|
31
|
+
*/
|
|
32
|
+
private mergeExecuteOptions;
|
|
33
|
+
/**
|
|
34
|
+
* Gets the FMTable instance
|
|
35
|
+
*/
|
|
36
|
+
private getTable;
|
|
37
|
+
/**
|
|
38
|
+
* Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
|
|
39
|
+
* @param useEntityIds - Optional override for entity ID usage
|
|
40
|
+
*/
|
|
41
|
+
private getTableIdOrName;
|
|
42
|
+
/**
|
|
43
|
+
* Creates a new QueryBuilder with modified configuration.
|
|
44
|
+
* Used by single(), maybeSingle(), count(), and select() to create new instances.
|
|
45
|
+
*/
|
|
46
|
+
private cloneWithChanges;
|
|
47
|
+
/**
|
|
48
|
+
* Select fields using column references.
|
|
49
|
+
* Allows renaming fields by using different keys in the object.
|
|
50
|
+
* Container fields cannot be selected and will cause a type error.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* db.from(users).list().select({
|
|
54
|
+
* name: users.name,
|
|
55
|
+
* userEmail: users.email // renamed!
|
|
56
|
+
* })
|
|
57
|
+
*
|
|
58
|
+
* @param fields - Object mapping output keys to column references (container fields excluded)
|
|
59
|
+
* @returns QueryBuilder with updated selected fields
|
|
60
|
+
*/
|
|
61
|
+
select<TSelect extends Record<string, Column<any, ExtractTableName<Occ>, false>>>(fields: TSelect): QueryBuilder<Occ, TSelect, SingleMode, IsCount, Expands>;
|
|
62
|
+
/**
|
|
63
|
+
* Transforms our filter format to odata-query's expected format
|
|
64
|
+
* - Arrays of operators are converted to AND conditions
|
|
65
|
+
* - Single operator objects pass through as-is
|
|
66
|
+
* - Shorthand values are handled by odata-query
|
|
67
|
+
*/
|
|
68
|
+
private transformFilter;
|
|
69
|
+
filter(filter: Filter<ExtractSchemaFromOccurrence<Occ>>): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
70
|
+
/**
|
|
71
|
+
* Filter results using operator expressions (new ORM-style API).
|
|
72
|
+
* Supports eq, gt, lt, and, or, etc. operators with Column references.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* .where(eq(users.hobby, "reading"))
|
|
76
|
+
* .where(and(eq(users.active, true), gt(users.age, 18)))
|
|
77
|
+
*/
|
|
78
|
+
where(expression: FilterExpression): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
79
|
+
/**
|
|
80
|
+
* Specify the sort order for query results.
|
|
81
|
+
*
|
|
82
|
+
* @example Single field (ascending by default)
|
|
83
|
+
* ```ts
|
|
84
|
+
* .orderBy("name")
|
|
85
|
+
* .orderBy(users.name) // Column reference
|
|
86
|
+
* .orderBy(asc(users.name)) // Explicit ascending
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* @example Single field with explicit direction
|
|
90
|
+
* ```ts
|
|
91
|
+
* .orderBy(["name", "desc"])
|
|
92
|
+
* .orderBy([users.name, "desc"]) // Column reference
|
|
93
|
+
* .orderBy(desc(users.name)) // Explicit descending
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example Multiple fields with directions
|
|
97
|
+
* ```ts
|
|
98
|
+
* .orderBy([["name", "asc"], ["createdAt", "desc"]])
|
|
99
|
+
* .orderBy([[users.name, "asc"], [users.createdAt, "desc"]]) // Column references
|
|
100
|
+
* .orderBy(users.name, desc(users.age)) // Variadic with helpers
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
orderBy(...orderByArgs: [
|
|
104
|
+
TypeSafeOrderBy<InferSchemaOutputFromFMTable<Occ>> | Column<any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>
|
|
105
|
+
] | [
|
|
106
|
+
Column<any, ExtractTableName<Occ>>,
|
|
107
|
+
...Array<Column<any, ExtractTableName<Occ>> | OrderByExpression<ExtractTableName<Occ>>>
|
|
108
|
+
]): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
109
|
+
top(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
110
|
+
skip(count: number): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands>;
|
|
111
|
+
expand<TargetTable extends FMTable<any, any>>(targetTable: ValidExpandTarget<Occ, TargetTable>, callback?: (builder: QueryBuilder<TargetTable, keyof InferSchemaOutputFromFMTable<TargetTable>, false, false>) => QueryBuilder<TargetTable, any, any, any, any>): QueryBuilder<Occ, Selected, SingleMode, IsCount, Expands & {
|
|
112
|
+
[K in ExtractTableName<TargetTable>]: {
|
|
113
|
+
schema: InferSchemaOutputFromFMTable<TargetTable>;
|
|
114
|
+
selected: keyof InferSchemaOutputFromFMTable<TargetTable>;
|
|
115
|
+
};
|
|
116
|
+
}>;
|
|
117
|
+
single(): QueryBuilder<Occ, Selected, "exact", IsCount, Expands>;
|
|
118
|
+
maybeSingle(): QueryBuilder<Occ, Selected, "maybe", IsCount, Expands>;
|
|
119
|
+
count(): QueryBuilder<Occ, Selected, SingleMode, true, Expands>;
|
|
120
|
+
/**
|
|
121
|
+
* Builds the OData query string from current query options and expand configs.
|
|
122
|
+
*/
|
|
123
|
+
private buildQueryString;
|
|
124
|
+
execute<EO extends ExecuteOptions>(options?: ExecuteMethodOptions<EO>): Promise<Result<ConditionallyWithODataAnnotations<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>, EO["includeODataAnnotations"] extends true ? true : false>>>;
|
|
125
|
+
getQueryString(): string;
|
|
126
|
+
getRequestConfig(): {
|
|
127
|
+
method: string;
|
|
128
|
+
url: string;
|
|
129
|
+
body?: any;
|
|
130
|
+
};
|
|
131
|
+
toRequest(baseUrl: string, options?: ExecuteOptions): Request;
|
|
132
|
+
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<QueryReturnType<InferSchemaOutputFromFMTable<Occ>, Selected, SingleMode, IsCount, Expands>>>;
|
|
133
|
+
}
|