@jd-data-limited/easy-fm 5.0.0-beta.2 → 5.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +93 -91
- package/dist/bin/cli.js +34 -5
- package/dist/bin/generateClient.d.ts +1 -0
- package/dist/bin/generateClient.js +48 -0
- package/dist/bin/generateTypes.js +19 -18
- package/dist/bin/init.d.ts +5 -0
- package/dist/bin/init.js +43 -0
- package/dist/bin/stressSearch.js +12 -12
- package/dist/connection/DataApiProvider.d.ts +15 -0
- package/dist/connection/DataApiProvider.js +246 -0
- package/dist/connection/dataApiDatabaseSession.d.ts +11 -0
- package/dist/connection/dataApiDatabaseSession.js +139 -0
- package/dist/connection/database.d.ts +22 -56
- package/dist/connection/database.js +175 -111
- package/dist/connection/databaseBase.d.ts +9 -22
- package/dist/connection/databaseBase.js +0 -3
- package/dist/connection/databaseProvider.d.ts +196 -0
- package/dist/connection/databaseProvider.js +6 -0
- package/dist/connection/databaseSessionPool.d.ts +8 -7
- package/dist/connection/databaseSessionPool.js +110 -106
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -3
- package/dist/layouts/layout.d.ts +0 -2
- package/dist/layouts/layout.js +6 -16
- package/dist/layouts/layoutBase.d.ts +0 -1
- package/dist/layouts/layoutInterface.d.ts +4 -2
- package/dist/records/fields/baseField.d.ts +4 -1
- package/dist/records/fields/containerField.d.ts +6 -23
- package/dist/records/fields/containerField.js +34 -68
- package/dist/records/fields/valueField.js +6 -6
- package/dist/records/getOperations/recordGetOperation.js +19 -18
- package/dist/records/layoutRecord.d.ts +8 -2
- package/dist/records/layoutRecord.js +45 -74
- package/dist/records/portalRecord.d.ts +4 -1
- package/dist/records/portalRecord.js +12 -2
- package/dist/records/recordBase.d.ts +6 -3
- package/dist/records/recordBase.js +6 -5
- package/dist/schema.d.ts +54 -0
- package/dist/schema.js +87 -0
- package/dist/typegen/dataApiSchemaSource.d.ts +3 -0
- package/dist/typegen/dataApiSchemaSource.js +32 -0
- package/dist/typegen/dependencies.d.ts +11 -0
- package/dist/typegen/dependencies.js +67 -0
- package/dist/typegen/generate.d.ts +6 -0
- package/dist/typegen/generate.js +57 -0
- package/dist/typegen/index.d.ts +6 -0
- package/dist/typegen/index.js +6 -0
- package/dist/typegen/init.d.ts +20 -0
- package/dist/typegen/init.js +141 -0
- package/dist/typegen/types.d.ts +49 -0
- package/dist/typegen/types.js +1 -0
- package/dist/typegen/zod.d.ts +3 -0
- package/dist/typegen/zod.js +32 -0
- package/package.json +26 -4
- package/dist/layouts/recordFieldsMap.d.ts +0 -2
- package/dist/layouts/recordFieldsMap.js +0 -4
- package/dist/records/field.d.ts +0 -77
- 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 {
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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/
|
|
73
|
-
- [`docs/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
npm
|
|
90
|
-
npm run test:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 {
|
|
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('
|
|
13
|
-
.description('
|
|
14
|
-
.
|
|
15
|
-
|
|
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
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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(' ', '_'))}:
|
|
144
|
+
`${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(' ', '_'))}: DATABASE.layout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
|
|
143
145
|
]);
|
|
144
146
|
}
|
|
145
|
-
stream.write(
|
|
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) {
|
package/dist/bin/init.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { configExists, writeMultiDatabaseInitialization } from '../typegen/init.js';
|
|
5
|
+
export { configExists } from '../typegen/init.js';
|
|
6
|
+
export async function initializeTypegen({ config }) {
|
|
7
|
+
const configPath = resolve(config);
|
|
8
|
+
if (await configExists(configPath)) {
|
|
9
|
+
console.log(chalk.yellow(`Config already exists: ${configPath}`));
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
console.log(chalk.bold.cyan('EasyFM typegen setup'));
|
|
13
|
+
const { output } = await inquirer.prompt([{ type: 'input', name: 'output', message: 'Generated client directory', default: './src/easyfm-generated', validate: required('Output directory') }]);
|
|
14
|
+
const databases = [];
|
|
15
|
+
let more = true;
|
|
16
|
+
while (more) {
|
|
17
|
+
const answers = await inquirer.prompt([
|
|
18
|
+
{ type: 'input', name: 'key', message: 'Database client name', validate: (value) => !value.trim() ? 'Client name is required' : databases.some(db => db.key === value) ? 'Client name must be unique' : true },
|
|
19
|
+
{ type: 'select', name: 'provider', message: 'Provider', choices: [{ name: 'Data API/OData', value: 'data-api/odata' }] },
|
|
20
|
+
{ type: 'select', name: 'transports', message: 'Transports', default: 'both', choices: [{ name: 'Both', value: 'both' }, { name: 'Data API', value: 'data-api' }, { name: 'OData', value: 'odata' }] },
|
|
21
|
+
{ type: 'input', name: 'hostname', message: 'FileMaker host URL', default: 'https://', validate: validHost },
|
|
22
|
+
{ type: 'input', name: 'database', message: 'Database filename', validate: required('Database filename') },
|
|
23
|
+
{ type: 'input', name: 'username', message: 'Username', validate: required('Username') },
|
|
24
|
+
{ type: 'password', name: 'password', message: 'Password', mask: '*', validate: required('Password') }
|
|
25
|
+
]);
|
|
26
|
+
databases.push(answers);
|
|
27
|
+
const result = await inquirer.prompt([{ type: 'confirm', name: 'more', message: 'Add another database?', default: false }]);
|
|
28
|
+
more = result.more;
|
|
29
|
+
}
|
|
30
|
+
await writeMultiDatabaseInitialization(configPath, { output, databases });
|
|
31
|
+
console.log(chalk.green(`Created ${configPath}`));
|
|
32
|
+
console.log(chalk.green(`Saved database-scoped credentials in ${resolve(dirname(configPath), '.env')}`));
|
|
33
|
+
console.log(chalk.dim('Keep .env out of version control.'));
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function required(label) { return (value) => value.trim() ? true : `${label} is required`; }
|
|
37
|
+
function validHost(value) { try {
|
|
38
|
+
const url = new URL(value);
|
|
39
|
+
return url.protocol === 'http:' || url.protocol === 'https:' ? true : 'Use an http:// or https:// URL';
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return 'Use a valid http:// or https:// URL';
|
|
43
|
+
} }
|
package/dist/bin/stressSearch.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Command, Option } from 'commander';
|
|
6
6
|
import { config } from 'dotenv';
|
|
7
|
-
import
|
|
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 =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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 {};
|