@proofkit/fmodata 0.1.0-alpha.13 → 0.1.0-alpha.14

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 (143) hide show
  1. package/README.md +489 -334
  2. package/dist/esm/client/batch-builder.d.ts +7 -4
  3. package/dist/esm/client/batch-builder.js +84 -25
  4. package/dist/esm/client/batch-builder.js.map +1 -1
  5. package/dist/esm/client/builders/default-select.d.ts +7 -0
  6. package/dist/esm/client/builders/default-select.js +42 -0
  7. package/dist/esm/client/builders/default-select.js.map +1 -0
  8. package/dist/esm/client/builders/expand-builder.d.ts +43 -0
  9. package/dist/esm/client/builders/expand-builder.js +173 -0
  10. package/dist/esm/client/builders/expand-builder.js.map +1 -0
  11. package/dist/esm/client/builders/index.d.ts +8 -0
  12. package/dist/esm/client/builders/query-string-builder.d.ts +15 -0
  13. package/dist/esm/client/builders/query-string-builder.js +25 -0
  14. package/dist/esm/client/builders/query-string-builder.js.map +1 -0
  15. package/dist/esm/client/builders/response-processor.d.ts +39 -0
  16. package/dist/esm/client/builders/response-processor.js +170 -0
  17. package/dist/esm/client/builders/response-processor.js.map +1 -0
  18. package/dist/esm/client/builders/select-mixin.d.ts +31 -0
  19. package/dist/esm/client/builders/select-mixin.js +30 -0
  20. package/dist/esm/client/builders/select-mixin.js.map +1 -0
  21. package/dist/esm/client/builders/select-utils.d.ts +8 -0
  22. package/dist/esm/client/builders/select-utils.js +15 -0
  23. package/dist/esm/client/builders/select-utils.js.map +1 -0
  24. package/dist/esm/client/builders/shared-types.d.ts +39 -0
  25. package/dist/esm/client/builders/table-utils.d.ts +35 -0
  26. package/dist/esm/client/builders/table-utils.js +45 -0
  27. package/dist/esm/client/builders/table-utils.js.map +1 -0
  28. package/dist/esm/client/database.d.ts +3 -22
  29. package/dist/esm/client/database.js +14 -76
  30. package/dist/esm/client/database.js.map +1 -1
  31. package/dist/esm/client/delete-builder.d.ts +11 -15
  32. package/dist/esm/client/delete-builder.js +26 -26
  33. package/dist/esm/client/delete-builder.js.map +1 -1
  34. package/dist/esm/client/entity-set.d.ts +32 -32
  35. package/dist/esm/client/entity-set.js +92 -69
  36. package/dist/esm/client/entity-set.js.map +1 -1
  37. package/dist/esm/client/error-parser.d.ts +12 -0
  38. package/dist/esm/client/error-parser.js +30 -0
  39. package/dist/esm/client/error-parser.js.map +1 -0
  40. package/dist/esm/client/filemaker-odata.d.ts +2 -4
  41. package/dist/esm/client/filemaker-odata.js +1 -5
  42. package/dist/esm/client/filemaker-odata.js.map +1 -1
  43. package/dist/esm/client/insert-builder.d.ts +7 -9
  44. package/dist/esm/client/insert-builder.js +70 -24
  45. package/dist/esm/client/insert-builder.js.map +1 -1
  46. package/dist/esm/client/query/expand-builder.d.ts +35 -0
  47. package/dist/esm/client/query/index.d.ts +3 -0
  48. package/dist/esm/client/query/query-builder.d.ts +134 -0
  49. package/dist/esm/client/query/query-builder.js +505 -0
  50. package/dist/esm/client/query/query-builder.js.map +1 -0
  51. package/dist/esm/client/query/response-processor.d.ts +22 -0
  52. package/dist/esm/client/query/types.d.ts +52 -0
  53. package/dist/esm/client/query/url-builder.d.ts +71 -0
  54. package/dist/esm/client/query/url-builder.js +107 -0
  55. package/dist/esm/client/query/url-builder.js.map +1 -0
  56. package/dist/esm/client/query-builder.d.ts +1 -111
  57. package/dist/esm/client/record-builder.d.ts +56 -63
  58. package/dist/esm/client/record-builder.js +158 -297
  59. package/dist/esm/client/record-builder.js.map +1 -1
  60. package/dist/esm/client/response-processor.d.ts +3 -3
  61. package/dist/esm/client/update-builder.d.ts +16 -21
  62. package/dist/esm/client/update-builder.js +56 -30
  63. package/dist/esm/client/update-builder.js.map +1 -1
  64. package/dist/esm/errors.d.ts +8 -1
  65. package/dist/esm/errors.js +17 -0
  66. package/dist/esm/errors.js.map +1 -1
  67. package/dist/esm/index.d.ts +3 -7
  68. package/dist/esm/index.js +37 -8
  69. package/dist/esm/index.js.map +1 -1
  70. package/dist/esm/orm/column.d.ts +45 -0
  71. package/dist/esm/orm/column.js +59 -0
  72. package/dist/esm/orm/column.js.map +1 -0
  73. package/dist/esm/orm/field-builders.d.ts +154 -0
  74. package/dist/esm/orm/field-builders.js +152 -0
  75. package/dist/esm/orm/field-builders.js.map +1 -0
  76. package/dist/esm/orm/index.d.ts +4 -0
  77. package/dist/esm/orm/operators.d.ts +175 -0
  78. package/dist/esm/orm/operators.js +221 -0
  79. package/dist/esm/orm/operators.js.map +1 -0
  80. package/dist/esm/orm/table.d.ts +341 -0
  81. package/dist/esm/orm/table.js +211 -0
  82. package/dist/esm/orm/table.js.map +1 -0
  83. package/dist/esm/transform.d.ts +20 -21
  84. package/dist/esm/transform.js +34 -34
  85. package/dist/esm/transform.js.map +1 -1
  86. package/dist/esm/types.d.ts +16 -13
  87. package/dist/esm/types.js.map +1 -1
  88. package/dist/esm/validation.d.ts +14 -4
  89. package/dist/esm/validation.js +45 -1
  90. package/dist/esm/validation.js.map +1 -1
  91. package/package.json +20 -17
  92. package/src/client/batch-builder.ts +100 -32
  93. package/src/client/builders/default-select.ts +69 -0
  94. package/src/client/builders/expand-builder.ts +236 -0
  95. package/src/client/builders/index.ts +11 -0
  96. package/src/client/builders/query-string-builder.ts +41 -0
  97. package/src/client/builders/response-processor.ts +273 -0
  98. package/src/client/builders/select-mixin.ts +74 -0
  99. package/src/client/builders/select-utils.ts +34 -0
  100. package/src/client/builders/shared-types.ts +41 -0
  101. package/src/client/builders/table-utils.ts +87 -0
  102. package/src/client/database.ts +19 -160
  103. package/src/client/delete-builder.ts +46 -51
  104. package/src/client/entity-set.ts +227 -302
  105. package/src/client/error-parser.ts +59 -0
  106. package/src/client/filemaker-odata.ts +3 -14
  107. package/src/client/insert-builder.ts +124 -43
  108. package/src/client/query/expand-builder.ts +164 -0
  109. package/src/client/query/index.ts +13 -0
  110. package/src/client/query/query-builder.ts +816 -0
  111. package/src/client/query/response-processor.ts +244 -0
  112. package/src/client/query/types.ts +102 -0
  113. package/src/client/query/url-builder.ts +179 -0
  114. package/src/client/query-builder.ts +8 -1454
  115. package/src/client/record-builder.ts +325 -585
  116. package/src/client/response-processor.ts +4 -5
  117. package/src/client/update-builder.ts +102 -73
  118. package/src/errors.ts +22 -1
  119. package/src/index.ts +55 -5
  120. package/src/orm/column.ts +78 -0
  121. package/src/orm/field-builders.ts +296 -0
  122. package/src/orm/index.ts +60 -0
  123. package/src/orm/operators.ts +428 -0
  124. package/src/orm/table.ts +759 -0
  125. package/src/transform.ts +62 -48
  126. package/src/types.ts +20 -63
  127. package/src/validation.ts +76 -4
  128. package/LICENSE.md +0 -21
  129. package/dist/esm/client/base-table.d.ts +0 -128
  130. package/dist/esm/client/base-table.js +0 -57
  131. package/dist/esm/client/base-table.js.map +0 -1
  132. package/dist/esm/client/build-occurrences.d.ts +0 -74
  133. package/dist/esm/client/build-occurrences.js +0 -31
  134. package/dist/esm/client/build-occurrences.js.map +0 -1
  135. package/dist/esm/client/query-builder.js +0 -900
  136. package/dist/esm/client/query-builder.js.map +0 -1
  137. package/dist/esm/client/table-occurrence.d.ts +0 -86
  138. package/dist/esm/client/table-occurrence.js +0 -58
  139. package/dist/esm/client/table-occurrence.js.map +0 -1
  140. package/src/client/base-table.ts +0 -178
  141. package/src/client/build-occurrences.ts +0 -155
  142. package/src/client/query-builder.ts.bak +0 -1457
  143. package/src/client/table-occurrence.ts +0 -156
@@ -1,38 +1,37 @@
1
- import { BaseTable } from './client/base-table.js';
2
- import { TableOccurrence } from './client/table-occurrence.js';
1
+ import { FMTable } from './orm/table.js';
3
2
  /**
4
3
  * Transforms field names to FileMaker field IDs (FMFID) in an object
5
4
  * @param data - Object with field names as keys
6
- * @param baseTable - BaseTable instance to get field IDs from
5
+ * @param table - FMTable instance to get field IDs from
7
6
  * @returns Object with FMFID keys instead of field names
8
7
  */
