@proofkit/fmodata 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +489 -334
- package/dist/esm/client/batch-builder.d.ts +7 -5
- package/dist/esm/client/batch-builder.js +84 -25
- package/dist/esm/client/batch-builder.js.map +1 -1
- package/dist/esm/client/builders/default-select.d.ts +7 -0
- package/dist/esm/client/builders/default-select.js +42 -0
- package/dist/esm/client/builders/default-select.js.map +1 -0
- package/dist/esm/client/builders/expand-builder.d.ts +43 -0
- package/dist/esm/client/builders/expand-builder.js +173 -0
- package/dist/esm/client/builders/expand-builder.js.map +1 -0
- package/dist/esm/client/builders/index.d.ts +8 -0
- package/dist/esm/client/builders/query-string-builder.d.ts +15 -0
- package/dist/esm/client/builders/query-string-builder.js +25 -0
- package/dist/esm/client/builders/query-string-builder.js.map +1 -0
- package/dist/esm/client/builders/response-processor.d.ts +39 -0
- package/dist/esm/client/builders/response-processor.js +170 -0
- package/dist/esm/client/builders/response-processor.js.map +1 -0
- package/dist/esm/client/builders/select-mixin.d.ts +31 -0
- package/dist/esm/client/builders/select-mixin.js +30 -0
- package/dist/esm/client/builders/select-mixin.js.map +1 -0
- package/dist/esm/client/builders/select-utils.d.ts +8 -0
- package/dist/esm/client/builders/select-utils.js +15 -0
- package/dist/esm/client/builders/select-utils.js.map +1 -0
- package/dist/esm/client/builders/shared-types.d.ts +39 -0
- package/dist/esm/client/builders/table-utils.d.ts +35 -0
- package/dist/esm/client/builders/table-utils.js +45 -0
- package/dist/esm/client/builders/table-utils.js.map +1 -0
- package/dist/esm/client/database.d.ts +3 -22
- package/dist/esm/client/database.js +14 -76
- package/dist/esm/client/database.js.map +1 -1
- package/dist/esm/client/delete-builder.d.ts +12 -19
- package/dist/esm/client/delete-builder.js +26 -26
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +32 -32
- package/dist/esm/client/entity-set.js +92 -69
- 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 +30 -0
- package/dist/esm/client/error-parser.js.map +1 -0
- package/dist/esm/client/filemaker-odata.d.ts +2 -4
- package/dist/esm/client/filemaker-odata.js +1 -5
- package/dist/esm/client/filemaker-odata.js.map +1 -1
- package/dist/esm/client/insert-builder.d.ts +9 -12
- package/dist/esm/client/insert-builder.js +70 -24
- 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 +3 -0
- package/dist/esm/client/query/query-builder.d.ts +133 -0
- package/dist/esm/client/query/query-builder.js +505 -0
- package/dist/esm/client/query/query-builder.js.map +1 -0
- package/dist/esm/client/query/response-processor.d.ts +22 -0
- package/dist/esm/client/query/types.d.ts +52 -0
- package/dist/esm/client/query/url-builder.d.ts +71 -0
- package/dist/esm/client/query/url-builder.js +107 -0
- package/dist/esm/client/query/url-builder.js.map +1 -0
- package/dist/esm/client/query-builder.d.ts +1 -111
- package/dist/esm/client/record-builder.d.ts +56 -64
- package/dist/esm/client/record-builder.js +158 -297
- package/dist/esm/client/record-builder.js.map +1 -1
- package/dist/esm/client/response-processor.d.ts +3 -3
- package/dist/esm/client/update-builder.d.ts +17 -25
- package/dist/esm/client/update-builder.js +56 -30
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +8 -1
- package/dist/esm/errors.js +17 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +3 -7
- package/dist/esm/index.js +37 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/orm/column.d.ts +45 -0
- package/dist/esm/orm/column.js +59 -0
- package/dist/esm/orm/column.js.map +1 -0
- package/dist/esm/orm/field-builders.d.ts +154 -0
- package/dist/esm/orm/field-builders.js +152 -0
- package/dist/esm/orm/field-builders.js.map +1 -0
- package/dist/esm/orm/index.d.ts +4 -0
- package/dist/esm/orm/operators.d.ts +175 -0
- package/dist/esm/orm/operators.js +221 -0
- package/dist/esm/orm/operators.js.map +1 -0
- package/dist/esm/orm/table.d.ts +341 -0
- package/dist/esm/orm/table.js +211 -0
- package/dist/esm/orm/table.js.map +1 -0
- package/dist/esm/transform.d.ts +20 -21
- package/dist/esm/transform.js +34 -34
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +73 -12
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/validation.d.ts +14 -4
- package/dist/esm/validation.js +45 -1
- package/dist/esm/validation.js.map +1 -1
- package/package.json +22 -17
- package/src/client/batch-builder.ts +102 -33
- package/src/client/builders/default-select.ts +69 -0
- package/src/client/builders/expand-builder.ts +236 -0
- package/src/client/builders/index.ts +11 -0
- package/src/client/builders/query-string-builder.ts +41 -0
- package/src/client/builders/response-processor.ts +273 -0
- package/src/client/builders/select-mixin.ts +74 -0
- package/src/client/builders/select-utils.ts +34 -0
- package/src/client/builders/shared-types.ts +41 -0
- package/src/client/builders/table-utils.ts +87 -0
- package/src/client/database.ts +19 -160
- package/src/client/delete-builder.ts +48 -52
- package/src/client/entity-set.ts +227 -302
- package/src/client/error-parser.ts +59 -0
- package/src/client/filemaker-odata.ts +3 -14
- package/src/client/insert-builder.ts +126 -44
- package/src/client/query/expand-builder.ts +164 -0
- package/src/client/query/index.ts +13 -0
- package/src/client/query/query-builder.ts +826 -0
- package/src/client/query/response-processor.ts +244 -0
- package/src/client/query/types.ts +102 -0
- package/src/client/query/url-builder.ts +179 -0
- package/src/client/query-builder.ts +8 -1454
- package/src/client/record-builder.ts +336 -586
- package/src/client/response-processor.ts +4 -5
- package/src/client/update-builder.ts +113 -75
- package/src/errors.ts +22 -1
- package/src/index.ts +58 -5
- package/src/orm/column.ts +78 -0
- package/src/orm/field-builders.ts +296 -0
- package/src/orm/index.ts +60 -0
- package/src/orm/operators.ts +428 -0
- package/src/orm/table.ts +759 -0
- package/src/transform.ts +62 -48
- package/src/types.ts +88 -63
- package/src/validation.ts +76 -4
- package/LICENSE.md +0 -21
- package/dist/esm/client/base-table.d.ts +0 -128
- package/dist/esm/client/base-table.js +0 -57
- package/dist/esm/client/base-table.js.map +0 -1
- package/dist/esm/client/build-occurrences.d.ts +0 -74
- package/dist/esm/client/build-occurrences.js +0 -31
- package/dist/esm/client/build-occurrences.js.map +0 -1
- package/dist/esm/client/query-builder.js +0 -900
- package/dist/esm/client/query-builder.js.map +0 -1
- package/dist/esm/client/table-occurrence.d.ts +0 -86
- package/dist/esm/client/table-occurrence.js +0 -58
- package/dist/esm/client/table-occurrence.js.map +0 -1
- package/src/client/base-table.ts +0 -178
- package/src/client/build-occurrences.ts +0 -155
- package/src/client/query-builder.ts.bak +0 -1457
- package/src/client/table-occurrence.ts +0 -156
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { QueryOptions } from "odata-query";
|
|
2
|
+
import type { ExecutionContext } from "../../types";
|
|
3
|
+
import type { FMTable } from "../../orm/table";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Expand configuration used by both QueryBuilder and RecordBuilder
|
|
7
|
+
*/
|
|
8
|
+
export type ExpandConfig = {
|
|
9
|
+
relation: string;
|
|
10
|
+
options?: Partial<QueryOptions<any>>;
|
|
11
|
+
targetTable?: FMTable<any, any>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Type to represent expanded relations in return types
|
|
16
|
+
*/
|
|
17
|
+
export type ExpandedRelations = Record<string, { schema: any; selected: any }>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Navigation context shared between builders
|
|
21
|
+
*/
|
|
22
|
+
export interface NavigationContext {
|
|
23
|
+
isNavigate?: boolean;
|
|
24
|
+
navigateRecordId?: string | number;
|
|
25
|
+
navigateRelation?: string;
|
|
26
|
+
navigateSourceTableName?: string;
|
|
27
|
+
navigateBaseRelation?: string;
|
|
28
|
+
navigateBasePath?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Common builder configuration
|
|
33
|
+
*/
|
|
34
|
+
export interface BuilderConfig<Occ extends FMTable<any, any> | undefined> {
|
|
35
|
+
occurrence?: Occ;
|
|
36
|
+
tableName: string;
|
|
37
|
+
databaseName: string;
|
|
38
|
+
context: ExecutionContext;
|
|
39
|
+
databaseUseEntityIds?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ExecutionContext } from "../../types";
|
|
2
|
+
import { getAcceptHeader } from "../../types";
|
|
3
|
+
import type { FMTable } from "../../orm/table";
|
|
4
|
+
import {
|
|
5
|
+
getTableName,
|
|
6
|
+
getTableId as getTableIdHelper,
|
|
7
|
+
isUsingEntityIds,
|
|
8
|
+
} from "../../orm/table";
|
|
9
|
+
import type { FFetchOptions } from "@fetchkit/ffetch";
|
|
10
|
+
import type { ExecuteOptions } from "../../types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves table identifier based on entity ID settings.
|
|
14
|
+
* Used by both QueryBuilder and RecordBuilder.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveTableId(
|
|
17
|
+
table: FMTable<any, any> | undefined,
|
|
18
|
+
fallbackTableName: string,
|
|
19
|
+
context: ExecutionContext,
|
|
20
|
+
useEntityIdsOverride?: boolean,
|
|
21
|
+
): string {
|
|
22
|
+
if (!table) {
|
|
23
|
+
return fallbackTableName;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const contextDefault = context._getUseEntityIds?.() ?? false;
|
|
27
|
+
const shouldUseIds = useEntityIdsOverride ?? contextDefault;
|
|
28
|
+
|
|
29
|
+
if (shouldUseIds) {
|
|
30
|
+
if (!isUsingEntityIds(table)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`useEntityIds is true but table "${getTableName(table)}" does not have entity IDs configured`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return getTableIdHelper(table);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return getTableName(table);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Merges database-level useEntityIds with per-request options.
|
|
43
|
+
*/
|
|
44
|
+
export function mergeEntityIdOptions<T extends Record<string, any>>(
|
|
45
|
+
options: T | undefined,
|
|
46
|
+
databaseDefault: boolean,
|
|
47
|
+
): T & { useEntityIds?: boolean } {
|
|
48
|
+
return {
|
|
49
|
+
...options,
|
|
50
|
+
useEntityIds: (options as any)?.useEntityIds ?? databaseDefault,
|
|
51
|
+
} as T & { useEntityIds?: boolean };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Type-safe helper for merging execute options with entity ID settings
|
|
56
|
+
*/
|
|
57
|
+
export function mergeExecuteOptions(
|
|
58
|
+
options: (RequestInit & FFetchOptions & ExecuteOptions) | undefined,
|
|
59
|
+
databaseUseEntityIds: boolean,
|
|
60
|
+
): RequestInit & FFetchOptions & { useEntityIds?: boolean } {
|
|
61
|
+
return mergeEntityIdOptions(options, databaseUseEntityIds);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates an OData Request object with proper headers.
|
|
66
|
+
* Used by both QueryBuilder and RecordBuilder to eliminate duplication.
|
|
67
|
+
*
|
|
68
|
+
* @param baseUrl - Base URL for the request
|
|
69
|
+
* @param config - Request configuration with method and url
|
|
70
|
+
* @param options - Optional execution options
|
|
71
|
+
* @returns Request object ready to use
|
|
72
|
+
*/
|
|
73
|
+
export function createODataRequest(
|
|
74
|
+
baseUrl: string,
|
|
75
|
+
config: { method: string; url: string },
|
|
76
|
+
options?: { includeODataAnnotations?: boolean },
|
|
77
|
+
): Request {
|
|
78
|
+
const fullUrl = `${baseUrl}${config.url}`;
|
|
79
|
+
|
|
80
|
+
return new Request(fullUrl, {
|
|
81
|
+
method: config.method,
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
Accept: getAcceptHeader(options?.includeODataAnnotations),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
package/src/client/database.ts
CHANGED
|
@@ -1,50 +1,11 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
2
|
import type { ExecutionContext, ExecutableBuilder, Metadata } from "../types";
|
|
3
|
-
import type { BaseTable } from "./base-table";
|
|
4
|
-
import type { TableOccurrence } from "./table-occurrence";
|
|
5
3
|
import { EntitySet } from "./entity-set";
|
|
6
4
|
import { BatchBuilder } from "./batch-builder";
|
|
7
5
|
import { SchemaManager } from "./schema-manager";
|
|
6
|
+
import { FMTable } from "../orm/table";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
type ExtractSchemaFromOccurrence<O> =
|
|
11
|
-
O extends TableOccurrence<infer BT, any, any, any>
|
|
12
|
-
? BT extends BaseTable<infer S, any, any, any>
|
|
13
|
-
? S
|
|
14
|
-
: never
|
|
15
|
-
: never;
|
|
16
|
-
|
|
17
|
-
// Helper type to find an occurrence by name in the occurrences tuple
|
|
18
|
-
type FindOccurrenceByName<
|
|
19
|
-
Occurrences extends readonly TableOccurrence<any, any, any, any>[],
|
|
20
|
-
Name extends string,
|
|
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
|
|
38
|
-
|
|
39
|
-
export class Database<
|
|
40
|
-
Occurrences extends readonly TableOccurrence<
|
|
41
|
-
any,
|
|
42
|
-
any,
|
|
43
|
-
any,
|
|
44
|
-
any
|
|
45
|
-
>[] = readonly [],
|
|
46
|
-
> {
|
|
47
|
-
private occurrenceMap: Map<string, TableOccurrence<any, any, any, any>>;
|
|
8
|
+
export class Database {
|
|
48
9
|
private _useEntityIds: boolean = false;
|
|
49
10
|
public readonly schema: SchemaManager;
|
|
50
11
|
|
|
@@ -52,7 +13,6 @@ export class Database<
|
|
|
52
13
|
private readonly databaseName: string,
|
|
53
14
|
private readonly context: ExecutionContext,
|
|
54
15
|
config?: {
|
|
55
|
-
occurrences?: Occurrences | undefined;
|
|
56
16
|
/**
|
|
57
17
|
* Whether to use entity IDs instead of field names in the actual requests to the server
|
|
58
18
|
* Defaults to true if all occurrences use entity IDs, false otherwise
|
|
@@ -61,129 +21,28 @@ export class Database<
|
|
|
61
21
|
useEntityIds?: boolean;
|
|
62
22
|
},
|
|
63
23
|
) {
|
|
64
|
-
this.occurrenceMap = new Map();
|
|
65
|
-
if (config?.occurrences) {
|
|
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
|
-
|
|
135
24
|
// Initialize schema manager
|
|
136
25
|
this.schema = new SchemaManager(this.databaseName, this.context);
|
|
26
|
+
this._useEntityIds = config?.useEntityIds ?? false;
|
|
137
27
|
}
|
|
138
28
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
*/
|
|
150
|
-
getOccurrence(name: string): TableOccurrence<any, any, any, any> | undefined {
|
|
151
|
-
return this.occurrenceMap.get(name);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
from<Name extends ExtractOccurrenceNames<Occurrences> | (string & {})>(
|
|
155
|
-
name: Name,
|
|
156
|
-
): Occurrences extends readonly []
|
|
157
|
-
? EntitySet<Record<string, StandardSchemaV1>, undefined>
|
|
158
|
-
: Name extends ExtractOccurrenceNames<Occurrences>
|
|
159
|
-
? EntitySet<
|
|
160
|
-
ExtractSchemaFromOccurrence<FindOccurrenceByName<Occurrences, Name>>,
|
|
161
|
-
FindOccurrenceByName<Occurrences, Name>
|
|
162
|
-
>
|
|
163
|
-
: EntitySet<Record<string, StandardSchemaV1>, undefined> {
|
|
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;
|
|
29
|
+
from<T extends FMTable<any, any>>(table: T): EntitySet<T> {
|
|
30
|
+
// Only override database-level useEntityIds if table explicitly sets it
|
|
31
|
+
// (not if it's undefined, which would override the database setting)
|
|
32
|
+
if (
|
|
33
|
+
Object.prototype.hasOwnProperty.call(table, FMTable.Symbol.UseEntityIds)
|
|
34
|
+
) {
|
|
35
|
+
const tableUseEntityIds = (table as any)[FMTable.Symbol.UseEntityIds];
|
|
36
|
+
if (typeof tableUseEntityIds === "boolean") {
|
|
37
|
+
this._useEntityIds = tableUseEntityIds;
|
|
38
|
+
}
|
|
186
39
|
}
|
|
40
|
+
return new EntitySet<T>({
|
|
41
|
+
occurrence: table as T,
|
|
42
|
+
databaseName: this.databaseName,
|
|
43
|
+
context: this.context,
|
|
44
|
+
database: this,
|
|
45
|
+
});
|
|
187
46
|
}
|
|
188
47
|
|
|
189
48
|
/**
|
|
@@ -4,33 +4,36 @@ import type {
|
|
|
4
4
|
Result,
|
|
5
5
|
WithSystemFields,
|
|
6
6
|
ExecuteOptions,
|
|
7
|
+
ExecuteMethodOptions,
|
|
7
8
|
} from "../types";
|
|
8
9
|
import { getAcceptHeader } from "../types";
|
|
9
|
-
import type {
|
|
10
|
+
import type { FMTable, InferSchemaOutputFromFMTable } from "../orm/table";
|
|
11
|
+
import {
|
|
12
|
+
getTableName,
|
|
13
|
+
getTableId as getTableIdHelper,
|
|
14
|
+
isUsingEntityIds,
|
|
15
|
+
} from "../orm/table";
|
|
10
16
|
import { QueryBuilder } from "./query-builder";
|
|
11
17
|
import { type FFetchOptions } from "@fetchkit/ffetch";
|
|
12
|
-
import {
|
|
18
|
+
import { parseErrorResponse } from "./error-parser";
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Initial delete builder returned from EntitySet.delete()
|
|
16
22
|
* Requires calling .byId() or .where() before .execute() is available
|
|
17
23
|
*/
|
|
18
|
-
export class DeleteBuilder<
|
|
19
|
-
private tableName: string;
|
|
24
|
+
export class DeleteBuilder<Occ extends FMTable<any, any>> {
|
|
20
25
|
private databaseName: string;
|
|
21
26
|
private context: ExecutionContext;
|
|
22
|
-
private
|
|
27
|
+
private table: Occ;
|
|
23
28
|
private databaseUseEntityIds: boolean;
|
|
24
29
|
|
|
25
30
|
constructor(config: {
|
|
26
|
-
occurrence
|
|
27
|
-
tableName: string;
|
|
31
|
+
occurrence: Occ;
|
|
28
32
|
databaseName: string;
|
|
29
33
|
context: ExecutionContext;
|
|
30
34
|
databaseUseEntityIds?: boolean;
|
|
31
35
|
}) {
|
|
32
|
-
this.
|
|
33
|
-
this.tableName = config.tableName;
|
|
36
|
+
this.table = config.occurrence;
|
|
34
37
|
this.databaseName = config.databaseName;
|
|
35
38
|
this.context = config.context;
|
|
36
39
|
this.databaseUseEntityIds = config.databaseUseEntityIds ?? false;
|
|
@@ -39,10 +42,9 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
39
42
|
/**
|
|
40
43
|
* Delete a single record by ID
|
|
41
44
|
*/
|
|
42
|
-
byId(id: string | number): ExecutableDeleteBuilder<
|
|
43
|
-
return new ExecutableDeleteBuilder<
|
|
44
|
-
occurrence: this.
|
|
45
|
-
tableName: this.tableName,
|
|
45
|
+
byId(id: string | number): ExecutableDeleteBuilder<Occ> {
|
|
46
|
+
return new ExecutableDeleteBuilder<Occ>({
|
|
47
|
+
occurrence: this.table,
|
|
46
48
|
databaseName: this.databaseName,
|
|
47
49
|
context: this.context,
|
|
48
50
|
mode: "byId",
|
|
@@ -56,20 +58,11 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
56
58
|
* @param fn Callback that receives a QueryBuilder for building the filter
|
|
57
59
|
*/
|
|
58
60
|
where(
|
|
59
|
-
fn: (
|
|
60
|
-
|
|
61
|
-
) => QueryBuilder<WithSystemFields<T>>,
|
|
62
|
-
): ExecutableDeleteBuilder<T> {
|
|
61
|
+
fn: (q: QueryBuilder<Occ>) => QueryBuilder<Occ>,
|
|
62
|
+
): ExecutableDeleteBuilder<Occ> {
|
|
63
63
|
// Create a QueryBuilder for the user to configure
|
|
64
|
-
const queryBuilder = new QueryBuilder<
|
|
65
|
-
|
|
66
|
-
keyof WithSystemFields<T>,
|
|
67
|
-
false,
|
|
68
|
-
false,
|
|
69
|
-
undefined
|
|
70
|
-
>({
|
|
71
|
-
occurrence: undefined,
|
|
72
|
-
tableName: this.tableName,
|
|
64
|
+
const queryBuilder = new QueryBuilder<Occ>({
|
|
65
|
+
occurrence: this.table,
|
|
73
66
|
databaseName: this.databaseName,
|
|
74
67
|
context: this.context,
|
|
75
68
|
});
|
|
@@ -77,9 +70,8 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
77
70
|
// Let the user configure it
|
|
78
71
|
const configuredBuilder = fn(queryBuilder);
|
|
79
72
|
|
|
80
|
-
return new ExecutableDeleteBuilder<
|
|
81
|
-
occurrence: this.
|
|
82
|
-
tableName: this.tableName,
|
|
73
|
+
return new ExecutableDeleteBuilder<Occ>({
|
|
74
|
+
occurrence: this.table,
|
|
83
75
|
databaseName: this.databaseName,
|
|
84
76
|
context: this.context,
|
|
85
77
|
mode: "byFilter",
|
|
@@ -93,30 +85,27 @@ export class DeleteBuilder<T extends Record<string, any>> {
|
|
|
93
85
|
* Executable delete builder - has execute() method
|
|
94
86
|
* Returned after calling .byId() or .where()
|
|
95
87
|
*/
|
|
96
|
-
export class ExecutableDeleteBuilder<
|
|
88
|
+
export class ExecutableDeleteBuilder<Occ extends FMTable<any, any>>
|
|
97
89
|
implements ExecutableBuilder<{ deletedCount: number }>
|
|
98
90
|
{
|
|
99
|
-
private tableName: string;
|
|
100
91
|
private databaseName: string;
|
|
101
92
|
private context: ExecutionContext;
|
|
102
|
-
private
|
|
93
|
+
private table: Occ;
|
|
103
94
|
private mode: "byId" | "byFilter";
|
|
104
95
|
private recordId?: string | number;
|
|
105
|
-
private queryBuilder?: QueryBuilder<
|
|
96
|
+
private queryBuilder?: QueryBuilder<Occ>;
|
|
106
97
|
private databaseUseEntityIds: boolean;
|
|
107
98
|
|
|
108
99
|
constructor(config: {
|
|
109
|
-
occurrence
|
|
110
|
-
tableName: string;
|
|
100
|
+
occurrence: Occ;
|
|
111
101
|
databaseName: string;
|
|
112
102
|
context: ExecutionContext;
|
|
113
103
|
mode: "byId" | "byFilter";
|
|
114
104
|
recordId?: string | number;
|
|
115
|
-
queryBuilder?: QueryBuilder<
|
|
105
|
+
queryBuilder?: QueryBuilder<Occ>;
|
|
116
106
|
databaseUseEntityIds?: boolean;
|
|
117
107
|
}) {
|
|
118
|
-
this.
|
|
119
|
-
this.tableName = config.tableName;
|
|
108
|
+
this.table = config.occurrence;
|
|
120
109
|
this.databaseName = config.databaseName;
|
|
121
110
|
this.context = config.context;
|
|
122
111
|
this.mode = config.mode;
|
|
@@ -143,28 +132,23 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
143
132
|
* @param useEntityIds - Optional override for entity ID usage
|
|
144
133
|
*/
|
|
145
134
|
private getTableId(useEntityIds?: boolean): string {
|
|
146
|
-
if (!this.occurrence) {
|
|
147
|
-
return this.tableName;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
135
|
const contextDefault = this.context._getUseEntityIds?.() ?? false;
|
|
151
136
|
const shouldUseIds = useEntityIds ?? contextDefault;
|
|
152
137
|
|
|
153
138
|
if (shouldUseIds) {
|
|
154
|
-
|
|
155
|
-
if (!identifiers.id) {
|
|
139
|
+
if (!isUsingEntityIds(this.table)) {
|
|
156
140
|
throw new Error(
|
|
157
|
-
`useEntityIds is true but
|
|
141
|
+
`useEntityIds is true but table "${getTableName(this.table)}" does not have entity IDs configured`,
|
|
158
142
|
);
|
|
159
143
|
}
|
|
160
|
-
return
|
|
144
|
+
return getTableIdHelper(this.table);
|
|
161
145
|
}
|
|
162
146
|
|
|
163
|
-
return this.
|
|
147
|
+
return getTableName(this.table);
|
|
164
148
|
}
|
|
165
149
|
|
|
166
150
|
async execute(
|
|
167
|
-
options?:
|
|
151
|
+
options?: ExecuteMethodOptions<ExecuteOptions>,
|
|
168
152
|
): Promise<Result<{ deletedCount: number }>> {
|
|
169
153
|
// Merge database-level useEntityIds with per-request options
|
|
170
154
|
const mergedOptions = this.mergeExecuteOptions(options);
|
|
@@ -186,10 +170,11 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
186
170
|
// Get the query string from the configured QueryBuilder
|
|
187
171
|
const queryString = this.queryBuilder.getQueryString();
|
|
188
172
|
// Remove the leading "/" and table name from the query string as we'll build our own URL
|
|
173
|
+
const tableName = getTableName(this.table);
|
|
189
174
|
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
190
175
|
? queryString.slice(`/${tableId}`.length)
|
|
191
|
-
: queryString.startsWith(`/${
|
|
192
|
-
? queryString.slice(`/${
|
|
176
|
+
: queryString.startsWith(`/${tableName}`)
|
|
177
|
+
? queryString.slice(`/${tableName}`.length)
|
|
193
178
|
: queryString;
|
|
194
179
|
|
|
195
180
|
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
@@ -236,10 +221,11 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
236
221
|
}
|
|
237
222
|
|
|
238
223
|
const queryString = this.queryBuilder.getQueryString();
|
|
224
|
+
const tableName = getTableName(this.table);
|
|
239
225
|
const queryParams = queryString.startsWith(`/${tableId}`)
|
|
240
226
|
? queryString.slice(`/${tableId}`.length)
|
|
241
|
-
: queryString.startsWith(`/${
|
|
242
|
-
? queryString.slice(`/${
|
|
227
|
+
: queryString.startsWith(`/${tableName}`)
|
|
228
|
+
? queryString.slice(`/${tableName}`.length)
|
|
243
229
|
: queryString;
|
|
244
230
|
|
|
245
231
|
url = `/${this.databaseName}/${tableId}${queryParams}`;
|
|
@@ -267,6 +253,16 @@ export class ExecutableDeleteBuilder<T extends Record<string, any>>
|
|
|
267
253
|
response: Response,
|
|
268
254
|
options?: ExecuteOptions,
|
|
269
255
|
): Promise<Result<{ deletedCount: number }>> {
|
|
256
|
+
// Check for error responses (important for batch operations)
|
|
257
|
+
if (!response.ok) {
|
|
258
|
+
const tableName = getTableName(this.table);
|
|
259
|
+
const error = await parseErrorResponse(
|
|
260
|
+
response,
|
|
261
|
+
response.url || `/${this.databaseName}/${tableName}`,
|
|
262
|
+
);
|
|
263
|
+
return { data: undefined, error };
|
|
264
|
+
}
|
|
265
|
+
|
|
270
266
|
// Check for empty response (204 No Content)
|
|
271
267
|
const text = await response.text();
|
|
272
268
|
if (!text || text.trim() === "") {
|