@proofkit/fmodata 0.1.0-alpha.4 → 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 +690 -31
- package/dist/esm/client/base-table.d.ts +122 -5
- package/dist/esm/client/base-table.js +46 -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/database.d.ts +54 -5
- 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 +22 -8
- package/dist/esm/client/entity-set.js +28 -8
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +22 -3
- package/dist/esm/client/filemaker-odata.js +122 -27
- 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 +26 -5
- package/dist/esm/client/query-builder.js +455 -208
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +19 -4
- package/dist/esm/client/record-builder.js +132 -40
- 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 +66 -2
- package/dist/esm/client/table-occurrence.js +36 -1
- 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 +7 -3
- package/dist/esm/index.js +27 -3
- 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 +155 -8
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +173 -16
- package/src/client/delete-builder.ts +149 -48
- package/src/client/entity-set.ts +99 -15
- package/src/client/filemaker-odata.ts +178 -34
- package/src/client/insert-builder.ts +350 -40
- package/src/client/query-builder.ts +609 -236
- package/src/client/record-builder.ts +186 -53
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +118 -4
- package/src/client/update-builder.ts +235 -49
- package/src/errors.ts +217 -0
- package/src/index.ts +43 -1
- package/src/transform.ts +249 -0
- package/src/types.ts +201 -35
- package/src/validation.ts +120 -36
|
@@ -1,13 +1,130 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* BaseTable defines the schema and configuration for a table.
|
|
4
|
+
*
|
|
5
|
+
* @template Schema - Record of field names to StandardSchemaV1 validators
|
|
6
|
+
* @template IdField - The name of the primary key field (optional, automatically read-only)
|
|
7
|
+
* @template Required - Additional field names to require on insert (beyond auto-inferred required fields)
|
|
8
|
+
* @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)
|
|
9
|
+
*
|
|
10
|
+
* @example Basic table with auto-inferred required fields
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { z } from "zod";
|
|
13
|
+
*
|
|
14
|
+
* const usersTable = new BaseTable({
|
|
15
|
+
* schema: {
|
|
16
|
+
* id: z.string(), // Auto-required (not nullable), auto-readOnly (idField)
|
|
17
|
+
* name: z.string(), // Auto-required (not nullable)
|
|
18
|
+
* email: z.string().nullable(), // Optional (nullable)
|
|
19
|
+
* },
|
|
20
|
+
* idField: "id",
|
|
21
|
+
* });
|
|
22
|
+
* // On insert: name is required, email is optional (id is excluded - readOnly)
|
|
23
|
+
* // On update: name and email available (id is excluded - readOnly)
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @example Table with additional required and readOnly fields
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { z } from "zod";
|
|
29
|
+
*
|
|
30
|
+
* const usersTable = new BaseTable({
|
|
31
|
+
* schema: {
|
|
32
|
+
* id: z.string(), // Auto-required, auto-readOnly (idField)
|
|
33
|
+
* createdAt: z.string(), // Read-only system field
|
|
34
|
+
* name: z.string(), // Auto-required
|
|
35
|
+
* email: z.string().nullable(), // Optional by default...
|
|
36
|
+
* legacyField: z.string().nullable(), // Optional by default...
|
|
37
|
+
* },
|
|
38
|
+
* idField: "id",
|
|
39
|
+
* required: ["legacyField"], // Make legacyField required for new inserts
|
|
40
|
+
* readOnly: ["createdAt"], // Exclude from insert/update
|
|
41
|
+
* });
|
|
42
|
+
* // On insert: name and legacyField required; email optional (id and createdAt excluded)
|
|
43
|
+
* // On update: all fields optional (id and createdAt excluded)
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* @example Table with multiple read-only fields
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { z } from "zod";
|
|
49
|
+
*
|
|
50
|
+
* const usersTable = new BaseTable({
|
|
51
|
+
* schema: {
|
|
52
|
+
* id: z.string(),
|
|
53
|
+
* createdAt: z.string(),
|
|
54
|
+
* modifiedAt: z.string(),
|
|
55
|
+
* createdBy: z.string(),
|
|
56
|
+
* notes: z.string().nullable(),
|
|
57
|
+
* },
|
|
58
|
+
* idField: "id",
|
|
59
|
+
* readOnly: ["createdAt", "modifiedAt", "createdBy"],
|
|
60
|
+
* });
|
|
61
|
+
* // On insert/update: only notes is available (id and system fields excluded)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare class BaseTable<Schema extends Record<string, StandardSchemaV1> = any, IdField extends keyof Schema | undefined = undefined, Required extends readonly (keyof Schema)[] = readonly [], ReadOnly extends readonly (keyof Schema)[] = readonly []> {
|
|
3
65
|
readonly schema: Schema;
|
|
4
66
|
readonly idField?: IdField;
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
67
|
+
readonly required?: Required;
|
|
68
|
+
readonly readOnly?: ReadOnly;
|
|
69
|
+
readonly fmfIds?: Record<keyof Schema, `FMFID:${string}`>;
|
|
7
70
|
constructor(config: {
|
|
8
71
|
schema: Schema;
|
|
9
72
|
idField?: IdField;
|
|
10
|
-
|
|
11
|
-
|
|
73
|
+
required?: Required;
|
|
74
|
+
readOnly?: ReadOnly;
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Returns the FileMaker field ID (FMFID) for a given field name, or the field name itself if not using IDs.
|
|
78
|
+
* @param fieldName - The field name to get the ID for
|
|
79
|
+
* @returns The FMFID string or the original field name
|
|
80
|
+
*/
|
|
81
|
+
getFieldId(fieldName: keyof Schema): string;
|
|
82
|
+
/**
|
|
83
|
+
* Returns the field name for a given FileMaker field ID (FMFID), or the ID itself if not found.
|
|
84
|
+
* @param fieldId - The FMFID to get the field name for
|
|
85
|
+
* @returns The field name or the original ID
|
|
86
|
+
*/
|
|
87
|
+
getFieldName(fieldId: string): string;
|
|
88
|
+
/**
|
|
89
|
+
* Returns true if this BaseTable is using FileMaker field IDs.
|
|
90
|
+
*/
|
|
91
|
+
isUsingFieldIds(): boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* BaseTableWithIds extends BaseTable to require FileMaker field IDs (fmfIds).
|
|
95
|
+
* Use this class when you need to work with FileMaker's internal field identifiers.
|
|
96
|
+
*
|
|
97
|
+
* @template Schema - Record of field names to StandardSchemaV1 validators
|
|
98
|
+
* @template IdField - The name of the primary key field (optional, automatically read-only)
|
|
99
|
+
* @template Required - Additional field names to require on insert (beyond auto-inferred required fields)
|
|
100
|
+
* @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { z } from "zod";
|
|
105
|
+
*
|
|
106
|
+
* const usersTableWithIds = new BaseTableWithIds({
|
|
107
|
+
* schema: {
|
|
108
|
+
* id: z.string(),
|
|
109
|
+
* name: z.string(),
|
|
110
|
+
* email: z.string().nullable(),
|
|
111
|
+
* },
|
|
112
|
+
* idField: "id",
|
|
113
|
+
* fmfIds: {
|
|
114
|
+
* id: "FMFID:1",
|
|
115
|
+
* name: "FMFID:2",
|
|
116
|
+
* email: "FMFID:3",
|
|
117
|
+
* },
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare class BaseTableWithIds<Schema extends Record<string, StandardSchemaV1> = any, IdField extends keyof Schema | undefined = undefined, Required extends readonly (keyof Schema)[] = readonly [], ReadOnly extends readonly (keyof Schema)[] = readonly []> extends BaseTable<Schema, IdField, Required, ReadOnly> {
|
|
122
|
+
readonly fmfIds: Record<keyof Schema, `FMFID:${string}`>;
|
|
123
|
+
constructor(config: {
|
|
124
|
+
schema: Schema;
|
|
125
|
+
fmfIds: Record<keyof Schema, `FMFID:${string}`>;
|
|
126
|
+
idField?: IdField;
|
|
127
|
+
required?: Required;
|
|
128
|
+
readOnly?: ReadOnly;
|
|
12
129
|
});
|
|
13
130
|
}
|
|
@@ -5,15 +5,56 @@ class BaseTable {
|
|
|
5
5
|
constructor(config) {
|
|
6
6
|
__publicField(this, "schema");
|
|
7
7
|
__publicField(this, "idField");
|
|
8
|
-
__publicField(this, "
|
|
9
|
-
__publicField(this, "
|
|
8
|
+
__publicField(this, "required");
|
|
9
|
+
__publicField(this, "readOnly");
|
|
10
|
+
__publicField(this, "fmfIds");
|
|
10
11
|
this.schema = config.schema;
|
|
11
12
|
this.idField = config.idField;
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
13
|
+
this.required = config.required;
|
|
14
|
+
this.readOnly = config.readOnly;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns the FileMaker field ID (FMFID) for a given field name, or the field name itself if not using IDs.
|
|
18
|
+
* @param fieldName - The field name to get the ID for
|
|
19
|
+
* @returns The FMFID string or the original field name
|
|
20
|
+
*/
|
|
21
|
+
getFieldId(fieldName) {
|
|
22
|
+
if (this.fmfIds && fieldName in this.fmfIds) {
|
|
23
|
+
return this.fmfIds[fieldName];
|
|
24
|
+
}
|
|
25
|
+
return String(fieldName);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Returns the field name for a given FileMaker field ID (FMFID), or the ID itself if not found.
|
|
29
|
+
* @param fieldId - The FMFID to get the field name for
|
|
30
|
+
* @returns The field name or the original ID
|
|
31
|
+
*/
|
|
32
|
+
getFieldName(fieldId) {
|
|
33
|
+
if (this.fmfIds) {
|
|
34
|
+
for (const [fieldName, fmfId] of Object.entries(this.fmfIds)) {
|
|
35
|
+
if (fmfId === fieldId) {
|
|
36
|
+
return fieldName;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return fieldId;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns true if this BaseTable is using FileMaker field IDs.
|
|
44
|
+
*/
|
|
45
|
+
isUsingFieldIds() {
|
|
46
|
+
return this.fmfIds !== void 0;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
class BaseTableWithIds extends BaseTable {
|
|
50
|
+
constructor(config) {
|
|
51
|
+
super(config);
|
|
52
|
+
__publicField(this, "fmfIds");
|
|
53
|
+
this.fmfIds = config.fmfIds;
|
|
14
54
|
}
|
|
15
55
|
}
|
|
16
56
|
export {
|
|
17
|
-
BaseTable
|
|
57
|
+
BaseTable,
|
|
58
|
+
BaseTableWithIds
|
|
18
59
|
};
|
|
19
60
|
//# sourceMappingURL=base-table.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-table.js","sources":["../../../src/client/base-table.ts"],"sourcesContent":["import { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport class BaseTable<\n Schema extends Record<string, StandardSchemaV1> = any,\n IdField extends keyof Schema | undefined = undefined,\n
|
|
1
|
+
{"version":3,"file":"base-table.js","sources":["../../../src/client/base-table.ts"],"sourcesContent":["import { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * BaseTable defines the schema and configuration for a table.\n *\n * @template Schema - Record of field names to StandardSchemaV1 validators\n * @template IdField - The name of the primary key field (optional, automatically read-only)\n * @template Required - Additional field names to require on insert (beyond auto-inferred required fields)\n * @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)\n *\n * @example Basic table with auto-inferred required fields\n * ```ts\n * import { z } from \"zod\";\n *\n * const usersTable = new BaseTable({\n * schema: {\n * id: z.string(), // Auto-required (not nullable), auto-readOnly (idField)\n * name: z.string(), // Auto-required (not nullable)\n * email: z.string().nullable(), // Optional (nullable)\n * },\n * idField: \"id\",\n * });\n * // On insert: name is required, email is optional (id is excluded - readOnly)\n * // On update: name and email available (id is excluded - readOnly)\n * ```\n *\n * @example Table with additional required and readOnly fields\n * ```ts\n * import { z } from \"zod\";\n *\n * const usersTable = new BaseTable({\n * schema: {\n * id: z.string(), // Auto-required, auto-readOnly (idField)\n * createdAt: z.string(), // Read-only system field\n * name: z.string(), // Auto-required\n * email: z.string().nullable(), // Optional by default...\n * legacyField: z.string().nullable(), // Optional by default...\n * },\n * idField: \"id\",\n * required: [\"legacyField\"], // Make legacyField required for new inserts\n * readOnly: [\"createdAt\"], // Exclude from insert/update\n * });\n * // On insert: name and legacyField required; email optional (id and createdAt excluded)\n * // On update: all fields optional (id and createdAt excluded)\n * ```\n *\n * @example Table with multiple read-only fields\n * ```ts\n * import { z } from \"zod\";\n *\n * const usersTable = new BaseTable({\n * schema: {\n * id: z.string(),\n * createdAt: z.string(),\n * modifiedAt: z.string(),\n * createdBy: z.string(),\n * notes: z.string().nullable(),\n * },\n * idField: \"id\",\n * readOnly: [\"createdAt\", \"modifiedAt\", \"createdBy\"],\n * });\n * // On insert/update: only notes is available (id and system fields excluded)\n * ```\n */\nexport class BaseTable<\n Schema extends Record<string, StandardSchemaV1> = any,\n IdField extends keyof Schema | undefined = undefined,\n Required extends readonly (keyof Schema)[] = readonly [],\n ReadOnly extends readonly (keyof Schema)[] = readonly [],\n> {\n public readonly schema: Schema;\n public readonly idField?: IdField;\n public readonly required?: Required;\n public readonly readOnly?: ReadOnly;\n public readonly fmfIds?: Record<keyof Schema, `FMFID:${string}`>;\n\n constructor(config: {\n schema: Schema;\n idField?: IdField;\n required?: Required;\n readOnly?: ReadOnly;\n }) {\n this.schema = config.schema;\n this.idField = config.idField;\n this.required = config.required;\n this.readOnly = config.readOnly;\n }\n\n /**\n * Returns the FileMaker field ID (FMFID) for a given field name, or the field name itself if not using IDs.\n * @param fieldName - The field name to get the ID for\n * @returns The FMFID string or the original field name\n */\n getFieldId(fieldName: keyof Schema): string {\n if (this.fmfIds && fieldName in this.fmfIds) {\n return this.fmfIds[fieldName];\n }\n return String(fieldName);\n }\n\n /**\n * Returns the field name for a given FileMaker field ID (FMFID), or the ID itself if not found.\n * @param fieldId - The FMFID to get the field name for\n * @returns The field name or the original ID\n */\n getFieldName(fieldId: string): string {\n if (this.fmfIds) {\n // Search for the field name that corresponds to this FMFID\n for (const [fieldName, fmfId] of Object.entries(this.fmfIds)) {\n if (fmfId === fieldId) {\n return fieldName;\n }\n }\n }\n return fieldId;\n }\n\n /**\n * Returns true if this BaseTable is using FileMaker field IDs.\n */\n isUsingFieldIds(): boolean {\n return this.fmfIds !== undefined;\n }\n}\n\n/**\n * BaseTableWithIds extends BaseTable to require FileMaker field IDs (fmfIds).\n * Use this class when you need to work with FileMaker's internal field identifiers.\n *\n * @template Schema - Record of field names to StandardSchemaV1 validators\n * @template IdField - The name of the primary key field (optional, automatically read-only)\n * @template Required - Additional field names to require on insert (beyond auto-inferred required fields)\n * @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n *\n * const usersTableWithIds = new BaseTableWithIds({\n * schema: {\n * id: z.string(),\n * name: z.string(),\n * email: z.string().nullable(),\n * },\n * idField: \"id\",\n * fmfIds: {\n * id: \"FMFID:1\",\n * name: \"FMFID:2\",\n * email: \"FMFID:3\",\n * },\n * });\n * ```\n */\nexport class BaseTableWithIds<\n Schema extends Record<string, StandardSchemaV1> = any,\n IdField extends keyof Schema | undefined = undefined,\n Required extends readonly (keyof Schema)[] = readonly [],\n ReadOnly extends readonly (keyof Schema)[] = readonly [],\n> extends BaseTable<Schema, IdField, Required, ReadOnly> {\n public override readonly fmfIds: Record<keyof Schema, `FMFID:${string}`>;\n\n constructor(config: {\n schema: Schema;\n fmfIds: Record<keyof Schema, `FMFID:${string}`>;\n idField?: IdField;\n required?: Required;\n readOnly?: ReadOnly;\n }) {\n super(config);\n this.fmfIds = config.fmfIds;\n }\n}\n"],"names":[],"mappings":";;;AAgEO,MAAM,UAKX;AAAA,EAOA,YAAY,QAKT;AAXa;AACA;AACA;AACA;AACA;AAQd,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO;AACtB,SAAK,WAAW,OAAO;AACvB,SAAK,WAAW,OAAO;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzB,WAAW,WAAiC;AAC1C,QAAI,KAAK,UAAU,aAAa,KAAK,QAAQ;AACpC,aAAA,KAAK,OAAO,SAAS;AAAA,IAAA;AAE9B,WAAO,OAAO,SAAS;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzB,aAAa,SAAyB;AACpC,QAAI,KAAK,QAAQ;AAEJ,iBAAA,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,KAAK,MAAM,GAAG;AAC5D,YAAI,UAAU,SAAS;AACd,iBAAA;AAAA,QAAA;AAAA,MACT;AAAA,IACF;AAEK,WAAA;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMT,kBAA2B;AACzB,WAAO,KAAK,WAAW;AAAA,EAAA;AAE3B;AA8BO,MAAM,yBAKH,UAA+C;AAAA,EAGvD,YAAY,QAMT;AACD,UAAM,MAAM;AATW;AAUvB,SAAK,SAAS,OAAO;AAAA,EAAA;AAEzB;"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ExecutableBuilder, ExecutionContext, Result, ExecuteOptions } from '../types.js';
|
|
2
|
+
import { FFetchOptions } from '@fetchkit/ffetch';
|
|
3
|
+
/**
|
|
4
|
+
* Helper type to extract result types from a tuple of ExecutableBuilders.
|
|
5
|
+
* Uses a mapped type which TypeScript 4.1+ can handle for tuples.
|
|
6
|
+
*/
|
|
7
|
+
type ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {
|
|
8
|
+
[K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Builder for batch operations that allows multiple queries to be executed together
|
|
12
|
+
* in a single transactional request.
|
|
13
|
+
*/
|
|
14
|
+
export declare class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]> implements ExecutableBuilder<ExtractTupleTypes<Builders>> {
|
|
15
|
+
private readonly databaseName;
|
|
16
|
+
private readonly context;
|
|
17
|
+
private builders;
|
|
18
|
+
private readonly originalBuilders;
|
|
19
|
+
constructor(builders: Builders, databaseName: string, context: ExecutionContext);
|
|
20
|
+
/**
|
|
21
|
+
* Add a request to the batch dynamically.
|
|
22
|
+
* This allows building up batch operations programmatically.
|
|
23
|
+
*
|
|
24
|
+
* @param builder - An executable builder to add to the batch
|
|
25
|
+
* @returns This BatchBuilder for method chaining
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const batch = db.batch([]);
|
|
29
|
+
* batch.addRequest(db.from('contacts').list());
|
|
30
|
+
* batch.addRequest(db.from('users').list());
|
|
31
|
+
* const result = await batch.execute();
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
addRequest<T>(builder: ExecutableBuilder<T>): this;
|
|
35
|
+
/**
|
|
36
|
+
* Get the request configuration for this batch operation.
|
|
37
|
+
* This is used internally by the execution system.
|
|
38
|
+
*/
|
|
39
|
+
getRequestConfig(): {
|
|
40
|
+
method: string;
|
|
41
|
+
url: string;
|
|
42
|
+
body?: any;
|
|
43
|
+
};
|
|
44
|
+
toRequest(baseUrl: string): Request;
|
|
45
|
+
processResponse(response: Response, options?: ExecuteOptions): Promise<Result<any>>;
|
|
46
|
+
/**
|
|
47
|
+
* Execute the batch operation.
|
|
48
|
+
*
|
|
49
|
+
* @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)
|
|
50
|
+
* @returns A tuple of results matching the input builders
|
|
51
|
+
*/
|
|
52
|
+
execute<EO extends ExecuteOptions>(options?: RequestInit & FFetchOptions & EO): Promise<Result<ExtractTupleTypes<Builders>>>;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { formatBatchRequestFromNative, parseBatchResponse } from "./batch-request.js";
|
|
5
|
+
function parsedToResponse(parsed) {
|
|
6
|
+
const headers = new Headers(parsed.headers);
|
|
7
|
+
if (parsed.body === null || parsed.body === void 0) {
|
|
8
|
+
return new Response(null, {
|
|
9
|
+
status: parsed.status,
|
|
10
|
+
statusText: parsed.statusText,
|
|
11
|
+
headers
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
const bodyString = typeof parsed.body === "string" ? parsed.body : JSON.stringify(parsed.body);
|
|
15
|
+
let status = parsed.status;
|
|
16
|
+
if (status === 204 && bodyString && bodyString.trim() !== "") {
|
|
17
|
+
status = 200;
|
|
18
|
+
}
|
|
19
|
+
return new Response(status === 204 ? null : bodyString, {
|
|
20
|
+
status,
|
|
21
|
+
statusText: parsed.statusText,
|
|
22
|
+
headers
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
class BatchBuilder {
|
|
26
|
+
constructor(builders, databaseName, context) {
|
|
27
|
+
__publicField(this, "builders");
|
|
28
|
+
__publicField(this, "originalBuilders");
|
|
29
|
+
this.databaseName = databaseName;
|
|
30
|
+
this.context = context;
|
|
31
|
+
this.builders = [...builders];
|
|
32
|
+
this.originalBuilders = builders;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Add a request to the batch dynamically.
|
|
36
|
+
* This allows building up batch operations programmatically.
|
|
37
|
+
*
|
|
38
|
+
* @param builder - An executable builder to add to the batch
|
|
39
|
+
* @returns This BatchBuilder for method chaining
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const batch = db.batch([]);
|
|
43
|
+
* batch.addRequest(db.from('contacts').list());
|
|
44
|
+
* batch.addRequest(db.from('users').list());
|
|
45
|
+
* const result = await batch.execute();
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
addRequest(builder) {
|
|
49
|
+
this.builders.push(builder);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get the request configuration for this batch operation.
|
|
54
|
+
* This is used internally by the execution system.
|
|
55
|
+
*/
|
|
56
|
+
getRequestConfig() {
|
|
57
|
+
return {
|
|
58
|
+
method: "POST",
|
|
59
|
+
url: `/${this.databaseName}/$batch`,
|
|
60
|
+
body: void 0
|
|
61
|
+
// Body is constructed in execute()
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
toRequest(baseUrl) {
|
|
65
|
+
const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;
|
|
66
|
+
return new Request(fullUrl, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: {
|
|
69
|
+
"Content-Type": "multipart/mixed",
|
|
70
|
+
"OData-Version": "4.0"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async processResponse(response, options) {
|
|
75
|
+
return {
|
|
76
|
+
data: void 0,
|
|
77
|
+
error: {
|
|
78
|
+
name: "NotImplementedError",
|
|
79
|
+
message: "Batch operations handle response processing internally",
|
|
80
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Execute the batch operation.
|
|
86
|
+
*
|
|
87
|
+
* @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)
|
|
88
|
+
* @returns A tuple of results matching the input builders
|
|
89
|
+
*/
|
|
90
|
+
async execute(options) {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
const baseUrl = (_b = (_a = this.context)._getBaseUrl) == null ? void 0 : _b.call(_a);
|
|
93
|
+
if (!baseUrl) {
|
|
94
|
+
return {
|
|
95
|
+
data: void 0,
|
|
96
|
+
error: {
|
|
97
|
+
name: "ConfigurationError",
|
|
98
|
+
message: "Base URL not available - execution context must implement _getBaseUrl()",
|
|
99
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const requests = this.builders.map(
|
|
105
|
+
(builder) => builder.toRequest(baseUrl)
|
|
106
|
+
);
|
|
107
|
+
const { body, boundary } = await formatBatchRequestFromNative(
|
|
108
|
+
requests,
|
|
109
|
+
baseUrl
|
|
110
|
+
);
|
|
111
|
+
const response = await this.context._makeRequest(
|
|
112
|
+
`/${this.databaseName}/$batch`,
|
|
113
|
+
{
|
|
114
|
+
...options,
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
...options == null ? void 0 : options.headers,
|
|
118
|
+
"Content-Type": `multipart/mixed; boundary=${boundary}`,
|
|
119
|
+
"OData-Version": "4.0"
|
|
120
|
+
},
|
|
121
|
+
body
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
if (response.error) {
|
|
125
|
+
return { data: void 0, error: response.error };
|
|
126
|
+
}
|
|
127
|
+
const firstLine = response.data.split("\r\n")[0] || response.data.split("\n")[0] || "";
|
|
128
|
+
const actualBoundary = firstLine.startsWith("--") ? firstLine.substring(2) : boundary;
|
|
129
|
+
const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;
|
|
130
|
+
const parsedResponses = parseBatchResponse(
|
|
131
|
+
response.data,
|
|
132
|
+
contentTypeHeader
|
|
133
|
+
);
|
|
134
|
+
if (parsedResponses.length !== this.builders.length) {
|
|
135
|
+
return {
|
|
136
|
+
data: void 0,
|
|
137
|
+
error: {
|
|
138
|
+
name: "BatchError",
|
|
139
|
+
message: `Expected ${this.builders.length} responses but got ${parsedResponses.length}`,
|
|
140
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const processedResults = [];
|
|
145
|
+
for (let i = 0; i < this.originalBuilders.length; i++) {
|
|
146
|
+
const builder = this.originalBuilders[i];
|
|
147
|
+
const parsed = parsedResponses[i];
|
|
148
|
+
if (!builder || !parsed) {
|
|
149
|
+
processedResults.push(void 0);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const nativeResponse = parsedToResponse(parsed);
|
|
153
|
+
const result = await builder.processResponse(nativeResponse, options);
|
|
154
|
+
if (result.error) {
|
|
155
|
+
processedResults.push(void 0);
|
|
156
|
+
} else {
|
|
157
|
+
processedResults.push(result.data);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
data: processedResults,
|
|
162
|
+
error: void 0
|
|
163
|
+
};
|
|
164
|
+
} catch (err) {
|
|
165
|
+
return {
|
|
166
|
+
data: void 0,
|
|
167
|
+
error: {
|
|
168
|
+
name: "BatchError",
|
|
169
|
+
message: err instanceof Error ? err.message : "Unknown error",
|
|
170
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
BatchBuilder
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=batch-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-builder.js","sources":["../../../src/client/batch-builder.ts"],"sourcesContent":["import type {\n ExecutableBuilder,\n ExecutionContext,\n Result,\n ExecuteOptions,\n} from \"../types\";\nimport { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport {\n formatBatchRequestFromNative,\n parseBatchResponse,\n type ParsedBatchResponse,\n} from \"./batch-request\";\n\n/**\n * Helper type to extract result types from a tuple of ExecutableBuilders.\n * Uses a mapped type which TypeScript 4.1+ can handle for tuples.\n */\ntype ExtractTupleTypes<T extends readonly ExecutableBuilder<any>[]> = {\n [K in keyof T]: T[K] extends ExecutableBuilder<infer U> ? U : never;\n};\n\n/**\n * Converts a ParsedBatchResponse to a native Response object\n * @param parsed - The parsed batch response\n * @returns A native Response object\n */\nfunction parsedToResponse(parsed: ParsedBatchResponse): Response {\n const headers = new Headers(parsed.headers);\n\n // Handle null body\n if (parsed.body === null || parsed.body === undefined) {\n return new Response(null, {\n status: parsed.status,\n statusText: parsed.statusText,\n headers,\n });\n }\n\n // Convert body to string if it's not already\n const bodyString =\n typeof parsed.body === \"string\" ? parsed.body : JSON.stringify(parsed.body);\n\n // Handle 204 No Content status - it cannot have a body per HTTP spec\n // If FileMaker returns 204 with a body, treat it as 200\n let status = parsed.status;\n if (status === 204 && bodyString && bodyString.trim() !== \"\") {\n status = 200;\n }\n\n return new Response(status === 204 ? null : bodyString, {\n status: status,\n statusText: parsed.statusText,\n headers,\n });\n}\n\n/**\n * Builder for batch operations that allows multiple queries to be executed together\n * in a single transactional request.\n */\nexport class BatchBuilder<Builders extends readonly ExecutableBuilder<any>[]>\n implements ExecutableBuilder<ExtractTupleTypes<Builders>>\n{\n private builders: ExecutableBuilder<any>[];\n private readonly originalBuilders: Builders;\n\n constructor(\n builders: Builders,\n private readonly databaseName: string,\n private readonly context: ExecutionContext,\n ) {\n // Convert readonly tuple to mutable array for dynamic additions\n this.builders = [...builders];\n // Store original tuple for type preservation\n this.originalBuilders = builders;\n }\n\n /**\n * Add a request to the batch dynamically.\n * This allows building up batch operations programmatically.\n *\n * @param builder - An executable builder to add to the batch\n * @returns This BatchBuilder for method chaining\n * @example\n * ```ts\n * const batch = db.batch([]);\n * batch.addRequest(db.from('contacts').list());\n * batch.addRequest(db.from('users').list());\n * const result = await batch.execute();\n * ```\n */\n addRequest<T>(builder: ExecutableBuilder<T>): this {\n this.builders.push(builder);\n return this;\n }\n\n /**\n * Get the request configuration for this batch operation.\n * This is used internally by the execution system.\n */\n getRequestConfig(): { method: string; url: string; body?: any } {\n // Note: This method is kept for compatibility but batch operations\n // should use execute() directly which handles the full Request/Response flow\n return {\n method: \"POST\",\n url: `/${this.databaseName}/$batch`,\n body: undefined, // Body is constructed in execute()\n };\n }\n\n toRequest(baseUrl: string): Request {\n // Batch operations are not designed to be nested, but we provide\n // a basic implementation for interface compliance\n const fullUrl = `${baseUrl}/${this.databaseName}/$batch`;\n return new Request(fullUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"multipart/mixed\",\n \"OData-Version\": \"4.0\",\n },\n });\n }\n\n async processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<any>> {\n // This should not typically be called for batch operations\n // as they handle their own response processing\n return {\n data: undefined,\n error: {\n name: \"NotImplementedError\",\n message: \"Batch operations handle response processing internally\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n /**\n * Execute the batch operation.\n *\n * @param options - Optional fetch options and batch-specific options (includes beforeRequest hook)\n * @returns A tuple of results matching the input builders\n */\n async execute<EO extends ExecuteOptions>(\n options?: RequestInit & FFetchOptions & EO,\n ): Promise<Result<ExtractTupleTypes<Builders>>> {\n const baseUrl = this.context._getBaseUrl?.();\n if (!baseUrl) {\n return {\n data: undefined,\n error: {\n name: \"ConfigurationError\",\n message:\n \"Base URL not available - execution context must implement _getBaseUrl()\",\n timestamp: new Date(),\n } as any,\n };\n }\n\n try {\n // Convert builders to native Request objects\n const requests: Request[] = this.builders.map((builder) =>\n builder.toRequest(baseUrl),\n );\n\n // Format batch request (automatically groups mutations into changesets)\n const { body, boundary } = await formatBatchRequestFromNative(\n requests,\n baseUrl,\n );\n\n // Execute the batch request\n const response = await this.context._makeRequest<string>(\n `/${this.databaseName}/$batch`,\n {\n ...options,\n method: \"POST\",\n headers: {\n ...options?.headers,\n \"Content-Type\": `multipart/mixed; boundary=${boundary}`,\n \"OData-Version\": \"4.0\",\n },\n body,\n },\n );\n\n if (response.error) {\n return { data: undefined, error: response.error };\n }\n\n // Extract the actual boundary from the response\n // FileMaker uses its own boundary, not the one we sent\n const firstLine =\n response.data.split(\"\\r\\n\")[0] || response.data.split(\"\\n\")[0] || \"\";\n const actualBoundary = firstLine.startsWith(\"--\")\n ? firstLine.substring(2)\n : boundary;\n\n // Parse the multipart response\n const contentTypeHeader = `multipart/mixed; boundary=${actualBoundary}`;\n const parsedResponses = parseBatchResponse(\n response.data,\n contentTypeHeader,\n );\n\n // Check if we got the expected number of responses\n if (parsedResponses.length !== this.builders.length) {\n return {\n data: undefined,\n error: {\n name: \"BatchError\",\n message: `Expected ${this.builders.length} responses but got ${parsedResponses.length}`,\n timestamp: new Date(),\n } as any,\n };\n }\n\n // Process each response using the corresponding builder\n // Build tuple by processing each builder in order\n type ResultTuple = ExtractTupleTypes<Builders>;\n\n // Process builders sequentially to preserve tuple order\n const processedResults: any[] = [];\n for (let i = 0; i < this.originalBuilders.length; i++) {\n const builder = this.originalBuilders[i];\n const parsed = parsedResponses[i];\n\n if (!builder || !parsed) {\n processedResults.push(undefined);\n continue;\n }\n\n // Convert parsed response to native Response\n const nativeResponse = parsedToResponse(parsed);\n\n // Let the builder process its own response\n const result = await builder.processResponse(nativeResponse, options);\n\n if (result.error) {\n processedResults.push(undefined);\n } else {\n processedResults.push(result.data);\n }\n }\n\n // Use a type assertion that TypeScript will respect\n // ExtractTupleTypes ensures this is a proper tuple type\n return {\n data: processedResults as unknown as ResultTuple,\n error: undefined,\n };\n } catch (err) {\n return {\n data: undefined,\n error: {\n name: \"BatchError\",\n message: err instanceof Error ? err.message : \"Unknown error\",\n timestamp: new Date(),\n } as any,\n };\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Request Utilities
|
|
3
|
+
*
|
|
4
|
+
* Utilities for formatting and parsing OData batch requests using multipart/mixed format.
|
|
5
|
+
* OData batch requests allow bundling multiple operations into a single HTTP request,
|
|
6
|
+
* with support for transactional changesets.
|
|
7
|
+
*/
|
|
8
|
+
export interface RequestConfig {
|
|
9
|
+
method: string;
|
|
10
|
+
url: string;
|
|
11
|
+
body?: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface ParsedBatchResponse {
|
|
15
|
+
status: number;
|
|
16
|
+
statusText: string;
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
body: any;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generates a random boundary string for multipart requests
|
|
22
|
+
* @param prefix - Prefix for the boundary (e.g., "batch_" or "changeset_")
|
|
23
|
+
* @returns A boundary string with the prefix and 32 random hex characters
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateBoundary(prefix?: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Formats multiple requests into a batch request body
|
|
28
|
+
* @param requests - Array of request configurations
|
|
29
|
+
* @param baseUrl - The base URL to prepend to relative URLs
|
|
30
|
+
* @param batchBoundary - Optional boundary string for the batch (generated if not provided)
|
|
31
|
+
* @returns Object containing the formatted body and boundary
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatBatchRequest(requests: RequestConfig[], baseUrl: string, batchBoundary?: string): {
|
|
34
|
+
body: string;
|
|
35
|
+
boundary: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Formats multiple Request objects into a batch request body
|
|
39
|
+
* Supports explicit changesets via Request arrays
|
|
40
|
+
* @param requests - Array of Request objects or Request arrays (for explicit changesets)
|
|
41
|
+
* @param baseUrl - The base URL to prepend to relative URLs
|
|
42
|
+
* @param batchBoundary - Optional boundary string for the batch (generated if not provided)
|
|
43
|
+
* @returns Promise resolving to object containing the formatted body and boundary
|
|
44
|
+
*/
|
|
45
|
+
export declare function formatBatchRequestFromNative(requests: Array<Request | Request[]>, baseUrl: string, batchBoundary?: string): Promise<{
|
|
46
|
+
body: string;
|
|
47
|
+
boundary: string;
|
|
48
|
+
}>;
|
|
49
|
+
/**
|
|
50
|
+
* Extracts the boundary from a Content-Type header
|
|
51
|
+
* @param contentType - The Content-Type header value
|
|
52
|
+
* @returns The boundary string, or null if not found
|
|
53
|
+
*/
|
|
54
|
+
export declare function extractBoundary(contentType: string): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Parses a batch response into individual responses
|
|
57
|
+
* @param responseText - The raw batch response text
|
|
58
|
+
* @param contentType - The Content-Type header from the response
|
|
59
|
+
* @returns Array of parsed responses in the same order as the request
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseBatchResponse(responseText: string, contentType: string): ParsedBatchResponse[];
|