@proofkit/fmodata 0.1.0-alpha.9 → 0.1.0-beta.23

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 (163) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +651 -449
  3. package/dist/esm/client/batch-builder.d.ts +10 -9
  4. package/dist/esm/client/batch-builder.js +119 -56
  5. package/dist/esm/client/batch-builder.js.map +1 -1
  6. package/dist/esm/client/batch-request.js +16 -21
  7. package/dist/esm/client/batch-request.js.map +1 -1
  8. package/dist/esm/client/builders/default-select.d.ts +10 -0
  9. package/dist/esm/client/builders/default-select.js +41 -0
  10. package/dist/esm/client/builders/default-select.js.map +1 -0
  11. package/dist/esm/client/builders/expand-builder.d.ts +45 -0
  12. package/dist/esm/client/builders/expand-builder.js +185 -0
  13. package/dist/esm/client/builders/expand-builder.js.map +1 -0
  14. package/dist/esm/client/builders/index.d.ts +9 -0
  15. package/dist/esm/client/builders/query-string-builder.d.ts +18 -0
  16. package/dist/esm/client/builders/query-string-builder.js +21 -0
  17. package/dist/esm/client/builders/query-string-builder.js.map +1 -0
  18. package/dist/esm/client/builders/response-processor.d.ts +43 -0
  19. package/dist/esm/client/builders/response-processor.js +175 -0
  20. package/dist/esm/client/builders/response-processor.js.map +1 -0
  21. package/dist/esm/client/builders/select-mixin.d.ts +25 -0
  22. package/dist/esm/client/builders/select-mixin.js +28 -0
  23. package/dist/esm/client/builders/select-mixin.js.map +1 -0
  24. package/dist/esm/client/builders/select-utils.d.ts +18 -0
  25. package/dist/esm/client/builders/select-utils.js +30 -0
  26. package/dist/esm/client/builders/select-utils.js.map +1 -0
  27. package/dist/esm/client/builders/shared-types.d.ts +40 -0
  28. package/dist/esm/client/builders/table-utils.d.ts +35 -0
  29. package/dist/esm/client/builders/table-utils.js +44 -0
  30. package/dist/esm/client/builders/table-utils.js.map +1 -0
  31. package/dist/esm/client/database.d.ts +34 -22
  32. package/dist/esm/client/database.js +48 -84
  33. package/dist/esm/client/database.js.map +1 -1
  34. package/dist/esm/client/delete-builder.d.ts +25 -30
  35. package/dist/esm/client/delete-builder.js +45 -30
  36. package/dist/esm/client/delete-builder.js.map +1 -1
  37. package/dist/esm/client/entity-set.d.ts +35 -43
  38. package/dist/esm/client/entity-set.js +110 -52
  39. package/dist/esm/client/entity-set.js.map +1 -1
  40. package/dist/esm/client/error-parser.d.ts +12 -0
  41. package/dist/esm/client/error-parser.js +25 -0
  42. package/dist/esm/client/error-parser.js.map +1 -0
  43. package/dist/esm/client/filemaker-odata.d.ts +26 -7
  44. package/dist/esm/client/filemaker-odata.js +65 -42
  45. package/dist/esm/client/filemaker-odata.js.map +1 -1
  46. package/dist/esm/client/insert-builder.d.ts +19 -24
  47. package/dist/esm/client/insert-builder.js +94 -58
  48. package/dist/esm/client/insert-builder.js.map +1 -1
  49. package/dist/esm/client/query/expand-builder.d.ts +35 -0
  50. package/dist/esm/client/query/index.d.ts +4 -0
  51. package/dist/esm/client/query/query-builder.d.ts +132 -0
  52. package/dist/esm/client/query/query-builder.js +456 -0
  53. package/dist/esm/client/query/query-builder.js.map +1 -0
  54. package/dist/esm/client/query/response-processor.d.ts +25 -0
  55. package/dist/esm/client/query/types.d.ts +77 -0
  56. package/dist/esm/client/query/url-builder.d.ts +71 -0
  57. package/dist/esm/client/query/url-builder.js +100 -0
  58. package/dist/esm/client/query/url-builder.js.map +1 -0
  59. package/dist/esm/client/query-builder.d.ts +2 -115
  60. package/dist/esm/client/record-builder.d.ts +108 -36
  61. package/dist/esm/client/record-builder.js +284 -119
  62. package/dist/esm/client/record-builder.js.map +1 -1
  63. package/dist/esm/client/response-processor.d.ts +4 -9
  64. package/dist/esm/client/sanitize-json.d.ts +35 -0
  65. package/dist/esm/client/sanitize-json.js +27 -0
  66. package/dist/esm/client/sanitize-json.js.map +1 -0
  67. package/dist/esm/client/schema-manager.d.ts +5 -5
  68. package/dist/esm/client/schema-manager.js +45 -31
  69. package/dist/esm/client/schema-manager.js.map +1 -1
  70. package/dist/esm/client/update-builder.d.ts +34 -40
  71. package/dist/esm/client/update-builder.js +99 -58
  72. package/dist/esm/client/update-builder.js.map +1 -1
  73. package/dist/esm/client/webhook-builder.d.ts +126 -0
  74. package/dist/esm/client/webhook-builder.js +189 -0
  75. package/dist/esm/client/webhook-builder.js.map +1 -0
  76. package/dist/esm/errors.d.ts +19 -2
  77. package/dist/esm/errors.js +39 -4
  78. package/dist/esm/errors.js.map +1 -1
  79. package/dist/esm/index.d.ts +10 -8
  80. package/dist/esm/index.js +40 -10
  81. package/dist/esm/index.js.map +1 -1
  82. package/dist/esm/logger.d.ts +47 -0
  83. package/dist/esm/logger.js +69 -0
  84. package/dist/esm/logger.js.map +1 -0
  85. package/dist/esm/logger.test.d.ts +1 -0
  86. package/dist/esm/orm/column.d.ts +62 -0
  87. package/dist/esm/orm/column.js +63 -0
  88. package/dist/esm/orm/column.js.map +1 -0
  89. package/dist/esm/orm/field-builders.d.ts +164 -0
  90. package/dist/esm/orm/field-builders.js +158 -0
  91. package/dist/esm/orm/field-builders.js.map +1 -0
  92. package/dist/esm/orm/index.d.ts +5 -0
  93. package/dist/esm/orm/operators.d.ts +173 -0
  94. package/dist/esm/orm/operators.js +260 -0
  95. package/dist/esm/orm/operators.js.map +1 -0
  96. package/dist/esm/orm/table.d.ts +355 -0
  97. package/dist/esm/orm/table.js +202 -0
  98. package/dist/esm/orm/table.js.map +1 -0
  99. package/dist/esm/transform.d.ts +20 -21
  100. package/dist/esm/transform.js +44 -45
  101. package/dist/esm/transform.js.map +1 -1
  102. package/dist/esm/types.d.ts +96 -30
  103. package/dist/esm/types.js +7 -0
  104. package/dist/esm/types.js.map +1 -0
  105. package/dist/esm/validation.d.ts +22 -12
  106. package/dist/esm/validation.js +132 -85
  107. package/dist/esm/validation.js.map +1 -1
  108. package/package.json +28 -20
  109. package/src/client/batch-builder.ts +153 -89
  110. package/src/client/batch-request.ts +25 -41
  111. package/src/client/builders/default-select.ts +75 -0
  112. package/src/client/builders/expand-builder.ts +246 -0
  113. package/src/client/builders/index.ts +11 -0
  114. package/src/client/builders/query-string-builder.ts +46 -0
  115. package/src/client/builders/response-processor.ts +279 -0
  116. package/src/client/builders/select-mixin.ts +65 -0
  117. package/src/client/builders/select-utils.ts +59 -0
  118. package/src/client/builders/shared-types.ts +45 -0
  119. package/src/client/builders/table-utils.ts +83 -0
  120. package/src/client/database.ts +89 -183
  121. package/src/client/delete-builder.ts +74 -84
  122. package/src/client/entity-set.ts +266 -293
  123. package/src/client/error-parser.ts +41 -0
  124. package/src/client/filemaker-odata.ts +98 -66
  125. package/src/client/insert-builder.ts +157 -118
  126. package/src/client/query/expand-builder.ts +160 -0
  127. package/src/client/query/index.ts +14 -0
  128. package/src/client/query/query-builder.ts +729 -0
  129. package/src/client/query/response-processor.ts +226 -0
  130. package/src/client/query/types.ts +126 -0
  131. package/src/client/query/url-builder.ts +151 -0
  132. package/src/client/query-builder.ts +10 -1455
  133. package/src/client/record-builder.ts +575 -240
  134. package/src/client/response-processor.ts +15 -42
  135. package/src/client/sanitize-json.ts +64 -0
  136. package/src/client/schema-manager.ts +61 -76
  137. package/src/client/update-builder.ts +161 -143
  138. package/src/client/webhook-builder.ts +265 -0
  139. package/src/errors.ts +49 -16
  140. package/src/index.ts +99 -54
  141. package/src/logger.test.ts +34 -0
  142. package/src/logger.ts +116 -0
  143. package/src/orm/column.ts +106 -0
  144. package/src/orm/field-builders.ts +250 -0
  145. package/src/orm/index.ts +61 -0
  146. package/src/orm/operators.ts +473 -0
  147. package/src/orm/table.ts +741 -0
  148. package/src/transform.ts +90 -70
  149. package/src/types.ts +154 -113
  150. package/src/validation.ts +200 -115
  151. package/dist/esm/client/base-table.d.ts +0 -125
  152. package/dist/esm/client/base-table.js +0 -57
  153. package/dist/esm/client/base-table.js.map +0 -1
  154. package/dist/esm/client/query-builder.js +0 -896
  155. package/dist/esm/client/query-builder.js.map +0 -1
  156. package/dist/esm/client/table-occurrence.d.ts +0 -72
  157. package/dist/esm/client/table-occurrence.js +0 -74
  158. package/dist/esm/client/table-occurrence.js.map +0 -1
  159. package/dist/esm/filter-types.d.ts +0 -76
  160. package/src/client/base-table.ts +0 -175
  161. package/src/client/query-builder.ts.bak +0 -1457
  162. package/src/client/table-occurrence.ts +0 -175
  163. package/src/filter-types.ts +0 -97
