@proofkit/fmodata 0.1.0-alpha.8 → 0.1.0-beta.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +651 -449
- package/dist/esm/client/batch-builder.d.ts +10 -9
- package/dist/esm/client/batch-builder.js +119 -56
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/batch-request.js +16 -21
- package/dist/esm/client/batch-request.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +10 -0
- package/dist/esm/client/builders/default-select.js +41 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +45 -0
- package/dist/esm/client/builders/expand-builder.js +185 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +9 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
- package/dist/esm/client/builders/query-string-builder.js +21 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +43 -0
- package/dist/esm/client/builders/response-processor.js +175 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +25 -0
- package/dist/esm/client/builders/select-mixin.js +28 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +18 -0
- package/dist/esm/client/builders/select-utils.js +30 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +40 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +44 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +34 -22
- package/dist/esm/client/database.js +48 -84
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +25 -30
- package/dist/esm/client/delete-builder.js +45 -30
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +35 -43
- package/dist/esm/client/entity-set.js +110 -52
- 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 +25 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +26 -7
- package/dist/esm/client/filemaker-odata.js +65 -42
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +19 -24
- package/dist/esm/client/insert-builder.js +94 -58
- 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 +4 -0
- package/dist/esm/client/query/query-builder.d.ts +132 -0
- package/dist/esm/client/query/query-builder.js +456 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +25 -0
- package/dist/esm/client/query/types.d.ts +77 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +100 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +2 -115
- package/dist/esm/client/record-builder.d.ts +108 -36
- package/dist/esm/client/record-builder.js +284 -119
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +4 -9
- package/dist/esm/client/sanitize-json.d.ts +35 -0
- package/dist/esm/client/sanitize-json.js +27 -0
- package/dist/esm/client/sanitize-json.js.map +1 -0
- package/dist/esm/client/schema-manager.d.ts +5 -5
- package/dist/esm/client/schema-manager.js +45 -31
- package/dist/esm/client/schema-manager.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -40
- package/dist/esm/client/update-builder.js +99 -58
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.d.ts +126 -0
- package/dist/esm/client/webhook-builder.js +189 -0
- package/dist/esm/client/webhook-builder.js.map +1 -0
- package/dist/esm/errors.d.ts +19 -2
- package/dist/esm/errors.js +39 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -8
- package/dist/esm/index.js +40 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +69 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/logger.test.d.ts +1 -0
- package/dist/esm/orm/column.d.ts +62 -0
- package/dist/esm/orm/column.js +63 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +164 -0
- package/dist/esm/orm/field-builders.js +158 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +5 -0
- package/dist/esm/orm/operators.d.ts +173 -0
- package/dist/esm/orm/operators.js +260 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +355 -0
- package/dist/esm/orm/table.js +202 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +44 -45
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +96 -30
- package/dist/esm/types.js +7 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.d.ts +22 -12
- package/dist/esm/validation.js +132 -85
- package/dist/esm/validation.js.map +1 -1
- package/package.json +28 -20
- package/src/client/batch-builder.ts +153 -89
- package/src/client/batch-request.ts +25 -41
- package/src/client/builders/default-select.ts +75 -0
- package/src/client/builders/expand-builder.ts +246 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +46 -0
- package/src/client/builders/response-processor.ts +279 -0
- package/src/client/builders/select-mixin.ts +65 -0
- package/src/client/builders/select-utils.ts +59 -0
- package/src/client/builders/shared-types.ts +45 -0
- package/src/client/builders/table-utils.ts +83 -0
- package/src/client/database.ts +89 -183
- package/src/client/delete-builder.ts +74 -84
- package/src/client/entity-set.ts +266 -293
- package/src/client/error-parser.ts +41 -0
- package/src/client/filemaker-odata.ts +98 -66
- package/src/client/insert-builder.ts +157 -118
- package/src/client/query/expand-builder.ts +160 -0
- package/src/client/query/index.ts +14 -0
- package/src/client/query/query-builder.ts +729 -0
- package/src/client/query/response-processor.ts +226 -0
- package/src/client/query/types.ts +126 -0
- package/src/client/query/url-builder.ts +151 -0
- package/src/client/query-builder.ts +10 -1455
- package/src/client/record-builder.ts +575 -240
- package/src/client/response-processor.ts +15 -42
- package/src/client/sanitize-json.ts +64 -0
- package/src/client/schema-manager.ts +61 -76
- package/src/client/update-builder.ts +161 -143
- package/src/client/webhook-builder.ts +265 -0
- package/src/errors.ts +49 -16
- package/src/index.ts +99 -54
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +116 -0
- package/src/orm/column.ts +106 -0
- package/src/orm/field-builders.ts +250 -0
- package/src/orm/index.ts +61 -0
- package/src/orm/operators.ts +473 -0
- package/src/orm/table.ts +741 -0
- package/src/transform.ts +90 -70
- package/src/types.ts +154 -113
- package/src/validation.ts +200 -115
- package/dist/esm/client/base-table.d.ts +0 -125
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -896
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -72
- package/dist/esm/client/table-occurrence.js +0 -74
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/client/base-table.ts +0 -166
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -175
- package/src/filter-types.ts +0 -97
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type FMODataErrorType, HTTPError, ODataError, SchemaLockedError } from "../errors";
|
|
2
|
+
import { safeJsonParse } from "./sanitize-json";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Parses an error response and returns an appropriate error object.
|
|
6
|
+
* This helper is used by builder processResponse methods to handle error responses
|
|
7
|
+
* consistently, particularly important for batch operations where errors need to be
|
|
8
|
+
* properly parsed from the response body.
|
|
9
|
+
*
|
|
10
|
+
* @param response - The Response object (may be from batch or direct request)
|
|
11
|
+
* @param url - The URL that was requested (for error context)
|
|
12
|
+
* @returns An appropriate error object (ODataError, SchemaLockedError, or HTTPError)
|
|
13
|
+
*/
|
|
14
|
+
export async function parseErrorResponse(response: Response, url: string): Promise<FMODataErrorType> {
|
|
15
|
+
// Try to parse error body if it's JSON
|
|
16
|
+
let errorBody: { error?: { code?: string | number; message?: string } } | undefined;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
20
|
+
errorBody = await safeJsonParse<typeof errorBody>(response);
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
// Ignore JSON parse errors - we'll fall back to HTTPError
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Check if it's an OData error response
|
|
27
|
+
if (errorBody?.error) {
|
|
28
|
+
const errorCode = errorBody.error.code;
|
|
29
|
+
const errorMessage = errorBody.error.message || response.statusText;
|
|
30
|
+
|
|
31
|
+
// Check for schema locked error (code 303)
|
|
32
|
+
if (errorCode === "303" || errorCode === 303) {
|
|
33
|
+
return new SchemaLockedError(url, errorMessage, errorBody.error);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return new ODataError(url, errorMessage, String(errorCode), errorBody.error);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fall back to generic HTTPError
|
|
40
|
+
return new HTTPError(url, response.status, response.statusText, errorBody);
|
|
41
|
+
}
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
import createClient, {
|
|
2
|
-
FFetchOptions,
|
|
3
|
-
TimeoutError,
|
|
4
2
|
AbortError,
|
|
3
|
+
CircuitOpenError,
|
|
4
|
+
type FFetchOptions,
|
|
5
5
|
NetworkError,
|
|
6
6
|
RetryLimitError,
|
|
7
|
-
|
|
7
|
+
TimeoutError,
|
|
8
8
|
} from "@fetchkit/ffetch";
|
|
9
|
+
import { get } from "es-toolkit/compat";
|
|
10
|
+
import { HTTPError, ODataError, ResponseParseError, SchemaLockedError } from "../errors";
|
|
11
|
+
import { createLogger, type InternalLogger, type Logger } from "../logger";
|
|
9
12
|
import type { Auth, ExecutionContext, Result } from "../types";
|
|
10
|
-
import {
|
|
13
|
+
import { getAcceptHeader } from "../types";
|
|
11
14
|
import { Database } from "./database";
|
|
12
|
-
import {
|
|
15
|
+
import { safeJsonParse } from "./sanitize-json";
|
|
16
|
+
|
|
17
|
+
const TRAILING_SLASH_REGEX = /\/+$/;
|
|
13
18
|
|
|
14
19
|
export class FMServerConnection implements ExecutionContext {
|
|
15
|
-
private fetchClient: ReturnType<typeof createClient>;
|
|
16
|
-
private serverUrl: string;
|
|
17
|
-
private auth: Auth;
|
|
18
|
-
private useEntityIds
|
|
20
|
+
private readonly fetchClient: ReturnType<typeof createClient>;
|
|
21
|
+
private readonly serverUrl: string;
|
|
22
|
+
private readonly auth: Auth;
|
|
23
|
+
private useEntityIds = false;
|
|
24
|
+
private includeSpecialColumns = false;
|
|
25
|
+
private readonly logger: InternalLogger;
|
|
19
26
|
constructor(config: {
|
|
20
27
|
serverUrl: string;
|
|
21
28
|
auth: Auth;
|
|
22
29
|
fetchClientOptions?: FFetchOptions;
|
|
30
|
+
logger?: Logger;
|
|
23
31
|
}) {
|
|
32
|
+
this.logger = createLogger(config.logger);
|
|
24
33
|
this.fetchClient = createClient({
|
|
25
34
|
retries: 0,
|
|
26
35
|
...config.fetchClientOptions,
|
|
@@ -31,8 +40,8 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
31
40
|
url.protocol = "https:";
|
|
32
41
|
}
|
|
33
42
|
// Remove any trailing slash from pathname
|
|
34
|
-
url.pathname = url.pathname.replace(
|
|
35
|
-
this.serverUrl = url.toString().replace(
|
|
43
|
+
url.pathname = url.pathname.replace(TRAILING_SLASH_REGEX, "");
|
|
44
|
+
this.serverUrl = url.toString().replace(TRAILING_SLASH_REGEX, "");
|
|
36
45
|
this.auth = config.auth;
|
|
37
46
|
}
|
|
38
47
|
|
|
@@ -52,12 +61,36 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
52
61
|
return this.useEntityIds;
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @internal
|
|
66
|
+
* Sets whether to include special columns (ROWID and ROWMODID) in requests
|
|
67
|
+
*/
|
|
68
|
+
_setIncludeSpecialColumns(includeSpecialColumns: boolean): void {
|
|
69
|
+
this.includeSpecialColumns = includeSpecialColumns;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @internal
|
|
74
|
+
* Gets whether to include special columns (ROWID and ROWMODID) in requests
|
|
75
|
+
*/
|
|
76
|
+
_getIncludeSpecialColumns(): boolean {
|
|
77
|
+
return this.includeSpecialColumns;
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
/**
|
|
56
81
|
* @internal
|
|
57
82
|
* Gets the base URL for OData requests
|
|
58
83
|
*/
|
|
59
84
|
_getBaseUrl(): string {
|
|
60
|
-
return `${this.serverUrl}${"apiKey" in this.auth ?
|
|
85
|
+
return `${this.serverUrl}${"apiKey" in this.auth ? "/otto" : ""}/fmi/odata/v4`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @internal
|
|
90
|
+
* Gets the logger instance
|
|
91
|
+
*/
|
|
92
|
+
_getLogger(): InternalLogger {
|
|
93
|
+
return this.logger;
|
|
61
94
|
}
|
|
62
95
|
|
|
63
96
|
/**
|
|
@@ -65,13 +98,32 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
65
98
|
*/
|
|
66
99
|
async _makeRequest<T>(
|
|
67
100
|
url: string,
|
|
68
|
-
options?: RequestInit &
|
|
101
|
+
options?: RequestInit &
|
|
102
|
+
FFetchOptions & {
|
|
103
|
+
useEntityIds?: boolean;
|
|
104
|
+
includeSpecialColumns?: boolean;
|
|
105
|
+
},
|
|
69
106
|
): Promise<Result<T>> {
|
|
70
|
-
const
|
|
107
|
+
const logger = this._getLogger();
|
|
108
|
+
const baseUrl = `${this.serverUrl}${"apiKey" in this.auth ? "/otto" : ""}/fmi/odata/v4`;
|
|
71
109
|
const fullUrl = baseUrl + url;
|
|
72
110
|
|
|
73
111
|
// Use per-request override if provided, otherwise use the database-level setting
|
|
74
112
|
const useEntityIds = options?.useEntityIds ?? this.useEntityIds;
|
|
113
|
+
const includeSpecialColumns = options?.includeSpecialColumns ?? this.includeSpecialColumns;
|
|
114
|
+
|
|
115
|
+
// Get includeODataAnnotations from options (it's passed through from execute options)
|
|
116
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for optional property access
|
|
117
|
+
const includeODataAnnotations = (options as any)?.includeODataAnnotations;
|
|
118
|
+
|
|
119
|
+
// Build Prefer header as comma-separated list when multiple preferences are set
|
|
120
|
+
const preferValues: string[] = [];
|
|
121
|
+
if (useEntityIds) {
|
|
122
|
+
preferValues.push("fmodata.entity-ids");
|
|
123
|
+
}
|
|
124
|
+
if (includeSpecialColumns) {
|
|
125
|
+
preferValues.push("fmodata.include-specialcolumns");
|
|
126
|
+
}
|
|
75
127
|
|
|
76
128
|
const headers = {
|
|
77
129
|
Authorization:
|
|
@@ -79,25 +131,23 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
79
131
|
? `Bearer ${this.auth.apiKey}`
|
|
80
132
|
: `Basic ${btoa(`${this.auth.username}:${this.auth.password}`)}`,
|
|
81
133
|
"Content-Type": "application/json",
|
|
82
|
-
Accept:
|
|
83
|
-
...(
|
|
134
|
+
Accept: getAcceptHeader(includeODataAnnotations),
|
|
135
|
+
...(preferValues.length > 0 ? { Prefer: preferValues.join(", ") } : {}),
|
|
84
136
|
...(options?.headers || {}),
|
|
85
137
|
};
|
|
86
138
|
|
|
139
|
+
// Prepare loggableHeaders by omitting the Authorization key
|
|
140
|
+
const { Authorization, ...loggableHeaders } = headers;
|
|
141
|
+
logger.debug("Request headers:", loggableHeaders);
|
|
142
|
+
|
|
87
143
|
// TEMPORARY WORKAROUND: Hopefully this feature will be fixed in the ffetch library
|
|
88
144
|
// Extract fetchHandler and headers separately, only for tests where we're overriding the fetch handler per-request
|
|
89
145
|
const fetchHandler = options?.fetchHandler;
|
|
90
|
-
const {
|
|
91
|
-
headers: _headers,
|
|
92
|
-
fetchHandler: _fetchHandler,
|
|
93
|
-
...restOptions
|
|
94
|
-
} = options || {};
|
|
146
|
+
const { headers: _headers, fetchHandler: _fetchHandler, ...restOptions } = options || {};
|
|
95
147
|
|
|
96
148
|
// If fetchHandler is provided, create a temporary client with it
|
|
97
149
|
// Otherwise use the existing client
|
|
98
|
-
const clientToUse = fetchHandler
|
|
99
|
-
? createClient({ retries: 0, fetchHandler })
|
|
100
|
-
: this.fetchClient;
|
|
150
|
+
const clientToUse = fetchHandler ? createClient({ retries: 0, fetchHandler }) : this.fetchClient;
|
|
101
151
|
|
|
102
152
|
try {
|
|
103
153
|
const finalOptions = {
|
|
@@ -105,22 +155,16 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
105
155
|
headers,
|
|
106
156
|
};
|
|
107
157
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
? await fetch(fullUrl, {
|
|
111
|
-
method: finalOptions.method,
|
|
112
|
-
headers: finalOptions.headers,
|
|
113
|
-
body: finalOptions.body,
|
|
114
|
-
})
|
|
115
|
-
: await clientToUse(fullUrl, finalOptions);
|
|
158
|
+
const resp = await clientToUse(fullUrl, finalOptions);
|
|
159
|
+
logger.debug(`${finalOptions.method ?? "GET"} ${resp.status} ${fullUrl}`);
|
|
116
160
|
|
|
117
161
|
// Handle HTTP errors
|
|
118
162
|
if (!resp.ok) {
|
|
119
163
|
// Try to parse error body if it's JSON
|
|
120
|
-
let errorBody;
|
|
164
|
+
let errorBody: { error?: { code?: string | number; message?: string } } | undefined;
|
|
121
165
|
try {
|
|
122
166
|
if (resp.headers.get("content-type")?.includes("application/json")) {
|
|
123
|
-
errorBody = await resp
|
|
167
|
+
errorBody = await safeJsonParse<typeof errorBody>(resp);
|
|
124
168
|
}
|
|
125
169
|
} catch {
|
|
126
170
|
// Ignore JSON parse errors
|
|
@@ -135,33 +179,19 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
135
179
|
if (errorCode === "303" || errorCode === 303) {
|
|
136
180
|
return {
|
|
137
181
|
data: undefined,
|
|
138
|
-
error: new SchemaLockedError(
|
|
139
|
-
fullUrl,
|
|
140
|
-
errorMessage,
|
|
141
|
-
errorBody.error,
|
|
142
|
-
),
|
|
182
|
+
error: new SchemaLockedError(fullUrl, errorMessage, errorBody.error),
|
|
143
183
|
};
|
|
144
184
|
}
|
|
145
185
|
|
|
146
186
|
return {
|
|
147
187
|
data: undefined,
|
|
148
|
-
error: new ODataError(
|
|
149
|
-
fullUrl,
|
|
150
|
-
errorMessage,
|
|
151
|
-
errorCode,
|
|
152
|
-
errorBody.error,
|
|
153
|
-
),
|
|
188
|
+
error: new ODataError(fullUrl, errorMessage, String(errorCode), errorBody.error),
|
|
154
189
|
};
|
|
155
190
|
}
|
|
156
191
|
|
|
157
192
|
return {
|
|
158
193
|
data: undefined,
|
|
159
|
-
error: new HTTPError(
|
|
160
|
-
fullUrl,
|
|
161
|
-
resp.status,
|
|
162
|
-
resp.statusText,
|
|
163
|
-
errorBody,
|
|
164
|
-
),
|
|
194
|
+
error: new HTTPError(fullUrl, resp.status, resp.statusText, errorBody),
|
|
165
195
|
};
|
|
166
196
|
}
|
|
167
197
|
|
|
@@ -169,15 +199,14 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
169
199
|
// FileMaker may return this with 204 No Content or 200 OK
|
|
170
200
|
const affectedRows = resp.headers.get("fmodata.affected_rows");
|
|
171
201
|
if (affectedRows !== null) {
|
|
172
|
-
return { data: parseInt(affectedRows, 10) as T, error: undefined };
|
|
202
|
+
return { data: Number.parseInt(affectedRows, 10) as T, error: undefined };
|
|
173
203
|
}
|
|
174
204
|
|
|
175
205
|
// Handle 204 No Content with no body
|
|
176
206
|
if (resp.status === 204) {
|
|
177
207
|
// Check for Location header (used for insert with return=minimal)
|
|
178
208
|
// Use optional chaining for safety with mocks that might not have proper headers
|
|
179
|
-
const locationHeader =
|
|
180
|
-
resp.headers?.get?.("Location") || resp.headers?.get?.("location");
|
|
209
|
+
const locationHeader = resp.headers?.get?.("Location") || resp.headers?.get?.("location");
|
|
181
210
|
if (locationHeader) {
|
|
182
211
|
// Return the location header so InsertBuilder can extract ROWID
|
|
183
212
|
return { data: { _location: locationHeader } as T, error: undefined };
|
|
@@ -187,12 +216,12 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
187
216
|
|
|
188
217
|
// Parse response
|
|
189
218
|
if (resp.headers.get("content-type")?.includes("application/json")) {
|
|
190
|
-
const data = await resp
|
|
219
|
+
const data = await safeJsonParse<T & { error?: { code?: string | number; message?: string } }>(resp);
|
|
191
220
|
|
|
192
221
|
// Check for embedded OData errors
|
|
193
|
-
if (data
|
|
194
|
-
const errorCode = data
|
|
195
|
-
const errorMessage = data
|
|
222
|
+
if (get(data, "error", null)) {
|
|
223
|
+
const errorCode = get(data, "error.code", null);
|
|
224
|
+
const errorMessage = get(data, "error.message", "Unknown OData error");
|
|
196
225
|
|
|
197
226
|
// Check for schema locked error (code 303)
|
|
198
227
|
if (errorCode === "303" || errorCode === 303) {
|
|
@@ -204,7 +233,7 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
204
233
|
|
|
205
234
|
return {
|
|
206
235
|
data: undefined,
|
|
207
|
-
error: new ODataError(fullUrl, errorMessage, errorCode, data.error),
|
|
236
|
+
error: new ODataError(fullUrl, errorMessage, String(errorCode), data.error),
|
|
208
237
|
};
|
|
209
238
|
}
|
|
210
239
|
|
|
@@ -224,6 +253,11 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
224
253
|
return { data: undefined, error: err };
|
|
225
254
|
}
|
|
226
255
|
|
|
256
|
+
// Handle JSON parse errors (ResponseParseError from safeJsonParse)
|
|
257
|
+
if (err instanceof ResponseParseError) {
|
|
258
|
+
return { data: undefined, error: err };
|
|
259
|
+
}
|
|
260
|
+
|
|
227
261
|
// Unknown error - wrap it as NetworkError
|
|
228
262
|
return {
|
|
229
263
|
data: undefined,
|
|
@@ -232,16 +266,14 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
232
266
|
}
|
|
233
267
|
}
|
|
234
268
|
|
|
235
|
-
database<
|
|
236
|
-
const Occurrences extends readonly TableOccurrence<any, any, any, any>[],
|
|
237
|
-
>(
|
|
269
|
+
database<IncludeSpecialColumns extends boolean = false>(
|
|
238
270
|
name: string,
|
|
239
271
|
config?: {
|
|
240
|
-
occurrences?: Occurrences | undefined;
|
|
241
272
|
useEntityIds?: boolean;
|
|
273
|
+
includeSpecialColumns?: IncludeSpecialColumns;
|
|
242
274
|
},
|
|
243
|
-
): Database<
|
|
244
|
-
return new Database(name, this, config);
|
|
275
|
+
): Database<IncludeSpecialColumns> {
|
|
276
|
+
return new Database<IncludeSpecialColumns>(name, this, config);
|
|
245
277
|
}
|
|
246
278
|
|
|
247
279
|
/**
|
|
@@ -251,7 +283,7 @@ export class FMServerConnection implements ExecutionContext {
|
|
|
251
283
|
async listDatabaseNames(): Promise<string[]> {
|
|
252
284
|
const result = await this._makeRequest<{
|
|
253
285
|
value?: Array<{ name: string }>;
|
|
254
|
-
}>("/");
|
|
286
|
+
}>("/$metadata", { headers: { Accept: "application/json" } });
|
|
255
287
|
if (result.error) {
|
|
256
288
|
throw result.error;
|
|
257
289
|
}
|