@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.
- package/README.md +376 -34
- package/dist/esm/client/base-table.d.ts +24 -29
- package/dist/esm/client/base-table.js +4 -7
- 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 +44 -12
- package/dist/esm/client/database.js +64 -10
- 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 +76 -9
- package/dist/esm/client/delete-builder.js.map +1 -1
- package/dist/esm/client/entity-set.d.ts +17 -6
- package/dist/esm/client/entity-set.js +26 -10
- package/dist/esm/client/entity-set.js.map +1 -1
- package/dist/esm/client/filemaker-odata.d.ts +11 -5
- package/dist/esm/client/filemaker-odata.js +46 -14
- 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 +195 -9
- package/dist/esm/client/insert-builder.js.map +1 -1
- package/dist/esm/client/query-builder.d.ts +20 -4
- package/dist/esm/client/query-builder.js +195 -19
- package/dist/esm/client/query-builder.js.map +1 -1
- package/dist/esm/client/record-builder.d.ts +18 -3
- package/dist/esm/client/record-builder.js +87 -5
- 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 +25 -42
- package/dist/esm/client/table-occurrence.js +9 -17
- 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 +119 -19
- package/dist/esm/client/update-builder.js.map +1 -1
- package/dist/esm/errors.d.ts +14 -1
- package/dist/esm/errors.js +26 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +5 -4
- package/dist/esm/index.js +7 -6
- package/dist/esm/transform.d.ts +9 -0
- package/dist/esm/transform.js +7 -0
- package/dist/esm/transform.js.map +1 -1
- package/dist/esm/types.d.ts +69 -1
- package/package.json +1 -1
- package/src/client/base-table.ts +30 -36
- package/src/client/batch-builder.ts +265 -0
- package/src/client/batch-request.ts +485 -0
- package/src/client/database.ts +110 -56
- package/src/client/delete-builder.ts +116 -14
- package/src/client/entity-set.ts +89 -12
- package/src/client/filemaker-odata.ts +65 -19
- package/src/client/insert-builder.ts +296 -18
- package/src/client/query-builder.ts +285 -18
- package/src/client/query-builder.ts.bak +1457 -0
- package/src/client/record-builder.ts +120 -12
- package/src/client/response-processor.ts +103 -0
- package/src/client/schema-manager.ts +246 -0
- package/src/client/table-occurrence.ts +41 -80
- package/src/client/update-builder.ts +195 -37
- package/src/errors.ts +33 -1
- package/src/index.ts +15 -3
- package/src/transform.ts +19 -6
- 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.
|
|
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
|
-
- [
|
|
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
|
-
|
|
28
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 {
|
|
142
|
+
import { defineBaseTable } from "@proofkit/fmodata";
|
|
135
143
|
|
|
136
|
-
const contactsBase =
|
|
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 {
|
|
163
|
+
import { defineTableOccurrence } from "@proofkit/fmodata";
|
|
154
164
|
|
|
155
|
-
const contactsTO =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
671
|
+
const _usersTO = defineTableOccurrence({
|
|
664
672
|
name: "users",
|
|
665
673
|
baseTable: usersBase,
|
|
666
|
-
|
|
667
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 `
|
|
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 {
|
|
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 =
|
|
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 =
|
|
1267
|
+
const usersTO = defineTableOccurrence({
|
|
926
1268
|
name: "users",
|
|
927
|
-
baseTable: usersBase,
|
|
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
|
-
*
|
|
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
|
-
*
|
|
98
|
-
*
|
|
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
|
-
*
|
|
102
|
+
* const users = defineBaseTable({
|
|
103
|
+
* schema: { id: z.string(), name: z.string() },
|
|
104
|
+
* idField: "id",
|
|
105
|
+
* });
|
|
106
|
+
* ```
|
|
105
107
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
+
defineBaseTable
|
|
59
56
|
};
|
|
60
57
|
//# sourceMappingURL=base-table.js.map
|