@proofkit/fmodata 0.1.0-alpha.6 → 0.1.0-alpha.7
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 +333 -3
- 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 +43 -11
- 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 +15 -4
- package/dist/esm/client/entity-set.js +23 -7
- 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 +19 -3
- package/dist/esm/client/query-builder.js +193 -17
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +17 -2
- 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/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 +3 -2
- package/dist/esm/index.js +3 -1
- 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/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +106 -52
- package/src/client/delete-builder.ts +116 -14
- package/src/client/entity-set.ts +80 -6
- package/src/client/filemaker-odata.ts +65 -19
- package/src/client/insert-builder.ts +296 -18
- package/src/client/query-builder.ts +278 -17
- package/src/client/record-builder.ts +119 -11
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/update-builder.ts +195 -37
- package/src/errors.ts +33 -1
- package/src/index.ts +13 -0
- package/src/transform.ts +19 -6
- package/src/types.ts +89 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, ODataRecordMetadata, InferSchemaType } from '../types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, ODataRecordMetadata, InferSchemaType, ExecuteOptions } from '../types.js';
|
|
2
2
|
import { TableOccurrence } from './table-occurrence.js';
|
|
3
3
|
import { BaseTable } from './base-table.js';
|
|
4
4
|
import { QueryBuilder } from './query-builder.js';
|
|
@@ -19,13 +19,24 @@ export declare class RecordBuilder<T extends Record<string, any>, IsSingleField
|
|
|
19
19
|
private isNavigateFromEntitySet?;
|
|
20
20
|
private navigateRelation?;
|
|
21
21
|
private navigateSourceTableName?;
|
|
22
|
+
private databaseUseEntityIds;
|
|
22
23
|
constructor(config: {
|
|
23
24
|
occurrence?: Occ;
|
|
24
25
|
tableName: string;
|
|
25
26
|
databaseName: string;
|
|
26
27
|
context: ExecutionContext;
|
|
27
28
|
recordId: string | number;
|
|
29
|
+
databaseUseEntityIds?: boolean;
|
|
28
30
|
});
|
|
31
|
+
/**
|
|
32
|
+
* Helper to merge database-level useEntityIds with per-request options
|
|
33
|
+
*/
|
|
34
|
+
private mergeExecuteOptions;
|
|
35
|
+
/**
|
|
36
|
+
* Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
|
|
37
|
+
* @param useEntityIds - Optional override for entity ID usage
|
|
38
|
+
*/
|
|
39
|
+
private getTableId;
|
|
29
40
|
getSingleField<K extends keyof T>(field: K): RecordBuilder<T, true, K, Occ>;
|
|
30
41
|
navigate<RelationName extends ExtractNavigationNames<Occ>>(relationName: RelationName): QueryBuilder<ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>> extends Record<string, StandardSchemaV1> ? InferSchemaType<ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>> : Record<string, any>>;
|
|
31
42
|
navigate(relationName: string): QueryBuilder<{
|
|
@@ -33,11 +44,15 @@ export declare class RecordBuilder<T extends Record<string, any>, IsSingleField
|
|
|
33
44
|
ROWMODID: number;
|
|
34
45
|
[key: string]: any;
|
|
35
46
|
}>;
|
|
36
|
-
execute(options?: RequestInit & FFetchOptions
|
|
47
|
+
execute(options?: RequestInit & FFetchOptions & {
|
|
48
|
+
useEntityIds?: boolean;
|
|
49
|
+
}): Promise<Result<IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata>>;
|
|
37
50
|
getRequestConfig(): {
|
|
38
51
|
method: string;
|
|
39
52
|
url: string;
|
|
40
53
|
body?: any;
|
|
41
54
|
};
|
|
55
|
+
toRequest(baseUrl: string): Request;
|
|
56
|
+
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata>>;
|
|
42
57
|
}
|
|
43
58
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
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
|
-
import { transformTableName, transformResponseFields } from "../transform.js";
|
|
4
|
+
import { getTableIdentifiers, transformTableName, transformResponseFields } from "../transform.js";
|
|
5
5
|
import { QueryBuilder } from "./query-builder.js";
|
|
6
6
|
import { validateSingleResponse } from "../validation.js";
|
|
7
7
|
class RecordBuilder {
|
|
@@ -16,11 +16,44 @@ class RecordBuilder {
|
|
|
16
16
|
__publicField(this, "isNavigateFromEntitySet");
|
|
17
17
|
__publicField(this, "navigateRelation");
|
|
18
18
|
__publicField(this, "navigateSourceTableName");
|
|
19
|
+
__publicField(this, "databaseUseEntityIds");
|
|
19
20
|
this.occurrence = config.occurrence;
|
|
20
21
|
this.tableName = config.tableName;
|
|
21
22
|
this.databaseName = config.databaseName;
|
|
22
23
|
this.context = config.context;
|
|
23
24
|
this.recordId = config.recordId;
|
|
25
|
+
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Helper to merge database-level useEntityIds with per-request options
|
|
29
|
+
*/
|
|
30
|
+
mergeExecuteOptions(options) {
|
|
31
|
+
return {
|
|
32
|
+
...options,
|
|
33
|
+
useEntityIds: (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
|
|
38
|
+
* @param useEntityIds - Optional override for entity ID usage
|
|
39
|
+
*/
|
|
40
|
+
getTableId(useEntityIds) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
if (!this.occurrence) {
|
|
43
|
+
return this.tableName;
|
|
44
|
+
}
|
|
45
|
+
const contextDefault = ((_b = (_a = this.context)._getUseEntityIds) == null ? void 0 : _b.call(_a)) ?? false;
|
|
46
|
+
const shouldUseIds = useEntityIds ?? contextDefault;
|
|
47
|
+
if (shouldUseIds) {
|
|
48
|
+
const identifiers = getTableIdentifiers(this.occurrence);
|
|
49
|
+
if (!identifiers.id) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`useEntityIds is true but TableOccurrence "${identifiers.name}" does not have an fmtId defined`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return identifiers.id;
|
|
55
|
+
}
|
|
56
|
+
return this.occurrence.getTableName();
|
|
24
57
|
}
|
|
25
58
|
getSingleField(field) {
|
|
26
59
|
const newBuilder = new RecordBuilder({
|
|
@@ -66,13 +99,14 @@ class RecordBuilder {
|
|
|
66
99
|
if (this.isNavigateFromEntitySet && this.navigateSourceTableName && this.navigateRelation) {
|
|
67
100
|
url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;
|
|
68
101
|
} else {
|
|
69
|
-
const tableId = this.
|
|
102
|
+
const tableId = this.getTableId((options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds);
|
|
70
103
|
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
71
104
|
}
|
|
72
105
|
if (this.operation === "getSingleField" && this.operationParam) {
|
|
73
106
|
url += `/${this.operationParam}`;
|
|
74
107
|
}
|
|
75
|
-
const
|
|
108
|
+
const mergedOptions = this.mergeExecuteOptions(options);
|
|
109
|
+
const result = await this.context._makeRequest(url, mergedOptions);
|
|
76
110
|
if (result.error) {
|
|
77
111
|
return { data: void 0, error: result.error };
|
|
78
112
|
}
|
|
@@ -81,7 +115,8 @@ class RecordBuilder {
|
|
|
81
115
|
const fieldResponse = response;
|
|
82
116
|
return { data: fieldResponse.value, error: void 0 };
|
|
83
117
|
}
|
|
84
|
-
|
|
118
|
+
const shouldUseIds = mergedOptions.useEntityIds ?? false;
|
|
119
|
+
if (((_a = this.occurrence) == null ? void 0 : _a.baseTable) && shouldUseIds) {
|
|
85
120
|
response = transformResponseFields(
|
|
86
121
|
response,
|
|
87
122
|
this.occurrence.baseTable,
|
|
@@ -113,7 +148,7 @@ class RecordBuilder {
|
|
|
113
148
|
if (this.isNavigateFromEntitySet && this.navigateSourceTableName && this.navigateRelation) {
|
|
114
149
|
url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;
|
|
115
150
|
} else {
|
|
116
|
-
const tableId = this.
|
|
151
|
+
const tableId = this.getTableId(this.databaseUseEntityIds);
|
|
117
152
|
url = `/${this.databaseName}/${tableId}('${this.recordId}')`;
|
|
118
153
|
}
|
|
119
154
|
if (this.operation === "getSingleField" && this.operationParam) {
|
|
@@ -124,6 +159,53 @@ class RecordBuilder {
|
|
|
124
159
|
url
|
|
125
160
|
};
|
|
126
161
|
}
|
|
162
|
+
toRequest(baseUrl) {
|
|
163
|
+
const config = this.getRequestConfig();
|
|
164
|
+
const fullUrl = `${baseUrl}${config.url}`;
|
|
165
|
+
return new Request(fullUrl, {
|
|
166
|
+
method: config.method,
|
|
167
|
+
headers: {
|
|
168
|
+
"Content-Type": "application/json",
|
|
169
|
+
Accept: "application/json"
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
async processResponse(response, options) {
|
|
174
|
+
var _a, _b, _c;
|
|
175
|
+
const rawResponse = await response.json();
|
|
176
|
+
if (this.operation === "getSingleField") {
|
|
177
|
+
const fieldResponse = rawResponse;
|
|
178
|
+
return { data: fieldResponse.value, error: void 0 };
|
|
179
|
+
}
|
|
180
|
+
const shouldUseIds = (options == null ? void 0 : options.useEntityIds) ?? this.databaseUseEntityIds;
|
|
181
|
+
let transformedResponse = rawResponse;
|
|
182
|
+
if (((_a = this.occurrence) == null ? void 0 : _a.baseTable) && shouldUseIds) {
|
|
183
|
+
transformedResponse = transformResponseFields(
|
|
184
|
+
rawResponse,
|
|
185
|
+
this.occurrence.baseTable,
|
|
186
|
+
void 0
|
|
187
|
+
// No expand configs for simple get
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const schema = (_c = (_b = this.occurrence) == null ? void 0 : _b.baseTable) == null ? void 0 : _c.schema;
|
|
191
|
+
const validation = await validateSingleResponse(
|
|
192
|
+
transformedResponse,
|
|
193
|
+
schema,
|
|
194
|
+
void 0,
|
|
195
|
+
// No selected fields for record.get()
|
|
196
|
+
void 0,
|
|
197
|
+
// No expand configs
|
|
198
|
+
"exact"
|
|
199
|
+
// Expect exactly one record
|
|
200
|
+
);
|
|
201
|
+
if (!validation.valid) {
|
|
202
|
+
return { data: void 0, error: validation.error };
|
|
203
|
+
}
|
|
204
|
+
if (validation.data === null) {
|
|
205
|
+
return { data: null, error: void 0 };
|
|
206
|
+
}
|
|
207
|
+
return { data: validation.data, error: void 0 };
|
|
208
|
+
}
|
|
127
209
|
}
|
|
128
210
|
export {
|
|
129
211
|
RecordBuilder
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-builder.js","sources":["../../../src/client/record-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n ODataFieldResponse,\n InferSchemaType,\n} from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport type { BaseTable } from \"./base-table\";\nimport { transformTableName, transformResponseFields } from \"../transform\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { validateSingleResponse } from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\n// import type { z } from \"zod/v4\";\n\n// Helper type to extract schema from a TableOccurrence\ntype ExtractSchemaFromOccurrence<O> =\n O extends TableOccurrence<infer BT, any, any, any>\n ? BT extends BaseTable<infer S, any>\n ? S\n : never\n : never;\n\n// Helper type to extract navigation relation names from an occurrence\ntype ExtractNavigationNames<\n O extends TableOccurrence<any, any, any, any> | undefined,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Nav extends Record<string, any>\n ? keyof Nav\n : never\n : never;\n\n// Helper type to resolve a navigation item (handles both direct and lazy-loaded)\ntype ResolveNavigationItem<T> = T extends () => infer R ? R : T;\n\n// Helper type to find target occurrence by relation name\ntype FindNavigationTarget<\n O extends TableOccurrence<any, any, any, any> | undefined,\n Name extends string,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Name extends keyof Nav\n ? ResolveNavigationItem<Nav[Name]>\n : never\n : never;\n\nexport class RecordBuilder<\n T extends Record<string, any>,\n IsSingleField extends boolean = false,\n FieldKey extends keyof T = keyof T,\n Occ extends TableOccurrence<any, any, any, any> | undefined =\n | TableOccurrence<any, any, any, any>\n | undefined,\n> implements\n ExecutableBuilder<\n IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata\n >\n{\n private occurrence?: Occ;\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private recordId: string | number;\n private operation?: \"getSingleField\" | \"navigate\";\n private operationParam?: string;\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n\n constructor(config: {\n occurrence?: Occ;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n recordId: string | number;\n }) {\n this.occurrence = config.occurrence;\n this.tableName = config.tableName;\n this.databaseName = config.databaseName;\n this.context = config.context;\n this.recordId = config.recordId;\n }\n\n getSingleField<K extends keyof T>(field: K): RecordBuilder<T, true, K, Occ> {\n const newBuilder = new RecordBuilder<T, true, K, Occ>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n });\n newBuilder.operation = \"getSingleField\";\n newBuilder.operationParam = field.toString();\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n // Overload for valid relation names - returns typed QueryBuilder\n navigate<RelationName extends ExtractNavigationNames<Occ>>(\n relationName: RelationName,\n ): QueryBuilder<\n ExtractSchemaFromOccurrence<\n FindNavigationTarget<Occ, RelationName>\n > extends Record<string, StandardSchemaV1>\n ? InferSchemaType<\n ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>\n >\n : Record<string, any>\n >;\n // Overload for arbitrary strings - returns generic QueryBuilder with system fields\n navigate(\n relationName: string,\n ): QueryBuilder<{ ROWID: number; ROWMODID: number; [key: string]: any }>;\n // Implementation\n navigate(relationName: string): QueryBuilder<any> {\n // Use the target occurrence if available, otherwise allow untyped navigation\n // (useful when types might be incomplete)\n const targetOccurrence = this.occurrence?.navigation[relationName];\n const builder = new QueryBuilder<any>({\n occurrence: targetOccurrence,\n tableName: targetOccurrence?.name ?? relationName,\n databaseName: this.databaseName,\n context: this.context,\n });\n // Store the navigation info - we'll use it in execute\n // Transform relation name to FMTID if using entity IDs\n const relationId = targetOccurrence\n ? transformTableName(targetOccurrence)\n : relationName;\n\n (builder as any).isNavigate = true;\n (builder as any).navigateRecordId = this.recordId;\n (builder as any).navigateRelation = relationId;\n\n // If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // Build the base path: /sourceTable/relation('recordId')/newRelation\n (builder as any).navigateSourceTableName = this.navigateSourceTableName;\n (builder as any).navigateBaseRelation = this.navigateRelation;\n } else {\n // Normal record navigation: /tableName('recordId')/relation\n // Transform source table name to FMTID if using entity IDs\n const sourceTableId = this.occurrence\n ? transformTableName(this.occurrence)\n : this.tableName;\n (builder as any).navigateSourceTableName = sourceTableId;\n }\n\n return builder;\n }\n\n async execute(\n options?: RequestInit & FFetchOptions,\n ): Promise<\n Result<IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata>\n > {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.occurrence\n ? transformTableName(this.occurrence)\n : this.tableName;\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n }\n\n const result = await this.context._makeRequest(url, options);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n let response = result.data;\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = response as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\n }\n\n // Transform response field IDs back to names if using entity IDs\n if (this.occurrence?.baseTable) {\n response = transformResponseFields(\n response,\n this.occurrence.baseTable,\n undefined, // No expand configs for simple get\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n undefined, // No selected fields for record.get()\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n return { data: validation.data, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.occurrence\n ? transformTableName(this.occurrence)\n : this.tableName;\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n }\n\n return {\n method: \"GET\",\n url,\n };\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"record-builder.js","sources":["../../../src/client/record-builder.ts"],"sourcesContent":["import type {\n ExecutionContext,\n ExecutableBuilder,\n Result,\n ODataRecordMetadata,\n ODataFieldResponse,\n InferSchemaType,\n ExecuteOptions,\n} from \"../types\";\nimport type { TableOccurrence } from \"./table-occurrence\";\nimport type { BaseTable } from \"./base-table\";\nimport { transformTableName, transformResponseFields, getTableIdentifiers } from \"../transform\";\nimport { QueryBuilder } from \"./query-builder\";\nimport { validateSingleResponse } from \"../validation\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\n// import type { z } from \"zod/v4\";\n\n// Helper type to extract schema from a TableOccurrence\ntype ExtractSchemaFromOccurrence<O> =\n O extends TableOccurrence<infer BT, any, any, any>\n ? BT extends BaseTable<infer S, any>\n ? S\n : never\n : never;\n\n// Helper type to extract navigation relation names from an occurrence\ntype ExtractNavigationNames<\n O extends TableOccurrence<any, any, any, any> | undefined,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Nav extends Record<string, any>\n ? keyof Nav\n : never\n : never;\n\n// Helper type to resolve a navigation item (handles both direct and lazy-loaded)\ntype ResolveNavigationItem<T> = T extends () => infer R ? R : T;\n\n// Helper type to find target occurrence by relation name\ntype FindNavigationTarget<\n O extends TableOccurrence<any, any, any, any> | undefined,\n Name extends string,\n> =\n O extends TableOccurrence<any, any, infer Nav, any>\n ? Name extends keyof Nav\n ? ResolveNavigationItem<Nav[Name]>\n : never\n : never;\n\nexport class RecordBuilder<\n T extends Record<string, any>,\n IsSingleField extends boolean = false,\n FieldKey extends keyof T = keyof T,\n Occ extends TableOccurrence<any, any, any, any> | undefined =\n | TableOccurrence<any, any, any, any>\n | undefined,\n> implements\n ExecutableBuilder<\n IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata\n >\n{\n private occurrence?: Occ;\n private tableName: string;\n private databaseName: string;\n private context: ExecutionContext;\n private recordId: string | number;\n private operation?: \"getSingleField\" | \"navigate\";\n private operationParam?: string;\n private isNavigateFromEntitySet?: boolean;\n private navigateRelation?: string;\n private navigateSourceTableName?: string;\n\n private databaseUseEntityIds: boolean;\n\n constructor(config: {\n occurrence?: Occ;\n tableName: string;\n databaseName: string;\n context: ExecutionContext;\n recordId: string | number;\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.recordId = config.recordId;\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 * 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 getSingleField<K extends keyof T>(field: K): RecordBuilder<T, true, K, Occ> {\n const newBuilder = new RecordBuilder<T, true, K, Occ>({\n occurrence: this.occurrence,\n tableName: this.tableName,\n databaseName: this.databaseName,\n context: this.context,\n recordId: this.recordId,\n });\n newBuilder.operation = \"getSingleField\";\n newBuilder.operationParam = field.toString();\n // Preserve navigation context\n newBuilder.isNavigateFromEntitySet = this.isNavigateFromEntitySet;\n newBuilder.navigateRelation = this.navigateRelation;\n newBuilder.navigateSourceTableName = this.navigateSourceTableName;\n return newBuilder;\n }\n\n // Overload for valid relation names - returns typed QueryBuilder\n navigate<RelationName extends ExtractNavigationNames<Occ>>(\n relationName: RelationName,\n ): QueryBuilder<\n ExtractSchemaFromOccurrence<\n FindNavigationTarget<Occ, RelationName>\n > extends Record<string, StandardSchemaV1>\n ? InferSchemaType<\n ExtractSchemaFromOccurrence<FindNavigationTarget<Occ, RelationName>>\n >\n : Record<string, any>\n >;\n // Overload for arbitrary strings - returns generic QueryBuilder with system fields\n navigate(\n relationName: string,\n ): QueryBuilder<{ ROWID: number; ROWMODID: number; [key: string]: any }>;\n // Implementation\n navigate(relationName: string): QueryBuilder<any> {\n // Use the target occurrence if available, otherwise allow untyped navigation\n // (useful when types might be incomplete)\n const targetOccurrence = this.occurrence?.navigation[relationName];\n const builder = new QueryBuilder<any>({\n occurrence: targetOccurrence,\n tableName: targetOccurrence?.name ?? relationName,\n databaseName: this.databaseName,\n context: this.context,\n });\n // Store the navigation info - we'll use it in execute\n // Transform relation name to FMTID if using entity IDs\n const relationId = targetOccurrence\n ? transformTableName(targetOccurrence)\n : relationName;\n\n (builder as any).isNavigate = true;\n (builder as any).navigateRecordId = this.recordId;\n (builder as any).navigateRelation = relationId;\n\n // If this RecordBuilder came from a navigated EntitySet, we need to preserve that base path\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // Build the base path: /sourceTable/relation('recordId')/newRelation\n (builder as any).navigateSourceTableName = this.navigateSourceTableName;\n (builder as any).navigateBaseRelation = this.navigateRelation;\n } else {\n // Normal record navigation: /tableName('recordId')/relation\n // Transform source table name to FMTID if using entity IDs\n const sourceTableId = this.occurrence\n ? transformTableName(this.occurrence)\n : this.tableName;\n (builder as any).navigateSourceTableName = sourceTableId;\n }\n\n return builder;\n }\n\n async execute(\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<\n Result<IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata>\n > {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // Normal record: /tableName('recordId') - use FMTID if configured\n const tableId = this.getTableId(options?.useEntityIds ?? this.databaseUseEntityIds);\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n }\n\n const mergedOptions = this.mergeExecuteOptions(options);\n const result = await this.context._makeRequest(url, mergedOptions);\n\n if (result.error) {\n return { data: undefined, error: result.error };\n }\n\n let response = result.data;\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = response as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\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 = mergedOptions.useEntityIds ?? false;\n \n if (this.occurrence?.baseTable && shouldUseIds) {\n response = transformResponseFields(\n response,\n this.occurrence.baseTable,\n undefined, // No expand configs for simple get\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n response,\n schema,\n undefined, // No selected fields for record.get()\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n return { data: validation.data, error: undefined };\n }\n\n getRequestConfig(): { method: string; url: string; body?: any } {\n let url: string;\n\n // Build the base URL depending on whether this came from a navigated EntitySet\n if (\n this.isNavigateFromEntitySet &&\n this.navigateSourceTableName &&\n this.navigateRelation\n ) {\n // From navigated EntitySet: /sourceTable/relation('recordId')\n url = `/${this.databaseName}/${this.navigateSourceTableName}/${this.navigateRelation}('${this.recordId}')`;\n } else {\n // For batch operations, use database-level setting (no per-request override available here)\n const tableId = this.getTableId(this.databaseUseEntityIds);\n url = `/${this.databaseName}/${tableId}('${this.recordId}')`;\n }\n\n if (this.operation === \"getSingleField\" && this.operationParam) {\n url += `/${this.operationParam}`;\n }\n\n return {\n method: \"GET\",\n url,\n };\n }\n\n toRequest(baseUrl: string): Request {\n const config = this.getRequestConfig();\n const fullUrl = `${baseUrl}${config.url}`;\n\n return new Request(fullUrl, {\n method: config.method,\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<\n Result<IsSingleField extends true ? T[FieldKey] : T & ODataRecordMetadata>\n > {\n const rawResponse = await response.json();\n\n // Handle single field operation\n if (this.operation === \"getSingleField\") {\n // Single field returns a JSON object with @context and value\n const fieldResponse = rawResponse as ODataFieldResponse<T>;\n return { data: fieldResponse.value as any, error: undefined };\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 simple get\n );\n }\n\n // Get schema from occurrence if available\n const schema = this.occurrence?.baseTable?.schema;\n\n // Validate the single record response\n const validation = await validateSingleResponse<any>(\n transformedResponse,\n schema,\n undefined, // No selected fields for record.get()\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\n if (validation.data === null) {\n return { data: null as any, error: undefined };\n }\n\n return { data: validation.data, error: undefined };\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { BaseTable } from './base-table.js';
|
|
3
|
+
import { ExecuteOptions } from '../types.js';
|
|
4
|
+
import { ExpandValidationConfig } from '../validation.js';
|
|
5
|
+
import { ValidationError, ResponseStructureError } from '../errors.js';
|
|
6
|
+
export type ODataResponse<T = unknown> = T & {
|
|
7
|
+
"@odata.context"?: string;
|
|
8
|
+
"@odata.count"?: number;
|
|
9
|
+
};
|
|
10
|
+
export type ODataListResponse<T = unknown> = ODataResponse<{
|
|
11
|
+
value: T[];
|
|
12
|
+
}>;
|
|
13
|
+
export type ODataRecordResponse<T = unknown> = ODataResponse<T & {
|
|
14
|
+
"@id"?: string;
|
|
15
|
+
"@editLink"?: string;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Strip OData annotations from a single record
|
|
19
|
+
*/
|
|
20
|
+
export declare function stripODataAnnotations<T extends Record<string, unknown>>(record: ODataRecordResponse<T>, options?: ExecuteOptions): T;
|
|
21
|
+
/**
|
|
22
|
+
* Transform field IDs back to names using the base table configuration
|
|
23
|
+
*/
|
|
24
|
+
export declare function applyFieldTransformation<T extends Record<string, unknown>>(response: ODataResponse<T> | ODataListResponse<T>, baseTable: BaseTable<Record<string, StandardSchemaV1>, any, any, any>, expandConfigs?: ExpandValidationConfig[]): ODataResponse<T> | ODataListResponse<T>;
|
|
25
|
+
/**
|
|
26
|
+
* Apply schema validation and transformation to data
|
|
27
|
+
*/
|
|
28
|
+
export declare function applyValidation<T extends Record<string, unknown>>(data: T | T[], schema?: Record<string, StandardSchemaV1>, selectedFields?: (keyof T)[], expandConfigs?: ExpandValidationConfig[]): Promise<{
|
|
29
|
+
valid: true;
|
|
30
|
+
data: T | T[];
|
|
31
|
+
} | {
|
|
32
|
+
valid: false;
|
|
33
|
+
error: ValidationError | ResponseStructureError;
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* Extract value array from OData list response, or wrap single record in array
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractListValue<T>(response: ODataListResponse<T> | ODataRecordResponse<T>): T[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
2
|
+
import { ExecutionContext } from '../types.js';
|
|
3
|
+
type GenericField = {
|
|
4
|
+
name: string;
|
|
5
|
+
nullable?: boolean;
|
|
6
|
+
primary?: boolean;
|
|
7
|
+
unique?: boolean;
|
|
8
|
+
global?: boolean;
|
|
9
|
+
repetitions?: number;
|
|
10
|
+
};
|
|
11
|
+
type StringField = GenericField & {
|
|
12
|
+
type: "string";
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
default?: "USER" | "USERNAME" | "CURRENT_USER";
|
|
15
|
+
};
|
|
16
|
+
type NumericField = GenericField & {
|
|
17
|
+
type: "numeric";
|
|
18
|
+
};
|
|
19
|
+
type DateField = GenericField & {
|
|
20
|
+
type: "date";
|
|
21
|
+
default?: "CURRENT_DATE" | "CURDATE";
|
|
22
|
+
};
|
|
23
|
+
type TimeField = GenericField & {
|
|
24
|
+
type: "time";
|
|
25
|
+
default?: "CURRENT_TIME" | "CURTIME";
|
|
26
|
+
};
|
|
27
|
+
type TimestampField = GenericField & {
|
|
28
|
+
type: "timestamp";
|
|
29
|
+
default?: "CURRENT_TIMESTAMP" | "CURTIMESTAMP";
|
|
30
|
+
};
|
|
31
|
+
type ContainerField = GenericField & {
|
|
32
|
+
type: "container";
|
|
33
|
+
externalSecurePath?: string;
|
|
34
|
+
};
|
|
35
|
+
export type Field = StringField | NumericField | DateField | TimeField | TimestampField | ContainerField;
|
|
36
|
+
export type { StringField, NumericField, DateField, TimeField, TimestampField, ContainerField, };
|
|
37
|
+
type FileMakerField = Omit<Field, "type" | "repetitions" | "maxLength"> & {
|
|
38
|
+
type: string;
|
|
39
|
+
};
|
|
40
|
+
type TableDefinition = {
|
|
41
|
+
tableName: string;
|
|
42
|
+
fields: FileMakerField[];
|
|
43
|
+
};
|
|
44
|
+
export declare class SchemaManager {
|
|
45
|
+
private readonly databaseName;
|
|
46
|
+
private readonly context;
|
|
47
|
+
constructor(databaseName: string, context: ExecutionContext);
|
|
48
|
+
createTable(tableName: string, fields: Field[], options?: RequestInit & FFetchOptions): Promise<TableDefinition>;
|
|
49
|
+
addFields(tableName: string, fields: Field[], options?: RequestInit & FFetchOptions): Promise<TableDefinition>;
|
|
50
|
+
deleteTable(tableName: string, options?: RequestInit & FFetchOptions): Promise<void>;
|
|
51
|
+
deleteField(tableName: string, fieldName: string, options?: RequestInit & FFetchOptions): Promise<void>;
|
|
52
|
+
createIndex(tableName: string, fieldName: string, options?: RequestInit & FFetchOptions): Promise<{
|
|
53
|
+
indexName: string;
|
|
54
|
+
}>;
|
|
55
|
+
deleteIndex(tableName: string, fieldName: string, options?: RequestInit & FFetchOptions): Promise<void>;
|
|
56
|
+
private static compileFieldDefinition;
|
|
57
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
class SchemaManager {
|
|
2
|
+
constructor(databaseName, context) {
|
|
3
|
+
this.databaseName = databaseName;
|
|
4
|
+
this.context = context;
|
|
5
|
+
}
|
|
6
|
+
async createTable(tableName, fields, options) {
|
|
7
|
+
const result = await this.context._makeRequest(
|
|
8
|
+
`/${this.databaseName}/FileMaker_Tables`,
|
|
9
|
+
{
|
|
10
|
+
method: "POST",
|
|
11
|
+
body: JSON.stringify({
|
|
12
|
+
tableName,
|
|
13
|
+
fields: fields.map(SchemaManager.compileFieldDefinition)
|
|
14
|
+
}),
|
|
15
|
+
...options
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
if (result.error) {
|
|
19
|
+
throw result.error;
|
|
20
|
+
}
|
|
21
|
+
return result.data;
|
|
22
|
+
}
|
|
23
|
+
async addFields(tableName, fields, options) {
|
|
24
|
+
const result = await this.context._makeRequest(
|
|
25
|
+
`/${this.databaseName}/FileMaker_Tables/${tableName}`,
|
|
26
|
+
{
|
|
27
|
+
method: "PATCH",
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
fields: fields.map(SchemaManager.compileFieldDefinition)
|
|
30
|
+
}),
|
|
31
|
+
...options
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
if (result.error) {
|
|
35
|
+
throw result.error;
|
|
36
|
+
}
|
|
37
|
+
return result.data;
|
|
38
|
+
}
|
|
39
|
+
async deleteTable(tableName, options) {
|
|
40
|
+
const result = await this.context._makeRequest(
|
|
41
|
+
`/${this.databaseName}/FileMaker_Tables/${tableName}`,
|
|
42
|
+
{ method: "DELETE", ...options }
|
|
43
|
+
);
|
|
44
|
+
if (result.error) {
|
|
45
|
+
throw result.error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async deleteField(tableName, fieldName, options) {
|
|
49
|
+
const result = await this.context._makeRequest(
|
|
50
|
+
`/${this.databaseName}/FileMaker_Tables/${tableName}/${fieldName}`,
|
|
51
|
+
{
|
|
52
|
+
method: "DELETE",
|
|
53
|
+
...options
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
if (result.error) {
|
|
57
|
+
throw result.error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async createIndex(tableName, fieldName, options) {
|
|
61
|
+
const result = await this.context._makeRequest(
|
|
62
|
+
`/${this.databaseName}/FileMaker_Indexes/${tableName}`,
|
|
63
|
+
{
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: JSON.stringify({ indexName: fieldName }),
|
|
66
|
+
...options
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
if (result.error) {
|
|
70
|
+
throw result.error;
|
|
71
|
+
}
|
|
72
|
+
return result.data;
|
|
73
|
+
}
|
|
74
|
+
async deleteIndex(tableName, fieldName, options) {
|
|
75
|
+
const result = await this.context._makeRequest(
|
|
76
|
+
`/${this.databaseName}/FileMaker_Indexes/${tableName}/${fieldName}`,
|
|
77
|
+
{
|
|
78
|
+
method: "DELETE",
|
|
79
|
+
...options
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
if (result.error) {
|
|
83
|
+
throw result.error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
static compileFieldDefinition(field) {
|
|
87
|
+
let type = field.type;
|
|
88
|
+
const repetitions = field.repetitions;
|
|
89
|
+
if (field.type === "string") {
|
|
90
|
+
type = "varchar";
|
|
91
|
+
const stringField = field;
|
|
92
|
+
if (stringField.maxLength !== void 0) {
|
|
93
|
+
type += `(${stringField.maxLength})`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (repetitions !== void 0) {
|
|
97
|
+
type += `[${repetitions}]`;
|
|
98
|
+
}
|
|
99
|
+
const result = {
|
|
100
|
+
name: field.name,
|
|
101
|
+
type
|
|
102
|
+
};
|
|
103
|
+
if (field.nullable !== void 0) result.nullable = field.nullable;
|
|
104
|
+
if (field.primary !== void 0) result.primary = field.primary;
|
|
105
|
+
if (field.unique !== void 0) result.unique = field.unique;
|
|
106
|
+
if (field.global !== void 0) result.global = field.global;
|
|
107
|
+
if (field.type === "string") {
|
|
108
|
+
const stringField = field;
|
|
109
|
+
if (stringField.default !== void 0)
|
|
110
|
+
result.default = stringField.default;
|
|
111
|
+
} else if (field.type === "date") {
|
|
112
|
+
const dateField = field;
|
|
113
|
+
if (dateField.default !== void 0) result.default = dateField.default;
|
|
114
|
+
} else if (field.type === "time") {
|
|
115
|
+
const timeField = field;
|
|
116
|
+
if (timeField.default !== void 0) result.default = timeField.default;
|
|
117
|
+
} else if (field.type === "timestamp") {
|
|
118
|
+
const timestampField = field;
|
|
119
|
+
if (timestampField.default !== void 0)
|
|
120
|
+
result.default = timestampField.default;
|
|
121
|
+
} else if (field.type === "container") {
|
|
122
|
+
const containerField = field;
|
|
123
|
+
if (containerField.externalSecurePath !== void 0)
|
|
124
|
+
result.externalSecurePath = containerField.externalSecurePath;
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
SchemaManager
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=schema-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-manager.js","sources":["../../../src/client/schema-manager.ts"],"sourcesContent":["import type { FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { ExecutionContext } from \"../types\";\n\ntype GenericField = {\n name: string;\n nullable?: boolean;\n primary?: boolean;\n unique?: boolean;\n global?: boolean;\n repetitions?: number;\n};\n\ntype StringField = GenericField & {\n type: \"string\";\n maxLength?: number;\n default?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n};\n\ntype NumericField = GenericField & {\n type: \"numeric\";\n};\n\ntype DateField = GenericField & {\n type: \"date\";\n default?: \"CURRENT_DATE\" | \"CURDATE\";\n};\n\ntype TimeField = GenericField & {\n type: \"time\";\n default?: \"CURRENT_TIME\" | \"CURTIME\";\n};\n\ntype TimestampField = GenericField & {\n type: \"timestamp\";\n default?: \"CURRENT_TIMESTAMP\" | \"CURTIMESTAMP\";\n};\n\ntype ContainerField = GenericField & {\n type: \"container\";\n externalSecurePath?: string;\n};\n\nexport type Field =\n | StringField\n | NumericField\n | DateField\n | TimeField\n | TimestampField\n | ContainerField;\n\nexport type {\n StringField,\n NumericField,\n DateField,\n TimeField,\n TimestampField,\n ContainerField,\n};\n\ntype FileMakerField = Omit<Field, \"type\" | \"repetitions\" | \"maxLength\"> & {\n type: string;\n};\n\ntype TableDefinition = {\n tableName: string;\n fields: FileMakerField[];\n};\n\nexport class SchemaManager {\n public constructor(\n private readonly databaseName: string,\n private readonly context: ExecutionContext,\n ) {}\n\n public async createTable(\n tableName: string,\n fields: Field[],\n options?: RequestInit & FFetchOptions,\n ): Promise<TableDefinition> {\n const result = await this.context._makeRequest<TableDefinition>(\n `/${this.databaseName}/FileMaker_Tables`,\n {\n method: \"POST\",\n body: JSON.stringify({\n tableName,\n fields: fields.map(SchemaManager.compileFieldDefinition),\n }),\n ...options,\n },\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n public async addFields(\n tableName: string,\n fields: Field[],\n options?: RequestInit & FFetchOptions,\n ): Promise<TableDefinition> {\n const result = await this.context._makeRequest<TableDefinition>(\n `/${this.databaseName}/FileMaker_Tables/${tableName}`,\n {\n method: \"PATCH\",\n body: JSON.stringify({\n fields: fields.map(SchemaManager.compileFieldDefinition),\n }),\n ...options,\n },\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n public async deleteTable(\n tableName: string,\n options?: RequestInit & FFetchOptions,\n ): Promise<void> {\n const result = await this.context._makeRequest(\n `/${this.databaseName}/FileMaker_Tables/${tableName}`,\n { method: \"DELETE\", ...options },\n );\n\n if (result.error) {\n throw result.error;\n }\n }\n\n public async deleteField(\n tableName: string,\n fieldName: string,\n options?: RequestInit & FFetchOptions,\n ): Promise<void> {\n const result = await this.context._makeRequest(\n `/${this.databaseName}/FileMaker_Tables/${tableName}/${fieldName}`,\n {\n method: \"DELETE\",\n ...options,\n },\n );\n\n if (result.error) {\n throw result.error;\n }\n }\n\n public async createIndex(\n tableName: string,\n fieldName: string,\n options?: RequestInit & FFetchOptions,\n ): Promise<{ indexName: string }> {\n const result = await this.context._makeRequest<{ indexName: string }>(\n `/${this.databaseName}/FileMaker_Indexes/${tableName}`,\n {\n method: \"POST\",\n body: JSON.stringify({ indexName: fieldName }),\n ...options,\n },\n );\n\n if (result.error) {\n throw result.error;\n }\n\n return result.data;\n }\n\n public async deleteIndex(\n tableName: string,\n fieldName: string,\n options?: RequestInit & FFetchOptions,\n ): Promise<void> {\n const result = await this.context._makeRequest(\n `/${this.databaseName}/FileMaker_Indexes/${tableName}/${fieldName}`,\n {\n method: \"DELETE\",\n ...options,\n },\n );\n\n if (result.error) {\n throw result.error;\n }\n }\n\n private static compileFieldDefinition(field: Field): FileMakerField {\n let type: string = field.type;\n const repetitions = field.repetitions;\n\n // Handle string fields - convert to varchar and add maxLength if present\n if (field.type === \"string\") {\n type = \"varchar\";\n const stringField = field as StringField;\n if (stringField.maxLength !== undefined) {\n type += `(${stringField.maxLength})`;\n }\n }\n\n // Add repetitions suffix if present\n if (repetitions !== undefined) {\n type += `[${repetitions}]`;\n }\n\n // Build the result object, excluding type, maxLength, and repetitions\n const result: any = {\n name: field.name,\n type,\n };\n\n // Add optional properties that FileMaker expects\n if (field.nullable !== undefined) result.nullable = field.nullable;\n if (field.primary !== undefined) result.primary = field.primary;\n if (field.unique !== undefined) result.unique = field.unique;\n if (field.global !== undefined) result.global = field.global;\n\n // Add type-specific properties\n if (field.type === \"string\") {\n const stringField = field as StringField;\n if (stringField.default !== undefined)\n result.default = stringField.default;\n } else if (field.type === \"date\") {\n const dateField = field as DateField;\n if (dateField.default !== undefined) result.default = dateField.default;\n } else if (field.type === \"time\") {\n const timeField = field as TimeField;\n if (timeField.default !== undefined) result.default = timeField.default;\n } else if (field.type === \"timestamp\") {\n const timestampField = field as TimestampField;\n if (timestampField.default !== undefined)\n result.default = timestampField.default;\n } else if (field.type === \"container\") {\n const containerField = field as ContainerField;\n if (containerField.externalSecurePath !== undefined)\n result.externalSecurePath = containerField.externalSecurePath;\n }\n\n return result as FileMakerField;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExecutionContext, ExecutableBuilder, Result, WithSystemFields } from '../types.js';
|
|
1
|
+
import { ExecutionContext, ExecutableBuilder, Result, WithSystemFields, ExecuteOptions } from '../types.js';
|
|
2
2
|
import { TableOccurrence } from './table-occurrence.js';
|
|
3
3
|
import { BaseTable } from './base-table.js';
|
|
4
4
|
import { QueryBuilder } from './query-builder.js';
|
|
@@ -7,39 +7,43 @@ import { FFetchOptions } from '@fetchkit/ffetch';
|
|
|
7
7
|
* Initial update builder returned from EntitySet.update(data)
|
|
8
8
|
* Requires calling .byId() or .where() before .execute() is available
|
|
9
9
|
*/
|
|
10
|
-
export declare class UpdateBuilder<T extends Record<string, any>, BT extends BaseTable<any, any, any, any
|
|
10
|
+
export declare class UpdateBuilder<T extends Record<string, any>, BT extends BaseTable<any, any, any, any>, ReturnPreference extends "minimal" | "representation" = "minimal"> {
|
|
11
11
|
private tableName;
|
|
12
12
|
private databaseName;
|
|
13
13
|
private context;
|
|
14
14
|
private occurrence?;
|
|
15
15
|
private data;
|
|
16
|
+
private returnPreference;
|
|
17
|
+
private databaseUseEntityIds;
|
|
16
18
|
constructor(config: {
|
|
17
19
|
occurrence?: TableOccurrence<any, any, any, any>;
|
|
18
20
|
tableName: string;
|
|
19
21
|
databaseName: string;
|
|
20
22
|
context: ExecutionContext;
|
|
21
23
|
data: Partial<T>;
|
|
24
|
+
returnPreference: ReturnPreference;
|
|
25
|
+
databaseUseEntityIds?: boolean;
|
|
22
26
|
});
|
|
23
27
|
/**
|
|
24
28
|
* Update a single record by ID
|
|
25
|
-
* Returns
|
|
29
|
+
* Returns updated count by default, or full record if returnFullRecord was set to true
|
|
26
30
|
*/
|
|
27
|
-
byId(id: string | number): ExecutableUpdateBuilder<T, true>;
|
|
31
|
+
byId(id: string | number): ExecutableUpdateBuilder<T, true, ReturnPreference>;
|
|
28
32
|
/**
|
|
29
33
|
* Update records matching a filter query
|
|
30
|
-
* Returns
|
|
34
|
+
* Returns updated count by default, or full record if returnFullRecord was set to true
|
|
31
35
|
* @param fn Callback that receives a QueryBuilder for building the filter
|
|
32
36
|
*/
|
|
33
|
-
where(fn: (q: QueryBuilder<WithSystemFields<T>>) => QueryBuilder<WithSystemFields<T>>): ExecutableUpdateBuilder<T, true>;
|
|
37
|
+
where(fn: (q: QueryBuilder<WithSystemFields<T>>) => QueryBuilder<WithSystemFields<T>>): ExecutableUpdateBuilder<T, true, ReturnPreference>;
|
|
34
38
|
}
|
|
35
39
|
/**
|
|
36
40
|
* Executable update builder - has execute() method
|
|
37
41
|
* Returned after calling .byId() or .where()
|
|
38
|
-
*
|
|
42
|
+
* Can return either updated count or full record based on returnFullRecord option
|
|
39
43
|
*/
|
|
40
|
-
export declare class ExecutableUpdateBuilder<T extends Record<string, any>, IsByFilter extends boolean> implements ExecutableBuilder<{
|
|
44
|
+
export declare class ExecutableUpdateBuilder<T extends Record<string, any>, IsByFilter extends boolean, ReturnPreference extends "minimal" | "representation" = "minimal"> implements ExecutableBuilder<ReturnPreference extends "minimal" ? {
|
|
41
45
|
updatedCount: number;
|
|
42
|
-
}> {
|
|
46
|
+
} : T> {
|
|
43
47
|
private tableName;
|
|
44
48
|
private databaseName;
|
|
45
49
|
private context;
|
|
@@ -48,6 +52,8 @@ export declare class ExecutableUpdateBuilder<T extends Record<string, any>, IsBy
|
|
|
48
52
|
private mode;
|
|
49
53
|
private recordId?;
|
|
50
54
|
private queryBuilder?;
|
|
55
|
+
private returnPreference;
|
|
56
|
+
private databaseUseEntityIds;
|
|
51
57
|
constructor(config: {
|
|
52
58
|
occurrence?: TableOccurrence<any, any, any, any>;
|
|
53
59
|
tableName: string;
|
|
@@ -57,13 +63,30 @@ export declare class ExecutableUpdateBuilder<T extends Record<string, any>, IsBy
|
|
|
57
63
|
mode: "byId" | "byFilter";
|
|
58
64
|
recordId?: string | number;
|
|
59
65
|
queryBuilder?: QueryBuilder<any>;
|
|
66
|
+
returnPreference: ReturnPreference;
|
|
67
|
+
databaseUseEntityIds?: boolean;
|
|
60
68
|
});
|
|
61
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Helper to merge database-level useEntityIds with per-request options
|
|
71
|
+
*/
|
|
72
|
+
private mergeExecuteOptions;
|
|
73
|
+
/**
|
|
74
|
+
* Gets the table ID (FMTID) if using entity IDs, otherwise returns the table name
|
|
75
|
+
* @param useEntityIds - Optional override for entity ID usage
|
|
76
|
+
*/
|
|
77
|
+
private getTableId;
|
|
78
|
+
execute(options?: RequestInit & FFetchOptions & {
|
|
79
|
+
useEntityIds?: boolean;
|
|
80
|
+
}): Promise<Result<ReturnPreference extends "minimal" ? {
|
|
62
81
|
updatedCount: number;
|
|
63
|
-
}>>;
|
|
82
|
+
} : T>>;
|
|
64
83
|
getRequestConfig(): {
|
|
65
84
|
method: string;
|
|
66
85
|
url: string;
|
|
67
86
|
body?: any;
|
|
68
87
|
};
|
|
88
|
+
toRequest(baseUrl: string): Request;
|
|
89
|
+
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<ReturnPreference extends "minimal" ? {
|
|
90
|
+
updatedCount: number;
|
|
91
|
+
} : T>>;
|
|
69
92
|
}
|