9
- export declare function transformFieldNamesToIds<T extends Record<string, any>>(data: T, baseTable: BaseTable<any, any, any, any>): Record<string, any>;
8
+ export declare function transformFieldNamesToIds<T extends Record<string, any>>(data: T, table: FMTable<any, any>): Record<string, any>;
10
9
  /**
11
10
  * Transforms FileMaker field IDs (FMFID) to field names in an object
12
11
  * @param data - Object with FMFID keys
13
- * @param baseTable - BaseTable instance to get field names from
12
+ * @param table - FMTable instance to get field names from
14
13
  * @returns Object with field names as keys instead of FMFIDs
15
14
  */
16
- export declare function transformFieldIdsToNames<T extends Record<string, any>>(data: T, baseTable: BaseTable<any, any, any, any>): Record<string, any>;
15
+ export declare function transformFieldIdsToNames<T extends Record<string, any>>(data: T, table: FMTable<any, any>): Record<string, any>;
17
16
  /**
18
17
  * Transforms a field name to FMFID or returns the field name if not using IDs
19
18
  * @param fieldName - The field name to transform
20
- * @param baseTable - BaseTable instance to get field ID from
19
+ * @param table - FMTable instance to get field ID from
21
20
  * @returns The FMFID or field name
22
21
  */
23
- export declare function transformFieldName(fieldName: string, baseTable: BaseTable<any, any, any, any>): string;
22
+ export declare function transformFieldName(fieldName: string, table: FMTable<any, any>): string;
24
23
  /**
25
- * Transforms a table occurrence name to FMTID or returns the name if not using IDs
26
- * @param occurrence - TableOccurrence instance to get table ID from
24
+ * Transforms a table name to FMTID or returns the name if not using IDs
25
+ * @param table - FMTable instance to get table ID from
27
26
  * @returns The FMTID or table name
28
27
  */
29
- export declare function transformTableName(occurrence: TableOccurrence<any, any, any, any>): string;
28
+ export declare function transformTableName(table: FMTable<any, any>): string;
30
29
  /**
31
- * Gets both table name and ID from an occurrence
32
- * @param occurrence - TableOccurrence instance
30
+ * Gets both table name and ID from a table
31
+ * @param table - FMTable instance
33
32
  * @returns Object with name (always present) and id (may be undefined if not using IDs)
34
33
  */