package/src/logger.ts ADDED
@@ -0,0 +1,116 @@
1
+ export const TTY_COLORS = {
2
+ reset: "\x1b[0m",
3
+ bright: "\x1b[1m",
4
+ dim: "\x1b[2m",
5
+ undim: "\x1b[22m",
6
+ underscore: "\x1b[4m",
7
+ blink: "\x1b[5m",
8
+ reverse: "\x1b[7m",
9
+ hidden: "\x1b[8m",
10
+ fg: {
11
+ black: "\x1b[30m",
12
+ red: "\x1b[31m",
13
+ green: "\x1b[32m",
14
+ yellow: "\x1b[33m",
15
+ blue: "\x1b[34m",
16
+ magenta: "\x1b[35m",
17
+ cyan: "\x1b[36m",
18
+ white: "\x1b[37m",
19
+ },
20
+ bg: {
21
+ black: "\x1b[40m",
22
+ red: "\x1b[41m",
23
+ green: "\x1b[42m",
24
+ yellow: "\x1b[43m",
25
+ blue: "\x1b[44m",
26
+ magenta: "\x1b[45m",
27
+ cyan: "\x1b[46m",
28
+ white: "\x1b[47m",
29
+ },
30
+ } as const;
31
+
32
+ export type LogLevel = "debug" | "info" | "success" | "warn" | "error";
33
+
34
+ export const levels = ["debug", "info", "success", "warn", "error"] as const;
35
+
36
+ export function shouldPublishLog(currentLogLevel: LogLevel, logLevel: LogLevel): boolean {
37
+ return levels.indexOf(logLevel) >= levels.indexOf(currentLogLevel);
38
+ }
39
+
40
+ export interface Logger {
41
+ disabled?: boolean | undefined;
42
+ disableColors?: boolean | undefined;
43
+ level?: Exclude<LogLevel, "success"> | undefined;
44
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic log arguments from user code
45
+ log?: ((level: Exclude<LogLevel, "success">, message: string, ...args: any[]) => void) | undefined;
46
+ }
47
+
48
+ export type LogHandlerParams = Parameters<NonNullable<Logger["log"]>> extends [LogLevel, ...infer Rest] ? Rest : never;
49
+
50
+ const levelColors: Record<LogLevel, string> = {
51
+ info: TTY_COLORS.fg.blue,
52
+ success: TTY_COLORS.fg.green,
53
+ warn: TTY_COLORS.fg.yellow,
54
+ error: TTY_COLORS.fg.red,
55
+ debug: TTY_COLORS.fg.magenta,
56
+ };
57
+
58
+ const formatMessage = (level: LogLevel, message: string, colorsEnabled: boolean): string => {
59
+ const timestamp = new Date().toISOString();
60
+
61
+ if (colorsEnabled) {
62
+ return `${TTY_COLORS.dim}${timestamp}${TTY_COLORS.reset} ${
63
+ levelColors[level]
64
+ }${level.toUpperCase()}${TTY_COLORS.reset} ${TTY_COLORS.bright}[FMODATA]:${TTY_COLORS.reset} ${message}`;
65
+ }
66
+
67
+ return `${timestamp} ${level.toUpperCase()} [FMODATA]: ${message}`;
68
+ };
69
+
70
+ export type InternalLogger = {
71
+ [K in LogLevel]: (...params: LogHandlerParams) => void;
72
+ } & {
73
+ get level(): LogLevel;
74
+ };
75
+
76
+ export const createLogger = (options?: Logger | undefined): InternalLogger => {
77
+ const enabled = options?.disabled !== true;
78
+ const logLevel = options?.level ?? "error";
79
+
80
+ const colorsEnabled = options?.disableColors !== true;
81
+
82
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic log arguments from user code
83
+ const LogFunc = (level: LogLevel, message: string, args: any[] = []): void => {
84
+ if (!(enabled && shouldPublishLog(logLevel, level))) {
85
+ return;
86
+ }
87
+
88
+ const formattedMessage = formatMessage(level, message, colorsEnabled);
89
+
90
+ if (!options || typeof options.log !== "function") {
91
+ if (level === "error") {
92
+ console.error(formattedMessage, ...args);
93
+ } else if (level === "warn") {
94
+ console.warn(formattedMessage, ...args);
95
+ } else {
96
+ console.log(formattedMessage, ...args);
97
+ }
98
+ return;
99
+ }
100
+
101
+ options.log(level === "success" ? "info" : level, message, ...args);
102
+ };
103
+
104
+ const logger = Object.fromEntries(
105
+ levels.map((level) => [level, (...[message, ...args]: LogHandlerParams) => LogFunc(level, message, args)]),
106
+ ) as Record<LogLevel, (...params: LogHandlerParams) => void>;
107
+
108
+ return {
109
+ ...logger,
110
+ get level() {
111
+ return logLevel;
112
+ },
113
+ };
114
+ };
115
+
116
+ export const logger = createLogger();
@@ -0,0 +1,106 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ /**
4
+ * Column represents a type-safe reference to a table field.
5
+ * Used in queries, filters, and operators to provide autocomplete and type checking.
6
+ *
7
+ * @template TOutput - The TypeScript type when reading from the database (output type)
8
+ * @template TInput - The TypeScript type when writing to the database (input type, for filters)
9
+ * @template TableName - The table name as a string literal type (for validation)
10
+ * @template IsContainer - Whether this column represents a container field (cannot be selected)
11
+ */
12
+ export class Column<
13
+ // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility
14
+ TOutput = any,
15
+ TInput = TOutput,
16
+ TableName extends string = string,
17
+ IsContainer extends boolean = false,
18
+ > {
19
+ readonly fieldName: string;
20
+ readonly entityId?: `FMFID:${string}`;
21
+ readonly tableName: TableName;
22
+ readonly tableEntityId?: `FMTID:${string}`;
23
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
24
+ readonly inputValidator?: StandardSchemaV1<TInput, any>;
25
+
26
+ // Phantom types for TypeScript inference - never actually hold values
27
+ readonly _phantomOutput!: TOutput;
28
+ readonly _phantomInput!: TInput;
29
+ readonly _isContainer!: IsContainer;
30
+
31
+ constructor(config: {
32
+ fieldName: string;
33
+ entityId?: `FMFID:${string}`;
34
+ tableName: TableName;
35
+ tableEntityId?: `FMTID:${string}`;
36
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
37
+ inputValidator?: StandardSchemaV1<TInput, any>;
38
+ }) {
39
+ this.fieldName = config.fieldName;
40
+ this.entityId = config.entityId;
41
+ this.tableName = config.tableName;
42
+ this.tableEntityId = config.tableEntityId;
43
+ this.inputValidator = config.inputValidator;
44
+ }
45
+
46
+ /**
47
+ * Get the field identifier (entity ID if available, otherwise field name).
48
+ * Used when building OData queries.
49
+ */
50
+ getFieldIdentifier(useEntityIds?: boolean): string {
51
+ if (useEntityIds && this.entityId) {
52
+ return this.entityId;
53
+ }
54
+ return this.fieldName;
55
+ }
56
+
57
+ /**
58
+ * Get the table identifier (entity ID if available, otherwise table name).
59
+ * Used when building OData queries.
60
+ */
61
+ getTableIdentifier(useEntityIds?: boolean): string {
62
+ if (useEntityIds && this.tableEntityId) {
63
+ return this.tableEntityId;
64
+ }
65
+ return this.tableName;
66
+ }
67
+
68
+ /**
69
+ * Check if this column is from a specific table.
70
+ * Useful for validation in cross-table operations.
71
+ */
72
+ isFromTable(tableName: string): boolean {
73
+ return this.tableName === tableName;
74
+ }
75
+
76
+ /**
77
+ * Create a string representation for debugging.
78
+ */
79
+ toString(): string {
80
+ return `${this.tableName}.${this.fieldName}`;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Type guard to check if a value is a Column instance.
86
+ */
87
+ // biome-ignore lint/suspicious/noExplicitAny: Type guard accepting any value type, generic constraint accepting any Column configuration
88
+ export function isColumn(value: any): value is Column<any, any, any, any> {
89
+ return value instanceof Column;
90
+ }
91
+
92
+ /**
93
+ * Create a Column with proper type inference from the inputValidator.
94
+ * This helper ensures TypeScript can infer TInput from the validator's input type.
95
+ * @internal
96
+ */
97
+ export function createColumn<TOutput, TInput, TName extends string, IsContainer extends boolean = false>(config: {
98
+ fieldName: string;
99
+ entityId?: `FMFID:${string}`;
100
+ tableName: TName;
101
+ tableEntityId?: `FMTID:${string}`;
102
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
103
+ inputValidator?: StandardSchemaV1<TInput, any>;
104
+ }): Column<TOutput, TInput, TName, IsContainer> {
105
+ return new Column(config) as Column<TOutput, TInput, TName, IsContainer>;
106
+ }
@@ -0,0 +1,250 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ /**
4
+ * Branded type for container field's database type.
5
+ * This allows TypeScript to distinguish container fields from regular string fields
6
+ * at the type level, enabling compile-time exclusion from select operations.
7
+ */
8
+ export type ContainerDbType = string & { readonly __container: true };
9
+
10
+ /**
11
+ * FieldBuilder provides a fluent API for defining table fields with type-safe metadata.
12
+ * Supports chaining methods to configure primary keys, nullability, read-only status, entity IDs, and validators.
13
+ *
14
+ * @template TOutput - The output type after applying outputValidator (what you get when reading)
15
+ * @template TInput - The input type after applying inputValidator (what you pass when writing)
16
+ * @template TDbType - The database type (what FileMaker stores/expects)
17
+ * @template TReadOnly - Whether this field is read-only (for type-level exclusion from insert/update)
18
+ */
19
+ // biome-ignore lint/suspicious/noExplicitAny: Default type parameter for flexibility
20
+ export class FieldBuilder<TOutput = any, TInput = TOutput, TDbType = TOutput, TReadOnly extends boolean = false> {
21
+ private _primaryKey = false;
22
+ private _notNull = false;
23
+ private _readOnly = false;
24
+ private _entityId?: `FMFID:${string}`;
25
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
26
+ private _outputValidator?: StandardSchemaV1<any, TOutput>;
27
+ // biome-ignore lint/suspicious/noExplicitAny: Required for type inference with infer
28
+ private _inputValidator?: StandardSchemaV1<TInput, any>;
29
+ private readonly _fieldType: string;
30
+ private _comment?: string;
31
+
32
+ constructor(fieldType: string) {
33
+ this._fieldType = fieldType;
34
+ }
35
+
36
+ /**
37
+ * Mark this field as the primary key for the table.
38
+ * Primary keys are automatically read-only and non-nullable.
39
+ */
40
+ primaryKey(): FieldBuilder<NonNullable<TOutput>, NonNullable<TInput>, NonNullable<TDbType>, true> {
41
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation
42
+ const builder = this._clone() as any;
43
+ builder._primaryKey = true;
44
+ builder._notNull = true; // Primary keys are automatically non-nullable
45
+ builder._readOnly = true; // Primary keys are automatically read-only
46
+ return builder;
47
+ }
48
+
49
+ /**
50
+ * Mark this field as non-nullable.
51
+ * Updates the type to exclude null/undefined.
52
+ */
53
+ notNull(): FieldBuilder<NonNullable<TOutput>, NonNullable<TInput>, NonNullable<TDbType>, TReadOnly> {
54
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation
55
+ const builder = this._clone() as any;
56
+ builder._notNull = true;
57
+ return builder;
58
+ }
59
+
60
+ /**
61
+ * Mark this field as read-only.
62
+ * Read-only fields are excluded from insert and update operations.
63
+ */
64
+ readOnly(): FieldBuilder<TOutput, TInput, TDbType, true> {
65
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation
66
+ const builder = this._clone() as any;
67
+ builder._readOnly = true;
68
+ return builder;
69
+ }
70
+
71
+ /**
72
+ * Assign a FileMaker field ID (FMFID) to this field.
73
+ * When useEntityIds is enabled, this ID will be used in API requests instead of the field name.
74
+ */
75
+ entityId(id: `FMFID:${string}`): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {
76
+ const builder = this._clone();
77
+ builder._entityId = id;
78
+ return builder;
79
+ }
80
+
81
+ /**
82
+ * Set a validator for the output (reading from database).
83
+ * The output validator transforms/validates data coming FROM the database in list or get operations.
84
+ *
85
+ * @example
86
+ * numberField().readValidator(z.coerce.boolean())
87
+ * // FileMaker returns 0/1, you get true/false
88
+ */
89
+ readValidator<O, VInput = TDbType>(
90
+ validator: StandardSchemaV1<VInput, O>,
91
+ ): FieldBuilder<O, TInput, TDbType, TReadOnly> {
92
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation
93
+ const builder = this._clone() as any;
94
+ builder._outputValidator = validator;
95
+ return builder;
96
+ }
97
+
98
+ /**
99
+ * Set a validator for the input (writing to database).
100
+ * The input validator transforms/validates data going TO the database in insert, update, and filter operations.
101
+ *
102
+ * @example
103
+ * numberField().writeValidator(z.boolean().transform(v => v ? 1 : 0))
104
+ * // You pass true/false, FileMaker gets 1/0
105
+ */
106
+ writeValidator<I>(validator: StandardSchemaV1<I, TDbType>): FieldBuilder<TOutput, I, TDbType, TReadOnly> {
107
+ // biome-ignore lint/suspicious/noExplicitAny: Type assertion for internal class mutation
108
+ const builder = this._clone() as any;
109
+ builder._inputValidator = validator;
110
+ return builder;
111
+ }
112
+
113
+ /**
114
+ * Add a comment to this field for metadata purposes.
115
+ * This helps future developers understand the purpose of the field.
116
+ *
117
+ * @example
118
+ * textField().comment("Account name of the user who last modified each record")
119
+ */
120
+ comment(comment: string): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {
121
+ const builder = this._clone();
122
+ builder._comment = comment;
123
+ return builder;
124
+ }
125
+
126
+ /**
127
+ * Get the metadata configuration for this field.
128
+ * @internal Used by fmTableOccurrence to extract field configuration
129
+ */
130
+ _getConfig() {
131
+ return {
132
+ fieldType: this._fieldType,
133
+ primaryKey: this._primaryKey,
134
+ notNull: this._notNull,
135
+ readOnly: this._readOnly,
136
+ entityId: this._entityId,
137
+ outputValidator: this._outputValidator,
138
+ inputValidator: this._inputValidator,
139
+ comment: this._comment,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Clone this builder to allow immutable chaining.
145
+ * @private
146
+ */
147
+ private _clone(): FieldBuilder<TOutput, TInput, TDbType, TReadOnly> {
148
+ const builder = new FieldBuilder<TOutput, TInput, TDbType, TReadOnly>(this._fieldType);
149
+ builder._primaryKey = this._primaryKey;
150
+ builder._notNull = this._notNull;
151
+ builder._readOnly = this._readOnly;
152
+ builder._entityId = this._entityId;
153
+ builder._outputValidator = this._outputValidator;
154
+ builder._inputValidator = this._inputValidator;
155
+ builder._comment = this._comment;
156
+ return builder;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Create a text field (Edm.String in FileMaker OData).
162
+ * By default, text fields are nullable.
163
+ *
164
+ * @example
165
+ * textField() // string | null
166
+ * textField().notNull() // string
167
+ * textField().entityId("FMFID:1") // with entity ID
168
+ */
169
+ export function textField(): FieldBuilder<string | null, string | null, string | null, false> {
170
+ return new FieldBuilder<string | null, string | null, string | null, false>("text");
171
+ }
172
+
173
+ /**
174
+ * Create a number field (Edm.Decimal in FileMaker OData).
175
+ * By default, number fields are nullable.
176
+ *
177
+ * @example
178
+ * numberField() // number | null
179
+ * numberField().notNull() // number
180
+ * numberField().outputValidator(z.coerce.boolean()) // transform to boolean on read
181
+ */
182
+ export function numberField(): FieldBuilder<number | null, number | null, number | null, false> {
183
+ return new FieldBuilder<number | null, number | null, number | null, false>("number");
184
+ }
185
+
186
+ /**
187
+ * Create a date field (Edm.Date in FileMaker OData).
188
+ * By default, date fields are nullable and represented as ISO date strings (YYYY-MM-DD).
189
+ *
190
+ * @example
191
+ * dateField() // string | null (ISO date format)
192
+ * dateField().notNull() // string
193
+ */
194
+ export function dateField(): FieldBuilder<string | null, string | null, string | null, false> {
195
+ return new FieldBuilder<string | null, string | null, string | null, false>("date");
196
+ }
197
+
198
+ /**
199
+ * Create a time field (Edm.TimeOfDay in FileMaker OData).
200
+ * By default, time fields are nullable and represented as ISO time strings (HH:mm:ss).
201
+ *
202
+ * @example
203
+ * timeField() // string | null (ISO time format)
204
+ * timeField().notNull() // string
205
+ */
206
+ export function timeField(): FieldBuilder<string | null, string | null, string | null, false> {
207
+ return new FieldBuilder<string | null, string | null, string | null, false>("time");
208
+ }
209
+
210
+ /**
211
+ * Create a timestamp field (Edm.DateTimeOffset in FileMaker OData).
212
+ * By default, timestamp fields are nullable and represented as ISO 8601 strings.
213
+ *
214
+ * @example
215
+ * timestampField() // string | null (ISO 8601 format)
216
+ * timestampField().notNull() // string
217
+ * timestampField().readOnly() // typical for CreationTimestamp
218
+ */
219
+ export function timestampField(): FieldBuilder<string | null, string | null, string | null, false> {
220
+ return new FieldBuilder<string | null, string | null, string | null, false>("timestamp");
221
+ }
222
+
223
+ /**
224
+ * Create a container field (Edm.Stream in FileMaker OData).
225
+ * Container fields store binary data and are represented as base64 strings in the API.
226
+ * By default, container fields are nullable.
227
+ *
228
+ * Note: Container fields cannot be selected via .select() - they can only be accessed
229
+ * via .getSingleField() due to FileMaker OData API limitations.
230
+ *
231
+ * @example
232
+ * containerField() // string | null (base64 encoded)
233
+ * containerField().notNull() // string
234
+ */
235
+ export function containerField(): FieldBuilder<string | null, string | null, ContainerDbType | null, false> {
236
+ return new FieldBuilder<string | null, string | null, ContainerDbType | null, false>("container");
237
+ }
238
+
239
+ /**
240
+ * Create a calculated field (read-only field computed by FileMaker).
241
+ * Calculated fields are automatically marked as read-only.
242
+ *
243
+ * @example
244
+ * calcField() // string | null
245
+ * calcField().notNull() // string
246
+ */
247
+ export function calcField(): FieldBuilder<string | null, string | null, string | null, true> {
248
+ const builder = new FieldBuilder<string | null, string | null, string | null, false>("calculated");
249
+ return builder.readOnly();
250
+ }
@@ -0,0 +1,61 @@
1
+ /** biome-ignore-all lint/performance/noBarrelFile: Re-exporting all ORM utilities */
2
+ // Field builders - main API for defining table schemas
3
+
4
+ // Column references - used in queries and filters
5
+ export { Column, isColumn } from "./column";
6
+ export {
7
+ type ContainerDbType,
8
+ calcField,
9
+ containerField,
10
+ dateField,
11
+ FieldBuilder,
12
+ numberField,
13
+ textField,
14
+ timeField,
15
+ timestampField,
16
+ } from "./field-builders";
17
+
18
+ // Filter operators - eq, gt, lt, and, or, etc.
19
+ export {
20
+ and,
21
+ asc,
22
+ contains,
23
+ desc,
24
+ endsWith,
25
+ eq,
26
+ FilterExpression,
27
+ gt,
28
+ gte,
29
+ inArray,
30
+ isNotNull,
31
+ isNull,
32
+ isOrderByExpression,
33
+ lt,
34
+ lte,
35
+ ne,
36
+ not,
37
+ notInArray,
38
+ // OrderBy operators
39
+ OrderByExpression,
40
+ or,
41
+ startsWith,
42
+ } from "./operators";
43
+
44
+ // Table definition - fmTableOccurrence function
45
+ export {
46
+ FMTable,
47
+ type FMTableWithColumns,
48
+ fmTableOccurrence,
49
+ getBaseTableConfig,
50
+ // getTableFields,
51
+ getDefaultSelect,
52
+ getFieldId,
53
+ getFieldName,
54
+ getTableColumns,
55
+ getTableEntityId,
56
+ getTableId,
57
+ // Helper functions for accessing FMTable internals
58
+ getTableName,
59
+ type InferTableSchema,
60
+ isUsingEntityIds,
61
+ } from "./table";