@proofkit/fmodata 0.1.0-alpha.9 → 0.1.0-beta.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +651 -449
- package/dist/esm/client/batch-builder.d.ts +10 -9
- package/dist/esm/client/batch-builder.js +119 -56
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/batch-request.js +16 -21
- package/dist/esm/client/batch-request.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +10 -0
- package/dist/esm/client/builders/default-select.js +41 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +45 -0
- package/dist/esm/client/builders/expand-builder.js +185 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +9 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
- package/dist/esm/client/builders/query-string-builder.js +21 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +43 -0
- package/dist/esm/client/builders/response-processor.js +175 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +25 -0
- package/dist/esm/client/builders/select-mixin.js +28 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +18 -0
- package/dist/esm/client/builders/select-utils.js +30 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +40 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +44 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +34 -22
- package/dist/esm/client/database.js +48 -84
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +25 -30
- package/dist/esm/client/delete-builder.js +45 -30
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +35 -43
- package/dist/esm/client/entity-set.js +110 -52
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/error-parser.d.ts +12 -0
- package/dist/esm/client/error-parser.js +25 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +26 -7
- package/dist/esm/client/filemaker-odata.js +65 -42
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +19 -24
- package/dist/esm/client/insert-builder.js +94 -58
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query/expand-builder.d.ts +35 -0
- package/dist/esm/client/query/index.d.ts +4 -0
- package/dist/esm/client/query/query-builder.d.ts +132 -0
- package/dist/esm/client/query/query-builder.js +456 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +25 -0
- package/dist/esm/client/query/types.d.ts +77 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +100 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +2 -115
- package/dist/esm/client/record-builder.d.ts +108 -36
- package/dist/esm/client/record-builder.js +284 -119
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +4 -9
- package/dist/esm/client/sanitize-json.d.ts +35 -0
- package/dist/esm/client/sanitize-json.js +27 -0
- package/dist/esm/client/sanitize-json.js.map +1 -0
- package/dist/esm/client/schema-manager.d.ts +5 -5
- package/dist/esm/client/schema-manager.js +45 -31
- package/dist/esm/client/schema-manager.js.map +1 -1
- package/dist/esm/client/update-builder.d.ts +34 -40
- package/dist/esm/client/update-builder.js +99 -58
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/client/webhook-builder.d.ts +126 -0
- package/dist/esm/client/webhook-builder.js +189 -0
- package/dist/esm/client/webhook-builder.js.map +1 -0
- package/dist/esm/errors.d.ts +19 -2
- package/dist/esm/errors.js +39 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +10 -8
- package/dist/esm/index.js +40 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger.d.ts +47 -0
- package/dist/esm/logger.js +69 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/logger.test.d.ts +1 -0
- package/dist/esm/orm/column.d.ts +62 -0
- package/dist/esm/orm/column.js +63 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +164 -0
- package/dist/esm/orm/field-builders.js +158 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +5 -0
- package/dist/esm/orm/operators.d.ts +173 -0
- package/dist/esm/orm/operators.js +260 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +355 -0
- package/dist/esm/orm/table.js +202 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +44 -45
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +96 -30
- package/dist/esm/types.js +7 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/validation.d.ts +22 -12
- package/dist/esm/validation.js +132 -85
- package/dist/esm/validation.js.map +1 -1
- package/package.json +28 -20
- package/src/client/batch-builder.ts +153 -89
- package/src/client/batch-request.ts +25 -41
- package/src/client/builders/default-select.ts +75 -0
- package/src/client/builders/expand-builder.ts +246 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +46 -0
- package/src/client/builders/response-processor.ts +279 -0
- package/src/client/builders/select-mixin.ts +65 -0
- package/src/client/builders/select-utils.ts +59 -0
- package/src/client/builders/shared-types.ts +45 -0
- package/src/client/builders/table-utils.ts +83 -0
- package/src/client/database.ts +89 -183
- package/src/client/delete-builder.ts +74 -84
- package/src/client/entity-set.ts +266 -293
- package/src/client/error-parser.ts +41 -0
- package/src/client/filemaker-odata.ts +98 -66
- package/src/client/insert-builder.ts +157 -118
- package/src/client/query/expand-builder.ts +160 -0
- package/src/client/query/index.ts +14 -0
- package/src/client/query/query-builder.ts +729 -0
- package/src/client/query/response-processor.ts +226 -0
- package/src/client/query/types.ts +126 -0
- package/src/client/query/url-builder.ts +151 -0
- package/src/client/query-builder.ts +10 -1455
- package/src/client/record-builder.ts +575 -240
- package/src/client/response-processor.ts +15 -42
- package/src/client/sanitize-json.ts +64 -0
- package/src/client/schema-manager.ts +61 -76
- package/src/client/update-builder.ts +161 -143
- package/src/client/webhook-builder.ts +265 -0
- package/src/errors.ts +49 -16
- package/src/index.ts +99 -54
- package/src/logger.test.ts +34 -0
- package/src/logger.ts +116 -0
- package/src/orm/column.ts +106 -0
- package/src/orm/field-builders.ts +250 -0
- package/src/orm/index.ts +61 -0
- package/src/orm/operators.ts +473 -0
- package/src/orm/table.ts +741 -0
- package/src/transform.ts +90 -70
- package/src/types.ts +154 -113
- package/src/validation.ts +200 -115
- package/dist/esm/client/base-table.d.ts +0 -125
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -896
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -72
- package/dist/esm/client/table-occurrence.js +0 -74
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/dist/esm/filter-types.d.ts +0 -76
- package/src/client/base-table.ts +0 -175
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -175
- package/src/filter-types.ts +0 -97
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { FMTable } from "../../orm/table";
|
|
2
|
+
import { transformFieldNamesArray } from "../../transform";
|
|
3
|
+
|
|
4
|
+
const VALID_FIELD_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9]*$/;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Determines if a field name needs to be quoted in OData queries.
|
|
8
|
+
* Per FileMaker docs: field names with special characters (spaces, underscores, etc.) must be quoted.
|
|
9
|
+
* Also quotes "id" as it's an OData reserved word.
|
|
10
|
+
* Entity IDs (FMFID:*, FMTID:*) are not quoted as they're identifiers, not field names.
|
|
11
|
+
*
|
|
12
|
+
* @param fieldName - The field name or identifier to check
|
|
13
|
+
* @returns true if the field name should be quoted in OData queries
|
|
14
|
+
*/
|
|
15
|
+
export function needsFieldQuoting(fieldName: string): boolean {
|
|
16
|
+
// Entity IDs are identifiers and don't need quoting
|
|
17
|
+
if (fieldName.startsWith("FMFID:") || fieldName.startsWith("FMTID:")) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
// Always quote "id" as it's an OData reserved word
|
|
21
|
+
if (fieldName === "id") {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
// Quote if field name contains spaces, underscores, or other special characters
|
|
25
|
+
return fieldName.includes(" ") || fieldName.includes("_") || !VALID_FIELD_NAME_REGEX.test(fieldName);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Formats select fields for use in OData query strings.
|
|
30
|
+
* - Transforms field names to FMFIDs if using entity IDs
|
|
31
|
+
* - Wraps "id" fields in double quotes (OData reserved)
|
|
32
|
+
* - URL-encodes special characters but preserves spaces
|
|
33
|
+
*/
|
|
34
|
+
export function formatSelectFields(
|
|
35
|
+
select: string[] | readonly string[] | undefined,
|
|
36
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
|
|
37
|
+
table?: FMTable<any, any>,
|
|
38
|
+
useEntityIds?: boolean,
|
|
39
|
+
): string {
|
|
40
|
+
if (!select || select.length === 0) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const selectArray = Array.isArray(select) ? select : [select];
|
|
45
|
+
|
|
46
|
+
// Transform to field IDs if using entity IDs
|
|
47
|
+
const transformedFields =
|
|
48
|
+
table && useEntityIds ? transformFieldNamesArray(selectArray.map(String), table) : selectArray.map(String);
|
|
49
|
+
|
|
50
|
+
return transformedFields
|
|
51
|
+
.map((field) => {
|
|
52
|
+
if (needsFieldQuoting(field)) {
|
|
53
|
+
return `"${field}"`;
|
|
54
|
+
}
|
|
55
|
+
const encoded = encodeURIComponent(field);
|
|
56
|
+
return encoded.replace(/%20/g, " ");
|
|
57
|
+
})
|
|
58
|
+
.join(",");
|
|
59
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { QueryOptions } from "odata-query";
|
|
2
|
+
import type { FMTable } from "../../orm/table";
|
|
3
|
+
import type { ExecutionContext } from "../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Expand configuration used by both QueryBuilder and RecordBuilder
|
|
7
|
+
*/
|
|
8
|
+
export interface ExpandConfig {
|
|
9
|
+
relation: string;
|
|
10
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any QueryOptions configuration
|
|
11
|
+
options?: Partial<QueryOptions<any>>;
|
|
12
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
|
|
13
|
+
targetTable?: FMTable<any, any>;
|
|
14
|
+
nestedExpandConfigs?: ExpandConfig[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type to represent expanded relations in return types
|
|
19
|
+
*/
|
|
20
|
+
// biome-ignore lint/suspicious/noExplicitAny: Dynamic schema and selected types from user input
|
|
21
|
+
export type ExpandedRelations = Record<string, { schema: any; selected: any }>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Navigation context shared between builders
|
|
25
|
+
*/
|
|
26
|
+
export interface NavigationContext {
|
|
27
|
+
isNavigate?: boolean;
|
|
28
|
+
navigateRecordId?: string | number;
|
|
29
|
+
navigateRelation?: string;
|
|
30
|
+
navigateSourceTableName?: string;
|
|
31
|
+
navigateBaseRelation?: string;
|
|
32
|
+
navigateBasePath?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Common builder configuration
|
|
37
|
+
*/
|
|
38
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
|
|
39
|
+
export interface BuilderConfig<Occ extends FMTable<any, any> | undefined> {
|
|
40
|
+
occurrence?: Occ;
|
|
41
|
+
tableName: string;
|
|
42
|
+
databaseName: string;
|
|
43
|
+
context: ExecutionContext;
|
|
44
|
+
databaseUseEntityIds?: boolean;
|
|
45
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { FFetchOptions } from "@fetchkit/ffetch";
|
|
2
|
+
import type { FMTable } from "../../orm/table";
|
|
3
|
+
import { getTableId as getTableIdHelper, getTableName, isUsingEntityIds } from "../../orm/table";
|
|
4
|
+
import type { ExecuteOptions, ExecutionContext } from "../../types";
|
|
5
|
+
import { getAcceptHeader } from "../../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves table identifier based on entity ID settings.
|
|
9
|
+
* Used by both QueryBuilder and RecordBuilder.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveTableId(
|
|
12
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
|
|
13
|
+
table: FMTable<any, any> | undefined,
|
|
14
|
+
fallbackTableName: string,
|
|
15
|
+
context: ExecutionContext,
|
|
16
|
+
useEntityIdsOverride?: boolean,
|
|
17
|
+
): string {
|
|
18
|
+
if (!table) {
|
|
19
|
+
return fallbackTableName;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const contextDefault = context._getUseEntityIds?.() ?? false;
|
|
23
|
+
const shouldUseIds = useEntityIdsOverride ?? contextDefault;
|
|
24
|
+
|
|
25
|
+
if (shouldUseIds) {
|
|
26
|
+
if (!isUsingEntityIds(table)) {
|
|
27
|
+
throw new Error(`useEntityIds is true but table "${getTableName(table)}" does not have entity IDs configured`);
|
|
28
|
+
}
|
|
29
|
+
return getTableIdHelper(table);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return getTableName(table);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Merges database-level useEntityIds with per-request options.
|
|
37
|
+
*/
|
|
38
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape
|
|
39
|
+
export function mergeEntityIdOptions<T extends Record<string, any>>(
|
|
40
|
+
options: T | undefined,
|
|
41
|
+
databaseDefault: boolean,
|
|
42
|
+
): T & { useEntityIds?: boolean } {
|
|
43
|
+
return {
|
|
44
|
+
...options,
|
|
45
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for optional property access
|
|
46
|
+
useEntityIds: (options as any)?.useEntityIds ?? databaseDefault,
|
|
47
|
+
} as T & { useEntityIds?: boolean };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Type-safe helper for merging execute options with entity ID settings
|
|
52
|
+
*/
|
|
53
|
+
export function mergeExecuteOptions(
|
|
54
|
+
options: (RequestInit & FFetchOptions & ExecuteOptions) | undefined,
|
|
55
|
+
databaseUseEntityIds: boolean,
|
|
56
|
+
): RequestInit & FFetchOptions & { useEntityIds?: boolean } {
|
|
57
|
+
return mergeEntityIdOptions(options, databaseUseEntityIds);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates an OData Request object with proper headers.
|
|
62
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
63
|
+
*
|
|
64
|
+
* @param baseUrl - Base URL for the request
|
|
65
|
+
* @param config - Request configuration with method and url
|
|
66
|
+
* @param options - Optional execution options
|
|
67
|
+
* @returns Request object ready to use
|
|
68
|
+
*/
|
|
69
|
+
export function createODataRequest(
|
|
70
|
+
baseUrl: string,
|
|
71
|
+
config: { method: string; url: string },
|
|
72
|
+
options?: { includeODataAnnotations?: boolean },
|
|
73
|
+
): Request {
|
|
74
|
+
const fullUrl = `${baseUrl}${config.url}`;
|
|
75
|
+
|
|
76
|
+
return new Request(fullUrl, {
|
|
77
|
+
method: config.method,
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
Accept: getAcceptHeader(options?.includeODataAnnotations),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
package/src/client/database.ts
CHANGED
|
@@ -1,208 +1,120 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
import type { TableOccurrence } from "./table-occurrence";
|
|
5
|
-
import { EntitySet } from "./entity-set";
|
|
2
|
+
import { FMTable } from "../orm/table";
|
|
3
|
+
import type { ExecutableBuilder, ExecutionContext, Metadata } from "../types";
|
|
6
4
|
import { BatchBuilder } from "./batch-builder";
|
|
5
|
+
import { EntitySet } from "./entity-set";
|
|
7
6
|
import { SchemaManager } from "./schema-manager";
|
|
7
|
+
import { WebhookManager } from "./webhook-builder";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
> = Occurrences extends readonly [
|
|
22
|
-
infer First,
|
|
23
|
-
...infer Rest extends readonly TableOccurrence<any, any, any, any>[],
|
|
24
|
-
]
|
|
25
|
-
? First extends TableOccurrence<any, any, any, any>
|
|
26
|
-
? First["name"] extends Name
|
|
27
|
-
? First
|
|
28
|
-
: FindOccurrenceByName<Rest, Name>
|
|
29
|
-
: never
|
|
30
|
-
: never;
|
|
31
|
-
|
|
32
|
-
// Helper type to extract all occurrence names from the tuple
|
|
33
|
-
type ExtractOccurrenceNames<
|
|
34
|
-
Occurrences extends readonly TableOccurrence<any, any, any, any>[],
|
|
35
|
-
> = Occurrences extends readonly []
|
|
36
|
-
? string // If no occurrences, allow any string
|
|
37
|
-
: Occurrences[number]["name"]; // Otherwise, extract union of names
|
|
9
|
+
interface MetadataArgs {
|
|
10
|
+
format?: "xml" | "json";
|
|
11
|
+
/**
|
|
12
|
+
* If provided, only the metadata for the specified table will be returned.
|
|
13
|
+
* Requires FileMaker Server 22.0.4 or later.
|
|
14
|
+
*/
|
|
15
|
+
tableName?: string;
|
|
16
|
+
/**
|
|
17
|
+
* If true, a reduced payload size will be returned by omitting certain annotations.
|
|
18
|
+
*/
|
|
19
|
+
reduceAnnotations?: boolean;
|
|
20
|
+
}
|
|
38
21
|
|
|
39
|
-
export class Database<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
> {
|
|
47
|
-
private occurrenceMap: Map<string, TableOccurrence<any, any, any, any>>;
|
|
48
|
-
private _useEntityIds: boolean = false;
|
|
49
|
-
public readonly schema: SchemaManager;
|
|
22
|
+
export class Database<IncludeSpecialColumns extends boolean = false> {
|
|
23
|
+
readonly schema: SchemaManager;
|
|
24
|
+
readonly webhook: WebhookManager;
|
|
25
|
+
private readonly databaseName: string;
|
|
26
|
+
private readonly context: ExecutionContext;
|
|
27
|
+
private _useEntityIds: boolean;
|
|
28
|
+
private readonly _includeSpecialColumns: IncludeSpecialColumns;
|
|
50
29
|
|
|
51
30
|
constructor(
|
|
52
|
-
|
|
53
|
-
|
|
31
|
+
databaseName: string,
|
|
32
|
+
context: ExecutionContext,
|
|
54
33
|
config?: {
|
|
55
|
-
occurrences?: Occurrences | undefined;
|
|
56
34
|
/**
|
|
57
35
|
* Whether to use entity IDs instead of field names in the actual requests to the server
|
|
58
36
|
* Defaults to true if all occurrences use entity IDs, false otherwise
|
|
59
37
|
* If set to false but some occurrences do not use entity IDs, an error will be thrown
|
|
60
38
|
*/
|
|
61
39
|
useEntityIds?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to include special columns (ROWID and ROWMODID) in responses.
|
|
42
|
+
* Note: Special columns are only included when there is no $select query.
|
|
43
|
+
*/
|
|
44
|
+
includeSpecialColumns?: IncludeSpecialColumns;
|
|
62
45
|
},
|
|
63
46
|
) {
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
// Validate consistency: either all occurrences use entity IDs or none do
|
|
67
|
-
const occurrencesWithIds: string[] = [];
|
|
68
|
-
const occurrencesWithoutIds: string[] = [];
|
|
69
|
-
|
|
70
|
-
for (const occ of config.occurrences) {
|
|
71
|
-
this.occurrenceMap.set(occ.name, occ);
|
|
72
|
-
|
|
73
|
-
const hasTableId = occ.isUsingTableId();
|
|
74
|
-
const hasFieldIds = occ.baseTable.isUsingFieldIds();
|
|
75
|
-
|
|
76
|
-
// An occurrence uses entity IDs if it has both fmtId and fmfIds
|
|
77
|
-
if (hasTableId && hasFieldIds) {
|
|
78
|
-
occurrencesWithIds.push(occ.name);
|
|
79
|
-
} else if (!hasTableId && !hasFieldIds) {
|
|
80
|
-
occurrencesWithoutIds.push(occ.name);
|
|
81
|
-
} else {
|
|
82
|
-
// Partial entity ID usage (only one of fmtId or fmfIds) - this is an error
|
|
83
|
-
throw new Error(
|
|
84
|
-
`TableOccurrence "${occ.name}" has inconsistent entity ID configuration. ` +
|
|
85
|
-
`Both fmtId (${hasTableId ? "present" : "missing"}) and fmfIds (${hasFieldIds ? "present" : "missing"}) must be defined together.`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Determine default value: true if all occurrences use entity IDs, false otherwise
|
|
91
|
-
const allOccurrencesUseEntityIds =
|
|
92
|
-
occurrencesWithIds.length > 0 && occurrencesWithoutIds.length === 0;
|
|
93
|
-
const hasMixedUsage =
|
|
94
|
-
occurrencesWithIds.length > 0 && occurrencesWithoutIds.length > 0;
|
|
95
|
-
|
|
96
|
-
// Handle explicit useEntityIds config
|
|
97
|
-
if (config.useEntityIds !== undefined) {
|
|
98
|
-
if (config.useEntityIds === false) {
|
|
99
|
-
// If explicitly set to false, allow mixed usage and use false
|
|
100
|
-
this._useEntityIds = false;
|
|
101
|
-
} else if (config.useEntityIds === true) {
|
|
102
|
-
// If explicitly set to true, validate that all occurrences use entity IDs
|
|
103
|
-
if (hasMixedUsage || occurrencesWithoutIds.length > 0) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`useEntityIds is set to true but some occurrences do not use entity IDs. ` +
|
|
106
|
-
`Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
|
|
107
|
-
`Either set useEntityIds to false or configure all occurrences with entity IDs.`,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
this._useEntityIds = true;
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
// Default: true if all occurrences use entity IDs, false otherwise
|
|
114
|
-
// But throw error if there's mixed usage when using defaults
|
|
115
|
-
if (hasMixedUsage) {
|
|
116
|
-
throw new Error(
|
|
117
|
-
`Cannot mix TableOccurrence instances with and without entity IDs in the same database. ` +
|
|
118
|
-
`Occurrences with entity IDs: [${occurrencesWithIds.join(", ")}]. ` +
|
|
119
|
-
`Occurrences without entity IDs: [${occurrencesWithoutIds.join(", ")}]. ` +
|
|
120
|
-
`Either all table occurrences must use entity IDs (fmtId + fmfIds), none should, or explicitly set useEntityIds to false.`,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
this._useEntityIds = allOccurrencesUseEntityIds;
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
// No occurrences provided, use explicit config or default to false
|
|
127
|
-
this._useEntityIds = config?.useEntityIds ?? false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Inform the execution context whether to use entity IDs
|
|
131
|
-
if (this.context._setUseEntityIds) {
|
|
132
|
-
this.context._setUseEntityIds(this._useEntityIds);
|
|
133
|
-
}
|
|
134
|
-
|
|
47
|
+
this.databaseName = databaseName;
|
|
48
|
+
this.context = context;
|
|
135
49
|
// Initialize schema manager
|
|
136
50
|
this.schema = new SchemaManager(this.databaseName, this.context);
|
|
51
|
+
this.webhook = new WebhookManager(this.databaseName, this.context);
|
|
52
|
+
this._useEntityIds = config?.useEntityIds ?? false;
|
|
53
|
+
this._includeSpecialColumns = (config?.includeSpecialColumns ?? false) as IncludeSpecialColumns;
|
|
137
54
|
}
|
|
138
55
|
|
|
139
56
|
/**
|
|
140
|
-
*
|
|
57
|
+
* @internal Used by EntitySet to access database configuration
|
|
141
58
|
*/
|
|
142
|
-
|
|
59
|
+
get _getUseEntityIds(): boolean {
|
|
143
60
|
return this._useEntityIds;
|
|
144
61
|
}
|
|
145
62
|
|
|
146
63
|
/**
|
|
147
|
-
*
|
|
148
|
-
* @internal
|
|
64
|
+
* @internal Used by EntitySet to access database configuration
|
|
149
65
|
*/
|
|
150
|
-
|
|
151
|
-
return this.
|
|
66
|
+
get _getIncludeSpecialColumns(): IncludeSpecialColumns {
|
|
67
|
+
return this._includeSpecialColumns;
|
|
152
68
|
}
|
|
153
69
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const occurrence = this.occurrenceMap.get(name as string);
|
|
165
|
-
|
|
166
|
-
if (occurrence) {
|
|
167
|
-
// Use EntitySet.create to preserve types better
|
|
168
|
-
type OccType = FindOccurrenceByName<Occurrences, Name>;
|
|
169
|
-
type SchemaType = ExtractSchemaFromOccurrence<OccType>;
|
|
170
|
-
|
|
171
|
-
return EntitySet.create<SchemaType, OccType>({
|
|
172
|
-
occurrence: occurrence as OccType,
|
|
173
|
-
tableName: name as string,
|
|
174
|
-
databaseName: this.databaseName,
|
|
175
|
-
context: this.context,
|
|
176
|
-
database: this,
|
|
177
|
-
}) as any;
|
|
178
|
-
} else {
|
|
179
|
-
// Return untyped EntitySet for dynamic table access
|
|
180
|
-
return new EntitySet<Record<string, StandardSchemaV1>, undefined>({
|
|
181
|
-
tableName: name as string,
|
|
182
|
-
databaseName: this.databaseName,
|
|
183
|
-
context: this.context,
|
|
184
|
-
database: this,
|
|
185
|
-
}) as any;
|
|
70
|
+
// biome-ignore lint/suspicious/noExplicitAny: Accepts any FMTable configuration
|
|
71
|
+
from<T extends FMTable<any, any>>(table: T): EntitySet<T, IncludeSpecialColumns> {
|
|
72
|
+
// Only override database-level useEntityIds if table explicitly sets it
|
|
73
|
+
// (not if it's undefined, which would override the database setting)
|
|
74
|
+
if (Object.hasOwn(table, FMTable.Symbol.UseEntityIds)) {
|
|
75
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for Symbol property access
|
|
76
|
+
const tableUseEntityIds = (table as any)[FMTable.Symbol.UseEntityIds];
|
|
77
|
+
if (typeof tableUseEntityIds === "boolean") {
|
|
78
|
+
this._useEntityIds = tableUseEntityIds;
|
|
79
|
+
}
|
|
186
80
|
}
|
|
81
|
+
return new EntitySet<T, IncludeSpecialColumns>({
|
|
82
|
+
occurrence: table as T,
|
|
83
|
+
databaseName: this.databaseName,
|
|
84
|
+
context: this.context,
|
|
85
|
+
database: this,
|
|
86
|
+
});
|
|
187
87
|
}
|
|
188
88
|
|
|
189
89
|
/**
|
|
190
90
|
* Retrieves the OData metadata for this database.
|
|
191
91
|
* @param args Optional configuration object
|
|
192
92
|
* @param args.format The format to retrieve metadata in. Defaults to "json".
|
|
93
|
+
* @param args.tableName If provided, only the metadata for the specified table will be returned. Requires FileMaker Server 22.0.4 or later.
|
|
94
|
+
* @param args.reduceAnnotations If true, a reduced payload size will be returned by omitting certain annotations.
|
|
193
95
|
* @returns The metadata in the specified format
|
|
194
96
|
*/
|
|
195
|
-
async getMetadata(args: { format: "xml" }): Promise<string>;
|
|
196
|
-
async getMetadata(args?: { format?: "json" }): Promise<Metadata>;
|
|
197
|
-
async getMetadata(args?: {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
97
|
+
async getMetadata(args: { format: "xml" } & MetadataArgs): Promise<string>;
|
|
98
|
+
async getMetadata(args?: { format?: "json" } & MetadataArgs): Promise<Metadata>;
|
|
99
|
+
async getMetadata(args?: MetadataArgs): Promise<string | Metadata> {
|
|
100
|
+
// Build the URL - if tableName is provided, append %23{tableName} to the path
|
|
101
|
+
let url = `/${this.databaseName}/$metadata`;
|
|
102
|
+
if (args?.tableName) {
|
|
103
|
+
url = `/${this.databaseName}/$metadata%23${args.tableName}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Build headers
|
|
107
|
+
const headers: Record<string, string> = {
|
|
108
|
+
Accept: args?.format === "xml" ? "application/xml" : "application/json",
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Add Prefer header if reduceAnnotations is true
|
|
112
|
+
if (args?.reduceAnnotations) {
|
|
113
|
+
headers.Prefer = 'include-annotations="-*"';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = await this.context._makeRequest<Record<string, Metadata> | string>(url, {
|
|
117
|
+
headers,
|
|
206
118
|
});
|
|
207
119
|
if (result.error) {
|
|
208
120
|
throw result.error;
|
|
@@ -212,9 +124,7 @@ export class Database<
|
|
|
212
124
|
const data = result.data as Record<string, Metadata>;
|
|
213
125
|
const metadata = data[this.databaseName];
|
|
214
126
|
if (!metadata) {
|
|
215
|
-
throw new Error(
|
|
216
|
-
`Metadata for database "${this.databaseName}" not found in response`,
|
|
217
|
-
);
|
|
127
|
+
throw new Error(`Metadata for database "${this.databaseName}" not found in response`);
|
|
218
128
|
}
|
|
219
129
|
return metadata;
|
|
220
130
|
}
|
|
@@ -244,9 +154,11 @@ export class Database<
|
|
|
244
154
|
* @param options - Optional script parameter and result schema
|
|
245
155
|
* @returns Promise resolving to script execution result
|
|
246
156
|
*/
|
|
157
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
|
|
247
158
|
async runScript<ResultSchema extends StandardSchemaV1<string, any> = never>(
|
|
248
159
|
scriptName: string,
|
|
249
160
|
options?: {
|
|
161
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any record shape
|
|
250
162
|
scriptParam?: string | number | Record<string, any>;
|
|
251
163
|
resultSchema?: ResultSchema;
|
|
252
164
|
},
|
|
@@ -280,30 +192,25 @@ export class Database<
|
|
|
280
192
|
|
|
281
193
|
// If resultSchema is provided, validate the result through it
|
|
282
194
|
if (options?.resultSchema && response.scriptResult !== undefined) {
|
|
283
|
-
const validationResult = options.resultSchema["~standard"].validate(
|
|
284
|
-
response.scriptResult.resultParameter,
|
|
285
|
-
);
|
|
195
|
+
const validationResult = options.resultSchema["~standard"].validate(response.scriptResult.resultParameter);
|
|
286
196
|
// Handle both sync and async validation
|
|
287
|
-
const result =
|
|
288
|
-
validationResult instanceof Promise
|
|
289
|
-
? await validationResult
|
|
290
|
-
: validationResult;
|
|
197
|
+
const result = validationResult instanceof Promise ? await validationResult : validationResult;
|
|
291
198
|
|
|
292
199
|
if (result.issues) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
`Script result validation failed: ${JSON.stringify(result.issues)}`,
|
|
295
|
-
);
|
|
200
|
+
throw new Error(`Script result validation failed: ${JSON.stringify(result.issues)}`);
|
|
296
201
|
}
|
|
297
202
|
|
|
298
203
|
return {
|
|
299
204
|
resultCode: response.scriptResult.code,
|
|
300
205
|
result: result.value,
|
|
206
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
|
|
301
207
|
} as any;
|
|
302
208
|
}
|
|
303
209
|
|
|
304
210
|
return {
|
|
305
211
|
resultCode: response.scriptResult.code,
|
|
306
212
|
result: response.scriptResult.resultParameter,
|
|
213
|
+
// biome-ignore lint/suspicious/noExplicitAny: Type assertion for generic return type
|
|
307
214
|
} as any;
|
|
308
215
|
}
|
|
309
216
|
|
|
@@ -326,9 +233,8 @@ export class Database<
|
|
|
326
233
|
* }
|
|
327
234
|
* ```
|
|
328
235
|
*/
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
): BatchBuilder<Builders> {
|
|
236
|
+
// biome-ignore lint/suspicious/noExplicitAny: Generic constraint accepting any ExecutableBuilder result type
|
|
237
|
+
batch<const Builders extends readonly ExecutableBuilder<any>[]>(builders: Builders): BatchBuilder<Builders> {
|
|
332
238
|
return new BatchBuilder(builders, this.databaseName, this.context);
|
|
333
239
|
}
|
|
334
240
|
}
|