@proofkit/fmodata 0.1.0-alpha.6 → 0.1.0-alpha.8

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.
Files changed (70) hide show
  1. package/README.md +376 -34
  2. package/dist/esm/client/base-table.d.ts +24 -29
  3. package/dist/esm/client/base-table.js +4 -7
  4. package/dist/esm/client/base-table.js.map +1 -1
  5. package/dist/esm/client/batch-builder.d.ts +54 -0
  6. package/dist/esm/client/batch-builder.js +179 -0
  7. package/dist/esm/client/batch-builder.js.map +1 -0
  8. package/dist/esm/client/batch-request.d.ts +61 -0
  9. package/dist/esm/client/batch-request.js +252 -0
  10. package/dist/esm/client/batch-request.js.map +1 -0
  11. package/dist/esm/client/database.d.ts +44 -12
  12. package/dist/esm/client/database.js +64 -10
  13. package/dist/esm/client/database.js.map +1 -1
  14. package/dist/esm/client/delete-builder.d.ts +21 -2
  15. package/dist/esm/client/delete-builder.js +76 -9
  16. package/dist/esm/client/delete-builder.js.map +1 -1
  17. package/dist/esm/client/entity-set.d.ts +17 -6
  18. package/dist/esm/client/entity-set.js +26 -10
  19. package/dist/esm/client/entity-set.js.map +1 -1
  20. package/dist/esm/client/filemaker-odata.d.ts +11 -5
  21. package/dist/esm/client/filemaker-odata.js +46 -14
  22. package/dist/esm/client/filemaker-odata.js.map +1 -1
  23. package/dist/esm/client/insert-builder.d.ts +38 -3
  24. package/dist/esm/client/insert-builder.js +195 -9
  25. package/dist/esm/client/insert-builder.js.map +1 -1
  26. package/dist/esm/client/query-builder.d.ts +20 -4
  27. package/dist/esm/client/query-builder.js +195 -19
  28. package/dist/esm/client/query-builder.js.map +1 -1
  29. package/dist/esm/client/record-builder.d.ts +18 -3
  30. package/dist/esm/client/record-builder.js +87 -5
  31. package/dist/esm/client/record-builder.js.map +1 -1
  32. package/dist/esm/client/response-processor.d.ts +38 -0
  33. package/dist/esm/client/schema-manager.d.ts +57 -0
  34. package/dist/esm/client/schema-manager.js +132 -0
  35. package/dist/esm/client/schema-manager.js.map +1 -0
  36. package/dist/esm/client/table-occurrence.d.ts +25 -42
  37. package/dist/esm/client/table-occurrence.js +9 -17
  38. package/dist/esm/client/table-occurrence.js.map +1 -1
  39. package/dist/esm/client/update-builder.d.ts +34 -11
  40. package/dist/esm/client/update-builder.js +119 -19
  41. package/dist/esm/client/update-builder.js.map +1 -1
  42. package/dist/esm/errors.d.ts +14 -1
  43. package/dist/esm/errors.js +26 -0
  44. package/dist/esm/errors.js.map +1 -1
  45. package/dist/esm/index.d.ts +5 -4
  46. package/dist/esm/index.js +7 -6
  47. package/dist/esm/transform.d.ts +9 -0
  48. package/dist/esm/transform.js +7 -0
  49. package/dist/esm/transform.js.map +1 -1
  50. package/dist/esm/types.d.ts +69 -1
  51. package/package.json +1 -1
  52. package/src/client/base-table.ts +30 -36
  53. package/src/client/batch-builder.ts +265 -0
  54. package/src/client/batch-request.ts +485 -0
  55. package/src/client/database.ts +110 -56
  56. package/src/client/delete-builder.ts +116 -14
  57. package/src/client/entity-set.ts +89 -12
  58. package/src/client/filemaker-odata.ts +65 -19
  59. package/src/client/insert-builder.ts +296 -18
  60. package/src/client/query-builder.ts +285 -18
  61. package/src/client/query-builder.ts.bak +1457 -0
  62. package/src/client/record-builder.ts +120 -12
  63. package/src/client/response-processor.ts +103 -0
  64. package/src/client/schema-manager.ts +246 -0
  65. package/src/client/table-occurrence.ts +41 -80
  66. package/src/client/update-builder.ts +195 -37
  67. package/src/errors.ts +33 -1
  68. package/src/index.ts +15 -3
  69. package/src/transform.ts +19 -6
  70. package/src/types.ts +89 -1
