@proofkit/fmodata 0.1.0-alpha.7 → 0.1.0-alpha.9

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.
@@ -22,10 +22,12 @@ class TableOccurrence {
22
22
  __publicField(this, "_navigationConfig");
23
23
  __publicField(this, "navigation");
24
24
  __publicField(this, "defaultSelect");
25
+ __publicField(this, "fmtId");
25
26
  this.name = config.name;
26
27
  this.baseTable = config.baseTable;
27
28
  this._navigationConfig = config.navigation ?? {};
28
29
  this.defaultSelect = config.defaultSelect ?? "schema";
30
+ this.fmtId = config.fmtId;
29
31
  this.navigation = createNavigationGetters(this._navigationConfig);
30
32
  }
31
33
  addNavigation(nav) {
@@ -33,7 +35,8 @@ class TableOccurrence {
33
35
  name: this.name,
34
36
  baseTable: this.baseTable,
35
37
  navigation: { ...this._navigationConfig, ...nav },
36
- defaultSelect: this.defaultSelect
38
+ defaultSelect: this.defaultSelect,
39
+ fmtId: this.fmtId
37
40
  });
38
41
  }
39
42
  /**
@@ -41,7 +44,7 @@ class TableOccurrence {
41
44
  * @returns The FMTID string or the table name
42
45
  */
43
46
  getTableId() {
44
- return "fmtId" in this && this.fmtId ? this.fmtId : this.name;
47
+ return this.fmtId ?? this.name;
45
48
  }
46
49
  /**
47
50
  * Returns the table occurrence name.
@@ -54,29 +57,18 @@ class TableOccurrence {
54
57
  * Returns true if this TableOccurrence is using FileMaker table occurrence IDs.
55
58
  */
56
59
  isUsingTableId() {
57
- return "fmtId" in this && this.fmtId !== void 0;
60
+ return this.fmtId !== void 0;
58
61
  }
59
62
  }
60
63
  function createTableOccurrence(config) {
61
64
  return new TableOccurrence(config);
62
65
  }
