@jd-data-limited/easy-fm 5.0.0-beta.2 → 5.0.0-beta.4

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 (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -91
  3. package/dist/bin/cli.js +34 -5
  4. package/dist/bin/generateClient.d.ts +1 -0
  5. package/dist/bin/generateClient.js +48 -0
  6. package/dist/bin/generateTypes.js +19 -18
  7. package/dist/bin/init.d.ts +6 -0
  8. package/dist/bin/init.js +46 -0
  9. package/dist/bin/stressSearch.js +12 -12
  10. package/dist/connection/DataApiProvider.d.ts +15 -0
  11. package/dist/connection/DataApiProvider.js +246 -0
  12. package/dist/connection/dataApiDatabaseSession.d.ts +11 -0
  13. package/dist/connection/dataApiDatabaseSession.js +139 -0
  14. package/dist/connection/database.d.ts +22 -56
  15. package/dist/connection/database.js +175 -111
  16. package/dist/connection/databaseBase.d.ts +9 -22
  17. package/dist/connection/databaseBase.js +0 -3
  18. package/dist/connection/databaseProvider.d.ts +196 -0
  19. package/dist/connection/databaseProvider.js +6 -0
  20. package/dist/connection/databaseSessionPool.d.ts +8 -7
  21. package/dist/connection/databaseSessionPool.js +110 -106
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +3 -3
  24. package/dist/layouts/layout.d.ts +0 -2
  25. package/dist/layouts/layout.js +6 -16
  26. package/dist/layouts/layoutBase.d.ts +0 -1
  27. package/dist/layouts/layoutInterface.d.ts +4 -2
  28. package/dist/records/fields/baseField.d.ts +4 -1
  29. package/dist/records/fields/containerField.d.ts +6 -23
  30. package/dist/records/fields/containerField.js +34 -68
  31. package/dist/records/fields/valueField.js +6 -6
  32. package/dist/records/getOperations/recordGetOperation.js +19 -18
  33. package/dist/records/layoutRecord.d.ts +8 -2
  34. package/dist/records/layoutRecord.js +45 -74
  35. package/dist/records/portalRecord.d.ts +4 -1
  36. package/dist/records/portalRecord.js +12 -2
  37. package/dist/records/recordBase.d.ts +6 -3
  38. package/dist/records/recordBase.js +6 -5
  39. package/dist/schema.d.ts +54 -0
  40. package/dist/schema.js +87 -0
  41. package/dist/typegen/dataApiSchemaSource.d.ts +3 -0
  42. package/dist/typegen/dataApiSchemaSource.js +32 -0
  43. package/dist/typegen/dependencies.d.ts +11 -0
  44. package/dist/typegen/dependencies.js +67 -0
  45. package/dist/typegen/generate.d.ts +5 -0
  46. package/dist/typegen/generate.js +85 -0
  47. package/dist/typegen/index.d.ts +6 -0
  48. package/dist/typegen/index.js +6 -0
  49. package/dist/typegen/init.d.ts +13 -0
  50. package/dist/typegen/init.js +113 -0
  51. package/dist/typegen/types.d.ts +40 -0
  52. package/dist/typegen/types.js +1 -0
  53. package/dist/typegen/zod.d.ts +3 -0
  54. package/dist/typegen/zod.js +32 -0
  55. package/package.json +26 -4
  56. package/dist/layouts/recordFieldsMap.d.ts +0 -2
  57. package/dist/layouts/recordFieldsMap.js +0 -4
  58. package/dist/records/field.d.ts +0 -77
  59. package/dist/records/field.js +0 -192
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 JD-Data-Limited
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2022 JD-Data-Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,93 +1,95 @@
1
- # easy-fm
2
-
3
- `easy-fm` is a Node.js client for Claris FileMaker Data API.
4
-
5
- It helps you connect Node.js code to FileMaker Server or FileMaker Cloud so you can fetch records, run finds, edit data, work with portals, and run scripts using a typed JavaScript or TypeScript API.
6
-
7
- ## Requirements
8
-
9
- - Node.js `22+`
10
- - FileMaker Data API enabled
11
- - account with `fmrest` privilege
12
- - at least one accessible layout
13
-
14
- Claris Data API reference:
15
- [`help.claris.com/en/data-api-guide/content/index.html`](https://help.claris.com/en/data-api-guide/content/index.html)
16
-
17
- ## Install
18
-
19
- ```sh
20
- npm install @jd-data-limited/easy-fm
21
- ```
22
-
23
- ## Quick Start
24
-
25
- ```ts
26
- import {FMHost} from "@jd-data-limited/easy-fm"
27
-
28
- const host = new FMHost("https://example.com")
29
-
30
- const database = host.database({
31
- database: "Contacts",
32
- credentials: {
33
- method: "filemaker",
34
- username: "api-user",
35
- password: "secret"
36
- },
37
- externalSources: []
1
+ # easy-fm
2
+
3
+ `easy-fm` is a Node.js client for Claris FileMaker Data API.
4
+
5
+ It helps you connect Node.js code to FileMaker Server or FileMaker Cloud so you can fetch records, run finds, edit data, work with portals, and run scripts using a typed JavaScript or TypeScript API.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js `22+`
10
+ - FileMaker Data API enabled
11
+ - account with `fmrest` privilege
12
+ - at least one accessible layout
13
+
14
+ Claris Data API reference:
15
+ [`help.claris.com/en/data-api-guide/content/index.html`](https://help.claris.com/en/data-api-guide/content/index.html)
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ npm install @jd-data-limited/easy-fm
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```ts
26
+ import {Database, DataApiProvider} from "@jd-data-limited/easy-fm"
27
+
28
+ const database = Database.create({
29
+ provider: new DataApiProvider({
30
+ hostname: "https://example.com",
31
+ database: "Contacts",
32
+ credentials: {
33
+ method: "filemaker",
34
+ username: "api-user",
35
+ password: "secret"
36
+ },
37
+ externalSources: []
38
+ })
38
39
  })
39
-
40
- const layout = database.layout("Contacts_API")
41
- const records = await layout.records.list({
42
- portals: {},
43
- limit: 25
44
- }).fetch()
45
-
46
- for (const record of records) {
47
- console.log(record.fields.FirstName.value, record.fields.LastName.value)
48
- }
49
- ```
50
-
51
- ## Important Notes
52
-
53
- - FileMaker Data API is layout-based, so fields and portals must be available on the layout you use.
54
- - Portal data is not fetched unless you request it.
55
- - `recordId` and `modId` are internal FileMaker IDs. Avoid treating `recordId` as a business ID; `modId` can still be useful for change or concurrency checks.
56
- - Date and timestamp formatting follows the host timezone rules you configure, with defaults chosen to better match FileMaker UI expectations.
57
-
58
- ## Documentation
59
-
60
- > Updating from v4 to v5? Check the guide here; [`docs/upgrade-to-v5.md`](./docs/upgrade-to-v5.md)
61
-
62
- Start here:
63
-
64
- - [`docs/getting-started.md`](./docs/getting-started.md): first connection, first find, closing sessions
65
- - [`docs/core-concepts.md`](./docs/core-concepts.md): host, database, layout, record, portal, IDs
66
-
67
- Task guides:
68
-
69
- - [`docs/working-with-records.md`](./docs/working-with-records.md): list, create, update, duplicate, delete, portal rows
70
- - [`docs/query-recipes.md`](./docs/query-recipes.md): find requests, sorting, paging, script hooks
40
+
41
+ const layout = database.layout("Contacts_API")
42
+ const records = await layout.records.list({
43
+ portals: {},
44
+ limit: 25
45
+ }).fetch()
46
+
47
+ for (const record of records) {
48
+ console.log(record.fields.FirstName.value, record.fields.LastName.value)
49
+ }
50
+ ```
51
+
52
+ ## Important Notes
53
+
54
+ - FileMaker Data API is layout-based, so fields and portals must be available on the layout you use.
55
+ - Portal data is not fetched unless you request it.
56
+ - `recordId` and `modId` are internal FileMaker IDs. Avoid treating `recordId` as a business ID; `modId` can still be useful for change or concurrency checks.
57
+ - Date and timestamp formatting follows the host timezone rules you configure, with defaults chosen to better match FileMaker UI expectations.
58
+
59
+ ## Documentation
60
+
61
+ > Updating from v4 to v5? Check the guide here; [`docs/upgrade-to-v5.md`](./docs/upgrade-to-v5.md)
62
+
63
+ Start here:
64
+
65
+ - [`docs/getting-started.md`](./docs/getting-started.md): first connection, first find, closing sessions
66
+ - [`docs/core-concepts.md`](./docs/core-concepts.md): host, database, layout, record, portal, IDs
67
+
68
+ Task guides:
69
+
70
+ - [`docs/working-with-records.md`](./docs/working-with-records.md): list, create, update, duplicate, delete, portal rows
71
+ - [`docs/query-recipes.md`](./docs/query-recipes.md): find requests, sorting, paging, script hooks
71
72
  - [`docs/authentication-and-sessions.md`](./docs/authentication-and-sessions.md): auth modes, pooling, lifecycle
72
- - [`docs/typescript-layouts.md`](./docs/typescript-layouts.md): typed layouts and stronger autocomplete
73
- - [`docs/testGuide.md`](./docs/testGuide.md): local test commands
74
-
75
- API reference:
76
-
77
- - [`docs_out/index.html`](./docs_out/index.html): generated TypeDoc site
78
-
79
- ## Build Docs
80
-
81
- ```sh
82
- npm run docs:build
83
- ```
84
-
85
- ## Test
86
-
87
- ```sh
88
- npm test
89
- npm run test:unit
90
- npm run test:integration
91
- ```
92
-
93
- Integration tests require a working FileMaker environment and credentials.
73
+ - [`docs/provider-api.md`](./docs/provider-api.md): custom transports and provider sessions
74
+ - [`docs/typescript-layouts.md`](./docs/typescript-layouts.md): typed layouts and stronger autocomplete
75
+ - [`docs/testGuide.md`](./docs/testGuide.md): local test commands
76
+
77
+ API reference:
78
+
79
+ - [`docs_out/index.html`](./docs_out/index.html): generated TypeDoc site
80
+
81
+ ## Build Docs
82
+
83
+ ```sh
84
+ npm run docs:build
85
+ ```
86
+
87
+ ## Test
88
+
89
+ ```sh
90
+ npm test
91
+ npm run test:unit
92
+ npm run test:integration
93
+ ```
94
+
95
+ Integration tests require a working FileMaker environment and credentials.
package/dist/bin/cli.js CHANGED
@@ -3,15 +3,44 @@
3
3
  * Copyright (c) 2024. See LICENSE file for more information
4
4
  */
5
5
  import { Command } from 'commander';
6
- import { generateTypesCLI } from './generateTypes.js';
6
+ import { pathToFileURL } from 'node:url';
7
+ import { dirname, resolve } from 'node:path';
8
+ import { loadEnvFile } from 'node:process';
9
+ import chalk from 'chalk';
10
+ import { configExists, initializeTypegen } from './init.js';
7
11
  const program = new Command();
8
12
  program
9
13
  .name('easyfm')
10
14
  .description('A NodeJS wrapper for the FileMaker Data CLI');
11
15
  program
12
- .command('generate-types')
13
- .description('Automatically generate layout interfaces from your database')
14
- .action(async () => {
15
- await generateTypesCLI();
16
+ .command('init')
17
+ .description('Interactively create an EasyFM typegen configuration')
18
+ .option('-c, --config <path>', 'config module', 'easyfm.config.js')
19
+ .action(async ({ config }) => {
20
+ await initializeTypegen({ config });
21
+ });
22
+ program
23
+ .command('typegen')
24
+ .description('Generate a typed, runtime-validated EasyFM client')
25
+ .option('-c, --config <path>', 'config module', 'easyfm.config.js')
26
+ .action(async ({ config }) => {
27
+ const configPath = resolve(config);
28
+ if (!await configExists(configPath)) {
29
+ console.log(chalk.yellow(`No config found at ${configPath}; starting setup.`));
30
+ await initializeTypegen({ config: configPath });
31
+ }
32
+ try {
33
+ loadEnvFile(resolve(dirname(configPath), '.env'));
34
+ }
35
+ catch (error) {
36
+ if (error.code !== 'ENOENT')
37
+ throw error;
38
+ }
39
+ const typegen = await import('../typegen/index.js');
40
+ if (await typegen.ensureZodInstalled(dirname(configPath)))
41
+ console.log(chalk.green('Installed Zod for generated runtime validation.'));
42
+ const loaded = await import(pathToFileURL(configPath).href);
43
+ const result = await typegen.generate(loaded.default);
44
+ console.log(chalk.green(`Generated ${result.layouts} layouts in ${result.output}`));
16
45
  });
17
46
  program.parse(process.argv);
@@ -0,0 +1 @@
1
+ export declare function generateClient(): Promise<void>;
@@ -0,0 +1,48 @@
1
+ import { Database } from '../connection/database.js';
2
+ import { DataApiProvider } from '../connection/DataApiProvider.js';
3
+ import * as fs from "node:fs";
4
+ const DATABASE_HOST = process?.env.FM_DB_HOST ?? 'http://localhost';
5
+ const DATABASE_NAME = process.env.FM_DB_NAME ?? 'EasyFMBenchmark.fmp12';
6
+ const DATABASE_ACCOUNT = process.env.FM_DB_ACCOUNT ?? 'Admin';
7
+ const DATABASE_PASSWORD = process.env.FM_DB_PASSWORD ?? 'Admin';
8
+ const FIELD_TYPE_MAP = {
9
+ number: { class: "NumberField", zodType: "z.string()" },
10
+ date: { class: "DateField", zodType: "z.string()" },
11
+ text: { class: "TextField", zodType: "z.string()" },
12
+ time: { class: "TimeField", zodType: "z.string()" },
13
+ timeStamp: { class: "TimeStampField", zodType: "z.string()" },
14
+ container: { class: "ContainerField", zodType: "z.string()" }
15
+ };
16
+ export async function generateClient() {
17
+ const DATABASE = Database.create({ provider: new DataApiProvider({
18
+ hostname: DATABASE_HOST,
19
+ database: 'EasyFMBenchmark',
20
+ credentials: {
21
+ method: 'filemaker',
22
+ username: DATABASE_ACCOUNT,
23
+ password: DATABASE_PASSWORD
24
+ },
25
+ externalSources: [],
26
+ debug: true
27
+ }), debug: true });
28
+ const LAYOUTS = await DATABASE.listLayouts();
29
+ const schema = await Promise.all(LAYOUTS.map(async (layout) => {
30
+ const metadata = await layout.getLayoutMeta();
31
+ // Generate type maps
32
+ const fieldTypesMap = metadata.fieldMetaData.map(field => {
33
+ return `${JSON.stringify(field.name)}: ${FIELD_TYPE_MAP[field.result].class}`;
34
+ });
35
+ // Generate validation
36
+ const fieldValidation = `z.object({${metadata.fieldMetaData.map(field => {
37
+ return `${JSON.stringify(field.name)}: ${FIELD_TYPE_MAP[field.result].zodType}`;
38
+ })})`;
39
+ return { layout, fieldTypesMap, fieldValidation };
40
+ }));
41
+ // Generate DB schema
42
+ const typeScriptSchema = `interface DatabaseSchema extends __DatabaseSchema {${schema.map(layout => {
43
+ return `${JSON.stringify(layout.layout.name)}: {${layout.fieldTypesMap.join(",")}}`;
44
+ })}}`;
45
+ fs.writeFileSync("./easyFMClient.ts", `import {Database, DataApiProvider, DatabaseSchema as __DatabaseSchema} from "@jd-data-limited/easy-fm"
46
+
47
+ ${typeScriptSchema}`);
48
+ }
@@ -3,7 +3,8 @@
3
3
  */
4
4
  import { ApiFieldResultTypes } from '../models/apiResults.js';
5
5
  import inquirer from 'inquirer';
6
- import FMHost from '../connection/FMHost.js';
6
+ import { Database } from '../connection/database.js';
7
+ import { DataApiProvider } from '../connection/DataApiProvider.js';
7
8
  import * as fs from 'node:fs';
8
9
  function sub(str) {
9
10
  return str
@@ -98,21 +99,22 @@ export async function generateTypesCLI() {
98
99
  mask: '*'
99
100
  }
100
101
  ]);
101
- const HOST = new FMHost(data.hostname, data.verify);
102
- const DATABASE = HOST.database({
103
- database: data.database,
104
- credentials: {
105
- method: 'filemaker',
106
- username: data.username,
107
- password: data.password
108
- },
109
- externalSources: []
110
- });
102
+ const DATABASE = Database.create({ provider: new DataApiProvider({
103
+ hostname: data.hostname,
104
+ verify: data.verify,
105
+ database: data.database,
106
+ credentials: {
107
+ method: 'filemaker',
108
+ username: data.username,
109
+ password: data.password
110
+ },
111
+ externalSources: []
112
+ }) });
111
113
  // await DATABASE.login()
112
114
  // Create file write stream
113
115
  const stream = fs.createWriteStream('./types.ts');
114
116
  const layouts = await DATABASE.listLayouts();
115
- stream.write('import FMHost, {Container, FieldBase, LayoutInterface, Portal} from "@jd-data-limited/easy-fm";\n');
117
+ stream.write('import {Database, DataApiProvider, ContainerField, BaseField, LayoutInterface, Portal} from "@jd-data-limited/easy-fm";\n');
116
118
  stream.write('// ' + layouts.length + ' layouts found\n\n');
117
119
  const layoutInterfaces = new Map();
118
120
  for (const layout of layouts) {
@@ -129,9 +131,9 @@ export async function generateTypesCLI() {
129
131
  const interfaceName = substituteName + 'LayoutInterface';
130
132
  layoutInterfaces.set(layout.name, {
131
133
  interfaceName,
132
- interface: `export interface ${interfaceName} extends LayoutInterface {
133
- fields: {${fields.join(',\n ')}},
134
- portals: {${portalsProcessed.join(',\n ')}}
134
+ interface: `export interface ${interfaceName} extends LayoutInterface {
135
+ fields: {${fields.join(',\n ')}},
136
+ portals: {${portalsProcessed.join(',\n ')}}
135
137
  }`
136
138
  });
137
139
  }
@@ -139,11 +141,10 @@ export async function generateTypesCLI() {
139
141
  for (const layout of layoutInterfaces) {
140
142
  layoutInterfaceLinks.push([
141
143
  `${JSON.stringify(layout[0])}: ${layout[1].interfaceName}`,
142
- `${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(' ', '_'))}: HOST.getLayout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
144
+ `${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(' ', '_'))}: DATABASE.layout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
143
145
  ]);
144
146
  }
145
- stream.write(safeStringInjection `const HOST = new FMHost(${data.hostname}, ${data.timezoneOffset}, ${data.verify});`);
146
- stream.write(`HOST.database<{layouts:{${layoutInterfaceLinks.map(i => i[0]).join(',')}}}>();`);
147
+ stream.write(`const DATABASE = Database.create<{layouts:{${layoutInterfaceLinks.map(i => i[0]).join(',')}}}>({provider: new DataApiProvider({hostname: ${JSON.stringify(data.hostname)}, database: ${JSON.stringify(data.database)}, credentials: {method: 'filemaker', username: ${JSON.stringify(data.username)}, password: ${JSON.stringify(data.password)}}, externalSources: []})});`);
147
148
  stream.write('\n\n');
148
149
  stream.write(`const LAYOUTS = {${layoutInterfaceLinks.map(i => i[1]).join(',')}};`);
149
150
  for (const layout of layoutInterfaces) {
@@ -0,0 +1,6 @@
1
+ export { configExists } from '../typegen/init.js';
2
+ export interface InitOptions {
3
+ config: string;
4
+ }
5
+ /** Interactive initializer shared by `easyfm init` and first `easyfm typegen`. */
6
+ export declare function initializeTypegen({ config }: InitOptions): Promise<boolean>;
@@ -0,0 +1,46 @@
1
+ import chalk from 'chalk';
2
+ import inquirer from 'inquirer';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { configExists, readEnvironment, writeDataApiInitialization } from '../typegen/init.js';
5
+ export { configExists } from '../typegen/init.js';
6
+ /** Interactive initializer shared by `easyfm init` and first `easyfm typegen`. */
7
+ export async function initializeTypegen({ config }) {
8
+ const configPath = resolve(config);
9
+ if (await configExists(configPath)) {
10
+ console.log(chalk.yellow(`Config already exists: ${configPath}`));
11
+ return false;
12
+ }
13
+ console.log(chalk.bold.cyan('EasyFM typegen setup'));
14
+ const envPath = resolve(dirname(configPath), '.env');
15
+ const existingEnvironment = await readEnvironment(envPath);
16
+ const answers = await inquirer.prompt([
17
+ { type: 'select', name: 'provider', message: 'Provider', choices: [{ name: 'Data API/OData', value: 'data-api/odata' }] },
18
+ { type: 'select', name: 'transports', message: 'Transports', default: 'both', choices: [
19
+ { name: 'Both', value: 'both' },
20
+ { name: 'Data API', value: 'data-api' },
21
+ { name: 'OData', value: 'odata' }
22
+ ] },
23
+ { type: 'input', name: 'hostname', message: 'FileMaker host URL', default: existingEnvironment.FM_HOST ?? 'https://', validate: validHost },
24
+ { type: 'input', name: 'database', message: 'Database filename', default: existingEnvironment.FM_DATABASE, validate: required('Database filename') },
25
+ { type: 'input', name: 'username', message: 'Username', default: existingEnvironment.FM_USERNAME, validate: required('Username') },
26
+ { type: 'password', name: 'password', message: existingEnvironment.FM_PASSWORD ? 'Password (leave blank to keep existing)' : 'Password', mask: '*', validate: value => existingEnvironment.FM_PASSWORD || value ? true : 'Password is required' },
27
+ { type: 'input', name: 'output', message: 'Generated client directory', default: './src/easyfm-generated', validate: required('Output directory') }
28
+ ]);
29
+ if (!answers.password)
30
+ answers.password = existingEnvironment.FM_PASSWORD ?? '';
31
+ await writeDataApiInitialization(configPath, answers);
32
+ console.log(chalk.green(`Created ${configPath}`));
33
+ console.log(chalk.green(`Saved Data API credentials in ${envPath}`));
34
+ console.log(chalk.dim('Keep .env out of version control.'));
35
+ return true;
36
+ }
37
+ function required(label) { return (value) => value.trim() ? true : `${label} is required`; }
38
+ function validHost(value) {
39
+ try {
40
+ const url = new URL(value);
41
+ return url.protocol === 'http:' || url.protocol === 'https:' ? true : 'Use an http:// or https:// URL';
42
+ }
43
+ catch {
44
+ return 'Use a valid http:// or https:// URL';
45
+ }
46
+ }
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { Command, Option } from 'commander';
6
6
  import { config } from 'dotenv';
7
- import FMHost, { query } from '../index.js';
7
+ import { DataApiProvider, Database, query } from '../index.js';
8
8
  config();
9
9
  function parseIntegerList(value) {
10
10
  return value
@@ -175,18 +175,18 @@ async function main() {
175
175
  warmup,
176
176
  concurrencyLevels
177
177
  });
178
- const fmHost = new FMHost(host, false);
179
178
  const rows = [];
180
- const connection = fmHost.database({
181
- database,
182
- credentials: {
183
- method: 'filemaker',
184
- username,
185
- password
186
- },
187
- externalSources: [],
188
- debug: options.debug
189
- });
179
+ const connection = Database.create({ provider: new DataApiProvider({
180
+ hostname: host,
181
+ database,
182
+ credentials: {
183
+ method: 'filemaker',
184
+ username,
185
+ password
186
+ },
187
+ externalSources: [],
188
+ debug: options.debug
189
+ }), debug: options.debug });
190
190
  try {
191
191
  const layout = connection.layout(options.layout);
192
192
  await layout.getLayoutMeta();
@@ -0,0 +1,15 @@
1
+ import type { databaseOptionsWithExternalSources, loginOptionsClaris, loginOptionsFileMaker, loginOptionsOAuth, loginOptionsToken } from '../types.js';
2
+ import type { DatabaseFormatting, DatabaseProvider, ProviderConnection, ProviderContext } from './databaseProvider.js';
3
+ type Credentials = loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken;
4
+ export interface DataApiProviderOptions extends databaseOptionsWithExternalSources<Credentials> {
5
+ hostname: string;
6
+ verify?: boolean;
7
+ }
8
+ export declare class DataApiProvider implements DatabaseProvider {
9
+ #private;
10
+ readonly name: string;
11
+ readonly formatting: DatabaseFormatting;
12
+ constructor(options: DataApiProviderOptions);
13
+ connect(context: ProviderContext): Promise<ProviderConnection>;
14
+ }
15
+ export {};