@proofkit/fmodata 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +760 -69
- package/dist/esm/client/base-table.d.ts +120 -5
- package/dist/esm/client/base-table.js +43 -5
- 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/build-occurrences.d.ts +74 -0
- package/dist/esm/client/build-occurrences.js +31 -0
- package/dist/esm/client/build-occurrences.js.map +1 -0
- package/dist/esm/client/database.d.ts +55 -6
- package/dist/esm/client/database.js +118 -15
- 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 +96 -32
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +26 -12
- package/dist/esm/client/entity-set.js +43 -12
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +23 -4
- package/dist/esm/client/filemaker-odata.js +124 -29
- 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 +231 -34
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +28 -7
- package/dist/esm/client/query-builder.js +470 -212
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +96 -10
- package/dist/esm/client/record-builder.js +378 -39
- 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 +69 -8
- package/dist/esm/client/table-occurrence.js +35 -24
- 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 +135 -31
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +73 -0
- package/dist/esm/errors.js +148 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +13 -3
- package/dist/esm/index.js +29 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/transform.d.ts +65 -0
- package/dist/esm/transform.js +114 -0
- package/dist/esm/transform.js.map +1 -0
- package/dist/esm/types.d.ts +89 -5
- package/dist/esm/validation.d.ts +6 -3
- package/dist/esm/validation.js +104 -33
- package/dist/esm/validation.js.map +1 -1
- package/package.json +10 -1
- package/src/client/base-table.ts +161 -8
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/build-occurrences.ts +155 -0
- package/src/client/database.ts +175 -18
- package/src/client/delete-builder.ts +149 -48
- package/src/client/entity-set.ts +134 -28
- package/src/client/filemaker-odata.ts +179 -35
- package/src/client/insert-builder.ts +350 -40
- package/src/client/query-builder.ts +632 -244
- package/src/client/query-builder.ts.bak +1457 -0
- package/src/client/record-builder.ts +692 -68
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +107 -51
- package/src/client/update-builder.ts +235 -49
- package/src/errors.ts +217 -0
- package/src/index.ts +63 -6
- package/src/transform.ts +249 -0
- package/src/types.ts +201 -35
- package/src/validation.ts +120 -36
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import type { BaseTable } from "./base-table";
|
|
3
|
+
import type { ExecuteOptions } from "../types";
|
|
4
|
+
import type { ExpandValidationConfig } from "../validation";
|
|
5
|
+
import { ValidationError, ResponseStructureError } from "../errors";
|
|
6
|
+
import { transformResponseFields } from "../transform";
|
|
7
|
+
import { validateListResponse, validateRecord } from "../validation";
|
|
8
|
+
|
|
9
|
+
// Type for raw OData responses
|
|
10
|
+
export type ODataResponse<T = unknown> = T & {
|
|
11
|
+
"@odata.context"?: string;
|
|
12
|
+
"@odata.count"?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ODataListResponse<T = unknown> = ODataResponse<{
|
|
16
|
+
value: T[];
|
|
17
|
+
}>;
|
|
18
|
+
|
|
19
|
+
export type ODataRecordResponse<T = unknown> = ODataResponse<
|
|
20
|
+
T & {
|
|
21
|
+
"@id"?: string;
|
|
22
|
+
"@editLink"?: string;
|
|
23
|
+
}
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Strip OData annotations from a single record
|
|
28
|
+
*/
|
|
29
|
+
export function stripODataAnnotations<T extends Record<string, unknown>>(
|
|
30
|
+
record: ODataRecordResponse<T>,
|
|
31
|
+
options?: ExecuteOptions,
|
|
32
|
+
): T {
|
|
33
|
+
if (options?.includeODataAnnotations === true) {
|
|
34
|
+
return record as T;
|
|
35
|
+
}
|
|
36
|
+
const { "@id": _id, "@editLink": _editLink, ...rest } = record;
|
|
37
|
+
return rest as T;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Transform field IDs back to names using the base table configuration
|
|
42
|
+
*/
|
|
43
|
+
export function applyFieldTransformation<T extends Record<string, unknown>>(
|
|
44
|
+
response: ODataResponse<T> | ODataListResponse<T>,
|
|
45
|
+
baseTable: BaseTable<Record<string, StandardSchemaV1>, any, any, any>,
|
|
46
|
+
expandConfigs?: ExpandValidationConfig[],
|
|
47
|
+
): ODataResponse<T> | ODataListResponse<T> {
|
|
48
|
+
return transformResponseFields(response, baseTable, expandConfigs) as
|
|
49
|
+
| ODataResponse<T>
|
|
50
|
+
| ODataListResponse<T>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Apply schema validation and transformation to data
|
|
55
|
+
*/
|
|
56
|
+
export async function applyValidation<T extends Record<string, unknown>>(
|
|
57
|
+
data: T | T[],
|
|
58
|
+
schema?: Record<string, StandardSchemaV1>,
|
|
59
|
+
selectedFields?: (keyof T)[],
|
|
60
|
+
expandConfigs?: ExpandValidationConfig[],
|
|
61
|
+
): Promise<
|
|
62
|
+
| { valid: true; data: T | T[] }
|
|
63
|
+
| { valid: false; error: ValidationError | ResponseStructureError }
|
|
64
|
+
> {
|
|
65
|
+
if (Array.isArray(data)) {
|
|
66
|
+
// Validate as a list
|
|
67
|
+
const validation = await validateListResponse<T>(
|
|
68
|
+
{ value: data },
|
|
69
|
+
schema,
|
|
70
|
+
selectedFields as string[] | undefined,
|
|
71
|
+
expandConfigs,
|
|
72
|
+
);
|
|
73
|
+
if (!validation.valid) {
|
|
74
|
+
return { valid: false, error: validation.error };
|
|
75
|
+
}
|
|
76
|
+
return { valid: true, data: validation.data };
|
|
77
|
+
} else {
|
|
78
|
+
// Validate as a single record
|
|
79
|
+
const validation = await validateRecord<T>(
|
|
80
|
+
data,
|
|
81
|
+
schema,
|
|
82
|
+
selectedFields,
|
|
83
|
+
expandConfigs,
|
|
84
|
+
);
|
|
85
|
+
if (!validation.valid) {
|
|
86
|
+
return { valid: false, error: validation.error };
|
|
87
|
+
}
|
|
88
|
+
return { valid: true, data: validation.data };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract value array from OData list response, or wrap single record in array
|
|
94
|
+
*/
|
|
95
|
+
export function extractListValue<T>(
|
|
96
|
+
response: ODataListResponse<T> | ODataRecordResponse<T>,
|
|
97
|
+
): T[] {
|
|
98
|
+
if ("value" in response && Array.isArray(response.value)) {
|
|
99
|
+
return response.value;
|
|
100
|
+
}
|
|
101
|
+
// Single record responses return the record directly
|
|
102
|
+
return [response as T];
|
|
103
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { FFetchOptions } from "@fetchkit/ffetch";
|
|
2
|
+
import type { ExecutionContext } from "../types";
|
|
3
|
+
|
|
4
|
+
type GenericField = {
|
|
5
|
+
name: string;
|
|
6
|
+
nullable?: boolean;
|
|
7
|
+
primary?: boolean;
|
|
8
|
+
unique?: boolean;
|
|
9
|
+
global?: boolean;
|
|
10
|
+
repetitions?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type StringField = GenericField & {
|
|
14
|
+
type: "string";
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
default?: "USER" | "USERNAME" | "CURRENT_USER";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type NumericField = GenericField & {
|
|
20
|
+
type: "numeric";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type DateField = GenericField & {
|
|
24
|
+
type: "date";
|
|
25
|
+
default?: "CURRENT_DATE" | "CURDATE";
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type TimeField = GenericField & {
|
|
29
|
+
type: "time";
|
|
30
|
+
default?: "CURRENT_TIME" | "CURTIME";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type TimestampField = GenericField & {
|
|
34
|
+
type: "timestamp";
|
|
35
|
+
default?: "CURRENT_TIMESTAMP" | "CURTIMESTAMP";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type ContainerField = GenericField & {
|
|
39
|
+
type: "container";
|
|
40
|
+
externalSecurePath?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type Field =
|
|
44
|
+
| StringField
|
|
45
|
+
| NumericField
|
|
46
|
+
| DateField
|
|
47
|
+
| TimeField
|
|
48
|
+
| TimestampField
|
|
49
|
+
| ContainerField;
|
|
50
|
+
|
|
51
|
+
export type {
|
|
52
|
+
StringField,
|
|
53
|
+
NumericField,
|
|
54
|
+
DateField,
|
|
55
|
+
TimeField,
|
|
56
|
+
TimestampField,
|
|
57
|
+
ContainerField,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type FileMakerField = Omit<Field, "type" | "repetitions" | "maxLength"> & {
|
|
61
|
+
type: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type TableDefinition = {
|
|
65
|
+
tableName: string;
|
|
66
|
+
fields: FileMakerField[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export class SchemaManager {
|
|
70
|
+
public constructor(
|
|
71
|
+
private readonly databaseName: string,
|
|
72
|
+
private readonly context: ExecutionContext,
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
public async createTable(
|
|
76
|
+
tableName: string,
|
|
77
|
+
fields: Field[],
|
|
78
|
+
options?: RequestInit & FFetchOptions,
|
|
79
|
+
): Promise<TableDefinition> {
|
|
80
|
+
const result = await this.context._makeRequest<TableDefinition>(
|
|
81
|
+
`/${this.databaseName}/FileMaker_Tables`,
|
|
82
|
+
{
|
|
83
|
+
method: "POST",
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
tableName,
|
|
86
|
+
fields: fields.map(SchemaManager.compileFieldDefinition),
|
|
87
|
+
}),
|
|
88
|
+
...options,
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (result.error) {
|
|
93
|
+
throw result.error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result.data;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public async addFields(
|
|
100
|
+
tableName: string,
|
|
101
|
+
fields: Field[],
|
|
102
|
+
options?: RequestInit & FFetchOptions,
|
|
103
|
+
): Promise<TableDefinition> {
|
|
104
|
+
const result = await this.context._makeRequest<TableDefinition>(
|
|
105
|
+
`/${this.databaseName}/FileMaker_Tables/${tableName}`,
|
|
106
|
+
{
|
|
107
|
+
method: "PATCH",
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
fields: fields.map(SchemaManager.compileFieldDefinition),
|
|
110
|
+
}),
|
|
111
|
+
...options,
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (result.error) {
|
|
116
|
+
throw result.error;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return result.data;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public async deleteTable(
|
|
123
|
+
tableName: string,
|
|
124
|
+
options?: RequestInit & FFetchOptions,
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
const result = await this.context._makeRequest(
|
|
127
|
+
`/${this.databaseName}/FileMaker_Tables/${tableName}`,
|
|
128
|
+
{ method: "DELETE", ...options },
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if (result.error) {
|
|
132
|
+
throw result.error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async deleteField(
|
|
137
|
+
tableName: string,
|
|
138
|
+
fieldName: string,
|
|
139
|
+
options?: RequestInit & FFetchOptions,
|
|
140
|
+
): Promise<void> {
|
|
141
|
+
const result = await this.context._makeRequest(
|
|
142
|
+
`/${this.databaseName}/FileMaker_Tables/${tableName}/${fieldName}`,
|
|
143
|
+
{
|
|
144
|
+
method: "DELETE",
|
|
145
|
+
...options,
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (result.error) {
|
|
150
|
+
throw result.error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public async createIndex(
|
|
155
|
+
tableName: string,
|
|
156
|
+
fieldName: string,
|
|
157
|
+
options?: RequestInit & FFetchOptions,
|
|
158
|
+
): Promise<{ indexName: string }> {
|
|
159
|
+
const result = await this.context._makeRequest<{ indexName: string }>(
|
|
160
|
+
`/${this.databaseName}/FileMaker_Indexes/${tableName}`,
|
|
161
|
+
{
|
|
162
|
+
method: "POST",
|
|
163
|
+
body: JSON.stringify({ indexName: fieldName }),
|
|
164
|
+
...options,
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (result.error) {
|
|
169
|
+
throw result.error;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return result.data;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public async deleteIndex(
|
|
176
|
+
tableName: string,
|
|
177
|
+
fieldName: string,
|
|
178
|
+
options?: RequestInit & FFetchOptions,
|
|
179
|
+
): Promise<void> {
|
|
180
|
+
const result = await this.context._makeRequest(
|
|
181
|
+
`/${this.databaseName}/FileMaker_Indexes/${tableName}/${fieldName}`,
|
|
182
|
+
{
|
|
183
|
+
method: "DELETE",
|
|
184
|
+
...options,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (result.error) {
|
|
189
|
+
throw result.error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private static compileFieldDefinition(field: Field): FileMakerField {
|
|
194
|
+
let type: string = field.type;
|
|
195
|
+
const repetitions = field.repetitions;
|
|
196
|
+
|
|
197
|
+
// Handle string fields - convert to varchar and add maxLength if present
|
|
198
|
+
if (field.type === "string") {
|
|
199
|
+
type = "varchar";
|
|
200
|
+
const stringField = field as StringField;
|
|
201
|
+
if (stringField.maxLength !== undefined) {
|
|
202
|
+
type += `(${stringField.maxLength})`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Add repetitions suffix if present
|
|
207
|
+
if (repetitions !== undefined) {
|
|
208
|
+
type += `[${repetitions}]`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Build the result object, excluding type, maxLength, and repetitions
|
|
212
|
+
const result: any = {
|
|
213
|
+
name: field.name,
|
|
214
|
+
type,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Add optional properties that FileMaker expects
|
|
218
|
+
if (field.nullable !== undefined) result.nullable = field.nullable;
|
|
219
|
+
if (field.primary !== undefined) result.primary = field.primary;
|
|
220
|
+
if (field.unique !== undefined) result.unique = field.unique;
|
|
221
|
+
if (field.global !== undefined) result.global = field.global;
|
|
222
|
+
|
|
223
|
+
// Add type-specific properties
|
|
224
|
+
if (field.type === "string") {
|
|
225
|
+
const stringField = field as StringField;
|
|
226
|
+
if (stringField.default !== undefined)
|
|
227
|
+
result.default = stringField.default;
|
|
228
|
+
} else if (field.type === "date") {
|
|
229
|
+
const dateField = field as DateField;
|
|
230
|
+
if (dateField.default !== undefined) result.default = dateField.default;
|
|
231
|
+
} else if (field.type === "time") {
|
|
232
|
+
const timeField = field as TimeField;
|
|
233
|
+
if (timeField.default !== undefined) result.default = timeField.default;
|
|
234
|
+
} else if (field.type === "timestamp") {
|
|
235
|
+
const timestampField = field as TimestampField;
|
|
236
|
+
if (timestampField.default !== undefined)
|
|
237
|
+
result.default = timestampField.default;
|
|
238
|
+
} else if (field.type === "container") {
|
|
239
|
+
const containerField = field as ContainerField;
|
|
240
|
+
if (containerField.externalSecurePath !== undefined)
|
|
241
|
+
result.externalSecurePath = containerField.externalSecurePath;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return result as FileMakerField;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -4,43 +4,13 @@ import { BaseTable } from "./base-table";
|
|
|
4
4
|
type ExtractSchema<BT> =
|
|
5
5
|
BT extends BaseTable<infer S, any, any, any> ? S : never;
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
[K in keyof T]: T[K] extends () => infer R ? R : T[K];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// Helper to create a getter-based navigation object
|
|
13
|
-
function createNavigationGetters<
|
|
14
|
-
Nav extends Record<
|
|
15
|
-
string,
|
|
16
|
-
| TableOccurrence<any, any, any, any>
|
|
17
|
-
| (() => TableOccurrence<any, any, any, any>)
|
|
18
|
-
>,
|
|
19
|
-
>(navConfig: Nav): ResolveNavigation<Nav> {
|
|
20
|
-
const result: any = {};
|
|
21
|
-
|
|
22
|
-
for (const key in navConfig) {
|
|
23
|
-
Object.defineProperty(result, key, {
|
|
24
|
-
get() {
|
|
25
|
-
const navItem = navConfig[key];
|
|
26
|
-
return typeof navItem === "function" ? navItem() : navItem;
|
|
27
|
-
},
|
|
28
|
-
enumerable: true,
|
|
29
|
-
configurable: true,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return result as ResolveNavigation<Nav>;
|
|
34
|
-
}
|
|
7
|
+
// Symbol for internal navigation setting (used by buildOccurrences)
|
|
8
|
+
const INTERNAL_NAV = Symbol("internal-navigation");
|
|
35
9
|
|
|
36
10
|
export class TableOccurrence<
|
|
37
11
|
BT extends BaseTable<any, any, any, any> = any,
|
|
38
12
|
Name extends string = string,
|
|
39
|
-
Nav extends Record<
|
|
40
|
-
string,
|
|
41
|
-
| TableOccurrence<any, any, any, any>
|
|
42
|
-
| (() => TableOccurrence<any, any, any, any>)
|
|
43
|
-
> = {},
|
|
13
|
+
Nav extends Record<string, TableOccurrence<any, any, any, any>> = {},
|
|
44
14
|
DefSelect extends
|
|
45
15
|
| "all"
|
|
46
16
|
| "schema"
|
|
@@ -48,38 +18,70 @@ export class TableOccurrence<
|
|
|
48
18
|
> {
|
|
49
19
|
public readonly name: Name;
|
|
50
20
|
public readonly baseTable: BT;
|
|
51
|
-
|
|
52
|
-
public readonly navigation: ResolveNavigation<Nav>;
|
|
21
|
+
public readonly navigation: Nav;
|
|
53
22
|
public readonly defaultSelect: DefSelect;
|
|
23
|
+
public readonly fmtId?: `FMTID:${string}`;
|
|
54
24
|
|
|
55
25
|
constructor(config: {
|
|
56
26
|
readonly name: Name;
|
|
57
27
|
readonly baseTable: BT;
|
|
58
|
-
readonly navigation?: Nav;
|
|
59
28
|
readonly defaultSelect?: DefSelect;
|
|
29
|
+
readonly fmtId?: `FMTID:${string}`;
|
|
30
|
+
/** @internal Used by buildOccurrences - do not use directly */
|
|
31
|
+
readonly [INTERNAL_NAV]?: Nav;
|
|
60
32
|
}) {
|
|
61
33
|
this.name = config.name;
|
|
62
34
|
this.baseTable = config.baseTable;
|
|
63
|
-
this.
|
|
35
|
+
this.navigation = (config[INTERNAL_NAV] ?? {}) as Nav;
|
|
64
36
|
this.defaultSelect = (config.defaultSelect ?? "schema") as DefSelect;
|
|
37
|
+
this.fmtId = config.fmtId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.
|
|
42
|
+
* @returns The FMTID string or the table name
|
|
43
|
+
*/
|
|
44
|
+
getTableId(): string {
|
|
45
|
+
return this.fmtId ?? this.name;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the table occurrence name.
|
|
50
|
+
* @returns The table name
|
|
51
|
+
*/
|
|
52
|
+
getTableName(): string {
|
|
53
|
+
return this.name;
|
|
54
|
+
}
|
|
65
55
|
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Returns true if this TableOccurrence is using FileMaker table occurrence IDs.
|
|
58
|
+
*/
|
|
59
|
+
isUsingTableId(): boolean {
|
|
60
|
+
return this.fmtId !== undefined;
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
/**
|
|
64
|
+
* @internal Creates a new TableOccurrence with navigation - used by buildOccurrences
|
|
65
|
+
*/
|
|
66
|
+
static _withNavigation<
|
|
67
|
+
BT extends BaseTable<any, any, any, any>,
|
|
68
|
+
Name extends string,
|
|
69
|
+
Nav extends Record<string, TableOccurrence<any, any, any, any>>,
|
|
70
|
+
DefSelect extends
|
|
71
|
+
| "all"
|
|
72
|
+
| "schema"
|
|
73
|
+
| readonly (keyof ExtractSchema<BT>)[] = "schema",
|
|
74
|
+
>(
|
|
75
|
+
base: TableOccurrence<BT, Name, any, DefSelect>,
|
|
76
|
+
navigation: Nav,
|
|
77
|
+
): TableOccurrence<BT, Name, Nav, DefSelect> {
|
|
77
78
|
return new TableOccurrence({
|
|
78
|
-
name:
|
|
79
|
-
baseTable:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
name: base.name,
|
|
80
|
+
baseTable: base.baseTable,
|
|
81
|
+
defaultSelect: base.defaultSelect,
|
|
82
|
+
fmtId: base.fmtId,
|
|
83
|
+
[INTERNAL_NAV]: navigation,
|
|
84
|
+
}) as TableOccurrence<BT, Name, Nav, DefSelect>;
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -95,6 +97,60 @@ export function createTableOccurrence<
|
|
|
95
97
|
name: Name;
|
|
96
98
|
baseTable: BT;
|
|
97
99
|
defaultSelect?: DefSelect;
|
|
100
|
+
fmtId?: `FMTID:${string}`;
|
|
101
|
+
}): TableOccurrence<BT, Name, {}, DefSelect> {
|
|
102
|
+
return new TableOccurrence(config);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a TableOccurrence with proper TypeScript type inference.
|
|
107
|
+
*
|
|
108
|
+
* Use this function to create TableOccurrence instances with full type safety.
|
|
109
|
+
* For navigation between tables, use `buildOccurrences()` after defining your TOs.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const users = defineTableOccurrence({
|
|
114
|
+
* name: "users",
|
|
115
|
+
* baseTable: usersBase,
|
|
116
|
+
* });
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @example With entity IDs
|
|
120
|
+
* ```ts
|
|
121
|
+
* const products = defineTableOccurrence({
|
|
122
|
+
* name: "products",
|
|
123
|
+
* baseTable: productsBase,
|
|
124
|
+
* fmtId: "FMTID:12345",
|
|
125
|
+
* });
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @example With navigation (use buildOccurrences)
|
|
129
|
+
* ```ts
|
|
130
|
+
* const _users = defineTableOccurrence({ name: "users", baseTable: usersBase });
|
|
131
|
+
* const _contacts = defineTableOccurrence({ name: "contacts", baseTable: contactsBase });
|
|
132
|
+
*
|
|
133
|
+
* const [users, contacts] = buildOccurrences({
|
|
134
|
+
* occurrences: [_users, _contacts],
|
|
135
|
+
* navigation: {
|
|
136
|
+
* users: ["contacts"],
|
|
137
|
+
* contacts: ["users"],
|
|
138
|
+
* },
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function defineTableOccurrence<
|
|
143
|
+
const Name extends string,
|
|
144
|
+
BT extends BaseTable<any, any, any, any>,
|
|
145
|
+
const DefSelect extends
|
|
146
|
+
| "all"
|
|
147
|
+
| "schema"
|
|
148
|
+
| readonly (keyof ExtractSchema<BT>)[] = "schema",
|
|
149
|
+
>(config: {
|
|
150
|
+
readonly name: Name;
|
|
151
|
+
readonly baseTable: BT;
|
|
152
|
+
readonly fmtId?: `FMTID:${string}`;
|
|
153
|
+
readonly defaultSelect?: DefSelect;
|
|
98
154
|
}): TableOccurrence<BT, Name, {}, DefSelect> {
|
|
99
155
|
return new TableOccurrence(config);
|
|
100
156
|
}
|