63
- class TableOccurrenceWithIds extends TableOccurrence {
64
- constructor(config) {
65
- super({
66
- ...config,
67
- navigation: config.navigation
68
- });
69
- __publicField(this, "fmtId");
70
- this.fmtId = config.fmtId;
71
- }
72
- }
73
- function createTableOccurrenceWithIds(config) {
74
- return new TableOccurrenceWithIds(config);
66
+ function defineTableOccurrence(config) {
67
+ return new TableOccurrence(config);
75
68
  }
76
69
  export {
77
70
  TableOccurrence,
78
- TableOccurrenceWithIds,
79
71
  createTableOccurrence,
80
- createTableOccurrenceWithIds
72
+ defineTableOccurrence
81
73
  };
82
74
  //# sourceMappingURL=table-occurrence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"table-occurrence.js","sources":["../../../src/client/table-occurrence.ts"],"sourcesContent":["import { BaseTable, BaseTableWithIds } from \"./base-table\";\n\n// Helper type to extract schema from BaseTable\ntype ExtractSchema<BT> =\n BT extends BaseTable<infer S, any, any, any> ? S : never;\n\n// Helper type to resolve navigation functions to their return types\ntype ResolveNavigation<T> = {\n [K in keyof T]: T[K] extends () => infer R ? R : T[K];\n};\n\n// Helper to create a getter-based navigation object\nfunction createNavigationGetters<\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >,\n>(navConfig: Nav): ResolveNavigation<Nav> {\n const result: any = {};\n\n for (const key in navConfig) {\n Object.defineProperty(result, key, {\n get() {\n const navItem = navConfig[key];\n return typeof navItem === \"function\" ? navItem() : navItem;\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return result as ResolveNavigation<Nav>;\n}\n\nexport class TableOccurrence<\n BT extends BaseTable<any, any, any, any> = any,\n Name extends string = string,\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n > = {},\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n> {\n public readonly name: Name;\n public readonly baseTable: BT;\n protected _navigationConfig: Nav;\n public readonly navigation: ResolveNavigation<Nav>;\n public readonly defaultSelect: DefSelect;\n constructor(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly navigation?: Nav;\n readonly defaultSelect?: DefSelect;\n }) {\n this.name = config.name;\n this.baseTable = config.baseTable;\n this._navigationConfig = (config.navigation ?? {}) as Nav;\n this.defaultSelect = (config.defaultSelect ?? \"schema\") as DefSelect;\n // Create navigation getters that lazily resolve functions\n this.navigation = createNavigationGetters(this._navigationConfig);\n }\n\n addNavigation<\n NewNav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >,\n >(nav: NewNav): TableOccurrence<BT, Name, Nav & NewNav, DefSelect> {\n return new TableOccurrence({\n name: this.name,\n baseTable: this.baseTable,\n navigation: { ...this._navigationConfig, ...nav } as Nav & NewNav,\n defaultSelect: this.defaultSelect,\n });\n }\n\n /**\n * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.\n * @returns The FMTID string or the table name\n */\n getTableId(): string {\n // Check if fmtId exists (only on TableOccurrenceWithIds)\n return \"fmtId\" in this && (this as any).fmtId\n ? (this as any).fmtId\n : this.name;\n }\n\n /**\n * Returns the table occurrence name.\n * @returns The table name\n */\n getTableName(): string {\n return this.name;\n }\n\n /**\n * Returns true if this TableOccurrence is using FileMaker table occurrence IDs.\n */\n isUsingTableId(): boolean {\n return \"fmtId\" in this && this.fmtId !== undefined;\n }\n}\n\n// Helper function to create TableOccurrence with proper type inference\nexport function createTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n name: Name;\n baseTable: BT;\n defaultSelect?: DefSelect;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config);\n}\n\n// Helper type to validate that all navigation values are TableOccurrenceWithIds\ntype ValidateNavWithIds<Nav> =\n Nav extends Record<\n string,\n | TableOccurrenceWithIds<any, any, any, any>\n | (() => TableOccurrenceWithIds<any, any, any, any>)\n >\n ? Nav\n : \"Error: All navigation table occurrences must be TableOccurrenceWithIds when using TableOccurrenceWithIds\";\n\n/**\n * TableOccurrenceWithIds extends TableOccurrence to require:\n * 1. A BaseTableWithIds (which has fmfIds defined)\n * 2. A required fmtId for this table occurrence\n * 3. All navigation relationships must also be TableOccurrenceWithIds\n *\n * This ensures bidirectional type-level enforcement: if you use FileMaker IDs,\n * they must be defined on both the BaseTable (fmfIds) and TableOccurrence (fmtId),\n * and all related table occurrences in navigation must also have IDs.\n *\n * @template BT - Must be a BaseTableWithIds (enforced at type level)\n * @template Name - The name of this table occurrence\n * @template Nav - Navigation relationships (must all be TableOccurrenceWithIds)\n * @template DefSelect - Default select behavior\n *\n * @example\n * ```ts\n * const usersBaseWithIds = new BaseTableWithIds({\n * schema: { id: z.string(), name: z.string() },\n * idField: \"id\",\n * fmfIds: { id: \"FMFID:1\", name: \"FMFID:2\" },\n * });\n *\n * const usersTO = new TableOccurrenceWithIds({\n * name: \"users\",\n * baseTable: usersBaseWithIds,\n * fmtId: \"FMTID:100\",\n * navigation: {\n * contacts: () => contactsTO, // Must also be TableOccurrenceWithIds\n * },\n * });\n * ```\n */\nexport class TableOccurrenceWithIds<\n BT extends BaseTableWithIds<any, any, any, any> = any,\n Name extends string = string,\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n > = {},\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n> extends TableOccurrence<BT, Name, Nav, DefSelect> {\n public readonly fmtId: `FMTID:${string}`;\n\n constructor(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly fmtId: `FMTID:${string}`;\n readonly navigation?: ValidateNavWithIds<Nav>;\n readonly defaultSelect?: DefSelect;\n }) {\n super({\n ...config,\n navigation: config.navigation as Nav,\n });\n this.fmtId = config.fmtId;\n }\n}\n\n// Helper function to create TableOccurrenceWithIds with proper type inference\nexport function createTableOccurrenceWithIds<\n const Name extends string,\n BT extends BaseTableWithIds<any, any, any, any>,\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n name: Name;\n baseTable: BT;\n fmtId: `FMTID:${string}`;\n defaultSelect?: DefSelect;\n}): TableOccurrenceWithIds<BT, Name, {}, DefSelect> {\n return new TableOccurrenceWithIds(config);\n}\n"],"names":[],"mappings":";;;AAYA,SAAS,wBAMP,WAAwC;AACxC,QAAM,SAAc,CAAC;AAErB,aAAW,OAAO,WAAW;AACpB,WAAA,eAAe,QAAQ,KAAK;AAAA,MACjC,MAAM;AACE,cAAA,UAAU,UAAU,GAAG;AAC7B,eAAO,OAAO,YAAY,aAAa,QAAY,IAAA;AAAA,MACrD;AAAA,MACA,YAAY;AAAA,MACZ,cAAc;AAAA,IAAA,CACf;AAAA,EAAA;AAGI,SAAA;AACT;AAEO,MAAM,gBAYX;AAAA,EAMA,YAAY,QAKT;AAVa;AACA;AACN;AACM;AACA;AAOd,SAAK,OAAO,OAAO;AACnB,SAAK,YAAY,OAAO;AACnB,SAAA,oBAAqB,OAAO,cAAc,CAAC;AAC3C,SAAA,gBAAiB,OAAO,iBAAiB;AAEzC,SAAA,aAAa,wBAAwB,KAAK,iBAAiB;AAAA,EAAA;AAAA,EAGlE,cAME,KAAiE;AACjE,WAAO,IAAI,gBAAgB;AAAA,MACzB,MAAM,KAAK;AAAA,MACX,WAAW,KAAK;AAAA,MAChB,YAAY,EAAE,GAAG,KAAK,mBAAmB,GAAG,IAAI;AAAA,MAChD,eAAe,KAAK;AAAA,IAAA,CACrB;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOH,aAAqB;AAEnB,WAAO,WAAW,QAAS,KAAa,QACnC,KAAa,QACd,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOX,eAAuB;AACrB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,iBAA0B;AACjB,WAAA,WAAW,QAAQ,KAAK,UAAU;AAAA,EAAA;AAE7C;AAGO,SAAS,sBAOd,QAI2C;AACpC,SAAA,IAAI,gBAAgB,MAAM;AACnC;AA6CO,MAAM,+BAYH,gBAA0C;AAAA,EAGlD,YAAY,QAMT;AACK,UAAA;AAAA,MACJ,GAAG;AAAA,MACH,YAAY,OAAO;AAAA,IAAA,CACpB;AAZa;AAad,SAAK,QAAQ,OAAO;AAAA,EAAA;AAExB;AAGO,SAAS,6BAOd,QAKkD;AAC3C,SAAA,IAAI,uBAAuB,MAAM;AAC1C;"}
1
+ {"version":3,"file":"table-occurrence.js","sources":["../../../src/client/table-occurrence.ts"],"sourcesContent":["import { BaseTable } from \"./base-table\";\n\n// Helper type to extract schema from BaseTable\ntype ExtractSchema<BT> =\n BT extends BaseTable<infer S, any, any, any> ? S : never;\n\n// Helper type to resolve navigation functions to their return types\ntype ResolveNavigation<T> = {\n [K in keyof T]: T[K] extends () => infer R ? R : T[K];\n};\n\n// Helper to create a getter-based navigation object\nfunction createNavigationGetters<\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >,\n>(navConfig: Nav): ResolveNavigation<Nav> {\n const result: any = {};\n\n for (const key in navConfig) {\n Object.defineProperty(result, key, {\n get() {\n const navItem = navConfig[key];\n return typeof navItem === \"function\" ? navItem() : navItem;\n },\n enumerable: true,\n configurable: true,\n });\n }\n\n return result as ResolveNavigation<Nav>;\n}\n\nexport class TableOccurrence<\n BT extends BaseTable<any, any, any, any> = any,\n Name extends string = string,\n Nav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n > = {},\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n> {\n public readonly name: Name;\n public readonly baseTable: BT;\n protected _navigationConfig: Nav;\n public readonly navigation: ResolveNavigation<Nav>;\n public readonly defaultSelect: DefSelect;\n public readonly fmtId?: `FMTID:${string}`;\n constructor(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly navigation?: Nav;\n readonly defaultSelect?: DefSelect;\n readonly fmtId?: `FMTID:${string}`;\n }) {\n this.name = config.name;\n this.baseTable = config.baseTable;\n this._navigationConfig = (config.navigation ?? {}) as Nav;\n this.defaultSelect = (config.defaultSelect ?? \"schema\") as DefSelect;\n this.fmtId = config.fmtId;\n // Create navigation getters that lazily resolve functions\n this.navigation = createNavigationGetters(this._navigationConfig);\n }\n\n addNavigation<\n NewNav extends Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >,\n >(nav: NewNav): TableOccurrence<BT, Name, Nav & NewNav, DefSelect> {\n return new TableOccurrence({\n name: this.name,\n baseTable: this.baseTable,\n navigation: { ...this._navigationConfig, ...nav } as Nav & NewNav,\n defaultSelect: this.defaultSelect,\n fmtId: this.fmtId,\n });\n }\n\n /**\n * Returns the FileMaker table occurrence ID (FMTID) if available, or the table name.\n * @returns The FMTID string or the table name\n */\n getTableId(): string {\n return this.fmtId ?? this.name;\n }\n\n /**\n * Returns the table occurrence name.\n * @returns The table name\n */\n getTableName(): string {\n return this.name;\n }\n\n /**\n * Returns true if this TableOccurrence is using FileMaker table occurrence IDs.\n */\n isUsingTableId(): boolean {\n return this.fmtId !== undefined;\n }\n}\n\n// Helper function to create TableOccurrence with proper type inference\nexport function createTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n name: Name;\n baseTable: BT;\n defaultSelect?: DefSelect;\n fmtId?: `FMTID:${string}`;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config);\n}\n\n/**\n * Creates a TableOccurrence with proper TypeScript type inference.\n *\n * Use this function instead of `new TableOccurrence()` to ensure\n * field names are properly typed.\n *\n * @example Without entity IDs\n * ```ts\n * const users = defineTableOccurrence({\n * name: \"users\",\n * baseTable: usersBase,\n * });\n * ```\n *\n * @example With entity IDs\n * ```ts\n * const products = defineTableOccurrence({\n * name: \"products\",\n * baseTable: productsBase,\n * fmtId: \"FMTID:12345\",\n * });\n * ```\n */\nexport function defineTableOccurrence<\n const Name extends string,\n BT extends BaseTable<any, any, any, any>,\n const DefSelect extends\n | \"all\"\n | \"schema\"\n | readonly (keyof ExtractSchema<BT>)[] = \"schema\",\n>(config: {\n readonly name: Name;\n readonly baseTable: BT;\n readonly fmtId?: `FMTID:${string}`;\n readonly defaultSelect?: DefSelect;\n readonly navigation?: Record<\n string,\n | TableOccurrence<any, any, any, any>\n | (() => TableOccurrence<any, any, any, any>)\n >;\n}): TableOccurrence<BT, Name, {}, DefSelect> {\n return new TableOccurrence(config) as TableOccurrence<\n BT,\n Name,\n {},\n DefSelect\n >;\n}\n"],"names":[],"mappings":";;;AAYA,SAAS,wBAMP,WAAwC;AACxC,QAAM,SAAc,CAAC;AAErB,aAAW,OAAO,WAAW;AACpB,WAAA,eAAe,QAAQ,KAAK;AAAA,MACjC,MAAM;AACE,cAAA,UAAU,UAAU,GAAG;AAC7B,eAAO,OAAO,YAAY,aAAa,QAAY,IAAA;AAAA,MACrD;AAAA,MACA,YAAY;AAAA,MACZ,cAAc;AAAA,IAAA,CACf;AAAA,EAAA;AAGI,SAAA;AACT;AAEO,MAAM,gBAYX;AAAA,EAOA,YAAY,QAMT;AAZa;AACA;AACN;AACM;AACA;AACA;AAQd,SAAK,OAAO,OAAO;AACnB,SAAK,YAAY,OAAO;AACnB,SAAA,oBAAqB,OAAO,cAAc,CAAC;AAC3C,SAAA,gBAAiB,OAAO,iBAAiB;AAC9C,SAAK,QAAQ,OAAO;AAEf,SAAA,aAAa,wBAAwB,KAAK,iBAAiB;AAAA,EAAA;AAAA,EAGlE,cAME,KAAiE;AACjE,WAAO,IAAI,gBAAgB;AAAA,MACzB,MAAM,KAAK;AAAA,MACX,WAAW,KAAK;AAAA,MAChB,YAAY,EAAE,GAAG,KAAK,mBAAmB,GAAG,IAAI;AAAA,MAChD,eAAe,KAAK;AAAA,MACpB,OAAO,KAAK;AAAA,IAAA,CACb;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOH,aAAqB;AACZ,WAAA,KAAK,SAAS,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5B,eAAuB;AACrB,WAAO,KAAK;AAAA,EAAA;AAAA;AAAA;AAAA;AAAA,EAMd,iBAA0B;AACxB,WAAO,KAAK,UAAU;AAAA,EAAA;AAE1B;AAGO,SAAS,sBAOd,QAK2C;AACpC,SAAA,IAAI,gBAAgB,MAAM;AACnC;AAyBO,SAAS,sBAOd,QAU2C;AACpC,SAAA,IAAI,gBAAgB,MAAM;AAMnC;"}
@@ -1,5 +1,5 @@
1
- export { BaseTable, BaseTableWithIds } from './client/base-table.js';
2
- export { TableOccurrence, createTableOccurrence, TableOccurrenceWithIds, createTableOccurrenceWithIds, } from './client/table-occurrence.js';
1
+ export { BaseTable, defineBaseTable } from './client/base-table.js';
2
+ export { TableOccurrence, createTableOccurrence, defineTableOccurrence, } from './client/table-occurrence.js';
3
3
  export { FMServerConnection } from './client/filemaker-odata.js';
4
4
  export type { Database } from './client/database.js';
5
5
  export type { EntitySet } from './client/entity-set.js';
package/dist/esm/index.js CHANGED
@@ -1,12 +1,11 @@
1
- import { BaseTable, BaseTableWithIds } from "./client/base-table.js";
2
- import { TableOccurrence, TableOccurrenceWithIds, createTableOccurrence, createTableOccurrenceWithIds } from "./client/table-occurrence.js";
1
+ import { BaseTable, defineBaseTable } from "./client/base-table.js";
2
+ import { TableOccurrence, createTableOccurrence, defineTableOccurrence } from "./client/table-occurrence.js";
3
3
  import { FMServerConnection } from "./client/filemaker-odata.js";
4
4
  import { AbortError, CircuitOpenError, NetworkError, RetryLimitError, TimeoutError } from "@fetchkit/ffetch";
5
5
  import { FMODataError, HTTPError, ODataError, RecordCountMismatchError, ResponseStructureError, SchemaLockedError, ValidationError, isFMODataError, isHTTPError, isODataError, isRecordCountMismatchError, isResponseStructureError, isSchemaLockedError, isValidationError } from "./errors.js";
6
6
  export {
7
7
  AbortError,
8
8
  BaseTable,
9
- BaseTableWithIds,
10
9
  CircuitOpenError,
11
10
  FMODataError,
12
11
  FMServerConnection,
@@ -18,11 +17,11 @@ export {
18
17
  RetryLimitError,
19
18
  SchemaLockedError,
20
19
  TableOccurrence,
21
- TableOccurrenceWithIds,
22
20
  TimeoutError,
23
21
  ValidationError,
24
22
  createTableOccurrence,
25
- createTableOccurrenceWithIds,
23
+ defineBaseTable,
24
+ defineTableOccurrence,
26
25
  isFMODataError,
27
26
  isHTTPError,
28
27
  isODataError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proofkit/fmodata",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.9",
4
4
  "description": "FileMaker OData API client",
5
5
  "repository": "git@github.com:proofgeist/proofkit.git",
6
6
  "author": "Eric <37158449+eluce2@users.noreply.github.com>",
@@ -65,25 +65,32 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
65
65
  export class BaseTable<
66
66
  Schema extends Record<string, StandardSchemaV1> = any,
67
67
  IdField extends keyof Schema | undefined = undefined,
68
- Required extends readonly (keyof Schema)[] = readonly [],
69
- ReadOnly extends readonly (keyof Schema)[] = readonly [],
68
+ Required extends readonly (keyof Schema | (string & {}))[] = readonly [],
69
+ ReadOnly extends readonly (keyof Schema | (string & {}))[] = readonly [],
70
70
  > {
71
71
  public readonly schema: Schema;
72
72
  public readonly idField?: IdField;
73
73
  public readonly required?: Required;
74
74
  public readonly readOnly?: ReadOnly;
75
- public readonly fmfIds?: Record<keyof Schema, `FMFID:${string}`>;
75
+ public readonly fmfIds?: Record<
76
+ keyof Schema | (string & {}),
77
+ `FMFID:${string}`
78
+ >;
76
79
 
77
80
  constructor(config: {
78
81
  schema: Schema;
79
82
  idField?: IdField;
80
83
  required?: Required;
81
84
  readOnly?: ReadOnly;
85
+ fmfIds?: Record<string, `FMFID:${string}`>;
82
86
  }) {
83
87
  this.schema = config.schema;
84
88
  this.idField = config.idField;
85
89
  this.required = config.required;
86
90
  this.readOnly = config.readOnly;
91
+ this.fmfIds = config.fmfIds as
92
+ | Record<keyof Schema, `FMFID:${string}`>
93
+ | undefined;
87
94
  }
88
95
 
89
96
  /**
@@ -124,49 +131,45 @@ export class BaseTable<
124
131
  }
125
132
 
126
133
  /**
127
- * BaseTableWithIds extends BaseTable to require FileMaker field IDs (fmfIds).
128
- * Use this class when you need to work with FileMaker's internal field identifiers.
134
+ * Creates a BaseTable with proper TypeScript type inference.
129
135
  *
130
- * @template Schema - Record of field names to StandardSchemaV1 validators
131
- * @template IdField - The name of the primary key field (optional, automatically read-only)
132
- * @template Required - Additional field names to require on insert (beyond auto-inferred required fields)
133
- * @template ReadOnly - Field names that cannot be modified via insert/update (idField is automatically read-only)
136
+ * This function should be used instead of `new BaseTable()` to ensure
137
+ * field names are properly typed throughout the library.
134
138
  *
135
- * @example
139
+ * @example Without entity IDs
136
140
  * ```ts
137
- * import { z } from "zod";
141
+ * const users = defineBaseTable({
142
+ * schema: { id: z.string(), name: z.string() },
143
+ * idField: "id",
144
+ * });
145
+ * ```
138
146
  *
139
- * const usersTableWithIds = new BaseTableWithIds({
140
- * schema: {
141
- * id: z.string(),
142
- * name: z.string(),
143
- * email: z.string().nullable(),
144
- * },
147
+ * @example With entity IDs (FileMaker field IDs)
148
+ * ```ts
149
+ * const products = defineBaseTable({
150
+ * schema: { id: z.string(), name: z.string() },
145
151
  * idField: "id",
146
- * fmfIds: {
147
- * id: "FMFID:1",
148
- * name: "FMFID:2",
149
- * email: "FMFID:3",
150
- * },
152
+ * fmfIds: { id: "FMFID:1", name: "FMFID:2" },
151
153
  * });
152
154
  * ```
153
155
  */
154
- export class BaseTableWithIds<
155
- Schema extends Record<string, StandardSchemaV1> = any,
156
+ export function defineBaseTable<
157
+ const Schema extends Record<string, StandardSchemaV1>,
156
158
  IdField extends keyof Schema | undefined = undefined,
157
- Required extends readonly (keyof Schema)[] = readonly [],
158
- ReadOnly extends readonly (keyof Schema)[] = readonly [],
159
- > extends BaseTable<Schema, IdField, Required, ReadOnly> {
160
- public override readonly fmfIds: Record<keyof Schema, `FMFID:${string}`>;
161
-
162
- constructor(config: {
163
- schema: Schema;
164
- fmfIds: Record<keyof Schema, `FMFID:${string}`>;
165
- idField?: IdField;
166
- required?: Required;
167
- readOnly?: ReadOnly;
168
- }) {
169
- super(config);
170
- this.fmfIds = config.fmfIds;
171
- }
159
+ const Required extends readonly (
160
+ | keyof Schema
161
+ | (string & {})
162
+ )[] = readonly [],
163
+ const ReadOnly extends readonly (
164
+ | keyof Schema
165
+ | (string & {})
166
+ )[] = readonly [],
167
+ >(config: {
168
+ schema: Schema;
169
+ idField?: IdField;
170
+ required?: Required;
171
+ readOnly?: ReadOnly;
172
+ fmfIds?: { [K in keyof Schema | (string & {})]: `FMFID:${string}` };
173
+ }): BaseTable<Schema, IdField, Required, ReadOnly> {
174
+ return new BaseTable(config);
172
175
  }
@@ -9,7 +9,7 @@ import { SchemaManager } from "./schema-manager";
9
9
  // Helper type to extract schema from a TableOccurrence
10
10
  type ExtractSchemaFromOccurrence<O> =
11
11
  O extends TableOccurrence<infer BT, any, any, any>
12
- ? BT extends BaseTable<infer S, any>
12
+ ? BT extends BaseTable<infer S, any, any, any>
13
13
  ? S
14
14
  : never
15
15
  : never;
@@ -169,11 +169,11 @@ export class Database<
169
169
  type SchemaType = ExtractSchemaFromOccurrence<OccType>;
170
170
 
171
171
  return EntitySet.create<SchemaType, OccType>({
172
- occurrence: occurrence as any,
172
+ occurrence: occurrence as OccType,
173
173
  tableName: name as string,
174
174
  databaseName: this.databaseName,
175
175
  context: this.context,
176
- database: this as any,
176
+ database: this,
177
177
  }) as any;
178
178
  } else {
179
179
  // Return untyped EntitySet for dynamic table access
@@ -181,7 +181,7 @@ export class Database<
181
181
  tableName: name as string,
182
182
  databaseName: this.databaseName,
183
183
  context: this.context,
184
- database: this as any,
184
+ database: this,
185
185
  }) as any;
186
186
  }
187
187
  }
@@ -28,7 +28,7 @@ type ExtractNavigationNames<
28
28
  // Helper type to extract schema from a TableOccurrence
29
29
  type ExtractSchemaFromOccurrence<O> =
30
30
  O extends TableOccurrence<infer BT, any, any, any>
31
- ? BT extends BaseTable<infer S, any>
31
+ ? BT extends BaseTable<infer S, any, any, any>
32
32
  ? S
33
33
  : never
34
34
  : never;
@@ -36,7 +36,7 @@ type ExtractSchemaFromOccurrence<O> =
36
36
  // Helper type to extract defaultSelect from a TableOccurrence
37
37
  type ExtractDefaultSelect<O> =
38
38
  O extends TableOccurrence<infer BT, any, any, infer DefSelect>
39
- ? BT extends BaseTable<infer S, any>
39
+ ? BT extends BaseTable<infer S, any, any, any>
40
40
  ? DefSelect extends "all"
41
41
  ? keyof S
42
42
  : DefSelect extends "schema"
@@ -85,7 +85,7 @@ type GetTargetSchemaType<
85
85
  > = [FindNavigationTarget<O, Rel>] extends [
86
86
  TableOccurrence<infer BT, any, any, any>,
87
87
  ]
88
- ? [BT] extends [BaseTable<infer S, any>]
88
+ ? [BT] extends [BaseTable<infer S, any, any, any>]
89
89
  ? [S] extends [Record<string, StandardSchemaV1>]
90
90
  ? InferSchemaType<S>
91
91
  : Record<string, any>
@@ -181,13 +181,13 @@ export class EntitySet<
181
181
  const fields = Object.keys(schema) as (keyof InferSchemaType<Schema>)[];
182
182
  // Deduplicate fields (same as select method)
183
183
  const uniqueFields = [...new Set(fields)];
184
- return builder.select(...uniqueFields);
184
+ return builder.select(...uniqueFields).top(1000);
185
185
  } else if (Array.isArray(defaultSelect)) {
186
186
  // Use the provided field names, deduplicated
187
187
  const uniqueFields = [
188
188
  ...new Set(defaultSelect),
189
189
  ] as (keyof InferSchemaType<Schema>)[];
190
- return builder.select(...uniqueFields);
190
+ return builder.select(...uniqueFields).top(1000);
191
191
  }
