@jd-data-limited/easy-fm 4.1.5 → 4.1.8
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/dist/bin/cli.js +17 -0
- package/dist/bin/generateTypes.js +161 -0
- package/{src → dist}/connection/FMHost.js +3 -2
- package/{src → dist}/connection/database.d.ts +0 -1
- package/{src → dist}/connection/database.js +22 -39
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/{src → dist}/records/field.d.ts +0 -1
- package/{src → dist}/records/getOperations/recordGetOperation.d.ts +6 -2
- package/{src → dist}/records/getOperations/recordGetOperation.js +9 -5
- package/{src → dist}/records/layoutRecord.d.ts +1 -1
- package/{src → dist}/records/layoutRecord.js +9 -7
- package/{src → dist}/records/recordBase.d.ts +0 -1
- package/{src → dist}/types.d.ts +12 -2
- package/package.json +23 -12
- package/src/FMError.ts +0 -1033
- package/src/bin/cli.js +0 -17
- package/src/bin/cli.ts +0 -22
- package/src/bin/generateTypes.d.ts +0 -1
- package/src/bin/generateTypes.js +0 -161
- package/src/bin/generateTypes.ts +0 -189
- package/src/connection/FMHost.ts +0 -123
- package/src/connection/HostBase.ts +0 -18
- package/src/connection/database.ts +0 -257
- package/src/connection/databaseBase.ts +0 -19
- package/src/connection/generateAuthorizationHeaders.ts +0 -32
- package/src/connection/requestHoldable.d.ts +0 -10
- package/src/connection/requestHoldable.js +0 -44
- package/src/databaseStructure.ts +0 -10
- package/src/index.d.ts +0 -18
- package/src/index.js +0 -18
- package/src/index.ts +0 -47
- package/src/layouts/layout.ts +0 -66
- package/src/layouts/layoutBase.ts +0 -16
- package/src/layouts/layoutInterface.ts +0 -13
- package/src/layouts/layoutRecordManager.ts +0 -66
- package/src/layouts/recordFieldsMap.ts +0 -7
- package/src/models/apiResults.ts +0 -110
- package/src/records/field.ts +0 -235
- package/src/records/getOperations/recordGetOperation.ts +0 -268
- package/src/records/layoutRecord.ts +0 -246
- package/src/records/layoutRecordBase.ts +0 -10
- package/src/records/portal.ts +0 -43
- package/src/records/portalBase.ts +0 -14
- package/src/records/portalRecord.ts +0 -52
- package/src/records/recordBase.ts +0 -91
- package/src/types.ts +0 -150
- package/src/utils/query.ts +0 -59
- /package/{src → dist}/FMError.d.ts +0 -0
- /package/{src → dist}/FMError.js +0 -0
- /package/{src → dist}/bin/cli.d.ts +0 -0
- /package/{src/bin/dist → dist}/bin/generateTypes.d.ts +0 -0
- /package/{src → dist}/connection/FMHost.d.ts +0 -0
- /package/{src → dist}/connection/HostBase.d.ts +0 -0
- /package/{src → dist}/connection/HostBase.js +0 -0
- /package/{src → dist}/connection/databaseBase.d.ts +0 -0
- /package/{src → dist}/connection/databaseBase.js +0 -0
- /package/{src → dist}/connection/generateAuthorizationHeaders.d.ts +0 -0
- /package/{src → dist}/connection/generateAuthorizationHeaders.js +0 -0
- /package/{src → dist}/databaseStructure.d.ts +0 -0
- /package/{src → dist}/databaseStructure.js +0 -0
- /package/{src → dist}/layouts/layout.d.ts +0 -0
- /package/{src → dist}/layouts/layout.js +0 -0
- /package/{src → dist}/layouts/layoutBase.d.ts +0 -0
- /package/{src → dist}/layouts/layoutBase.js +0 -0
- /package/{src → dist}/layouts/layoutInterface.d.ts +0 -0
- /package/{src → dist}/layouts/layoutInterface.js +0 -0
- /package/{src → dist}/layouts/layoutRecordManager.d.ts +0 -0
- /package/{src → dist}/layouts/layoutRecordManager.js +0 -0
- /package/{src → dist}/layouts/recordFieldsMap.d.ts +0 -0
- /package/{src → dist}/layouts/recordFieldsMap.js +0 -0
- /package/{src → dist}/models/apiResults.d.ts +0 -0
- /package/{src → dist}/models/apiResults.js +0 -0
- /package/{src → dist}/records/field.js +0 -0
- /package/{src → dist}/records/layoutRecordBase.d.ts +0 -0
- /package/{src → dist}/records/layoutRecordBase.js +0 -0
- /package/{src → dist}/records/portal.d.ts +0 -0
- /package/{src → dist}/records/portal.js +0 -0
- /package/{src → dist}/records/portalBase.d.ts +0 -0
- /package/{src → dist}/records/portalBase.js +0 -0
- /package/{src → dist}/records/portalRecord.d.ts +0 -0
- /package/{src → dist}/records/portalRecord.js +0 -0
- /package/{src → dist}/records/recordBase.js +0 -0
- /package/{src → dist}/types.js +0 -0
- /package/{src → dist}/utils/query.d.ts +0 -0
- /package/{src → dist}/utils/query.js +0 -0
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2024. See LICENSE file for more information
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { generateTypesCLI } from './generateTypes.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name('easyfm')
|
|
10
|
+
.description('A NodeJS wrapper for the FileMaker Data CLI');
|
|
11
|
+
program
|
|
12
|
+
.command('generate-types')
|
|
13
|
+
.description('Automatically generate layout interfaces from your database')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
await generateTypesCLI();
|
|
16
|
+
});
|
|
17
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
import { ApiFieldResultTypes } from '../models/apiResults.js';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import FMHost from '../connection/FMHost.js';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
function sub(str) {
|
|
9
|
+
return str
|
|
10
|
+
.replace(/[^a-zA-Z0-9_]/g, '') // Remove non-alphanumeric characters
|
|
11
|
+
.replace(/^[0-9]+/g, ''); // Removing leading numbers
|
|
12
|
+
}
|
|
13
|
+
function generateFieldType(field) {
|
|
14
|
+
switch (field.result) {
|
|
15
|
+
case ApiFieldResultTypes.TEXT:
|
|
16
|
+
return 'Field<string>';
|
|
17
|
+
case ApiFieldResultTypes.CONTAINER:
|
|
18
|
+
return 'Field<Container>';
|
|
19
|
+
case ApiFieldResultTypes.DATE:
|
|
20
|
+
return 'Field<Date>';
|
|
21
|
+
case ApiFieldResultTypes.NUMBER:
|
|
22
|
+
return 'Field<number>';
|
|
23
|
+
case ApiFieldResultTypes.TIME:
|
|
24
|
+
return 'Field<Date>';
|
|
25
|
+
case ApiFieldResultTypes.TIMESTAMP:
|
|
26
|
+
return 'Field<Date>';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function processFieldsChunk(fields) {
|
|
30
|
+
const existingFields = [];
|
|
31
|
+
fields = fields.filter(field => {
|
|
32
|
+
if (existingFields.includes(field.name))
|
|
33
|
+
return false;
|
|
34
|
+
existingFields.push(field.name);
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
return fields.map(field => {
|
|
38
|
+
return JSON.stringify(field.name) + ': ' + generateFieldType(field);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function camelToSnakeCase(str) {
|
|
42
|
+
return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
function safeStringInjection(str, ...args) {
|
|
45
|
+
let result = '';
|
|
46
|
+
for (let i = 0; i < str.length; i++) {
|
|
47
|
+
result += str[i];
|
|
48
|
+
if (i < args.length) {
|
|
49
|
+
result += JSON.stringify(args[i].toString());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
export async function generateTypesCLI() {
|
|
55
|
+
process.stdout.write('\x1Bc');
|
|
56
|
+
console.log('For this tool to work, you are required to have access to an account on the target database.\n' +
|
|
57
|
+
'This account must have the fmrest extended privilege enabled.\n' +
|
|
58
|
+
'This tool can only export type interfaces for layouts that the given account has access to.');
|
|
59
|
+
console.log('\n');
|
|
60
|
+
const data = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: 'input',
|
|
63
|
+
name: 'hostname',
|
|
64
|
+
message: 'The IP address/domain of your FileMaker server (must start with either http:// or https://)',
|
|
65
|
+
validate(input) {
|
|
66
|
+
return input.startsWith('http://') || input.startsWith('https://') || 'Please enter a valid IP address/domain starting with http:// or https://';
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'number',
|
|
71
|
+
name: 'timezoneOffset',
|
|
72
|
+
message: 'Server timezone offset (minutes)',
|
|
73
|
+
default: 0 - (new Date()).getTimezoneOffset()
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'confirm',
|
|
77
|
+
name: 'verify',
|
|
78
|
+
default: true,
|
|
79
|
+
message: "Verify your server's identity",
|
|
80
|
+
when(previousAnswers) {
|
|
81
|
+
return previousAnswers.hostname.startsWith('https://');
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'input',
|
|
86
|
+
name: 'database',
|
|
87
|
+
message: 'Database filename'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'input',
|
|
91
|
+
name: 'username',
|
|
92
|
+
message: 'Database username'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'password',
|
|
96
|
+
name: 'password',
|
|
97
|
+
message: 'Database password',
|
|
98
|
+
mask: '*'
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
const HOST = new FMHost(data.hostname, () => data.timezoneOffset, 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
|
+
});
|
|
111
|
+
await DATABASE.login();
|
|
112
|
+
// Create file write stream
|
|
113
|
+
const stream = fs.createWriteStream('./types.ts');
|
|
114
|
+
const layouts = await DATABASE.listLayouts();
|
|
115
|
+
stream.write('import FMHost, {Container, FieldBase, LayoutInterface, Portal} from "@jd-data-limited/easy-fm";\n');
|
|
116
|
+
stream.write('// ' + layouts.length + ' layouts found\n\n');
|
|
117
|
+
const layoutInterfaces = new Map();
|
|
118
|
+
for (const layout of layouts) {
|
|
119
|
+
const substituteName = sub(layout.name);
|
|
120
|
+
if (!substituteName)
|
|
121
|
+
continue;
|
|
122
|
+
console.log(substituteName);
|
|
123
|
+
const metadata = await layout.getLayoutMeta();
|
|
124
|
+
const fields = processFieldsChunk(metadata.fieldMetaData);
|
|
125
|
+
const portals = metadata.portalMetaData;
|
|
126
|
+
const portalsProcessed = Object.keys(portals).map(portalName => {
|
|
127
|
+
return `${JSON.stringify(portalName)}: Portal<{${processFieldsChunk(portals[portalName]).join('\n')}}>`;
|
|
128
|
+
});
|
|
129
|
+
const interfaceName = substituteName + 'LayoutInterface';
|
|
130
|
+
layoutInterfaces.set(layout.name, {
|
|
131
|
+
interfaceName,
|
|
132
|
+
interface: `export interface ${interfaceName} extends LayoutInterface {
|
|
133
|
+
fields: {${fields.join(',\n ')}},
|
|
134
|
+
portals: {${portalsProcessed.join(',\n ')}}
|
|
135
|
+
}`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const layoutInterfaceLinks = [];
|
|
139
|
+
for (const layout of layoutInterfaces) {
|
|
140
|
+
layoutInterfaceLinks.push([
|
|
141
|
+
`${JSON.stringify(layout[0])}: ${layout[1].interfaceName}`,
|
|
142
|
+
`${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(' ', '_'))}: HOST.getLayout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
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('\n\n');
|
|
148
|
+
stream.write(`const LAYOUTS = {${layoutInterfaceLinks.map(i => i[1]).join(',')}};`);
|
|
149
|
+
for (const layout of layoutInterfaces) {
|
|
150
|
+
stream.write('\n\n');
|
|
151
|
+
stream.write(layout[1].interface + ';');
|
|
152
|
+
}
|
|
153
|
+
// StructureKind
|
|
154
|
+
//
|
|
155
|
+
// const project = new Project({
|
|
156
|
+
// tsConfigFilePath: "../tsconfig.json"
|
|
157
|
+
// })
|
|
158
|
+
// project.resolveSourceFileDependencies()
|
|
159
|
+
await DATABASE.logout();
|
|
160
|
+
console.log('DONE! Wrote database types to ./types.ts');
|
|
161
|
+
}
|
|
@@ -17,8 +17,9 @@ export default class FMHost {
|
|
|
17
17
|
constructor(_hostname, timezoneOffset = (moment) => 0 - (new Date()).getTimezoneOffset(), verify = true) {
|
|
18
18
|
if (!(/^https?:\/\//).test(_hostname))
|
|
19
19
|
throw new Error('hostname MUST begin with either http:// or https://');
|
|
20
|
-
|
|
21
|
-
this.
|
|
20
|
+
const url = new URL(_hostname);
|
|
21
|
+
this.protocol = url.protocol;
|
|
22
|
+
this.hostname = url.hostname;
|
|
22
23
|
this.timezoneOffsetFunc = timezoneOffset;
|
|
23
24
|
this.verify = verify;
|
|
24
25
|
}
|
|
@@ -78,27 +78,25 @@ export class Database extends EventEmitter {
|
|
|
78
78
|
this._token = (this.connection_details.credentials).token;
|
|
79
79
|
return this.token;
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return this.generateExternalSourceLogin(_i);
|
|
91
|
-
})
|
|
81
|
+
const url = new URL(`${this.endpoint}/sessions`);
|
|
82
|
+
url.hostname = this.host.hostname;
|
|
83
|
+
const res = await fetch(url, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: generateAuthorizationHeaders(this.connection_details.credentials),
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
fmDataSource: this.connection_details.externalSources.map(i => {
|
|
88
|
+
const _i = i;
|
|
89
|
+
return this.generateExternalSourceLogin(_i);
|
|
92
90
|
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
const _res = (await res.json());
|
|
94
|
+
if (res.status === 200) {
|
|
95
|
+
this._token = res.headers.get('x-fm-data-access-token') ?? '';
|
|
96
|
+
return this._token;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw new FMError(_res.messages[0].code, _res.status, res);
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
get token() {
|
|
@@ -118,35 +116,21 @@ export class Database extends EventEmitter {
|
|
|
118
116
|
? url.toString()
|
|
119
117
|
: typeof url === 'string' ? url : url.url}`);
|
|
120
118
|
}
|
|
121
|
-
const
|
|
119
|
+
const urlParsed = (url instanceof URL
|
|
122
120
|
? url
|
|
123
121
|
: typeof url === 'string'
|
|
124
122
|
? new URL(url)
|
|
125
|
-
: new URL(url.url))
|
|
123
|
+
: new URL(url.url));
|
|
124
|
+
const reqIsToDBHost = urlParsed.hostname === this.host.hostname && urlParsed.pathname.startsWith("/fmi/data");
|
|
126
125
|
if (reqIsToDBHost && this.token === '')
|
|
127
126
|
await this.login(true);
|
|
128
127
|
if (!options.headers)
|
|
129
128
|
options.headers = {};
|
|
130
129
|
if (reqIsToDBHost)
|
|
131
130
|
options.headers.authorization = 'Bearer ' + this._token;
|
|
132
|
-
// options.redirect = "manual"
|
|
133
|
-
// options.headers.cookies = this._generateCookieHeader()
|
|
134
|
-
// console.log(options.headers.cookies)
|
|
135
|
-
// options.rejectUnauthorized = this.host.verify
|
|
136
131
|
const _fetch = options.useCookieJar
|
|
137
132
|
? await fetchWithCookies(this.cookies, url, options)
|
|
138
133
|
: await fetch(url, options);
|
|
139
|
-
// if (_fetch.headers.get('set-cookie')) {
|
|
140
|
-
// console.log(_fetch.headers.get('set-cookie'))
|
|
141
|
-
// for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
|
|
142
|
-
// const cookieSplit = cookie.split('=')
|
|
143
|
-
// console.log("COOKIE SPLIT:", cookieSplit)
|
|
144
|
-
// if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
|
|
145
|
-
// }
|
|
146
|
-
// }
|
|
147
|
-
// console.log(this.cookies, _fetch.status)
|
|
148
|
-
// // console.log(this, this.cookies, url)
|
|
149
|
-
// console.trace()
|
|
150
134
|
if (!_fetch.ok && (!options.retries || options.retries > 0)) {
|
|
151
135
|
if (this.debug) {
|
|
152
136
|
console.log(`EASYFM DEBUG: RE-ATTEMPTING REQUEST (${_fetch.status}) ${url instanceof URL
|
|
@@ -186,7 +170,6 @@ export class Database extends EventEmitter {
|
|
|
186
170
|
*/
|
|
187
171
|
async listLayouts(page = 0) {
|
|
188
172
|
const req = await this._apiRequestJSON(`${this.endpoint}/layouts?page=${encodeURIComponent(page)}`);
|
|
189
|
-
console.log(req);
|
|
190
173
|
if (!req.response)
|
|
191
174
|
throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message);
|
|
192
175
|
const cycleLayoutNames = (layouts) => {
|
|
@@ -213,6 +196,6 @@ export class Database extends EventEmitter {
|
|
|
213
196
|
this.#layoutCache.clear();
|
|
214
197
|
}
|
|
215
198
|
script(name, parameter = '') {
|
|
216
|
-
return
|
|
199
|
+
return { name, parameter };
|
|
217
200
|
}
|
|
218
201
|
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import FMHost from './connection/FMHost.js';
|
|
2
|
+
import type * as TYPES from './types.js';
|
|
3
|
+
export { FMError } from './FMError.js';
|
|
4
|
+
export { type PickPortals } from './types.js';
|
|
5
|
+
export { Database } from './connection/database.js';
|
|
6
|
+
export { Layout } from './layouts/layout.js';
|
|
7
|
+
export { RecordBase } from './records/recordBase.js';
|
|
8
|
+
export { LayoutRecord } from './records/layoutRecord.js';
|
|
9
|
+
export { type LayoutInterface, type PortalInterface } from './layouts/layoutInterface.js';
|
|
10
|
+
export { PortalRecord } from './records/portalRecord.js';
|
|
11
|
+
export { Portal } from './records/portal.js';
|
|
12
|
+
export { type FindRequest, type FindRequestRaw, RecordGetOperation } from './records/getOperations/recordGetOperation.js';
|
|
13
|
+
export { LayoutRecordManager } from './layouts/layoutRecordManager.js';
|
|
14
|
+
export { type RecordFieldsMap } from './layouts/recordFieldsMap.js';
|
|
15
|
+
export { asDate, asTime, asTimestamp, query, queryEscape } from './utils/query.js';
|
|
16
|
+
export { type Container, Field } from './records/field.js';
|
|
17
|
+
export default FMHost;
|
|
18
|
+
export { type TYPES };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
import FMHost from './connection/FMHost.js';
|
|
5
|
+
export { FMError } from './FMError.js';
|
|
6
|
+
export { Database } from './connection/database.js';
|
|
7
|
+
export { Layout } from './layouts/layout.js';
|
|
8
|
+
export { RecordBase } from './records/recordBase.js';
|
|
9
|
+
export { LayoutRecord } from './records/layoutRecord.js';
|
|
10
|
+
export { PortalRecord } from './records/portalRecord.js';
|
|
11
|
+
export { Portal } from './records/portal.js';
|
|
12
|
+
export { RecordGetOperation } from './records/getOperations/recordGetOperation.js';
|
|
13
|
+
export { LayoutRecordManager } from './layouts/layoutRecordManager.js';
|
|
14
|
+
export { asDate, asTime, asTimestamp, query, queryEscape } from './utils/query.js';
|
|
15
|
+
export { Field } from './records/field.js';
|
|
16
|
+
export default FMHost;
|
|
@@ -17,6 +17,10 @@ type PortalData<T extends LayoutInterface> = {
|
|
|
17
17
|
};
|
|
18
18
|
export interface GetOperationOptions<T extends LayoutInterface> {
|
|
19
19
|
portals: Partial<PortalData<T>>;
|
|
20
|
+
requests?: Array<{
|
|
21
|
+
req: FindRequest;
|
|
22
|
+
omit?: boolean;
|
|
23
|
+
}>;
|
|
20
24
|
limit?: number;
|
|
21
25
|
offset?: number;
|
|
22
26
|
}
|
|
@@ -30,9 +34,9 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
30
34
|
}>;
|
|
31
35
|
protected portals: Partial<PortalData<T>>;
|
|
32
36
|
protected offset: number;
|
|
33
|
-
protected
|
|
37
|
+
protected requests: Array<{
|
|
34
38
|
req: FindRequestRaw;
|
|
35
|
-
omit
|
|
39
|
+
omit?: boolean;
|
|
36
40
|
}>;
|
|
37
41
|
constructor(layout: LayoutBase, options: OPTIONS);
|
|
38
42
|
get isFindRequest(): boolean;
|
|
@@ -11,19 +11,23 @@ export class RecordGetOperation {
|
|
|
11
11
|
sortData = [];
|
|
12
12
|
portals;
|
|
13
13
|
offset = 1;
|
|
14
|
-
|
|
14
|
+
requests = [];
|
|
15
15
|
constructor(layout, options) {
|
|
16
16
|
this.layout = layout;
|
|
17
17
|
this.sortData = [];
|
|
18
18
|
this.portals = options.portals;
|
|
19
19
|
this.offset = options.offset ?? 1; // Offset refers to the starting record. offset 1 is the same as no offset.
|
|
20
20
|
this.limit = options.limit ?? 100;
|
|
21
|
+
if (options.requests) {
|
|
22
|
+
for (const req of options.requests)
|
|
23
|
+
this.addRequest(req.req, req.omit ?? false);
|
|
24
|
+
}
|
|
21
25
|
}
|
|
22
26
|
get isFindRequest() {
|
|
23
|
-
return this.
|
|
27
|
+
return this.requests.length !== 0;
|
|
24
28
|
}
|
|
25
29
|
formatQueries() {
|
|
26
|
-
const test = this.
|
|
30
|
+
const test = this.requests.map(query => {
|
|
27
31
|
const out = {};
|
|
28
32
|
for (const key of Object.keys(query.req)) {
|
|
29
33
|
if (query.req[key])
|
|
@@ -58,7 +62,7 @@ export class RecordGetOperation {
|
|
|
58
62
|
params['script.prerequest'] = this.scriptData.prerequest.name;
|
|
59
63
|
if (this.scriptData.prerequest?.parameter)
|
|
60
64
|
params['script.prerequest.param'] = this.scriptData.prerequest.parameter;
|
|
61
|
-
if (this.
|
|
65
|
+
if (this.requests.length !== 0)
|
|
62
66
|
params.query = this.formatQueries();
|
|
63
67
|
const portals = Object.keys(this.portals);
|
|
64
68
|
params.portal = portals;
|
|
@@ -146,7 +150,7 @@ export class RecordGetOperation {
|
|
|
146
150
|
* @return {Object} - The current object instance.
|
|
147
151
|
*/
|
|
148
152
|
addRequest(query, omit = false) {
|
|
149
|
-
this.
|
|
153
|
+
this.requests.push({ req: this.parseFindRequest(query), omit });
|
|
150
154
|
return this;
|
|
151
155
|
}
|
|
152
156
|
/**
|
|
@@ -32,5 +32,5 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
32
32
|
duplicate(): Promise<LayoutRecord<LAYOUT>>;
|
|
33
33
|
delete(): Promise<void>;
|
|
34
34
|
fieldsToObject(filter?: (a: Field<FieldValue>) => boolean): Omit<ApiRowDataDef, 'portalData'>;
|
|
35
|
-
toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<
|
|
35
|
+
toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field<FieldValue>) => any): ApiRowDataDef;
|
|
36
36
|
}
|
|
@@ -5,7 +5,7 @@ import { RecordBase } from './recordBase.js';
|
|
|
5
5
|
import { PortalRecord } from './portalRecord.js';
|
|
6
6
|
import { Portal } from './portal.js';
|
|
7
7
|
import { FMError } from '../FMError.js';
|
|
8
|
-
import
|
|
8
|
+
import moment from 'moment';
|
|
9
9
|
export class LayoutRecord extends RecordBase {
|
|
10
10
|
portals = {};
|
|
11
11
|
portalsToInclude;
|
|
@@ -44,13 +44,16 @@ export class LayoutRecord extends RecordBase {
|
|
|
44
44
|
if (extraBody.scripts.presort.parameter)
|
|
45
45
|
data['script.presort.param'] = extraBody.scripts.presort.parameter;
|
|
46
46
|
}
|
|
47
|
+
if (extraBody.options)
|
|
48
|
+
data.options = extraBody.options;
|
|
49
|
+
if (extraBody.deleteRelatedRecords)
|
|
50
|
+
data.fieldData.deleteRelated = extraBody.deleteRelatedRecords.map(i => `${i.table}.${i.recordId}`);
|
|
47
51
|
if (this.recordId === -1) {
|
|
48
52
|
// This is a new LayoutRecord
|
|
49
53
|
const res = await this.layout.database._apiRequestJSON(`${this.layout.endpoint}/records`, {
|
|
50
54
|
method: 'POST',
|
|
51
55
|
body: JSON.stringify(data)
|
|
52
56
|
});
|
|
53
|
-
console.log(res);
|
|
54
57
|
if (!res.response) {
|
|
55
58
|
throw new FMError(res.messages[0].code, res.httpStatus, res);
|
|
56
59
|
}
|
|
@@ -165,12 +168,12 @@ export class LayoutRecord extends RecordBase {
|
|
|
165
168
|
}
|
|
166
169
|
}
|
|
167
170
|
fieldsToObject(filter = (a) => a.edited) {
|
|
168
|
-
const
|
|
171
|
+
const fieldsProcessed = {};
|
|
169
172
|
let field;
|
|
170
173
|
for (field of this.fieldsArray.filter(field => filter(field))) {
|
|
171
174
|
let value = field.value;
|
|
172
175
|
if (value instanceof Date) {
|
|
173
|
-
let _value = moment
|
|
176
|
+
let _value = moment(value);
|
|
174
177
|
_value = _value
|
|
175
178
|
.utcOffset(this.layout.database.host.timezoneOffsetFunc(_value));
|
|
176
179
|
switch (field.metadata.result) {
|
|
@@ -184,12 +187,12 @@ export class LayoutRecord extends RecordBase {
|
|
|
184
187
|
value = _value.format(this.layout.database.host.timeStampFormat);
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
|
-
|
|
190
|
+
fieldsProcessed[field.id] = value;
|
|
188
191
|
}
|
|
189
192
|
const obj = {
|
|
190
193
|
recordId: this.recordId.toString(),
|
|
191
194
|
modId: this.modId.toString(),
|
|
192
|
-
fieldData:
|
|
195
|
+
fieldData: fieldsProcessed
|
|
193
196
|
};
|
|
194
197
|
return obj;
|
|
195
198
|
}
|
|
@@ -203,7 +206,6 @@ export class LayoutRecord extends RecordBase {
|
|
|
203
206
|
if (portals) {
|
|
204
207
|
obj.portalData = {};
|
|
205
208
|
for (const portal of portals) {
|
|
206
|
-
// @ts-expect-error
|
|
207
209
|
obj.portalData[portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => {
|
|
208
210
|
return record.toObject(portalFieldFilter);
|
|
209
211
|
});
|
package/{src → dist}/types.d.ts
RENAMED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
1
|
import type * as http from 'http';
|
|
4
2
|
import { type FMError } from './FMError.js';
|
|
5
3
|
import { type LayoutInterface } from './layouts/layoutInterface.js';
|
|
@@ -51,6 +49,18 @@ export interface ScriptRequestData {
|
|
|
51
49
|
}
|
|
52
50
|
export interface extraBodyOptions {
|
|
53
51
|
scripts?: ScriptRequestData;
|
|
52
|
+
options?: {
|
|
53
|
+
/**
|
|
54
|
+
* Defines what entry mode to use (FileMaker Server 2024 and newer)
|
|
55
|
+
* user (default) - Field validation occurs as per normal. Request is blocked if validation failed.
|
|
56
|
+
* script - Field validation is overridden/ignored when allowed in the database schema.
|
|
57
|
+
*/
|
|
58
|
+
entrymode: 'user' | 'script';
|
|
59
|
+
};
|
|
60
|
+
deleteRelatedRecords?: Array<{
|
|
61
|
+
table: string;
|
|
62
|
+
recordId: number;
|
|
63
|
+
}>;
|
|
54
64
|
}
|
|
55
65
|
export interface Script {
|
|
56
66
|
name: string;
|
package/package.json
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jd-data-limited/easy-fm",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.8",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@types/follow-redirects": "^1.14.4",
|
|
6
|
+
"@types/node": "^22.7.7",
|
|
7
|
+
"commander": "^14.0.0",
|
|
6
8
|
"follow-redirects": "^1.15.5",
|
|
7
9
|
"moment": "^2.29.4",
|
|
8
10
|
"node-fetch": "^3.3.2",
|
|
9
11
|
"node-fetch-cookies": "^2.1.1"
|
|
10
12
|
},
|
|
11
13
|
"type": "module",
|
|
12
|
-
"main": "
|
|
14
|
+
"main": "dist/index.js",
|
|
13
15
|
"module": "es2022",
|
|
14
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://jd-data.com/project/easyfm/",
|
|
15
17
|
"url": "https://github.com/JD-Data-Limited/easy-fm",
|
|
16
18
|
"license": "SEE LICENSE IN LICENSE",
|
|
17
19
|
"description": "easy-fm is a Node.js module that allows you to interact with\r a [FileMaker database stored](https://www.claris.com/filemaker/) on\r a [FileMaker server](https://www.claris.com/filemaker/server/). This module interacts with your server using the\r [FileMaker Data API](https://help.claris.com/en/data-api-guide/content/index.html).",
|
|
18
20
|
"files": [
|
|
19
|
-
"./
|
|
21
|
+
"./dist/**/*"
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "npm run build && jest"
|
|
24
26
|
},
|
|
25
27
|
"repository": {
|
|
26
28
|
"type": "git",
|
|
@@ -28,8 +30,11 @@
|
|
|
28
30
|
},
|
|
29
31
|
"keywords": [
|
|
30
32
|
"FileMaker",
|
|
31
|
-
"Data
|
|
32
|
-
"
|
|
33
|
+
"Data",
|
|
34
|
+
"API",
|
|
35
|
+
"REST",
|
|
36
|
+
"simplicity",
|
|
37
|
+
"business"
|
|
33
38
|
],
|
|
34
39
|
"author": "JD-Data Limited",
|
|
35
40
|
"bugs": {
|
|
@@ -39,10 +44,16 @@
|
|
|
39
44
|
"node": ">=20.9.0"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
47
|
+
"@babel/cli": "^7.25.7",
|
|
48
|
+
"@babel/core": "^7.25.8",
|
|
49
|
+
"@babel/preset-env": "^7.25.8",
|
|
50
|
+
"@babel/preset-typescript": "^7.25.7",
|
|
42
51
|
"@types/btoa": "^1.2.3",
|
|
43
52
|
"@types/events": "^3.0.0",
|
|
44
53
|
"@types/inquirer": "^9.0.7",
|
|
54
|
+
"@types/jest": "^29.5.13",
|
|
45
55
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
56
|
+
"babel-jest": "^29.7.0",
|
|
46
57
|
"dotenv": "^16.3.1",
|
|
47
58
|
"eslint": "^8.57.0",
|
|
48
59
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
@@ -50,11 +61,11 @@
|
|
|
50
61
|
"eslint-plugin-n": "^16.6.2",
|
|
51
62
|
"eslint-plugin-promise": "^6.1.1",
|
|
52
63
|
"inquirer": "^9.2.13",
|
|
64
|
+
"jest": "^29.7.0",
|
|
65
|
+
"ts-jest": "^29.2.5",
|
|
53
66
|
"ts-morph": "^21.0.1",
|
|
54
|
-
"
|
|
55
|
-
"typescript": "^5.
|
|
56
|
-
"webpack": "^5.81.0",
|
|
57
|
-
"webpack-cli": "^5.0.2"
|
|
67
|
+
"tsx": "^4.19.4",
|
|
68
|
+
"typescript": "^5.6.3"
|
|
58
69
|
},
|
|
59
70
|
"bin": {
|
|
60
71
|
"easyfm": "src/bin/cli.js"
|