@proofkit/fmodata 0.1.0-alpha.6 → 0.1.0-alpha.8
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 +376 -34
- package/dist/esm/client/base-table.d.ts +24 -29
- package/dist/esm/client/base-table.js +4 -7
- package/dist/esm/client/base-table.js.map +1 -1
- package/dist/esm/client/batch-builder.d.ts +54 -0
- package/dist/esm/client/batch-builder.js +179 -0
- package/dist/esm/client/batch-builder.js.map +1 -0
- package/dist/esm/client/batch-request.d.ts +61 -0
- package/dist/esm/client/batch-request.js +252 -0
- package/dist/esm/client/batch-request.js.map +1 -0
- package/dist/esm/client/database.d.ts +44 -12
- package/dist/esm/client/database.js +64 -10
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +21 -2
- package/dist/esm/client/delete-builder.js +76 -9
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +17 -6
- package/dist/esm/client/entity-set.js +26 -10
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +11 -5
- package/dist/esm/client/filemaker-odata.js +46 -14
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +38 -3
- package/dist/esm/client/insert-builder.js +195 -9
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +20 -4
- package/dist/esm/client/query-builder.js +195 -19
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +18 -3
- package/dist/esm/client/record-builder.js +87 -5
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +38 -0
- package/dist/esm/client/schema-manager.d.ts +57 -0
- package/dist/esm/client/schema-manager.js +132 -0
- package/dist/esm/client/schema-manager.js.map +1 -0
- package/dist/esm/client/table-occurrence.d.ts +25 -42
- package/dist/esm/client/table-occurrence.js +9 -17
- package/dist/esm/client/table-occurrence.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -11
- package/dist/esm/client/update-builder.js +119 -19
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +14 -1
- package/dist/esm/errors.js +26 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +5 -4
- package/dist/esm/index.js +7 -6
- package/dist/esm/transform.d.ts +9 -0
- package/dist/esm/transform.js +7 -0
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +69 -1
- package/package.json +1 -1
- package/src/client/base-table.ts +30 -36
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +110 -56
- package/src/client/delete-builder.ts +116 -14
- package/src/client/entity-set.ts +89 -12
- package/src/client/filemaker-odata.ts +65 -19
- package/src/client/insert-builder.ts +296 -18
- package/src/client/query-builder.ts +285 -18
- package/src/client/query-builder.ts.bak +1457 -0
- package/src/client/record-builder.ts +120 -12
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +41 -80
- package/src/client/update-builder.ts +195 -37
- package/src/errors.ts +33 -1
- package/src/index.ts +15 -3
- package/src/transform.ts +19 -6
- package/src/types.ts +89 -1
package/dist/esm/types.d.ts
CHANGED
|
@@ -13,11 +13,28 @@ export interface ExecutableBuilder<T> {
|
|
|
13
13
|
url: string;
|
|
14
14
|
body?: any;
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Convert this builder to a native Request object for batch processing.
|
|
18
|
+
* @param baseUrl - The base URL for the OData service
|
|
19
|
+
* @returns A native Request object
|
|
20
|
+
*/
|
|
21
|
+
toRequest(baseUrl: string): Request;
|
|
22
|
+
/**
|
|
23
|
+
* Process a raw Response object into a typed Result.
|
|
24
|
+
* This allows builders to apply their own validation and transformation logic.
|
|
25
|
+
* @param response - The native Response object from the batch operation
|
|
26
|
+
* @param options - Optional execution options (e.g., skipValidation, includeODataAnnotations)
|
|
27
|
+
* @returns A typed Result with the builder's expected return type
|
|
28
|
+
*/
|
|
29
|
+
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<T>>;
|
|
16
30
|
}
|
|
17
31
|
export interface ExecutionContext {
|
|
18
|
-
_makeRequest<T>(url: string, options?: RequestInit & FFetchOptions
|
|
32
|
+
_makeRequest<T>(url: string, options?: RequestInit & FFetchOptions & {
|
|
33
|
+
useEntityIds?: boolean;
|
|
34
|
+
}): Promise<Result<T>>;
|
|
19
35
|
_setUseEntityIds?(useEntityIds: boolean): void;
|
|
20
36
|
_getUseEntityIds?(): boolean;
|
|
37
|
+
_getBaseUrl?(): string;
|
|
21
38
|
}
|
|
22
39
|
export type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {
|
|
23
40
|
[K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output> ? Output : never;
|
|
@@ -70,6 +87,10 @@ export type UpdateData<BT> = BT extends import('./client/base-table.js').BaseTab
|
|
|
70
87
|
export type ExecuteOptions = {
|
|
71
88
|
includeODataAnnotations?: boolean;
|
|
72
89
|
skipValidation?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Overrides the default behavior of the database to use entity IDs (rather than field names) in THIS REQUEST ONLY
|
|
92
|
+
*/
|
|
93
|
+
useEntityIds?: boolean;
|
|
73
94
|
};
|
|
74
95
|
export type ConditionallyWithODataAnnotations<T, IncludeODataAnnotations extends boolean> = IncludeODataAnnotations extends true ? T & {
|
|
75
96
|
"@id": string;
|
|
@@ -80,4 +101,51 @@ export type ExtractSchemaFromOccurrence<Occ> = Occ extends {
|
|
|
80
101
|
schema: infer S;
|
|
81
102
|
};
|
|
82
103
|
} ? S extends Record<string, StandardSchemaV1> ? S : Record<string, StandardSchemaV1> : Record<string, StandardSchemaV1>;
|
|
104
|
+
export type GenericFieldMetadata = {
|
|
105
|
+
$Nullable?: boolean;
|
|
106
|
+
"@Index"?: boolean;
|
|
107
|
+
"@Calculation"?: boolean;
|
|
108
|
+
"@Summary"?: boolean;
|
|
109
|
+
"@Global"?: boolean;
|
|
110
|
+
"@Org.OData.Core.V1.Permissions"?: "Org.OData.Core.V1.Permission@Read";
|
|
111
|
+
};
|
|
112
|
+
export type StringFieldMetadata = GenericFieldMetadata & {
|
|
113
|
+
$Type: "Edm.String";
|
|
114
|
+
$DefaultValue?: "USER" | "USERNAME" | "CURRENT_USER";
|
|
115
|
+
$MaxLength?: number;
|
|
116
|
+
};
|
|
117
|
+
export type DecimalFieldMetadata = GenericFieldMetadata & {
|
|
118
|
+
$Type: "Edm.Decimal";
|
|
119
|
+
"@AutoGenerated"?: boolean;
|
|
120
|
+
};
|
|
121
|
+
export type DateFieldMetadata = GenericFieldMetadata & {
|
|
122
|
+
$Type: "Edm.Date";
|
|
123
|
+
$DefaultValue?: "CURDATE" | "CURRENT_DATE";
|
|
124
|
+
};
|
|
125
|
+
export type TimeOfDayFieldMetadata = GenericFieldMetadata & {
|
|
126
|
+
$Type: "Edm.TimeOfDay";
|
|
127
|
+
$DefaultValue?: "CURTIME" | "CURRENT_TIME";
|
|
128
|
+
};
|
|
129
|
+
export type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {
|
|
130
|
+
$Type: "Edm.Date";
|
|
131
|
+
$DefaultValue?: "CURTIMESTAMP" | "CURRENT_TIMESTAMP";
|
|
132
|
+
"@VersionId"?: boolean;
|
|
133
|
+
};
|
|
134
|
+
export type StreamFieldMetadata = {
|
|
135
|
+
$Type: "Edm.Stream";
|
|
136
|
+
$Nullable?: boolean;
|
|
137
|
+
"@EnclosedPath": string;
|
|
138
|
+
"@ExternalOpenPath": string;
|
|
139
|
+
"@ExternalSecurePath"?: string;
|
|
140
|
+
};
|
|
141
|
+
export type FieldMetadata = StringFieldMetadata | DecimalFieldMetadata | DateFieldMetadata | TimeOfDayFieldMetadata | DateTimeOffsetFieldMetadata | StreamFieldMetadata;
|
|
142
|
+
export type EntityType = {
|
|
143
|
+
$Kind: "EntityType";
|
|
144
|
+
$Key: string[];
|
|
145
|
+
} & Record<string, FieldMetadata>;
|
|
146
|
+
export type EntitySet = {
|
|
147
|
+
$Kind: "EntitySet";
|
|
148
|
+
$Type: string;
|
|
149
|
+
};
|
|
150
|
+
export type Metadata = Record<string, EntityType | EntitySet>;
|
|
83
151
|
export {};
|
package/package.json
CHANGED
package/src/client/base-table.ts
CHANGED
|
@@ -79,11 +79,15 @@ export class BaseTable<
|
|
|
79
79
|
idField?: IdField;
|
|
80
80
|
required?: Required;
|
|
81
81
|
readOnly?: ReadOnly;
|
|
82
|
+
fmfIds?: Record<string, `FMFID:${string}`>;
|
|
82
83
|
}) {
|
|
83
84
|
this.schema = config.schema;
|
|
84
85
|
this.idField = config.idField;
|
|
85
86
|
this.required = config.required;
|
|
86
87
|
this.readOnly = config.readOnly;
|
|
88
|
+
this.fmfIds = config.fmfIds as
|
|
89
|
+
| Record<keyof Schema, `FMFID:${string}`>
|
|
90
|
+
| undefined;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/**
|
|
@@ -124,49 +128,39 @@ export class BaseTable<
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
/**
|
|
127
|
-
*
|
|
128
|
-
* Use this class when you need to work with FileMaker's internal field identifiers.
|
|
131
|
+
* Creates a BaseTable with proper TypeScript type inference.
|
|
129
132
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @template Required - Additional field names to require on insert (beyond auto-inferred required fields)
|
|
133
|
-
* @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)
|
|
133
|
+
* This function should be used instead of `new BaseTable()` to ensure
|
|
134
|
+
* field names are properly typed throughout the library.
|
|
134
135
|
*
|
|
135
|
-
* @example
|
|
136
|
+
* @example Without entity IDs
|
|
136
137
|
* ```ts
|
|
137
|
-
*
|
|
138
|
+
* const users = defineBaseTable({
|
|
139
|
+
* schema: { id: z.string(), name: z.string() },
|
|
140
|
+
* idField: "id",
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
138
143
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* email: z.string().nullable(),
|
|
144
|
-
* },
|
|
144
|
+
* @example With entity IDs (FileMaker field IDs)
|
|
145
|
+
* ```ts
|
|
146
|
+
* const products = defineBaseTable({
|
|
147
|
+
* schema: { id: z.string(), name: z.string() },
|
|
145
148
|
* idField: "id",
|
|
146
|
-
* fmfIds: {
|
|
147
|
-
* id: "FMFID:1",
|
|
148
|
-
* name: "FMFID:2",
|
|
149
|
-
* email: "FMFID:3",
|
|
150
|
-
* },
|
|
149
|
+
* fmfIds: { id: "FMFID:1", name: "FMFID:2" },
|
|
151
150
|
* });
|
|
152
151
|
* ```
|
|
153
152
|
*/
|
|
154
|
-
export
|
|
155
|
-
Schema extends Record<string, StandardSchemaV1
|
|
153
|
+
export function defineBaseTable<
|
|
154
|
+
const Schema extends Record<string, StandardSchemaV1>,
|
|
156
155
|
IdField extends keyof Schema | undefined = undefined,
|
|
157
|
-
Required extends readonly (keyof Schema)[] = readonly [],
|
|
158
|
-
ReadOnly extends readonly (keyof Schema)[] = readonly [],
|
|
159
|
-
>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
readOnly?: ReadOnly;
|
|
168
|
-
}) {
|
|
169
|
-
super(config);
|
|
170
|
-
this.fmfIds = config.fmfIds;
|
|
171
|
-
}
|
|
156
|
+
const Required extends readonly (keyof Schema)[] = readonly [],
|
|
157
|
+
const ReadOnly extends readonly (keyof Schema)[] = readonly [],
|
|
158
|
+
>(config: {
|
|
159
|
+
schema: Schema;
|
|
160
|
+
idField?: IdField;
|
|
161
|
+
required?: Required;
|
|
162
|
+
readOnly?: ReadOnly;
|
|
163
|
+
fmfIds?: { [K in keyof Schema]: `FMFID:${string}` };
|
|
164
|
+
}): BaseTable<Schema, IdField, Required, ReadOnly> {
|
|
165
|
+
return new BaseTable(config);
|
|
172
166
|
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExecutableBuilder,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
Result,
|
|
5
|
+
ExecuteOptions,
|
|
6
|
+
} from "../types";
|
|
7
|
+
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
8
|
+
import {
|
|
9
|
+
formatBatchRequestFromNative,
|
|
10
|
+
parseBatchResponse,
|
|
11
|
+
type ParsedBatchResponse,
|
|
12
|
+
} from "./batch-request";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper type to extract result types from a tuple of ExecutableBuilders.
|
|
16
|
+
* Uses a mapped type which TypeScript 4.1+ can handle for tuples.
|
|
17
|
+
*/
|
|
18
|
+
type ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {
|
|
19
|
+
[K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Converts a ParsedBatchResponse to a native Response object
|
|
24
|
+
* @param parsed - The parsed batch response
|
|
25
|
+
* @returns A native Response object
|
|
26
|
+
*/
|
|
27
|
+
function parsedToResponse(parsed: ParsedBatchResponse): Response {
|
|
28
|
+
const headers = new Headers(parsed.headers);
|
|
29
|
+
|
|
30
|
+
// Handle null body
|
|
31
|
+
if (parsed.body === null || parsed.body === undefined) {
|
|
32
|
+
return new Response(null, {
|
|
33
|
+
status: parsed.status,
|
|
34
|
+
statusText: parsed.statusText,
|
|
35
|
+
headers,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Convert body to string if it's not already
|
|
40
|
+
const bodyString =
|
|
41
|
+
typeof parsed.body === "string" ? parsed.body : JSON.stringify(parsed.body);
|
|
42
|
+
|
|
43
|
+
// Handle 204 No Content status - it cannot have a body per HTTP spec
|
|
44
|
+
// If FileMaker returns 204 with a body, treat it as 200
|
|
45
|
+
let status = parsed.status;
|
|
46
|
+
if (status === 204 && bodyString && bodyString.trim() !== "") {
|
|
47
|
+
status = 200;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return new Response(status === 204 ? null : bodyString, {
|
|
51
|
+
status: status,
|
|
52
|
+
statusText: parsed.statusText,
|
|
53
|
+
headers,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Builder for batch operations that allows multiple queries to be executed together
|
|
59
|
+
* in a single transactional request.
|
|
60
|
+
*/
|
|
61
|
+
export class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]>
|
|
62
|
+
implements ExecutableBuilder<ExtractTupleTypes<Builders>>
|
|
63
|
+
{
|
|
64
|
+
private builders: ExecutableBuilder<any>[];
|
|
65
|
+
private readonly originalBuilders: Builders;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
builders: Builders,
|
|
69
|
+
private readonly databaseName: string,
|
|
70
|
+
private readonly context: ExecutionContext,
|
|
71
|
+
) {
|
|
72
|
+
// Convert readonly tuple to mutable array for dynamic additions
|
|
73
|
+
this.builders = [...builders];
|
|
74
|
+
// Store original tuple for type preservation
|
|
75
|
+
this.originalBuilders = builders;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Add a request to the batch dynamically.
|
|
80
|
+
* This allows building up batch operations programmatically.
|
|
81
|
+
*
|
|
82
|
+
* @param builder - An executable builder to add to the batch
|
|
83
|
+
* @returns This BatchBuilder for method chaining
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const batch = db.batch([]);
|
|
87
|
+
* batch.addRequest(db.from('contacts').list());
|
|
88
|
+
* batch.addRequest(db.from('users').list());
|
|
89
|
+
* const result = await batch.execute();
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
addRequest<T>(builder: ExecutableBuilder<T>): this {
|
|
93
|
+
this.builders.push(builder);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the request configuration for this batch operation.
|
|
99
|
+
* This is used internally by the execution system.
|
|
100
|
+
*/
|
|
101
|
+
getRequestConfig(): { method: string; url: string; body?: any } {
|
|
102
|
+
// Note: This method is kept for compatibility but batch operations
|
|
103
|
+
// should use execute() directly which handles the full Request/Response flow
|
|
104
|
+
return {
|
|
105
|
+
method: "POST",
|
|
106
|
+
url: `/${this.databaseName}/$batch`,
|
|
107
|
+
body: undefined, // Body is constructed in execute()
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
toRequest(baseUrl: string): Request {
|
|
112
|
+
// Batch operations are not designed to be nested, but we provide
|
|
113
|
+
// a basic implementation for interface compliance
|
|
114
|
+
const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;
|
|
115
|
+
return new Request(fullUrl, {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: {
|
|
118
|
+
"Content-Type": "multipart/mixed",
|
|
119
|
+
"OData-Version": "4.0",
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async processResponse(
|
|
125
|
+
response: Response,
|
|
126
|
+
options?: ExecuteOptions,
|
|
127
|
+
): Promise<Result<any>> {
|
|
128
|
+
// This should not typically be called for batch operations
|
|
129
|
+
// as they handle their own response processing
|
|
130
|
+
return {
|
|
131
|
+
data: undefined,
|
|
132
|
+
error: {
|
|
133
|
+
name: "NotImplementedError",
|
|
134
|
+
message: "Batch operations handle response processing internally",
|
|
135
|
+
timestamp: new Date(),
|
|
136
|
+
} as any,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Execute the batch operation.
|
|
142
|
+
*
|
|
143
|
+
* @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)
|
|
144
|
+
* @returns A tuple of results matching the input builders
|
|
145
|
+
*/
|
|
146
|
+
async execute<EO extends ExecuteOptions>(
|
|
147
|
+
options?: RequestInit & FFetchOptions & EO,
|
|
148
|
+
): Promise<Result<ExtractTupleTypes<Builders>>> {
|
|
149
|
+
const baseUrl = this.context._getBaseUrl?.();
|
|
150
|
+
if (!baseUrl) {
|
|
151
|
+
return {
|
|
152
|
+
data: undefined,
|
|
153
|
+
error: {
|
|
154
|
+
name: "ConfigurationError",
|
|
155
|
+
message:
|
|
156
|
+
"Base URL not available - execution context must implement _getBaseUrl()",
|
|
157
|
+
timestamp: new Date(),
|
|
158
|
+
} as any,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
// Convert builders to native Request objects
|
|
164
|
+
const requests: Request[] = this.builders.map((builder) =>
|
|
165
|
+
builder.toRequest(baseUrl),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Format batch request (automatically groups mutations into changesets)
|
|
169
|
+
const { body, boundary } = await formatBatchRequestFromNative(
|
|
170
|
+
requests,
|
|
171
|
+
baseUrl,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Execute the batch request
|
|
175
|
+
const response = await this.context._makeRequest<string>(
|
|
176
|
+
`/${this.databaseName}/$batch`,
|
|
177
|
+
{
|
|
178
|
+
...options,
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: {
|
|
181
|
+
...options?.headers,
|
|
182
|
+
"Content-Type": `multipart/mixed; boundary=${boundary}`,
|
|
183
|
+
"OData-Version": "4.0",
|
|
184
|
+
},
|
|
185
|
+
body,
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (response.error) {
|
|
190
|
+
return { data: undefined, error: response.error };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extract the actual boundary from the response
|
|
194
|
+
// FileMaker uses its own boundary, not the one we sent
|
|
195
|
+
const firstLine =
|
|
196
|
+
response.data.split("\r\n")[0] || response.data.split("\n")[0] || "";
|
|
197
|
+
const actualBoundary = firstLine.startsWith("--")
|
|
198
|
+
? firstLine.substring(2)
|
|
199
|
+
: boundary;
|
|
200
|
+
|
|
201
|
+
// Parse the multipart response
|
|
202
|
+
const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;
|
|
203
|
+
const parsedResponses = parseBatchResponse(
|
|
204
|
+
response.data,
|
|
205
|
+
contentTypeHeader,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// Check if we got the expected number of responses
|
|
209
|
+
if (parsedResponses.length !== this.builders.length) {
|
|
210
|
+
return {
|
|
211
|
+
data: undefined,
|
|
212
|
+
error: {
|
|
213
|
+
name: "BatchError",
|
|
214
|
+
message: `Expected ${this.builders.length} responses but got ${parsedResponses.length}`,
|
|
215
|
+
timestamp: new Date(),
|
|
216
|
+
} as any,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Process each response using the corresponding builder
|
|
221
|
+
// Build tuple by processing each builder in order
|
|
222
|
+
type ResultTuple = ExtractTupleTypes<Builders>;
|
|
223
|
+
|
|
224
|
+
// Process builders sequentially to preserve tuple order
|
|
225
|
+
const processedResults: any[] = [];
|
|
226
|
+
for (let i = 0; i < this.originalBuilders.length; i++) {
|
|
227
|
+
const builder = this.originalBuilders[i];
|
|
228
|
+
const parsed = parsedResponses[i];
|
|
229
|
+
|
|
230
|
+
if (!builder || !parsed) {
|
|
231
|
+
processedResults.push(undefined);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Convert parsed response to native Response
|
|
236
|
+
const nativeResponse = parsedToResponse(parsed);
|
|
237
|
+
|
|
238
|
+
// Let the builder process its own response
|
|
239
|
+
const result = await builder.processResponse(nativeResponse, options);
|
|
240
|
+
|
|
241
|
+
if (result.error) {
|
|
242
|
+
processedResults.push(undefined);
|
|
243
|
+
} else {
|
|
244
|
+
processedResults.push(result.data);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Use a type assertion that TypeScript will respect
|
|
249
|
+
// ExtractTupleTypes ensures this is a proper tuple type
|
|
250
|
+
return {
|
|
251
|
+
data: processedResults as unknown as ResultTuple,
|
|
252
|
+
error: undefined,
|
|
253
|
+
};
|
|
254
|
+
} catch (err) {
|
|
255
|
+
return {
|
|
256
|
+
data: undefined,
|
|
257
|
+
error: {
|
|
258
|
+
name: "BatchError",
|
|
259
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
260
|
+
timestamp: new Date(),
|
|
261
|
+
} as any,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|