35
- export declare function getTableIdentifiers(occurrence: TableOccurrence<any, any, any, any>): {
34
+ export declare function getTableIdentifiers(table: FMTable<any, any>): {
36
35
  name: string;
37
36
  id: string | undefined;
38
37
  };
@@ -41,25 +40,25 @@ export declare function getTableIdentifiers(occurrence: TableOccurrence<any, any
41
40
  * Handles both single records and arrays of records, as well as nested expand relationships.
42
41
  *
43
42
  * @param data - Response data from FileMaker (can be single record, array, or wrapped in value property)
44
- * @param baseTable - BaseTable instance for the main table
43
+ * @param table - FMTable instance for the main table
45
44
  * @param expandConfigs - Configuration for expanded relations (optional)
46
45
  * @returns Transformed data with field names instead of IDs
47
46
  */
48
- export declare function transformResponseFields(data: any, baseTable: BaseTable<any, any, any, any>, expandConfigs?: Array<{
47
+ export declare function transformResponseFields(data: any, table: FMTable<any, any>, expandConfigs?: Array<{
49
48
  relation: string;
50
- occurrence?: TableOccurrence<any, any, any, any>;
49
+ table?: FMTable<any, any>;
51
50
  }>): any;
52
51
  /**
53
52
  * Transforms an array of field names to FMFIDs
54
53
  * @param fieldNames - Array of field names
55
- * @param baseTable - BaseTable instance to get field IDs from
54
+ * @param table - FMTable instance to get field IDs from
56
55
  * @returns Array of FMFIDs or field names
57
56
  */
58
- export declare function transformFieldNamesArray(fieldNames: string[], baseTable: BaseTable<any, any, any, any>): string[];
57
+ export declare function transformFieldNamesArray(fieldNames: string[], table: FMTable<any, any>): string[];
59
58
  /**
60
59
  * Transforms a field name in an orderBy string (e.g., "name desc" -> "FMFID:1 desc")
61
60
  * @param orderByString - The orderBy string (field name with optional asc/desc)
62
- * @param baseTable - BaseTable instance to get field ID from
61
+ * @param table - FMTable instance to get field ID from
63
62
  * @returns Transformed orderBy string with FMFID
64
63
  */
65
- export declare function transformOrderByField(orderByString: string, baseTable: BaseTable<any, any, any, any>): string;
64
+ export declare function transformOrderByField(orderByString: string, table: FMTable<any, any> | undefined): string;
@@ -1,28 +1,22 @@
1
- function transformFieldNamesToIds(data, baseTable) {
2
- if (!baseTable.isUsingFieldIds()) {
1
+ import { getFieldId, getBaseTableConfig, isUsingEntityIds, getTableId, getFieldName } from "./orm/table.js";
2
+ function transformFieldNamesToIds(data, table) {
3
+ const config = getBaseTableConfig(table);
4
+ if (!config.fmfIds) {
3
5
  return data;
4
6
  }
5
7
  const transformed = {};
6
8
  for (const [fieldName, value] of Object.entries(data)) {
7
- const fieldId = baseTable.getFieldId(fieldName);
9
+ const fieldId = getFieldId(table, fieldName);
8
10
  transformed[fieldId] = value;
9
11
  }
10
12
  return transformed;
11
13
  }
12
- function transformFieldName(fieldName, baseTable) {
13
- return baseTable.getFieldId(fieldName);
14
+ function transformFieldName(fieldName, table) {
15
+ return getFieldId(table, fieldName);
14
16
  }
15
- function transformTableName(occurrence) {
16
- return occurrence.getTableId();
17
- }
18
- function getTableIdentifiers(occurrence) {
19
- return {
20
- name: occurrence.getTableName(),
21
- id: occurrence.isUsingTableId() ? occurrence.getTableId() : void 0
22
- };
23
- }
24
- function transformResponseFields(data, baseTable, expandConfigs) {
25
- if (!baseTable.isUsingFieldIds()) {
17
+ function transformResponseFields(data, table, expandConfigs) {
18
+ const config = getBaseTableConfig(table);
19
+ if (!config.fmfIds) {
26
20
  return data;
27
21
  }
28
22
  if (data === null || data === void 0) {
@@ -32,18 +26,18 @@ function transformResponseFields(data, baseTable, expandConfigs) {
32
26
  return {
33
27
  ...data,
34
28
  value: data.value.map(
35
- (record) => transformSingleRecord(record, baseTable, expandConfigs)
29
+ (record) => transformSingleRecord(record, table, expandConfigs)
36
30
  )
37
31
  };
38
32
  }
39
33
  if (Array.isArray(data)) {
40
34
  return data.map(
41
- (record) => transformSingleRecord(record, baseTable, expandConfigs)
35
+ (record) => transformSingleRecord(record, table, expandConfigs)
42
36
  );
43
37
  }
44
- return transformSingleRecord(data, baseTable, expandConfigs);
38
+ return transformSingleRecord(data, table, expandConfigs);
45
39
  }
46
- function transformSingleRecord(record, baseTable, expandConfigs) {
40
+ function transformSingleRecord(record, table, expandConfigs) {
47
41
  if (!record || typeof record !== "object") {
48
42
  return record;
49
43
  }
@@ -56,16 +50,16 @@ function transformSingleRecord(record, baseTable, expandConfigs) {
56
50
  let expandConfig = expandConfigs == null ? void 0 : expandConfigs.find((ec) => ec.relation === key);
57
51
  if (!expandConfig && key.startsWith("FMTID:")) {
58
52
  expandConfig = expandConfigs == null ? void 0 : expandConfigs.find(
59
- (ec) => ec.occurrence && ec.occurrence.isUsingTableId() && ec.occurrence.getTableId() === key
53
+ (ec) => ec.table && isUsingEntityIds(ec.table) && getTableId(ec.table) === key
60
54
  );
61
55
  }
62
- if (expandConfig && expandConfig.occurrence) {
56
+ if (expandConfig && expandConfig.table) {
63
57
  const relationKey = expandConfig.relation;
64
58
  if (Array.isArray(value)) {
65
59
  transformed[relationKey] = value.map(
66
60
  (nestedRecord) => transformSingleRecord(
67
61
  nestedRecord,
68
- expandConfig.occurrence.baseTable,
62
+ expandConfig.table,
69
63
  void 0
70
64
  // Don't pass nested expand configs for now
71
65
  )
@@ -73,7 +67,7 @@ function transformSingleRecord(record, baseTable, expandConfigs) {
73
67
  } else if (value && typeof value === "object") {
74
68
  transformed[relationKey] = transformSingleRecord(
75
69
  value,
76
- expandConfig.occurrence.baseTable,
70
+ expandConfig.table,
77
71
  void 0
78
72
  );
79
73
  } else {
@@ -81,34 +75,40 @@ function transformSingleRecord(record, baseTable, expandConfigs) {
81
75
  }
82
76
  continue;
83
77
  }
84
- const fieldName = baseTable.getFieldName(key);
78
+ const fieldName = getFieldName(table, key);
85
79
  transformed[fieldName] = value;
86
80
  }
87
81
  return transformed;
88
82
  }
89
- function transformFieldNamesArray(fieldNames, baseTable) {
90
- if (!baseTable.isUsingFieldIds()) {
83
+ function transformFieldNamesArray(fieldNames, table) {
84
+ const config = getBaseTableConfig(table);
85
+ if (!config.fmfIds) {
91
86
  return fieldNames;
92
87
  }
93
- return fieldNames.map((fieldName) => baseTable.getFieldId(fieldName));
88
+ return fieldNames.map((fieldName) => getFieldId(table, fieldName));
94
89
  }
95
- function transformOrderByField(orderByString, baseTable) {
96
- if (!baseTable.isUsingFieldIds()) {
90
+ function transformOrderByField(orderByString, table) {
91
+ if (!table) {
92
+ return orderByString;
93
+ }
94
+ const config = getBaseTableConfig(table);
95
+ if (!config || !config.fmfIds) {
97
96
  return orderByString;
98
97
  }
99
98
  const parts = orderByString.trim().split(/\s+/);
100
99
  const fieldName = parts[0];
100
+ if (!fieldName) {
101
+ return orderByString;
102
+ }
101
103
  const direction = parts[1];
102
- const fieldId = baseTable.getFieldId(fieldName);
104
+ const fieldId = getFieldId(table, fieldName);
103
105
  return direction ? `${fieldId} ${direction}` : fieldId;
104
106
  }
105
107
  export {
106
- getTableIdentifiers,
107
108
  transformFieldName,
108
109
  transformFieldNamesArray,
109
110
  transformFieldNamesToIds,
110
111
  transformOrderByField,
111
- transformResponseFields,
112
- transformTableName
112
+ transformResponseFields
113
113
  };
114
114
  //# sourceMappingURL=transform.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"transform.js","sources":["../../src/transform.ts"],"sourcesContent":["import type { BaseTable } from \"./client/base-table\";\nimport type { TableOccurrence } from \"./client/table-occurrence\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Transforms field names to FileMaker field IDs (FMFID) in an object\n * @param data - Object with field names as keys\n * @param baseTable - BaseTable instance to get field IDs from\n * @returns Object with FMFID keys instead of field names\n */\nexport function transformFieldNamesToIds<T extends Record<string, any>>(\n data: T,\n baseTable: BaseTable<any, any, any, any>,\n): Record<string, any> {\n if (!baseTable.isUsingFieldIds()) {\n return data;\n }\n\n const transformed: Record<string, any> = {};\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldId = baseTable.getFieldId(fieldName as any);\n transformed[fieldId] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms FileMaker field IDs (FMFID) to field names in an object\n * @param data - Object with FMFID keys\n * @param baseTable - BaseTable instance to get field names from\n * @returns Object with field names as keys instead of FMFIDs\n */\nexport function transformFieldIdsToNames<T extends Record<string, any>>(\n data: T,\n baseTable: BaseTable<any, any, any, any>,\n): Record<string, any> {\n if (!baseTable.isUsingFieldIds()) {\n return data;\n }\n\n const transformed: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Check if this is an OData metadata field (starts with @)\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n const fieldName = baseTable.getFieldName(key);\n transformed[fieldName] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms a field name to FMFID or returns the field name if not using IDs\n * @param fieldName - The field name to transform\n * @param baseTable - BaseTable instance to get field ID from\n * @returns The FMFID or field name\n */\nexport function transformFieldName(\n fieldName: string,\n baseTable: BaseTable<any, any, any, any>,\n): string {\n return baseTable.getFieldId(fieldName as any);\n}\n\n/**\n * Transforms a table occurrence name to FMTID or returns the name if not using IDs\n * @param occurrence - TableOccurrence instance to get table ID from\n * @returns The FMTID or table name\n */\nexport function transformTableName(\n occurrence: TableOccurrence<any, any, any, any>,\n): string {\n return occurrence.getTableId();\n}\n\n/**\n * Gets both table name and ID from an occurrence\n * @param occurrence - TableOccurrence instance\n * @returns Object with name (always present) and id (may be undefined if not using IDs)\n */\nexport function getTableIdentifiers(\n occurrence: TableOccurrence<any, any, any, any>,\n): { name: string; id: string | undefined } {\n return {\n name: occurrence.getTableName(),\n id: occurrence.isUsingTableId() ? occurrence.getTableId() : undefined,\n };\n}\n\n/**\n * Transforms response data by converting field IDs back to field names recursively.\n * Handles both single records and arrays of records, as well as nested expand relationships.\n *\n * @param data - Response data from FileMaker (can be single record, array, or wrapped in value property)\n * @param baseTable - BaseTable instance for the main table\n * @param expandConfigs - Configuration for expanded relations (optional)\n * @returns Transformed data with field names instead of IDs\n */\nexport function transformResponseFields(\n data: any,\n baseTable: BaseTable<any, any, any, any>,\n expandConfigs?: Array<{\n relation: string;\n occurrence?: TableOccurrence<any, any, any, any>;\n }>,\n): any {\n if (!baseTable.isUsingFieldIds()) {\n return data;\n }\n\n // Handle null/undefined\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle OData list response with value array\n if (data.value && Array.isArray(data.value)) {\n return {\n ...data,\n value: data.value.map((record: any) =>\n transformSingleRecord(record, baseTable, expandConfigs),\n ),\n };\n }\n\n // Handle array of records\n if (Array.isArray(data)) {\n return data.map((record) =>\n transformSingleRecord(record, baseTable, expandConfigs),\n );\n }\n\n // Handle single record\n return transformSingleRecord(data, baseTable, expandConfigs);\n}\n\n/**\n * Transforms a single record, converting field IDs to names and handling nested expands\n */\nfunction transformSingleRecord(\n record: any,\n baseTable: BaseTable<any, any, any, any>,\n expandConfigs?: Array<{\n relation: string;\n occurrence?: TableOccurrence<any, any, any, any>;\n }>,\n): any {\n if (!record || typeof record !== \"object\") {\n return record;\n }\n\n const transformed: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(record)) {\n // Preserve OData metadata fields\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n // Check if this is an expanded relation (by relation name)\n let expandConfig = expandConfigs?.find((ec) => ec.relation === key);\n\n // If not found by relation name, check if this key is a FMTID\n // (FileMaker returns expanded relations with FMTID keys when using entity IDs)\n if (!expandConfig && key.startsWith(\"FMTID:\")) {\n expandConfig = expandConfigs?.find(\n (ec) =>\n ec.occurrence &&\n ec.occurrence.isUsingTableId() &&\n ec.occurrence.getTableId() === key,\n );\n }\n\n if (expandConfig && expandConfig.occurrence) {\n // Transform the expanded relation data recursively\n // Use the relation name (not the FMTID) as the key\n const relationKey = expandConfig.relation;\n\n if (Array.isArray(value)) {\n transformed[relationKey] = value.map((nestedRecord) =>\n transformSingleRecord(\n nestedRecord,\n expandConfig.occurrence!.baseTable,\n undefined, // Don't pass nested expand configs for now\n ),\n );\n } else if (value && typeof value === \"object\") {\n transformed[relationKey] = transformSingleRecord(\n value,\n expandConfig.occurrence.baseTable,\n undefined,\n );\n } else {\n transformed[relationKey] = value;\n }\n continue;\n }\n\n // Transform field ID to field name\n const fieldName = baseTable.getFieldName(key);\n transformed[fieldName] = value;\n }\n\n return transformed;\n}\n\n/**\n * Transforms an array of field names to FMFIDs\n * @param fieldNames - Array of field names\n * @param baseTable - BaseTable instance to get field IDs from\n * @returns Array of FMFIDs or field names\n */\nexport function transformFieldNamesArray(\n fieldNames: string[],\n baseTable: BaseTable<any, any, any, any>,\n): string[] {\n if (!baseTable.isUsingFieldIds()) {\n return fieldNames;\n }\n\n return fieldNames.map((fieldName) => baseTable.getFieldId(fieldName as any));\n}\n\n/**\n * Transforms a field name in an orderBy string (e.g., \"name desc\" -> \"FMFID:1 desc\")\n * @param orderByString - The orderBy string (field name with optional asc/desc)\n * @param baseTable - BaseTable instance to get field ID from\n * @returns Transformed orderBy string with FMFID\n */\nexport function transformOrderByField(\n orderByString: string,\n baseTable: BaseTable<any, any, any, any>,\n): string {\n if (!baseTable.isUsingFieldIds()) {\n return orderByString;\n }\n\n // Parse the orderBy string to extract field name and direction\n const parts = orderByString.trim().split(/\\s+/);\n const fieldName = parts[0];\n const direction = parts[1]; // \"asc\" or \"desc\" or undefined\n\n const fieldId = baseTable.getFieldId(fieldName as any);\n return direction ? `${fieldId} ${direction}` : fieldId;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform.js","sources":["../../src/transform.ts"],"sourcesContent":["import type { FMTable } from \"./orm/table\";\nimport {\n getBaseTableConfig,\n getFieldId,\n getFieldName,\n getTableId,\n getTableName,\n isUsingEntityIds,\n} from \"./orm/table\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Transforms field names to FileMaker field IDs (FMFID) in an object\n * @param data - Object with field names as keys\n * @param table - FMTable instance to get field IDs from\n * @returns Object with FMFID keys instead of field names\n */\nexport function transformFieldNamesToIds<T extends Record<string, any>>(\n data: T,\n table: FMTable<any, any>,\n): Record<string, any> {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n const transformed: Record<string, any> = {};\n for (const [fieldName, value] of Object.entries(data)) {\n const fieldId = getFieldId(table, fieldName);\n transformed[fieldId] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms FileMaker field IDs (FMFID) to field names in an object\n * @param data - Object with FMFID keys\n * @param table - FMTable instance to get field names from\n * @returns Object with field names as keys instead of FMFIDs\n */\nexport function transformFieldIdsToNames<T extends Record<string, any>>(\n data: T,\n table: FMTable<any, any>,\n): Record<string, any> {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n const transformed: Record<string, any> = {};\n for (const [key, value] of Object.entries(data)) {\n // Check if this is an OData metadata field (starts with @)\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n const fieldName = getFieldName(table, key);\n transformed[fieldName] = value;\n }\n return transformed;\n}\n\n/**\n * Transforms a field name to FMFID or returns the field name if not using IDs\n * @param fieldName - The field name to transform\n * @param table - FMTable instance to get field ID from\n * @returns The FMFID or field name\n */\nexport function transformFieldName(\n fieldName: string,\n table: FMTable<any, any>,\n): string {\n return getFieldId(table, fieldName);\n}\n\n/**\n * Transforms a table name to FMTID or returns the name if not using IDs\n * @param table - FMTable instance to get table ID from\n * @returns The FMTID or table name\n */\nexport function transformTableName(table: FMTable<any, any>): string {\n return getTableId(table);\n}\n\n/**\n * Gets both table name and ID from a table\n * @param table - FMTable instance\n * @returns Object with name (always present) and id (may be undefined if not using IDs)\n */\nexport function getTableIdentifiers(\n table: FMTable<any, any>,\n): { name: string; id: string | undefined } {\n return {\n name: getTableName(table),\n id: isUsingEntityIds(table) ? getTableId(table) : undefined,\n };\n}\n\n/**\n * Transforms response data by converting field IDs back to field names recursively.\n * Handles both single records and arrays of records, as well as nested expand relationships.\n *\n * @param data - Response data from FileMaker (can be single record, array, or wrapped in value property)\n * @param table - FMTable instance for the main table\n * @param expandConfigs - Configuration for expanded relations (optional)\n * @returns Transformed data with field names instead of IDs\n */\nexport function transformResponseFields(\n data: any,\n table: FMTable<any, any>,\n expandConfigs?: Array<{\n relation: string;\n table?: FMTable<any, any>;\n }>,\n): any {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return data;\n }\n\n // Handle null/undefined\n if (data === null || data === undefined) {\n return data;\n }\n\n // Handle OData list response with value array\n if (data.value && Array.isArray(data.value)) {\n return {\n ...data,\n value: data.value.map((record: any) =>\n transformSingleRecord(record, table, expandConfigs),\n ),\n };\n }\n\n // Handle array of records\n if (Array.isArray(data)) {\n return data.map((record) =>\n transformSingleRecord(record, table, expandConfigs),\n );\n }\n\n // Handle single record\n return transformSingleRecord(data, table, expandConfigs);\n}\n\n/**\n * Transforms a single record, converting field IDs to names and handling nested expands\n */\nfunction transformSingleRecord(\n record: any,\n table: FMTable<any, any>,\n expandConfigs?: Array<{\n relation: string;\n table?: FMTable<any, any>;\n }>,\n): any {\n if (!record || typeof record !== \"object\") {\n return record;\n }\n\n const transformed: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(record)) {\n // Preserve OData metadata fields\n if (key.startsWith(\"@\")) {\n transformed[key] = value;\n continue;\n }\n\n // Check if this is an expanded relation (by relation name)\n let expandConfig = expandConfigs?.find((ec) => ec.relation === key);\n\n // If not found by relation name, check if this key is a FMTID\n // (FileMaker returns expanded relations with FMTID keys when using entity IDs)\n if (!expandConfig && key.startsWith(\"FMTID:\")) {\n expandConfig = expandConfigs?.find(\n (ec) =>\n ec.table && isUsingEntityIds(ec.table) && getTableId(ec.table) === key,\n );\n }\n\n if (expandConfig && expandConfig.table) {\n // Transform the expanded relation data recursively\n // Use the relation name (not the FMTID) as the key\n const relationKey = expandConfig.relation;\n\n if (Array.isArray(value)) {\n transformed[relationKey] = value.map((nestedRecord) =>\n transformSingleRecord(\n nestedRecord,\n expandConfig.table!,\n undefined, // Don't pass nested expand configs for now\n ),\n );\n } else if (value && typeof value === \"object\") {\n transformed[relationKey] = transformSingleRecord(\n value,\n expandConfig.table,\n undefined,\n );\n } else {\n transformed[relationKey] = value;\n }\n continue;\n }\n\n // Transform field ID to field name\n const fieldName = getFieldName(table, key);\n transformed[fieldName] = value;\n }\n\n return transformed;\n}\n\n/**\n * Transforms an array of field names to FMFIDs\n * @param fieldNames - Array of field names\n * @param table - FMTable instance to get field IDs from\n * @returns Array of FMFIDs or field names\n */\nexport function transformFieldNamesArray(\n fieldNames: string[],\n table: FMTable<any, any>,\n): string[] {\n const config = getBaseTableConfig(table);\n if (!config.fmfIds) {\n return fieldNames;\n }\n\n return fieldNames.map((fieldName) => getFieldId(table, fieldName));\n}\n\n/**\n * Transforms a field name in an orderBy string (e.g., \"name desc\" -> \"FMFID:1 desc\")\n * @param orderByString - The orderBy string (field name with optional asc/desc)\n * @param table - FMTable instance to get field ID from\n * @returns Transformed orderBy string with FMFID\n */\nexport function transformOrderByField(\n orderByString: string,\n table: FMTable<any, any> | undefined,\n): string {\n if (!table) {\n return orderByString;\n }\n const config = getBaseTableConfig(table);\n if (!config || !config.fmfIds) {\n return orderByString;\n }\n\n // Parse the orderBy string to extract field name and direction\n const parts = orderByString.trim().split(/\\s+/);\n const fieldName = parts[0];\n if (!fieldName) {\n return orderByString;\n }\n const direction = parts[1]; // \"asc\" or \"desc\" or undefined\n\n const fieldId = getFieldId(table, fieldName);\n return direction ? `${fieldId} ${direction}` : fieldId;\n}\n"],"names":[],"mappings":"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"}
@@ -68,23 +68,27 @@ export type Result<T, E = import('./errors.js').FMODataErrorType> = {
68
68
  data: undefined;
69
69
  error: E;
70
70
  };
71
+ export type BatchItemResult<T> = {
72
+ data: T | undefined;
73
+ error: import('./errors.js').FMODataErrorType | undefined;
74
+ status: number;
75
+ };
76
+ export type BatchResult<T extends readonly any[]> = {
77
+ results: {
78
+ [K in keyof T]: BatchItemResult<T[K]>;
79
+ };
80
+ successCount: number;
81
+ errorCount: number;
82
+ truncated: boolean;
83
+ firstErrorIndex: number | null;
84
+ };
71
85
  export type MakeFieldsRequired<T, Keys extends keyof T> = Partial<T> & Required<Pick<T, Keys>>;
72
86
  export type AutoRequiredKeys<Schema extends Record<string, StandardSchemaV1>> = {
73
87
  [K in keyof Schema]: Extract<StandardSchemaV1.InferOutput<Schema[K]>, null | undefined> extends never ? K : never;
74
88
  }[keyof Schema];
75
89
  export type ExcludedFields<IdField extends keyof any | undefined, ReadOnly extends readonly any[]> = IdField extends keyof any ? IdField | ReadOnly[number] : ReadOnly[number];
76
- type ComputeInsertData<Schema extends Record<string, StandardSchemaV1>, IdField extends keyof Schema | undefined, Required extends readonly any[], ReadOnly extends readonly any[]> = [Required[number]] extends [keyof InferSchemaType<Schema>] ? Required extends readonly (keyof InferSchemaType<Schema>)[] ? MakeFieldsRequired<Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>, Exclude<AutoRequiredKeys<Schema> | Required[number], ExcludedFields<IdField, ReadOnly>>> : MakeFieldsRequired<Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>, Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>> : MakeFieldsRequired<Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>, Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>>;
77
- export type InsertData<BT> = BT extends import('./client/base-table.js').BaseTable<any, any, any, any> ? BT extends {
78
- schema: infer Schema;
79
- idField?: infer IdField;
80
- required?: infer Required;
81
- readOnly?: infer ReadOnly;
82
- } ? Schema extends Record<string, StandardSchemaV1> ? IdField extends keyof Schema | undefined ? Required extends readonly any[] ? ReadOnly extends readonly any[] ? ComputeInsertData<Schema, Extract<IdField, keyof Schema | undefined>, Required, ReadOnly> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>>;
83
- export type UpdateData<BT> = BT extends import('./client/base-table.js').BaseTable<any, any, any, any> ? BT extends {
84
- schema: infer Schema;
85
- idField?: infer IdField;
86
- readOnly?: infer ReadOnly;
87
- } ? Schema extends Record<string, StandardSchemaV1> ? IdField extends keyof Schema | undefined ? ReadOnly extends readonly any[] ? Partial<Omit<InferSchemaType<Schema>, ExcludedFields<Extract<IdField, keyof Schema | undefined>, ReadOnly>>> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>> : Partial<Record<string, any>>;
90
+ export type InsertData<BT> = Partial<Record<string, any>>;
91
+ export type UpdateData<BT> = Partial<Record<string, any>>;
88
92
  export type ExecuteOptions = {
89
93
  includeODataAnnotations?: boolean;
90
94
  skipValidation?: boolean;
@@ -155,4 +159,3 @@ export type EntitySet = {
155
159
  $Type: string;
156
160
  };
157
161
  export type Metadata = Record<string, EntityType | EntitySet>;
158
- export {};
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport type Auth = { username: string; password: string } | { apiKey: string };\n\nexport interface ExecutableBuilder<T> {\n execute(): Promise<Result<T>>;\n getRequestConfig(): { method: string; url: string; body?: any };\n\n /**\n * Convert this builder to a native Request object for batch processing.\n * @param baseUrl - The base URL for the OData service\n * @param options - Optional execution options (e.g., includeODataAnnotations)\n * @returns A native Request object\n */\n toRequest(baseUrl: string, options?: ExecuteOptions): Request;\n\n /**\n * Process a raw Response object into a typed Result.\n * This allows builders to apply their own validation and transformation logic.\n * @param response - The native Response object from the batch operation\n * @param options - Optional execution options (e.g., skipValidation, includeODataAnnotations)\n * @returns A typed Result with the builder's expected return type\n */\n processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<T>>;\n}\n\nexport interface ExecutionContext {\n _makeRequest<T>(\n url: string,\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<Result<T>>;\n _setUseEntityIds?(useEntityIds: boolean): void;\n _getUseEntityIds?(): boolean;\n _getBaseUrl?(): string;\n}\n\nexport type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {\n [K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output>\n ? Output\n : never;\n};\n\nexport type WithSystemFields<T> =\n T extends Record<string, any>\n ? T & {\n ROWID: number;\n ROWMODID: number;\n }\n : never;\n\n// Helper type to exclude system fields from a union of keys\nexport type ExcludeSystemFields<T extends keyof any> = Exclude<\n T,\n \"ROWID\" | \"ROWMODID\"\n>;\n\n// Helper type to omit system fields from an object type\nexport type OmitSystemFields<T> = Omit<T, \"ROWID\" | \"ROWMODID\">;\n\n// OData record metadata fields (present on each record)\nexport type ODataRecordMetadata = {\n \"@id\": string;\n \"@editLink\": string;\n};\n\n// OData response wrapper (top-level, internal use only)\nexport type ODataListResponse<T> = {\n \"@context\": string;\n value: (T & ODataRecordMetadata)[];\n};\n\nexport type ODataSingleResponse<T> = T &\n ODataRecordMetadata & {\n \"@context\": string;\n };\n\n// OData response for single field values\nexport type ODataFieldResponse<T> = {\n \"@context\": string;\n value: T;\n};\n\n// Result pattern for execute responses\nexport type Result<T, E = import(\"./errors\").FMODataErrorType> =\n | { data: T; error: undefined }\n | { data: undefined; error: E };\n\n// Make specific keys required, rest optional\nexport type MakeFieldsRequired<T, Keys extends keyof T> = Partial<T> &\n Required<Pick<T, Keys>>;\n\n// Extract keys from schema where validator doesn't allow null/undefined (auto-required fields)\nexport type AutoRequiredKeys<Schema extends Record<string, StandardSchemaV1>> =\n {\n [K in keyof Schema]: Extract<\n StandardSchemaV1.InferOutput<Schema[K]>,\n null | undefined\n > extends never\n ? K\n : never;\n }[keyof Schema];\n\n// Helper type to compute excluded fields (readOnly fields + idField)\nexport type ExcludedFields<\n IdField extends keyof any | undefined,\n ReadOnly extends readonly any[],\n> = IdField extends keyof any ? IdField | ReadOnly[number] : ReadOnly[number];\n\n// Helper type for InsertData computation\ntype ComputeInsertData<\n Schema extends Record<string, StandardSchemaV1>,\n IdField extends keyof Schema | undefined,\n Required extends readonly any[],\n ReadOnly extends readonly any[],\n> = [Required[number]] extends [keyof InferSchemaType<Schema>]\n ? Required extends readonly (keyof InferSchemaType<Schema>)[]\n ? MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<\n AutoRequiredKeys<Schema> | Required[number],\n ExcludedFields<IdField, ReadOnly>\n >\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >;\n\n// Extract insert data type from BaseTable\n// Auto-infers required fields from validator nullability + user-specified required fields\n// Excludes readOnly fields and idField\nexport type InsertData<BT> = BT extends import(\"./client/base-table\").BaseTable<\n any,\n any,\n any,\n any\n>\n ? BT extends {\n schema: infer Schema;\n idField?: infer IdField;\n required?: infer Required;\n readOnly?: infer ReadOnly;\n }\n ? Schema extends Record<string, StandardSchemaV1>\n ? IdField extends keyof Schema | undefined\n ? Required extends readonly any[]\n ? ReadOnly extends readonly any[]\n ? ComputeInsertData<\n Schema,\n Extract<IdField, keyof Schema | undefined>,\n Required,\n ReadOnly\n >\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>;\n\n// Extract update data type from BaseTable\n// All fields are optional for updates, excludes readOnly fields and idField\nexport type UpdateData<BT> = BT extends import(\"./client/base-table\").BaseTable<\n any,\n any,\n any,\n any\n>\n ? BT extends {\n schema: infer Schema;\n idField?: infer IdField;\n readOnly?: infer ReadOnly;\n }\n ? Schema extends Record<string, StandardSchemaV1>\n ? IdField extends keyof Schema | undefined\n ? ReadOnly extends readonly any[]\n ? Partial<\n Omit<\n InferSchemaType<Schema>,\n ExcludedFields<\n Extract<IdField, keyof Schema | undefined>,\n ReadOnly\n >\n >\n >\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>\n : Partial<Record<string, any>>;\n\nexport type ExecuteOptions = {\n includeODataAnnotations?: boolean;\n skipValidation?: boolean;\n /**\n * Overrides the default behavior of the database to use entity IDs (rather than field names) in THIS REQUEST ONLY\n */\n useEntityIds?: boolean;\n};\n\n/**\n * Get the Accept header value based on includeODataAnnotations option\n * @param includeODataAnnotations - Whether to include OData annotations\n * @returns Accept header value\n */\nexport function getAcceptHeader(includeODataAnnotations?: boolean): string {\n return includeODataAnnotations === true\n ? \"application/json\"\n : \"application/json;odata.metadata=none\";\n}\n\nexport type ConditionallyWithODataAnnotations<\n T,\n IncludeODataAnnotations extends boolean,\n> = IncludeODataAnnotations extends true\n ? T & {\n \"@id\": string;\n \"@editLink\": string;\n }\n : T;\n\n// Helper type to extract schema from a TableOccurrence\nexport type ExtractSchemaFromOccurrence<Occ> = Occ extends {\n baseTable: { schema: infer S };\n}\n ? S extends Record<string, StandardSchemaV1>\n ? S\n : Record<string, StandardSchemaV1>\n : Record<string, StandardSchemaV1>;\n\nexport type GenericFieldMetadata = {\n $Nullable?: boolean;\n \"@Index\"?: boolean;\n \"@Calculation\"?: boolean;\n \"@Summary\"?: boolean;\n \"@Global\"?: boolean;\n \"@Org.OData.Core.V1.Permissions\"?: \"Org.OData.Core.V1.Permission@Read\";\n};\n\nexport type StringFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.String\";\n $DefaultValue?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n $MaxLength?: number;\n};\n\nexport type DecimalFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Decimal\";\n \"@AutoGenerated\"?: boolean;\n};\n\nexport type DateFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURDATE\" | \"CURRENT_DATE\";\n};\n\nexport type TimeOfDayFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.TimeOfDay\";\n $DefaultValue?: \"CURTIME\" | \"CURRENT_TIME\";\n};\n\nexport type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURTIMESTAMP\" | \"CURRENT_TIMESTAMP\";\n \"@VersionId\"?: boolean;\n};\n\nexport type StreamFieldMetadata = {\n $Type: \"Edm.Stream\";\n $Nullable?: boolean;\n \"@EnclosedPath\": string;\n \"@ExternalOpenPath\": string;\n \"@ExternalSecurePath\"?: string;\n};\n\nexport type FieldMetadata =\n | StringFieldMetadata\n | DecimalFieldMetadata\n | DateFieldMetadata\n | TimeOfDayFieldMetadata\n | DateTimeOffsetFieldMetadata\n | StreamFieldMetadata;\n\nexport type EntityType = {\n $Kind: \"EntityType\";\n $Key: string[];\n} & Record<string, FieldMetadata>;\n\nexport type EntitySet = {\n $Kind: \"EntitySet\";\n $Type: string;\n};\n\nexport type Metadata = Record<string, EntityType | EntitySet>;\n"],"names":[],"mappings":"AAqNO,SAAS,gBAAgB,yBAA2C;AAClE,SAAA,4BAA4B,OAC/B,qBACA;AACN;"}
1
+ {"version":3,"file":"types.js","sources":["../../src/types.ts"],"sourcesContent":["import { type FFetchOptions } from \"@fetchkit/ffetch\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nexport type Auth = { username: string; password: string } | { apiKey: string };\n\nexport interface ExecutableBuilder<T> {\n execute(): Promise<Result<T>>;\n getRequestConfig(): { method: string; url: string; body?: any };\n\n /**\n * Convert this builder to a native Request object for batch processing.\n * @param baseUrl - The base URL for the OData service\n * @param options - Optional execution options (e.g., includeODataAnnotations)\n * @returns A native Request object\n */\n toRequest(baseUrl: string, options?: ExecuteOptions): Request;\n\n /**\n * Process a raw Response object into a typed Result.\n * This allows builders to apply their own validation and transformation logic.\n * @param response - The native Response object from the batch operation\n * @param options - Optional execution options (e.g., skipValidation, includeODataAnnotations)\n * @returns A typed Result with the builder's expected return type\n */\n processResponse(\n response: Response,\n options?: ExecuteOptions,\n ): Promise<Result<T>>;\n}\n\nexport interface ExecutionContext {\n _makeRequest<T>(\n url: string,\n options?: RequestInit & FFetchOptions & { useEntityIds?: boolean },\n ): Promise<Result<T>>;\n _setUseEntityIds?(useEntityIds: boolean): void;\n _getUseEntityIds?(): boolean;\n _getBaseUrl?(): string;\n}\n\nexport type InferSchemaType<Schema extends Record<string, StandardSchemaV1>> = {\n [K in keyof Schema]: Schema[K] extends StandardSchemaV1<any, infer Output>\n ? Output\n : never;\n};\n\nexport type WithSystemFields<T> =\n T extends Record<string, any>\n ? T & {\n ROWID: number;\n ROWMODID: number;\n }\n : never;\n\n// Helper type to exclude system fields from a union of keys\nexport type ExcludeSystemFields<T extends keyof any> = Exclude<\n T,\n \"ROWID\" | \"ROWMODID\"\n>;\n\n// Helper type to omit system fields from an object type\nexport type OmitSystemFields<T> = Omit<T, \"ROWID\" | \"ROWMODID\">;\n\n// OData record metadata fields (present on each record)\nexport type ODataRecordMetadata = {\n \"@id\": string;\n \"@editLink\": string;\n};\n\n// OData response wrapper (top-level, internal use only)\nexport type ODataListResponse<T> = {\n \"@context\": string;\n value: (T & ODataRecordMetadata)[];\n};\n\nexport type ODataSingleResponse<T> = T &\n ODataRecordMetadata & {\n \"@context\": string;\n };\n\n// OData response for single field values\nexport type ODataFieldResponse<T> = {\n \"@context\": string;\n value: T;\n};\n\n// Result pattern for execute responses\nexport type Result<T, E = import(\"./errors\").FMODataErrorType> =\n | { data: T; error: undefined }\n | { data: undefined; error: E };\n\n// Batch operation result types\nexport type BatchItemResult<T> = {\n data: T | undefined;\n error: import(\"./errors\").FMODataErrorType | undefined;\n status: number; // HTTP status code (0 for truncated)\n};\n\nexport type BatchResult<T extends readonly any[]> = {\n results: { [K in keyof T]: BatchItemResult<T[K]> };\n successCount: number;\n errorCount: number;\n truncated: boolean;\n firstErrorIndex: number | null;\n};\n\n// Make specific keys required, rest optional\nexport type MakeFieldsRequired<T, Keys extends keyof T> = Partial<T> &\n Required<Pick<T, Keys>>;\n\n// Extract keys from schema where validator doesn't allow null/undefined (auto-required fields)\nexport type AutoRequiredKeys<Schema extends Record<string, StandardSchemaV1>> =\n {\n [K in keyof Schema]: Extract<\n StandardSchemaV1.InferOutput<Schema[K]>,\n null | undefined\n > extends never\n ? K\n : never;\n }[keyof Schema];\n\n// Helper type to compute excluded fields (readOnly fields + idField)\nexport type ExcludedFields<\n IdField extends keyof any | undefined,\n ReadOnly extends readonly any[],\n> = IdField extends keyof any ? IdField | ReadOnly[number] : ReadOnly[number];\n\n// Helper type for InsertData computation\ntype ComputeInsertData<\n Schema extends Record<string, StandardSchemaV1>,\n IdField extends keyof Schema | undefined,\n Required extends readonly any[],\n ReadOnly extends readonly any[],\n> = [Required[number]] extends [keyof InferSchemaType<Schema>]\n ? Required extends readonly (keyof InferSchemaType<Schema>)[]\n ? MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<\n AutoRequiredKeys<Schema> | Required[number],\n ExcludedFields<IdField, ReadOnly>\n >\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >\n : MakeFieldsRequired<\n Omit<InferSchemaType<Schema>, ExcludedFields<IdField, ReadOnly>>,\n Exclude<AutoRequiredKeys<Schema>, ExcludedFields<IdField, ReadOnly>>\n >;\n\n// Legacy types for backward compatibility\n// Note: These types are deprecated. Use InsertDataFromFMTable and UpdateDataFromFMTable from the ORM API instead.\nexport type InsertData<BT> = Partial<Record<string, any>>;\nexport type UpdateData<BT> = Partial<Record<string, any>>;\n\nexport type ExecuteOptions = {\n includeODataAnnotations?: boolean;\n skipValidation?: boolean;\n /**\n * Overrides the default behavior of the database to use entity IDs (rather than field names) in THIS REQUEST ONLY\n */\n useEntityIds?: boolean;\n};\n\n/**\n * Get the Accept header value based on includeODataAnnotations option\n * @param includeODataAnnotations - Whether to include OData annotations\n * @returns Accept header value\n */\nexport function getAcceptHeader(includeODataAnnotations?: boolean): string {\n return includeODataAnnotations === true\n ? \"application/json\"\n : \"application/json;odata.metadata=none\";\n}\n\nexport type ConditionallyWithODataAnnotations<\n T,\n IncludeODataAnnotations extends boolean,\n> = IncludeODataAnnotations extends true\n ? T & {\n \"@id\": string;\n \"@editLink\": string;\n }\n : T;\n\n// Helper type to extract schema from a TableOccurrence (legacy) or FMTable\nexport type ExtractSchemaFromOccurrence<Occ> = Occ extends {\n baseTable: { schema: infer S };\n}\n ? S extends Record<string, StandardSchemaV1>\n ? S\n : Record<string, StandardSchemaV1>\n : Record<string, StandardSchemaV1>;\n\nexport type GenericFieldMetadata = {\n $Nullable?: boolean;\n \"@Index\"?: boolean;\n \"@Calculation\"?: boolean;\n \"@Summary\"?: boolean;\n \"@Global\"?: boolean;\n \"@Org.OData.Core.V1.Permissions\"?: \"Org.OData.Core.V1.Permission@Read\";\n};\n\nexport type StringFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.String\";\n $DefaultValue?: \"USER\" | \"USERNAME\" | \"CURRENT_USER\";\n $MaxLength?: number;\n};\n\nexport type DecimalFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Decimal\";\n \"@AutoGenerated\"?: boolean;\n};\n\nexport type DateFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURDATE\" | \"CURRENT_DATE\";\n};\n\nexport type TimeOfDayFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.TimeOfDay\";\n $DefaultValue?: \"CURTIME\" | \"CURRENT_TIME\";\n};\n\nexport type DateTimeOffsetFieldMetadata = GenericFieldMetadata & {\n $Type: \"Edm.Date\";\n $DefaultValue?: \"CURTIMESTAMP\" | \"CURRENT_TIMESTAMP\";\n \"@VersionId\"?: boolean;\n};\n\nexport type StreamFieldMetadata = {\n $Type: \"Edm.Stream\";\n $Nullable?: boolean;\n \"@EnclosedPath\": string;\n \"@ExternalOpenPath\": string;\n \"@ExternalSecurePath\"?: string;\n};\n\nexport type FieldMetadata =\n | StringFieldMetadata\n | DecimalFieldMetadata\n | DateFieldMetadata\n | TimeOfDayFieldMetadata\n | DateTimeOffsetFieldMetadata\n | StreamFieldMetadata;\n\nexport type EntityType = {\n $Kind: \"EntityType\";\n $Key: string[];\n} & Record<string, FieldMetadata>;\n\nexport type EntitySet = {\n $Kind: \"EntitySet\";\n $Type: string;\n};\n\nexport type Metadata = Record<string, EntityType | EntitySet>;\n"],"names":[],"mappings":"AA0KO,SAAS,gBAAgB,yBAA2C;AAClE,SAAA,4BAA4B,OAC/B,qBACA;AACN;"}
@@ -1,13 +1,23 @@
1
1
  import { ODataRecordMetadata } from './types.js';
2
2
  import { StandardSchemaV1 } from '@standard-schema/spec';
3
- import { TableOccurrence } from './client/table-occurrence.js';
3
+ import { FMTable } from './orm/table.js';
4
4
  import { ValidationError, ResponseStructureError, RecordCountMismatchError } from './errors.js';
5
+ /**
6
+ * Validates and transforms input data for insert/update operations.
7
+ * Applies input validators (writeValidators) to transform user input to database format.
8
+ * Fields without input validators are passed through unchanged.
9
+ *
10
+ * @param data - The input data to validate and transform
11
+ * @param inputSchema - Optional schema containing input validators for each field
12
+ * @returns Transformed data ready to send to the server
13
+ * @throws ValidationError if any field fails validation
14
+ */
15
+ export declare function validateAndTransformInput<T extends Record<string, any>>(data: Partial<T>, inputSchema?: Record<string, StandardSchemaV1>): Promise<Partial<T>>;
5
16
  export type ExpandValidationConfig = {
6
17
  relation: string;
7
18
  targetSchema?: Record<string, StandardSchemaV1>;
8
- targetOccurrence?: TableOccurrence<any, any, any, any>;
9
- targetBaseTable?: any;
10
- occurrence?: TableOccurrence<any, any, any, any>;
19
+ targetTable?: FMTable<any, any>;
20
+ table?: FMTable<any, any>;
11
21
  selectedFields?: string[];
12
22
  nestedExpands?: ExpandValidationConfig[];
13
23
  };
@@ -1,4 +1,47 @@
1
- import { RecordCountMismatchError, ResponseStructureError, ValidationError } from "./errors.js";
1
+ import { ValidationError, RecordCountMismatchError, ResponseStructureError } from "./errors.js";
2
+ async function validateAndTransformInput(data, inputSchema) {
3
+ if (!inputSchema) {
4
+ return data;
5
+ }
6
+ const transformedData = { ...data };
7
+ for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) {
8
+ if (fieldName in data) {
9
+ const inputValue = data[fieldName];
10
+ try {
11
+ let result = fieldSchema["~standard"].validate(inputValue);
12
+ if (result instanceof Promise) {
13
+ result = await result;
14
+ }
15
+ if (result.issues) {
16
+ throw new ValidationError(
17
+ `Input validation failed for field '${fieldName}'`,
18
+ result.issues,
19
+ {
20
+ field: fieldName,
21
+ value: inputValue,
22
+ cause: result.issues
23
+ }
24
+ );
25
+ }
26
+ transformedData[fieldName] = result.value;
27
+ } catch (error) {
28
+ if (error instanceof ValidationError) {
29
+ throw error;
30
+ }
31
+ throw new ValidationError(
32
+ `Input validation failed for field '${fieldName}'`,
33
+ [],
34
+ {
35
+ field: fieldName,
36
+ value: inputValue,
37
+ cause: error
38
+ }
39
+ );
40
+ }
41
+ }
42
+ }
43
+ return transformedData;
44
+ }
2
45
  async function validateRecord(record, schema, selectedFields, expandConfigs) {
3
46
  var _a, _b;
4
47
  const { "@id": id, "@editLink": editLink, ...rest } = record;
@@ -333,6 +376,7 @@ async function validateSingleResponse(response, schema, selectedFields, expandCo
333
376
  };
334
377
  }
335
378
  export {
379
+ validateAndTransformInput,
336
380
  validateListResponse,
337
381
  validateRecord,
338
382
  validateSingleResponse
@@ -1 +1 @@
1
- {"version":3,"file":"validation.js","sources":["../../src/validation.ts"],"sourcesContent":["import type { ODataRecordMetadata } from \"./types\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { TableOccurrence } from \"./client/table-occurrence\";\nimport {\n ValidationError,\n ResponseStructureError,\n RecordCountMismatchError,\n} from \"./errors\";\n\n// Type for expand validation configuration\nexport type ExpandValidationConfig = {\n relation: string;\n targetSchema?: Record<string, StandardSchemaV1>;\n targetOccurrence?: TableOccurrence<any, any, any, any>;\n targetBaseTable?: any; // BaseTable instance for transformation\n occurrence?: TableOccurrence<any, any, any, any>; // For transformation\n selectedFields?: string[];\n nestedExpands?: ExpandValidationConfig[];\n};\n\n/**\n * Validates a single record against a schema, only validating selected fields.\n * Also validates expanded relations if expandConfigs are provided.\n */\nexport async function validateRecord<T extends Record<string, any>>(\n record: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n): Promise<\n | { valid: true; data: T & ODataRecordMetadata }\n | { valid: false; error: ValidationError }\n> {\n // Extract OData metadata fields (don't validate them - include if present)\n const { \"@id\": id, \"@editLink\": editLink, ...rest } = record;\n\n // Only include metadata fields if they actually exist and have values\n const metadata: Partial<ODataRecordMetadata> = {};\n if (id) metadata[\"@id\"] = id;\n if (editLink) metadata[\"@editLink\"] = editLink;\n\n // If no schema, just return the data with metadata\n if (!schema) {\n return {\n valid: true,\n data: { ...rest, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Filter out FileMaker system fields that shouldn't be in responses by default\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n\n // If selected fields are specified, validate only those fields\n if (selectedFields && selectedFields.length > 0) {\n const validatedRecord: Record<string, any> = {};\n\n for (const field of selectedFields) {\n const fieldName = String(field);\n const fieldSchema = schema[fieldName];\n\n if (fieldSchema) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) result = await result;\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: input,\n cause: result.issues,\n },\n ),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws directly, wrap it\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: input,\n cause: originalError,\n },\n ),\n };\n }\n } else {\n // For fields not in schema (like when explicitly selecting ROWID/ROWMODID)\n // include them from the original response\n validatedRecord[fieldName] = rest[fieldName];\n }\n }\n\n // Validate expanded relations\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage\n .toLowerCase()\n .includes(expandConfig.relation.toLowerCase()) ||\n (expandConfig.selectedFields &&\n expandConfig.selectedFields.some((field) =>\n errorMessage.toLowerCase().includes(field.toLowerCase()),\n ));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n // Merge validated data with metadata\n return {\n valid: true,\n data: { ...validatedRecord, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Validate all fields in schema, but exclude ROWID/ROWMODID by default\n const validatedRecord: Record<string, any> = { ...restWithoutSystemFields };\n\n for (const [fieldName, fieldSchema] of Object.entries(schema)) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) result = await result;\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: input,\n cause: result.issues,\n },\n ),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws an error directly, catch and wrap it\n // This preserves the original error instance for instanceof checks\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: input,\n cause: originalError,\n },\n ),\n };\n }\n }\n\n // Validate expanded relations even when not using selected fields\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage\n .toLowerCase()\n .includes(expandConfig.relation.toLowerCase()) ||\n (expandConfig.selectedFields &&\n expandConfig.selectedFields.some((field) =>\n errorMessage.toLowerCase().includes(field.toLowerCase()),\n ));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n return {\n valid: true,\n data: { ...validatedRecord, ...metadata } as T & ODataRecordMetadata,\n };\n}\n\n/**\n * Validates a list response against a schema.\n */\nexport async function validateListResponse<T extends Record<string, any>>(\n response: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata)[] }\n | { valid: false; error: ResponseStructureError | ValidationError }\n> {\n // Check if response has the expected structure\n if (!response || typeof response !== \"object\") {\n return {\n valid: false,\n error: new ResponseStructureError(\"an object\", response),\n };\n }\n\n // Extract @context (for internal validation, but we won't return it)\n const { \"@context\": context, value, ...rest } = response;\n\n if (!Array.isArray(value)) {\n return {\n valid: false,\n error: new ResponseStructureError(\n \"'value' property to be an array\",\n value,\n ),\n };\n }\n\n // Validate each record in the array\n const validatedRecords: (T & ODataRecordMetadata)[] = [];\n\n for (let i = 0; i < value.length; i++) {\n const record = value[i];\n const validation = await validateRecord<T>(\n record,\n schema,\n selectedFields,\n expandConfigs,\n );\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n validatedRecords.push(validation.data);\n }\n\n return {\n valid: true,\n data: validatedRecords,\n };\n}\n\n/**\n * Validates a single record response against a schema.\n */\nexport async function validateSingleResponse<T extends Record<string, any>>(\n response: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n mode: \"exact\" | \"maybe\" = \"maybe\",\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata) | null }\n | { valid: false; error: RecordCountMismatchError | ValidationError }\n> {\n // Check for multiple records (error in both modes)\n if (\n response.value &&\n Array.isArray(response.value) &&\n response.value.length > 1\n ) {\n return {\n valid: false,\n error: new RecordCountMismatchError(\n mode === \"exact\" ? \"one\" : \"at-most-one\",\n response.value.length,\n ),\n };\n }\n\n // Handle empty responses\n if (!response || (response.value && response.value.length === 0)) {\n if (mode === \"exact\") {\n return {\n valid: false,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n // mode === \"maybe\" - return null for empty\n return {\n valid: true,\n data: null,\n };\n }\n\n // Single record validation\n const record = response.value?.[0] ?? response;\n const validation = await validateRecord<T>(\n record,\n schema,\n selectedFields,\n expandConfigs,\n );\n\n if (!validation.valid) {\n return validation as { valid: false; error: ValidationError };\n }\n\n return {\n valid: true,\n data: validation.data,\n };\n}\n"],"names":["validatedRecord"],"mappings":"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"}
1
+ {"version":3,"file":"validation.js","sources":["../../src/validation.ts"],"sourcesContent":["import type { ODataRecordMetadata } from \"./types\";\nimport { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { FMTable } from \"./orm/table\";\nimport {\n ValidationError,\n ResponseStructureError,\n RecordCountMismatchError,\n} from \"./errors\";\n\n/**\n * Validates and transforms input data for insert/update operations.\n * Applies input validators (writeValidators) to transform user input to database format.\n * Fields without input validators are passed through unchanged.\n *\n * @param data - The input data to validate and transform\n * @param inputSchema - Optional schema containing input validators for each field\n * @returns Transformed data ready to send to the server\n * @throws ValidationError if any field fails validation\n */\nexport async function validateAndTransformInput<T extends Record<string, any>>(\n data: Partial<T>,\n inputSchema?: Record<string, StandardSchemaV1>,\n): Promise<Partial<T>> {\n // If no input schema, return data as-is\n if (!inputSchema) {\n return data;\n }\n\n const transformedData: Record<string, any> = { ...data };\n\n // Process each field that has an input validator\n for (const [fieldName, fieldSchema] of Object.entries(inputSchema)) {\n // Only process fields that are present in the input data\n if (fieldName in data) {\n const inputValue = data[fieldName];\n\n try {\n // Run the input validator to transform the value\n let result = fieldSchema[\"~standard\"].validate(inputValue);\n if (result instanceof Promise) {\n result = await result;\n }\n\n // Check for validation errors\n if (result.issues) {\n throw new ValidationError(\n `Input validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: inputValue,\n cause: result.issues,\n },\n );\n }\n\n // Store the transformed value\n transformedData[fieldName] = result.value;\n } catch (error) {\n // If it's already a ValidationError, re-throw it\n if (error instanceof ValidationError) {\n throw error;\n }\n\n // Otherwise, wrap the error\n throw new ValidationError(\n `Input validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: inputValue,\n cause: error,\n },\n );\n }\n }\n }\n\n // Fields without input validators are already in transformedData (passed through)\n return transformedData as Partial<T>;\n}\n\n// Type for expand validation configuration\nexport type ExpandValidationConfig = {\n relation: string;\n targetSchema?: Record<string, StandardSchemaV1>;\n targetTable?: FMTable<any, any>;\n table?: FMTable<any, any>; // For transformation\n selectedFields?: string[];\n nestedExpands?: ExpandValidationConfig[];\n};\n\n/**\n * Validates a single record against a schema, only validating selected fields.\n * Also validates expanded relations if expandConfigs are provided.\n */\nexport async function validateRecord<T extends Record<string, any>>(\n record: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n): Promise<\n | { valid: true; data: T & ODataRecordMetadata }\n | { valid: false; error: ValidationError }\n> {\n // Extract OData metadata fields (don't validate them - include if present)\n const { \"@id\": id, \"@editLink\": editLink, ...rest } = record;\n\n // Only include metadata fields if they actually exist and have values\n const metadata: Partial<ODataRecordMetadata> = {};\n if (id) metadata[\"@id\"] = id;\n if (editLink) metadata[\"@editLink\"] = editLink;\n\n // If no schema, just return the data with metadata\n if (!schema) {\n return {\n valid: true,\n data: { ...rest, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Filter out FileMaker system fields that shouldn't be in responses by default\n const { ROWID, ROWMODID, ...restWithoutSystemFields } = rest;\n\n // If selected fields are specified, validate only those fields\n if (selectedFields && selectedFields.length > 0) {\n const validatedRecord: Record<string, any> = {};\n\n for (const field of selectedFields) {\n const fieldName = String(field);\n const fieldSchema = schema[fieldName];\n\n if (fieldSchema) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) result = await result;\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: input,\n cause: result.issues,\n },\n ),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws directly, wrap it\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: input,\n cause: originalError,\n },\n ),\n };\n }\n } else {\n // For fields not in schema (like when explicitly selecting ROWID/ROWMODID)\n // include them from the original response\n validatedRecord[fieldName] = rest[fieldName];\n }\n }\n\n // Validate expanded relations\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage\n .toLowerCase()\n .includes(expandConfig.relation.toLowerCase()) ||\n (expandConfig.selectedFields &&\n expandConfig.selectedFields.some((field) =>\n errorMessage.toLowerCase().includes(field.toLowerCase()),\n ));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n // Merge validated data with metadata\n return {\n valid: true,\n data: { ...validatedRecord, ...metadata } as T & ODataRecordMetadata,\n };\n }\n\n // Validate all fields in schema, but exclude ROWID/ROWMODID by default\n const validatedRecord: Record<string, any> = { ...restWithoutSystemFields };\n\n for (const [fieldName, fieldSchema] of Object.entries(schema)) {\n const input = rest[fieldName];\n try {\n let result = fieldSchema[\"~standard\"].validate(input);\n if (result instanceof Promise) result = await result;\n\n // if the `issues` field exists, the validation failed\n if (result.issues) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n result.issues,\n {\n field: fieldName,\n value: input,\n cause: result.issues,\n },\n ),\n };\n }\n\n validatedRecord[fieldName] = result.value;\n } catch (originalError) {\n // If the validator throws an error directly, catch and wrap it\n // This preserves the original error instance for instanceof checks\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for field '${fieldName}'`,\n [],\n {\n field: fieldName,\n value: input,\n cause: originalError,\n },\n ),\n };\n }\n }\n\n // Validate expanded relations even when not using selected fields\n if (expandConfigs && expandConfigs.length > 0) {\n for (const expandConfig of expandConfigs) {\n const expandValue = rest[expandConfig.relation];\n\n // Check if expand field is missing\n if (expandValue === undefined) {\n // Check for inline error array (FileMaker returns errors inline when expand fails)\n if (Array.isArray(rest.error) && rest.error.length > 0) {\n // Extract error message from inline error\n const errorDetail = rest.error[0]?.error;\n if (errorDetail?.message) {\n const errorMessage = errorDetail.message;\n // Check if the error is related to this expand by checking if:\n // 1. The error mentions the relation name, OR\n // 2. The error mentions any of the selected fields\n const isRelatedToExpand =\n errorMessage\n .toLowerCase()\n .includes(expandConfig.relation.toLowerCase()) ||\n (expandConfig.selectedFields &&\n expandConfig.selectedFields.some((field) =>\n errorMessage.toLowerCase().includes(field.toLowerCase()),\n ));\n\n if (isRelatedToExpand) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${errorMessage}`,\n [],\n {\n field: expandConfig.relation,\n },\n ),\n };\n }\n }\n }\n // If no inline error but expand was expected, that's also an issue\n // However, this might be a legitimate case (e.g., no related records)\n // So we'll only fail if there's an explicit error array\n } else {\n // Original validation logic for when expand exists\n if (Array.isArray(expandValue)) {\n // Validate each item in the expanded array\n const validatedExpandedItems: any[] = [];\n for (let i = 0; i < expandValue.length; i++) {\n const item = expandValue[i];\n const itemValidation = await validateRecord(\n item,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}' at index ${i}: ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedExpandedItems.push(itemValidation.data);\n }\n validatedRecord[expandConfig.relation] = validatedExpandedItems;\n } else {\n // Single expanded item (shouldn't happen in OData, but handle it)\n const itemValidation = await validateRecord(\n expandValue,\n expandConfig.targetSchema,\n expandConfig.selectedFields as string[] | undefined,\n expandConfig.nestedExpands,\n );\n if (!itemValidation.valid) {\n return {\n valid: false,\n error: new ValidationError(\n `Validation failed for expanded relation '${expandConfig.relation}': ${itemValidation.error.message}`,\n itemValidation.error.issues,\n {\n field: expandConfig.relation,\n cause: itemValidation.error.cause,\n },\n ),\n };\n }\n validatedRecord[expandConfig.relation] = itemValidation.data;\n }\n }\n }\n }\n\n return {\n valid: true,\n data: { ...validatedRecord, ...metadata } as T & ODataRecordMetadata,\n };\n}\n\n/**\n * Validates a list response against a schema.\n */\nexport async function validateListResponse<T extends Record<string, any>>(\n response: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata)[] }\n | { valid: false; error: ResponseStructureError | ValidationError }\n> {\n // Check if response has the expected structure\n if (!response || typeof response !== \"object\") {\n return {\n valid: false,\n error: new ResponseStructureError(\"an object\", response),\n };\n }\n\n // Extract @context (for internal validation, but we won't return it)\n const { \"@context\": context, value, ...rest } = response;\n\n if (!Array.isArray(value)) {\n return {\n valid: false,\n error: new ResponseStructureError(\n \"'value' property to be an array\",\n value,\n ),\n };\n }\n\n // Validate each record in the array\n const validatedRecords: (T & ODataRecordMetadata)[] = [];\n\n for (let i = 0; i < value.length; i++) {\n const record = value[i];\n const validation = await validateRecord<T>(\n record,\n schema,\n selectedFields,\n expandConfigs,\n );\n\n if (!validation.valid) {\n return {\n valid: false,\n error: validation.error,\n };\n }\n\n validatedRecords.push(validation.data);\n }\n\n return {\n valid: true,\n data: validatedRecords,\n };\n}\n\n/**\n * Validates a single record response against a schema.\n */\nexport async function validateSingleResponse<T extends Record<string, any>>(\n response: any,\n schema: Record<string, StandardSchemaV1> | undefined,\n selectedFields?: (keyof T)[],\n expandConfigs?: ExpandValidationConfig[],\n mode: \"exact\" | \"maybe\" = \"maybe\",\n): Promise<\n | { valid: true; data: (T & ODataRecordMetadata) | null }\n | { valid: false; error: RecordCountMismatchError | ValidationError }\n> {\n // Check for multiple records (error in both modes)\n if (\n response.value &&\n Array.isArray(response.value) &&\n response.value.length > 1\n ) {\n return {\n valid: false,\n error: new RecordCountMismatchError(\n mode === \"exact\" ? \"one\" : \"at-most-one\",\n response.value.length,\n ),\n };\n }\n\n // Handle empty responses\n if (!response || (response.value && response.value.length === 0)) {\n if (mode === \"exact\") {\n return {\n valid: false,\n error: new RecordCountMismatchError(\"one\", 0),\n };\n }\n // mode === \"maybe\" - return null for empty\n return {\n valid: true,\n data: null,\n };\n }\n\n // Single record validation\n const record = response.value?.[0] ?? response;\n const validation = await validateRecord<T>(\n record,\n schema,\n selectedFields,\n expandConfigs,\n );\n\n if (!validation.valid) {\n return validation as { valid: false; error: ValidationError };\n }\n\n return {\n valid: true,\n data: validation.data,\n };\n}\n"],"names":["validatedRecord"],"mappings":"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"}