192
192
  // If defaultSelect is "all", no changes needed (current behavior)
193
193
  }
@@ -199,7 +199,10 @@ export class EntitySet<
199
199
  (builder as any).navigateSourceTableName = this.navigateSourceTableName;
200
200
  // navigateRecordId is intentionally not set (undefined) to indicate navigation from EntitySet
201
201
  }
202
- return builder;
202
+
203
+ // Apply default pagination limit of 1000 records to prevent stack overflow
204
+ // with large datasets. Users can override with .top() if needed.
205
+ return builder.top(1000);
203
206
  }
204
207
 
205
208
  get(
@@ -25,6 +25,14 @@ import {
25
25
  getTableIdentifiers,
26
26
  } from "../transform";
27
27
 
28
+
29
+ /**
30
+ * Default maximum number of records to return in a list query.
31
+ * This prevents stack overflow issues with large datasets while still
32
+ * allowing substantial data retrieval. Users can override with .top().
33
+ */
34
+ const DEFAULT_TOP = 1000;
35
+
28
36
  // Helper type to extract navigation relation names from an occurrence
29
37
  type ExtractNavigationNames<
30
38
  O extends TableOccurrence<any, any, any, any> | undefined,
@@ -73,7 +81,7 @@ type GetTargetSchemaType<
73
81
  > = [FindNavigationTarget<O, Rel>] extends [
74
82
  TableOccurrence<infer BT, any, any, any>,
75
83
  ]
76
- ? [BT] extends [BaseTable<infer S, any>]
84
+ ? [BT] extends [BaseTable<infer S, any, any, any>]
77
85
  ? [S] extends [Record<string, StandardSchemaV1>]
78
86
  ? InferSchemaType<S>
79
87
  : Record<string, any>
@@ -391,10 +399,6 @@ export class QueryBuilder<
391
399
  count: number,
392
400
  ): QueryBuilder<T, Selected, SingleMode, IsCount, Occ, Expands> {
393
401
  this.queryOptions.top = count;
394
- console.log("top method", {
395
- count,
396
- databaseUseEntityIds: this.databaseUseEntityIds,
397
- });
398
402
  return this;
399
403
  }
400
404
 
@@ -1162,6 +1166,7 @@ export class QueryBuilder<
1162
1166
  if (queryOptionsWithoutExpand.select) {
1163
1167
  queryOptionsWithoutExpand.select = this.formatSelectFields(
1164
1168
  queryOptionsWithoutExpand.select,
1169
+ this.occurrence?.baseTable,
1165
1170
  ) as any;
1166
1171
  }
1167
1172
 
@@ -1228,6 +1233,7 @@ export class QueryBuilder<
1228
1233
  if (queryOptionsWithoutExpand.select) {
1229
1234
  queryOptionsWithoutExpand.select = this.formatSelectFields(
1230
1235
  queryOptionsWithoutExpand.select,
1236
+ this.occurrence?.baseTable,
1231
1237
  ) as any;
1232
1238
  }
1233
1239