package/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  A strongly-typed FileMaker OData API client.
4
4
 
5
- ⚠️ WARNING: This library is in "alpha" status. The API is subject to change. Feedback is welcome on the [community forum](https://community.ottomatic.cloud/c/proofkit/13) or on [GitHub](https://github.com/proofgeist/proofkit/issues).
5
+ ⚠️ WARNING: This library is in "alpha" status. It's still in active development and the API is subject to change. Feedback is welcome on the [community forum](https://community.ottomatic.cloud/c/proofkit/13) or on [GitHub](https://github.com/proofgeist/proofkit/issues).
6
6
 
7
7
  Roadmap:
8
8
 
9
9
  - [ ] Crossjoin support
10
- - [ ] Batch operations
10
+ - [x] Batch operations
11
+ - [ ] Automatically chunk requests into smaller batches (e.g. max 512 inserts per batch)
12
+ - [x] Schema updates (add/update tables and fields)
11
13
  - [ ] Proper docs at proofkit.dev
12
14
  - [ ] @proofkit/typegen integration
13
15
 
@@ -24,8 +26,8 @@ Here's a minimal example to get you started:
24
26
  ```typescript
25
27
  import {
26
28
  FMServerConnection,
27
- BaseTable,
28
- TableOccurrence,
29
+ defineBaseTable,
30
+ defineTableOccurrence,
29
31
  } from "@proofkit/fmodata";
30
32
  import { z } from "zod/v4";
31
33
 
@@ -43,7 +45,7 @@ const connection = new FMServerConnection({
43
45
  });
44
46
 
45
47
  // 2. Define your table schema
46
- const usersBase = new BaseTable({
48
+ const usersBase = defineBaseTable({
47
49
  schema: {
48
50
  id: z.string(),
49
51
  username: z.string(),
@@ -54,7 +56,7 @@ const usersBase = new BaseTable({
54
56
  });
55
57
 
56
58
  // 3. Create a table occurrence
57
- const usersTO = new TableOccurrence({
59
+ const usersTO = defineTableOccurrence({
58
60
  name: "users",
59
61
  baseTable: usersBase,
60
62
  });
@@ -79,7 +81,7 @@ if (data) {
79
81
 
80
82
  ## Core Concepts
81
83
 
82
- This library relies heavily on the builder pattern for defining your queries and operations. Most operations require a final call to `execute()` to send the request to the server. The builder pattern is designed to support batch operations in the future, allowing you to execute multiple operations in a single request as supported by the FileMaker OData API. **Note:** Batch operations are not yet supported but are planned before the production release. It's also helpful for testing the library, as you can call `getQueryString()` to get the OData query string without executing the request.
84
+ This library relies heavily on the builder pattern for defining your queries and operations. Most operations require a final call to `execute()` to send the request to the server. The builder pattern allows you to build complex queries and also supports batch operations, allowing you to execute multiple operations in a single request as supported by the FileMaker OData API. It's also helpful for testing the library, as you can call `getQueryString()` to get the OData query string without executing the request.
83
85
 
84
86
  As such, there are layers to the library to help you build your queries and operations.
85
87
 
@@ -125,15 +127,21 @@ const connection = new FMServerConnection({
125
127
 
126
128
  ### Schema Definitions
127
129
 
128
- This library relies on a schema-first approach for good type-safety and optional runtime validation. These are absracted into BaseTable and TableOccurrence classes to match FileMaker concepts.
130
+ This library relies on a schema-first approach for good type-safety and optional runtime validation. These are abstracted into BaseTable and TableOccurrence classes to match FileMaker concepts.
131
+
132
+ **Helper Functions vs Constructors:**
133
+
134
+ - **`defineBaseTable()`** and **`defineTableOccurrence()`** - Recommended for better type inference, especially when using entity IDs (FMFID/FMTID). These functions provide improved TypeScript type inference for field names in queries.
135
+
136
+ - **`new BaseTable()`** and **`new TableOccurrence()`** - Still supported for backward compatibility, but may have slightly less precise type inference in some cases.
129
137
 
130
138
  A `BaseTable` defines the schema for your FileMaker table using Standard Schema. These examples show zod, but you can use any other validation library that supports Standard Schema.
131
139
 
132
140
  ```typescript
133
141
  import { z } from "zod/v4";
134
- import { BaseTable } from "@proofkit/fmodata";
142
+ import { defineBaseTable } from "@proofkit/fmodata";
135
143
 
136
- const contactsBase = new BaseTable({
144
+ const contactsBase = defineBaseTable({
137
145
  schema: {
138
146
  id: z.string(),
139
147
  name: z.string(),
@@ -149,10 +157,12 @@ const contactsBase = new BaseTable({
149
157
 
150
158
  A `TableOccurrence` is the actual entry point for the OData service on the FileMaker server. It's where you can define the relations between tables and also allows you to reference the same base table multiple times with different names.
151
159
 
160
+ **Recommended:** Use `defineTableOccurrence()` for better type inference. You can also use `new TableOccurrence()` directly.
161
+
152
162
  ```typescript
153
- import { TableOccurrence } from "@proofkit/fmodata";
163
+ import { defineTableOccurrence } from "@proofkit/fmodata";
154
164
 
155
- const contactsTO = new TableOccurrence({
165
+ const contactsTO = defineTableOccurrence({
156
166
  name: "contacts", // The table occurrence name in FileMaker
157
167
  baseTable: contactsBase,
158
168
  });
@@ -164,21 +174,21 @@ FileMaker will automatically return all non-container fields from a schema if yo
164
174
 
165
175
  ```typescript
166
176
  // Option 1 (default): "schema" - Select all fields from the schema (same as "all" but more explicit)
167
- const usersTO = new TableOccurrence({
177
+ const usersTO = defineTableOccurrence({
168
178
  name: "users",
169
179
  baseTable: usersBase,
170
180
  defaultSelect: "schema", // a $select parameter will be always be added to the query for only the fields you've defined in the BaseTable schema
171
181
  });
172
182
 
173
183
  // Option 2: "all" - Select all fields (default behavior)
174
- const usersTO = new TableOccurrence({
184
+ const usersTO = defineTableOccurrence({
175
185
  name: "users",
176
186
  baseTable: usersBase,
177
187
  defaultSelect: "all", // Don't always a $select parameter to the query; FileMaker will return all non-container fields from the table
178
188
  });
179
189
 
180
190
  // Option 3: Array of field names - Select only specific fields by default
181
- const usersTO = new TableOccurrence({
191
+ const usersTO = defineTableOccurrence({
182
192
  name: "users",
183
193
  baseTable: usersBase,
184
194
  defaultSelect: ["username", "email"], // Only select these fields by default
@@ -528,7 +538,7 @@ if (result.data) {
528
538
  Fields are automatically required for insert if their validator doesn't allow `null` or `undefined`. You can specify additional required fields:
529
539
 
530
540
  ```typescript
531
- const usersBase = new BaseTable({
541
+ const usersBase = defineBaseTable({
532
542
  schema: {
533
543
  id: z.string(), // Auto-required (not nullable), but excluded from insert (idField)
534
544
  username: z.string(), // Auto-required (not nullable)
@@ -633,7 +643,7 @@ const result = await db
633
643
  Define relationships between tables using the `navigation` option:
634
644
 
635
645
  ```typescript
636
- const contactsBase = new BaseTable({
646
+ const contactsBase = defineBaseTable({
637
647
  schema: {
638
648
  id: z.string(),
639
649
  name: z.string(),
@@ -642,7 +652,7 @@ const contactsBase = new BaseTable({
642
652
  idField: "id",
643
653
  });
644
654
 
645
- const usersBase = new BaseTable({
655
+ const usersBase = defineBaseTable({
646
656
  schema: {
647
657
  id: z.string(),
648
658
  username: z.string(),
@@ -652,20 +662,24 @@ const usersBase = new BaseTable({
652
662
  });
653
663
 
654
664
  // Define navigation using functions to handle circular dependencies
655
- const contactsTO = new TableOccurrence({
665
+ // Create base occurrences first, then add navigation
666
+ const _contactsTO = defineTableOccurrence({
656
667
  name: "contacts",
657
668
  baseTable: contactsBase,
658
- navigation: {
659
- users: () => usersTO, // Relationship to users table
660
- },
661
669
  });
662
670
 
663
- const usersTO = new TableOccurrence({
671
+ const _usersTO = defineTableOccurrence({
664
672
  name: "users",
665
673
  baseTable: usersBase,
666
- navigation: {
667
- contacts: () => contactsTO, // Relationship to contacts table
668
- },
674
+ });
675
+
676
+ // Then add navigation
677
+ const contactsTO = _contactsTO.addNavigation({
678
+ users: () => _usersTO,
679
+ });
680
+
681
+ const usersTO = _usersTO.addNavigation({
682
+ contacts: () => _contactsTO,
669
683
  });
670
684
 
671
685
  // You can also add navigation after creation
@@ -803,6 +817,334 @@ console.log(result.result.recordId);
803
817
 
804
818
  **Note:** OData doesn't support script names with special characters (e.g., `@`, `&`, `/`) or script names beginning with a number. TypeScript will catch these at compile time.
805
819
 
820
+ ## Batch Operations
821
+
822
+ Batch operations allow you to execute multiple queries and operations together in a single request. All operations in a batch are executed atomically - they all succeed or all fail together. This is both more efficient (fewer network round-trips) and ensures data consistency across related operations.
823
+
824
+ ### Basic Batch with Multiple Queries
825
+
826
+ Execute multiple read operations in a single batch:
827
+
828
+ ```typescript
829
+ // Create query builders
830
+ const contactsQuery = db.from("contacts").list().top(5);
831
+ const usersQuery = db.from("users").list().top(5);
832
+
833
+ // Execute both queries in a single batch
834
+ const result = await db.batch([contactsQuery, usersQuery]).execute();
835
+
836
+ if (result.data) {
837
+ // Result is a tuple matching the input builders
838
+ const [contacts, users] = result.data;
839
+
840
+ console.log("Contacts:", contacts);
841
+ console.log("Users:", users);
842
+ }
843
+ ```
844
+
845
+ ### Mixed Operations (Reads and Writes)
846
+
847
+ Combine queries, inserts, updates, and deletes in a single batch:
848
+
849
+ ```typescript
850
+ // Mix different operation types
851
+ const listQuery = db.from("contacts").list().top(10);
852
+ const insertOp = db.from("contacts").insert({
853
+ name: "John Doe",
854
+ email: "john@example.com",
855
+ });
856
+ const updateOp = db.from("users").update({ active: true }).byId("user-123");
857
+
858
+ // All operations execute atomically
859
+ const result = await db.batch([listQuery, insertOp, updateOp]).execute();
860
+
861
+ if (result.data) {
862
+ const [contactsList, insertResult, updateResult] = result.data;
863
+
864
+ console.log("Fetched contacts:", contactsList);
865
+ console.log("Inserted contact:", insertResult);
866
+ console.log("Updated user:", updateResult);
867
+ }
868
+ ```
869
+
870
+ ### Transactional Behavior
871
+
872
+ Batch operations are transactional for write operations (inserts, updates, deletes). If any operation in the batch fails, all write operations are rolled back:
873
+
874
+ ```typescript
875
+ const result = await db
876
+ .batch([
877
+ db.from("users").insert({ username: "alice", email: "alice@example.com" }),
878
+ db.from("users").insert({ username: "bob", email: "bob@example.com" }),
879
+ db.from("users").insert({ username: "charlie", email: "invalid" }), // This fails
880
+ ])
881
+ .execute();
882
+
883
+ if (result.error) {
884
+ // All three inserts are rolled back - no users were created
885
+ console.error("Batch failed:", result.error);
886
+ }
887
+ ```
888
+
889
+ **Note:** Batch operations automatically group write operations (POST, PATCH, DELETE) into changesets for transactional behavior, while read operations (GET) are executed individually within the batch.
890
+
891
+ ## Schema Management
892
+
893
+ The library provides methods for managing database schema through the `db.schema` property. You can create and delete tables, add and remove fields, and manage indexes.
894
+
895
+ ### Creating Tables
896
+
897
+ Create a new table with field definitions:
898
+
899
+ ```typescript
900
+ import type { Field } from "@proofkit/fmodata";
901
+
902
+ const fields: Field[] = [
903
+ {
904
+ name: "id",
905
+ type: "string",
906
+ primary: true,
907
+ maxLength: 36,
908
+ },
909
+ {
910
+ name: "username",
911
+ type: "string",
912
+ nullable: false,
913
+ unique: true,
914
+ maxLength: 50,
915
+ },
916
+ {
917
+ name: "email",
918
+ type: "string",
919
+ nullable: false,
920
+ maxLength: 255,
921
+ },
922
+ {
923
+ name: "age",
924
+ type: "numeric",
925
+ nullable: true,
926
+ },
927
+ {
928
+ name: "created_at",
929
+ type: "timestamp",
930
+ default: "CURRENT_TIMESTAMP",
931
+ },
932
+ ];
933
+
934
+ const tableDefinition = await db.schema.createTable("users", fields);
935
+ console.log(tableDefinition.tableName); // "users"
936
+ console.log(tableDefinition.fields); // Array of field definitions
937
+ ```
938
+
939
+ ### Field Types
940
+
941
+ The library supports various field types:
942
+
943
+ **String Fields:**
944
+
945
+ ```typescript
946
+ {
947
+ name: "username",
948
+ type: "string",
949
+ maxLength: 100, // Optional: varchar(100)
950
+ nullable: true,
951
+ unique: true,
952
+ default: "USER" | "USERNAME" | "CURRENT_USER", // Optional
953
+ repetitions: 5, // Optional: for repeating fields
954
+ }
955
+ ```
956
+
957
+ **Numeric Fields:**
958
+
959
+ ```typescript
960
+ {
961
+ name: "age",
962
+ type: "numeric",
963
+ nullable: true,
964
+ primary: false,
965
+ unique: false,
966
+ }
967
+ ```
968
+
969
+ **Date Fields:**
970
+
971
+ ```typescript
972
+ {
973
+ name: "birth_date",
974
+ type: "date",
975
+ default: "CURRENT_DATE" | "CURDATE", // Optional
976
+ nullable: true,
977
+ }
978
+ ```
979
+
980
+ **Time Fields:**
981
+
982
+ ```typescript
983
+ {
984
+ name: "start_time",
985
+ type: "time",
986
+ default: "CURRENT_TIME" | "CURTIME", // Optional
987
+ nullable: true,
988
+ }
989
+ ```
990
+
991
+ **Timestamp Fields:**
992
+
993
+ ```typescript
994
+ {
995
+ name: "created_at",
996
+ type: "timestamp",
997
+ default: "CURRENT_TIMESTAMP" | "CURTIMESTAMP", // Optional
998
+ nullable: false,
999
+ }
1000
+ ```
1001
+
1002
+ **Container Fields:**
1003
+
1004
+ ```typescript
1005
+ {
1006
+ name: "avatar",
1007
+ type: "container",
1008
+ externalSecurePath: "/secure/path", // Optional
1009
+ nullable: true,
1010
+ }
1011
+ ```
1012
+
1013
+ ### Adding Fields to Existing Tables
1014
+
1015
+ Add new fields to an existing table:
1016
+
1017
+ ```typescript
1018
+ const newFields: Field[] = [
1019
+ {
1020
+ name: "phone",
1021
+ type: "string",
1022
+ nullable: true,
1023
+ maxLength: 20,
1024
+ },
1025
+ {
1026
+ name: "bio",
1027
+ type: "string",
1028
+ nullable: true,
1029
+ maxLength: 1000,
1030
+ },
1031
+ ];
1032
+
1033
+ const updatedTable = await db.schema.addFields("users", newFields);
1034
+ ```
1035
+
1036
+ ### Deleting Tables and Fields
1037
+
1038
+ Delete an entire table:
1039
+
1040
+ ```typescript
1041
+ await db.schema.deleteTable("old_table");
1042
+ ```
1043
+
1044
+ Delete a specific field from a table:
1045
+
1046
+ ```typescript
1047
+ await db.schema.deleteField("users", "old_field");
1048
+ ```
1049
+
1050
+ ### Managing Indexes
1051
+
1052
+ Create an index on a field:
1053
+
1054
+ ```typescript
1055
+ const index = await db.schema.createIndex("users", "email");
1056
+ console.log(index.indexName); // "email"
1057
+ ```
1058
+
1059
+ Delete an index:
1060
+
1061
+ ```typescript
1062
+ await db.schema.deleteIndex("users", "email");
1063
+ ```
1064
+
1065
+ ### Complete Example
1066
+
1067
+ Here's a complete example of creating a table with various field types:
1068
+
1069
+ ```typescript
1070
+ const fields: Field[] = [
1071
+ // Primary key
1072
+ {
1073
+ name: "id",
1074
+ type: "string",
1075
+ primary: true,
1076
+ maxLength: 36,
1077
+ },
1078
+
1079
+ // String fields
1080
+ {
1081
+ name: "username",
1082
+ type: "string",
1083
+ nullable: false,
1084
+ unique: true,
1085
+ maxLength: 50,
1086
+ },
1087
+ {
1088
+ name: "email",
1089
+ type: "string",
1090
+ nullable: false,
1091
+ maxLength: 255,
1092
+ },
1093
+
1094
+ // Numeric field
1095
+ {
1096
+ name: "age",
1097
+ type: "numeric",
1098
+ nullable: true,
1099
+ },
1100
+
1101
+ // Date/time fields
1102
+ {
1103
+ name: "birth_date",
1104
+ type: "date",
1105
+ nullable: true,
1106
+ },
1107
+ {
1108
+ name: "created_at",
1109
+ type: "timestamp",
1110
+ default: "CURRENT_TIMESTAMP",
1111
+ nullable: false,
1112
+ },
1113
+
1114
+ // Container field
1115
+ {
1116
+ name: "avatar",
1117
+ type: "container",
1118
+ nullable: true,
1119
+ },
1120
+
1121
+ // Repeating field
1122
+ {
1123
+ name: "tags",
1124
+ type: "string",
1125
+ repetitions: 5,
1126
+ maxLength: 50,
1127
+ },
1128
+ ];
1129
+
1130
+ // Create the table
1131
+ const table = await db.schema.createTable("users", fields);
1132
+
1133
+ // Later, add more fields
1134
+ await db.schema.addFields("users", [
1135
+ {
1136
+ name: "phone",
1137
+ type: "string",
1138
+ nullable: true,
1139
+ },
1140
+ ]);
1141
+
1142
+ // Create an index on email
1143
+ await db.schema.createIndex("users", "email");
1144
+ ```
1145
+
1146
+ **Note:** Schema management operations require appropriate access privileges on your FileMaker account. Operations will throw errors if you don't have the necessary permissions.
1147
+
806
1148
  ## Advanced Features
807
1149
 
808
1150
  ### Type Safety
@@ -810,7 +1152,7 @@ console.log(result.result.recordId);
810
1152
  The library provides full TypeScript type inference:
811
1153
 
812
1154
  ```typescript
813
- const usersBase = new BaseTable({
1155
+ const usersBase = defineBaseTable({
814
1156
  schema: {
815
1157
  id: z.string(),
816
1158
  username: z.string(),
@@ -819,7 +1161,7 @@ const usersBase = new BaseTable({
819
1161
  idField: "id",
820
1162
  });
821
1163
 
822
- const usersTO = new TableOccurrence({
1164
+ const usersTO = defineTableOccurrence({
823
1165
  name: "users",
824
1166
  baseTable: usersBase,
825
1167
  });
@@ -848,7 +1190,7 @@ db.from("users")
848
1190
  The library automatically infers which fields are required based on whether their validator allows `null` or `undefined`:
849
1191
 
850
1192
  ```typescript
851
- const usersBase = new BaseTable({
1193
+ const usersBase = defineBaseTable({
852
1194
  schema: {
853
1195
  id: z.string(), // Auto-required, auto-readOnly (idField)
854
1196
  username: z.string(), // Auto-required (not nullable)
@@ -892,7 +1234,7 @@ db.from("users")
892
1234
 
893
1235
  This library supports using FileMaker's internal field identifiers (FMFID) and table occurrence identifiers (FMTID) instead of names. This protects your integration from both field and table occurrence name changes.
894
1236
 
895
- To enable this feature, simply define your schema with the `BaseTableWithIds` and `TableOccurrenceWithIds` classes. Behind the scenes, the library will transform your request and the response back to the names you specify in these schemas. This is an all-or-nothing feature. For it to work properly, you must define all table occurrences passed to a `Database` with the `TableOccurrenceWithIds` class.
1237
+ To enable this feature, simply define your schema with entity IDs using the `defineBaseTable` and `defineTableOccurrence` functions. Behind the scenes, the library will transform your request and the response back to the names you specify in these schemas. This is an all-or-nothing feature. For it to work properly, you must define all table occurrences passed to a `Database` with entity IDs (both `fmfIds` on the base table and `fmtId` on the table occurrence).
896
1238
 
897
1239
  _Note for OttoFMS proxy: This feature requires version 4.14 or later of OttoFMS_
898
1240
 
@@ -901,11 +1243,11 @@ How do I find these ids? They can be found in the XML version of the `$metadata`
901
1243
  #### Basic Usage
902
1244
 
903
1245
  ```typescript
904
- import { BaseTableWithIds, TableOccurrenceWithIds } from "@proofkit/fmodata";
1246
+ import { defineBaseTable, defineTableOccurrence } from "@proofkit/fmodata";
905
1247
  import { z } from "zod/v4";
906
1248
 
907
1249
  // Define a base table with FileMaker field IDs
908
- const usersBase = new BaseTableWithIds({
1250
+ const usersBase = defineBaseTable({
909
1251
  schema: {
910
1252
  id: z.string(),
911
1253
  username: z.string(),
@@ -922,9 +1264,9 @@ const usersBase = new BaseTableWithIds({
922
1264
  });
923
1265
 
924
1266
  // Create a table occurrence with a FileMaker table occurrence ID
925
- const usersTO = new TableOccurrenceWithIds({
1267
+ const usersTO = defineTableOccurrence({
926
1268
  name: "users",
927
- baseTable: usersBase, // Must be a BaseTableWithIds
1269
+ baseTable: usersBase,
928
1270
  fmtId: "FMTID:12432533",
929
1271
  });
930
1272
  ```
@@ -72,6 +72,7 @@ export declare class BaseTable<Schema extends Record<string, StandardSchemaV1> =
72
72
  idField?: IdField;
73
73
  required?: Required;
74
74
  readOnly?: ReadOnly;
75
+ fmfIds?: Record<string, `FMFID:${string}`>;
75
76
  });
76
77
  /**
77
78
  * Returns the FileMaker field ID (FMFID) for a given field name, or the field name itself if not using IDs.
@@ -91,40 +92,34 @@ export declare class BaseTable<Schema extends Record<string, StandardSchemaV1> =
91
92
  isUsingFieldIds(): boolean;
92
93
  }
93
94
  /**
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.
95
+ * Creates a BaseTable with proper TypeScript type inference.
96
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)
97
+ * This function should be used instead of `new BaseTable()` to ensure
98
+ * field names are properly typed throughout the library.
101
99
  *
102
- * @example
100
+ * @example Without entity IDs
103
101
  * ```ts
104
- * import { z } from "zod";
102
+ * const users = defineBaseTable({
103
+ * schema: { id: z.string(), name: z.string() },
104
+ * idField: "id",
105
+ * });
106
+ * ```
105
107
  *
106
- * const usersTableWithIds = new BaseTableWithIds({
107
- * schema: {
108
- * id: z.string(),
109
- * name: z.string(),
110
- * email: z.string().nullable(),
111
- * },
108
+ * @example With entity IDs (FileMaker field IDs)
109
+ * ```ts
110
+ * const products = defineBaseTable({
111
+ * schema: { id: z.string(), name: z.string() },
112
112
  * idField: "id",
113
- * fmfIds: {
114
- * id: "FMFID:1",
115
- * name: "FMFID:2",
116
- * email: "FMFID:3",
117
- * },
113
+ * fmfIds: { id: "FMFID:1", name: "FMFID:2" },
118
114
  * });
119
115
  * ```
120
116
  */
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;
129
- });
130
- }
117
+ export declare function defineBaseTable<const Schema extends Record<string, StandardSchemaV1>, IdField extends keyof Schema | undefined = undefined, const Required extends readonly (keyof Schema)[] = readonly [], const ReadOnly extends readonly (keyof Schema)[] = readonly []>(config: {
118
+ schema: Schema;
119
+ idField?: IdField;
120
+ required?: Required;
121
+ readOnly?: ReadOnly;
122
+ fmfIds?: {
123
+ [K in keyof Schema]: `FMFID:${string}`;
124
+ };
125
+ }): BaseTable<Schema, IdField, Required, ReadOnly>;
@@ -12,6 +12,7 @@ class BaseTable {
12
12
  this.idField = config.idField;
13
13
  this.required = config.required;
14
14
  this.readOnly = config.readOnly;
15
+ this.fmfIds = config.fmfIds;
15
16
  }
16
17
  /**
17
18
  * Returns the FileMaker field ID (FMFID) for a given field name, or the field name itself if not using IDs.
@@ -46,15 +47,11 @@ class BaseTable {
46
47
  return this.fmfIds !== void 0;
47
48
  }
48
49
  }
49
- class BaseTableWithIds extends BaseTable {
50
- constructor(config) {
51
- super(config);
52
- __publicField(this, "fmfIds");
53
- this.fmfIds = config.fmfIds;
54
- }
50
+ function defineBaseTable(config) {
51
+ return new BaseTable(config);
55
52
  }
56
53
  export {
57
54
  BaseTable,
58
- BaseTableWithIds
55
+ defineBaseTable
59
56
  };
60
57
  //# sourceMappingURL=base-table.js.map