@jd-data-limited/easy-fm 4.1.14 → 5.0.0-beta.1
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/README.md +60 -197
- package/dist/bin/generateTypes.js +1 -1
- package/dist/bin/stressSearch.d.ts +2 -0
- package/dist/bin/stressSearch.js +243 -0
- package/dist/connection/CookieJar.d.ts +19 -0
- package/dist/connection/CookieJar.js +99 -0
- package/dist/connection/FMHost.d.ts +24 -8
- package/dist/connection/FMHost.js +28 -8
- package/dist/connection/HostBase.d.ts +4 -2
- package/dist/connection/Session.d.ts +34 -0
- package/dist/connection/Session.js +109 -0
- package/dist/connection/database.d.ts +34 -31
- package/dist/connection/database.js +72 -133
- package/dist/connection/databaseBase.d.ts +22 -10
- package/dist/connection/databaseConstantSession.d.ts +15 -0
- package/dist/connection/databaseConstantSession.js +70 -0
- package/dist/connection/databaseSessionPool.d.ts +14 -0
- package/dist/connection/databaseSessionPool.js +148 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +12 -0
- package/dist/layouts/layout.d.ts +5 -3
- package/dist/layouts/layout.js +11 -15
- package/dist/layouts/layoutBase.d.ts +3 -2
- package/dist/layouts/layoutRecordManager.d.ts +6 -15
- package/dist/layouts/layoutRecordManager.js +6 -15
- package/dist/models/apiResults.d.ts +208 -93
- package/dist/models/apiResults.js +95 -25
- package/dist/records/field.d.ts +17 -2
- package/dist/records/field.js +161 -237
- package/dist/records/getOperations/recordGetOperation.d.ts +7 -4
- package/dist/records/getOperations/recordGetOperation.js +15 -18
- package/dist/records/layoutRecord.d.ts +24 -7
- package/dist/records/layoutRecord.js +68 -73
- package/dist/records/portal.d.ts +0 -4
- package/dist/records/portal.js +0 -4
- package/dist/records/recordBase.d.ts +3 -2
- package/dist/types.d.ts +41 -17
- package/dist/types.js +12 -0
- package/dist/utils/addHeaders.d.ts +5 -0
- package/dist/utils/addHeaders.js +24 -0
- package/dist/utils/query.d.ts +7 -0
- package/dist/utils/query.js +5 -0
- package/package.json +33 -26
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { type HostBase } from './HostBase.js';
|
|
2
|
-
import { type
|
|
3
|
-
import { type RequestInfo, type RequestInit, type Response } from 'node-fetch';
|
|
2
|
+
import { type z, type ZodType } from 'zod';
|
|
4
3
|
export interface DatabaseBase {
|
|
5
4
|
host: HostBase;
|
|
6
5
|
readonly name: string;
|
|
7
6
|
endpoint: string;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated login is deprecated and is now handled by newer session management. This function is simply a placeholder.
|
|
9
|
+
*/
|
|
10
|
+
login: () => Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Immediately closes all open sessions and prevents new ones from being created.
|
|
13
|
+
*/
|
|
14
|
+
close: () => Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Immediately closes all open sessions and prevents new ones from being created.
|
|
17
|
+
* Alias of {@link close}
|
|
18
|
+
*/
|
|
19
|
+
logout: () => Promise<void>;
|
|
20
|
+
fetch: (url: string | URL, options?: RequestInit) => Promise<Response>;
|
|
21
|
+
fetchJSON: <T extends ZodType | null = null>(url: string | URL, options: RequestInit & {
|
|
22
|
+
type: T;
|
|
23
|
+
}) => Promise<T extends ZodType ? z.infer<T> & {
|
|
24
|
+
httpStatus: number;
|
|
25
|
+
} : {
|
|
26
|
+
httpStatus: number;
|
|
27
|
+
}>;
|
|
16
28
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Database } from './database.js';
|
|
2
|
+
import type { DatabaseStructure } from '../databaseStructure.js';
|
|
3
|
+
import { type Session } from './Session.js';
|
|
4
|
+
import { type databaseOptionsWithExternalSources, type loginOptionsClaris, type loginOptionsOAuth, type loginOptionsToken } from '../types.js';
|
|
5
|
+
import type FMHost from './FMHost.js';
|
|
6
|
+
/**
|
|
7
|
+
* DatabaseConstantSession handles database connections that must live on a single session which is kept alive.
|
|
8
|
+
* For example, connections that rely on OAuth.
|
|
9
|
+
*/
|
|
10
|
+
export declare class DatabaseConstantSession<T extends DatabaseStructure> extends Database<T> {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(host: FMHost, connectionDetails: databaseOptionsWithExternalSources<loginOptionsOAuth | loginOptionsClaris | loginOptionsToken>, structure: T);
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
withSession<T>(callback: (session: Session) => Promise<T>): Promise<T>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Database } from './database.js';
|
|
2
|
+
import { session } from './Session.js';
|
|
3
|
+
import { generateAuthorizationHeaders } from './generateAuthorizationHeaders.js';
|
|
4
|
+
import { FMError } from '../FMError.js';
|
|
5
|
+
import { ApiResults } from '../models/apiResults.js';
|
|
6
|
+
import { CookieJar } from './CookieJar.js';
|
|
7
|
+
/**
|
|
8
|
+
* DatabaseConstantSession handles database connections that must live on a single session which is kept alive.
|
|
9
|
+
* For example, connections that rely on OAuth.
|
|
10
|
+
*/
|
|
11
|
+
export class DatabaseConstantSession extends Database {
|
|
12
|
+
#session;
|
|
13
|
+
constructor(host, connectionDetails, structure) {
|
|
14
|
+
super(host, connectionDetails);
|
|
15
|
+
if (connectionDetails.externalSources.length !== 0)
|
|
16
|
+
throw new Error('External sources are currently only supported for connections with the \'FileMaker\' login method.');
|
|
17
|
+
this.#session = (async () => {
|
|
18
|
+
// Ensure we have host metadata
|
|
19
|
+
await this.host.getMetadata();
|
|
20
|
+
if (connectionDetails.credentials.method === 'token') {
|
|
21
|
+
return session({
|
|
22
|
+
token: (connectionDetails.credentials).token,
|
|
23
|
+
keepAlive: 60_000,
|
|
24
|
+
endpoint: this.endpoint,
|
|
25
|
+
abortSignal: this.abortController.signal
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const url = new URL(`${this.endpoint}/sessions`);
|
|
29
|
+
url.hostname = this.host.hostname;
|
|
30
|
+
const cookiejar = new CookieJar();
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: generateAuthorizationHeaders(connectionDetails.credentials),
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
fmDataSource: connectionDetails.externalSources.map(data => ({
|
|
36
|
+
database: data.database,
|
|
37
|
+
username: data.credentials.username,
|
|
38
|
+
password: data.credentials.password
|
|
39
|
+
}))
|
|
40
|
+
})
|
|
41
|
+
});
|
|
42
|
+
for (const cookie of res.headers.getSetCookie())
|
|
43
|
+
cookiejar.addCookie(url, cookie);
|
|
44
|
+
const json = ApiResults.parse(await res.json());
|
|
45
|
+
if (res.status === 200) {
|
|
46
|
+
return session({
|
|
47
|
+
token: res.headers.get('x-fm-data-access-token') ?? '',
|
|
48
|
+
keepAlive: 60_000,
|
|
49
|
+
endpoint: this.endpoint,
|
|
50
|
+
baseCookieJar: cookiejar,
|
|
51
|
+
abortSignal: this.abortController.signal
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
throw new FMError(json.messages[0].code, res.status, res);
|
|
56
|
+
}
|
|
57
|
+
})();
|
|
58
|
+
}
|
|
59
|
+
async close() {
|
|
60
|
+
if (this.abortController.signal.aborted)
|
|
61
|
+
return;
|
|
62
|
+
await super.close();
|
|
63
|
+
await (await this.#session).logout();
|
|
64
|
+
}
|
|
65
|
+
async withSession(callback) {
|
|
66
|
+
if (!this.canOpenNewConnections)
|
|
67
|
+
throw new Error('Cannot open new connections');
|
|
68
|
+
return await callback(await this.#session);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Database } from './database.js';
|
|
2
|
+
import type { DatabaseStructure } from '../databaseStructure.js';
|
|
3
|
+
import { type Session } from './Session.js';
|
|
4
|
+
import { type databaseOptionsWithExternalSources, type loginOptionsFileMaker } from '../types.js';
|
|
5
|
+
import type FMHost from './FMHost.js';
|
|
6
|
+
/**
|
|
7
|
+
* DatabaseSessionPool handles database connections that can be pooled.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DatabaseSessionPool<T extends DatabaseStructure> extends Database<T> {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(host: FMHost, connectionDetails: databaseOptionsWithExternalSources<loginOptionsFileMaker>);
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
withSession<T>(callback: (session: Session) => Promise<T>, retry?: boolean): Promise<T>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Database } from './database.js';
|
|
2
|
+
import { HttpError, session } from './Session.js';
|
|
3
|
+
import { generateAuthorizationHeaders } from './generateAuthorizationHeaders.js';
|
|
4
|
+
import { FMError } from '../FMError.js';
|
|
5
|
+
import { ApiResults } from '../models/apiResults.js';
|
|
6
|
+
import { CookieJar } from './CookieJar.js';
|
|
7
|
+
/**
|
|
8
|
+
* DatabaseSessionPool handles database connections that can be pooled.
|
|
9
|
+
*/
|
|
10
|
+
export class DatabaseSessionPool extends Database {
|
|
11
|
+
#connectionDetails;
|
|
12
|
+
#activeSessions = new Set();
|
|
13
|
+
#withSessionQueue = [];
|
|
14
|
+
constructor(host, connectionDetails) {
|
|
15
|
+
super(host, connectionDetails);
|
|
16
|
+
this.#connectionDetails = connectionDetails;
|
|
17
|
+
}
|
|
18
|
+
async close() {
|
|
19
|
+
if (this.abortController.signal.aborted)
|
|
20
|
+
return;
|
|
21
|
+
await super.close();
|
|
22
|
+
// System might be shutting down, so we want to make sure we're quick
|
|
23
|
+
const sessions = [...this.#activeSessions];
|
|
24
|
+
this.#activeSessions.clear();
|
|
25
|
+
await Promise.allSettled(sessions.map(session => session.session.logout()));
|
|
26
|
+
}
|
|
27
|
+
get #maxSessions() {
|
|
28
|
+
return this.#connectionDetails.credentials.sessionPoolSize ?? 8;
|
|
29
|
+
}
|
|
30
|
+
// Opens a new database session
|
|
31
|
+
async #openSession() {
|
|
32
|
+
if (!this.canOpenNewConnections)
|
|
33
|
+
throw new Error('Cannot open new connections');
|
|
34
|
+
await this.host.getMetadata();
|
|
35
|
+
const url = new URL(`${this.endpoint}/sessions`);
|
|
36
|
+
url.hostname = this.host.hostname;
|
|
37
|
+
const cookiejar = new CookieJar();
|
|
38
|
+
const res = await fetch(url, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: generateAuthorizationHeaders(this.#connectionDetails.credentials),
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
fmDataSource: this.#connectionDetails.externalSources.map(data => ({
|
|
43
|
+
database: data.database,
|
|
44
|
+
username: data.credentials.username,
|
|
45
|
+
password: data.credentials.password
|
|
46
|
+
}))
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
for (const cookie of res.headers.getSetCookie())
|
|
50
|
+
cookiejar.addCookie(url, cookie);
|
|
51
|
+
const _res = ApiResults.parse(await res.json());
|
|
52
|
+
if (res.status === 200) {
|
|
53
|
+
return session({
|
|
54
|
+
token: res.headers.get('x-fm-data-access-token') ?? '',
|
|
55
|
+
endpoint: this.endpoint,
|
|
56
|
+
baseCookieJar: cookiejar,
|
|
57
|
+
abortSignal: this.abortController.signal
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
throw new FMError(_res.messages[0].code, res.status, res);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
*
|
|
66
|
+
* @param session
|
|
67
|
+
* @param callback
|
|
68
|
+
* @param retry when true, if an HTTP 401 error occurs, we'll re-attempt it with another session.
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
async #withSessionInternal(session, callback, retry) {
|
|
72
|
+
try {
|
|
73
|
+
session.working = true;
|
|
74
|
+
const result = await callback(session.session);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
if (e instanceof HttpError && e.status === 401) {
|
|
79
|
+
// Invalidate this session
|
|
80
|
+
this.#activeSessions.delete(session);
|
|
81
|
+
if (retry)
|
|
82
|
+
return await this.withSession(callback, false);
|
|
83
|
+
}
|
|
84
|
+
throw e;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
if (this.#activeSessions.has(session)) {
|
|
88
|
+
session.working = false;
|
|
89
|
+
}
|
|
90
|
+
await this.#scheduleQueuedWork();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async #processWithSessionQueue(session) {
|
|
94
|
+
const job = this.#withSessionQueue.shift();
|
|
95
|
+
if (!job)
|
|
96
|
+
return;
|
|
97
|
+
await this.#withSessionInternal(session, job.callback, job.retry).then(job.resolve).catch(job.reject);
|
|
98
|
+
}
|
|
99
|
+
async #scheduleQueuedWork() {
|
|
100
|
+
if (this.#withSessionQueue.length === 0)
|
|
101
|
+
return;
|
|
102
|
+
for (const session of this.#activeSessions) {
|
|
103
|
+
if (!session.working) {
|
|
104
|
+
session.working = true;
|
|
105
|
+
void this.#processWithSessionQueue(session);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (this.#activeSessions.size < this.#maxSessions) {
|
|
110
|
+
void this.#openSession().then(async (session) => {
|
|
111
|
+
const newSession = {
|
|
112
|
+
working: true,
|
|
113
|
+
session,
|
|
114
|
+
disconnectTimeout: null
|
|
115
|
+
};
|
|
116
|
+
this.#activeSessions.add(newSession);
|
|
117
|
+
await this.#processWithSessionQueue(newSession);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async withSession(callback, retry = true) {
|
|
122
|
+
return await new Promise((resolve, reject) => {
|
|
123
|
+
// First, see if there's an available session
|
|
124
|
+
for (const session of this.#activeSessions) {
|
|
125
|
+
if (!session.working) {
|
|
126
|
+
session.working = true;
|
|
127
|
+
this.#withSessionInternal(session, callback, retry).then(resolve).catch(reject);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// If there's no available sessions, next check if we can open a new one
|
|
132
|
+
if (this.#activeSessions.size < this.#maxSessions) {
|
|
133
|
+
this.#openSession().then(async (session) => {
|
|
134
|
+
const newSession = {
|
|
135
|
+
working: true,
|
|
136
|
+
session,
|
|
137
|
+
disconnectTimeout: null
|
|
138
|
+
};
|
|
139
|
+
this.#activeSessions.add(newSession);
|
|
140
|
+
return await this.#withSessionInternal(newSession, callback, retry);
|
|
141
|
+
}).then(resolve).catch(reject);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Finally if all else fails, queue the job
|
|
145
|
+
this.#withSessionQueue.push({ callback, resolve, reject, retry });
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
import FMHost from './connection/FMHost.js';
|
|
2
2
|
import type * as TYPES from './types.js';
|
|
3
|
+
/** Error type returned for FileMaker Data API failures. */
|
|
3
4
|
export { FMError } from './FMError.js';
|
|
5
|
+
/** Utility type for narrowing portal data on typed layouts. */
|
|
4
6
|
export { type PickPortals } from './types.js';
|
|
7
|
+
/** Base database connection abstraction used by all auth modes. */
|
|
5
8
|
export { Database } from './connection/database.js';
|
|
9
|
+
/** Layout-scoped API wrapper for metadata, scripts, and record operations. */
|
|
6
10
|
export { Layout } from './layouts/layout.js';
|
|
11
|
+
/** Base record implementation shared by layout and portal records. */
|
|
7
12
|
export { RecordBase } from './records/recordBase.js';
|
|
13
|
+
/** Record returned from a layout. Supports fetch, commit, duplicate, and delete. */
|
|
8
14
|
export { LayoutRecord } from './records/layoutRecord.js';
|
|
15
|
+
/** Type helpers for describing typed layouts and portals. */
|
|
9
16
|
export { type LayoutInterface, type PortalInterface } from './layouts/layoutInterface.js';
|
|
17
|
+
/** Record returned from a portal row. */
|
|
10
18
|
export { PortalRecord } from './records/portalRecord.js';
|
|
19
|
+
/** Wrapper around portal rows included in a layout record. */
|
|
11
20
|
export { Portal } from './records/portal.js';
|
|
21
|
+
/** Types and builder used for list/find record operations. */
|
|
12
22
|
export { type FindRequest, type FindRequestRaw, RecordGetOperation } from './records/getOperations/recordGetOperation.js';
|
|
23
|
+
/** Entry point for `layout.records.*` operations. */
|
|
13
24
|
export { LayoutRecordManager } from './layouts/layoutRecordManager.js';
|
|
25
|
+
/** Type alias for map of fields on a typed record. */
|
|
14
26
|
export { type RecordFieldsMap } from './layouts/recordFieldsMap.js';
|
|
27
|
+
/** Helpers for safe FileMaker find query construction and date/time formatting. */
|
|
15
28
|
export { asDate, asTime, asTimestamp, query, queryEscape } from './utils/query.js';
|
|
29
|
+
/** Field wrapper used for reading, editing, and container access. */
|
|
16
30
|
export { type Container, Field } from './records/field.js';
|
|
31
|
+
/** Default export. Represents FileMaker host/server. */
|
|
17
32
|
export default FMHost;
|
|
18
|
-
export {
|
|
33
|
+
export {
|
|
34
|
+
/** Namespace re-export of library public types. */
|
|
35
|
+
type TYPES };
|
package/dist/index.js
CHANGED
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
import FMHost from './connection/FMHost.js';
|
|
5
|
+
/** Error type returned for FileMaker Data API failures. */
|
|
5
6
|
export { FMError } from './FMError.js';
|
|
7
|
+
/** Base database connection abstraction used by all auth modes. */
|
|
6
8
|
export { Database } from './connection/database.js';
|
|
9
|
+
/** Layout-scoped API wrapper for metadata, scripts, and record operations. */
|
|
7
10
|
export { Layout } from './layouts/layout.js';
|
|
11
|
+
/** Base record implementation shared by layout and portal records. */
|
|
8
12
|
export { RecordBase } from './records/recordBase.js';
|
|
13
|
+
/** Record returned from a layout. Supports fetch, commit, duplicate, and delete. */
|
|
9
14
|
export { LayoutRecord } from './records/layoutRecord.js';
|
|
15
|
+
/** Record returned from a portal row. */
|
|
10
16
|
export { PortalRecord } from './records/portalRecord.js';
|
|
17
|
+
/** Wrapper around portal rows included in a layout record. */
|
|
11
18
|
export { Portal } from './records/portal.js';
|
|
19
|
+
/** Types and builder used for list/find record operations. */
|
|
12
20
|
export { RecordGetOperation } from './records/getOperations/recordGetOperation.js';
|
|
21
|
+
/** Entry point for `layout.records.*` operations. */
|
|
13
22
|
export { LayoutRecordManager } from './layouts/layoutRecordManager.js';
|
|
23
|
+
/** Helpers for safe FileMaker find query construction and date/time formatting. */
|
|
14
24
|
export { asDate, asTime, asTimestamp, query, queryEscape } from './utils/query.js';
|
|
25
|
+
/** Field wrapper used for reading, editing, and container access. */
|
|
15
26
|
export { Field } from './records/field.js';
|
|
27
|
+
/** Default export. Represents FileMaker host/server. */
|
|
16
28
|
export default FMHost;
|
package/dist/layouts/layout.d.ts
CHANGED
|
@@ -3,13 +3,15 @@ import { type Script, type ScriptResult } from '../types.js';
|
|
|
3
3
|
import { type LayoutInterface } from './layoutInterface.js';
|
|
4
4
|
import { type LayoutBase } from './layoutBase.js';
|
|
5
5
|
import { type DatabaseBase } from '../connection/databaseBase.js';
|
|
6
|
-
import {
|
|
6
|
+
import { ApiLayoutMetadata } from '../models/apiResults.js';
|
|
7
|
+
import { type z } from 'zod';
|
|
7
8
|
export declare class Layout<T extends LayoutInterface> implements LayoutBase {
|
|
8
9
|
readonly database: DatabaseBase;
|
|
9
10
|
readonly name: string;
|
|
10
11
|
readonly records: LayoutRecordManager<T>;
|
|
11
|
-
metadata: ApiLayoutMetadata | null;
|
|
12
|
+
metadata: z.infer<typeof ApiLayoutMetadata> | null;
|
|
12
13
|
constructor(database: DatabaseBase, name: string);
|
|
14
|
+
/** Base endpoint for this layout on FileMaker Data API. */
|
|
13
15
|
get endpoint(): string;
|
|
14
16
|
/**
|
|
15
17
|
* Executes a FileMaker script on this layout asynchronously and returns the result.
|
|
@@ -23,5 +25,5 @@ export declare class Layout<T extends LayoutInterface> implements LayoutBase {
|
|
|
23
25
|
* @returns {Promise<ApiLayoutMetadata>} The layout metadata.
|
|
24
26
|
* @throws {FMError} If an error occurs during the API request.
|
|
25
27
|
*/
|
|
26
|
-
getLayoutMeta(): Promise<ApiLayoutMetadata
|
|
28
|
+
getLayoutMeta(): Promise<z.infer<typeof ApiLayoutMetadata>>;
|
|
27
29
|
}
|
package/dist/layouts/layout.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { LayoutRecordManager } from './layoutRecordManager.js';
|
|
5
5
|
import { FMError } from '../FMError.js';
|
|
6
|
+
import { ApiLayoutMetadata, ApiScriptResult } from '../models/apiResults.js';
|
|
6
7
|
export class Layout {
|
|
7
8
|
database;
|
|
8
9
|
name;
|
|
@@ -12,6 +13,7 @@ export class Layout {
|
|
|
12
13
|
this.database = database;
|
|
13
14
|
this.name = name;
|
|
14
15
|
}
|
|
16
|
+
/** Base endpoint for this layout on FileMaker Data API. */
|
|
15
17
|
get endpoint() {
|
|
16
18
|
return `${this.database.endpoint}/layouts/${this.name}`;
|
|
17
19
|
}
|
|
@@ -24,19 +26,15 @@ export class Layout {
|
|
|
24
26
|
let url = `${this.endpoint}/script/${encodeURIComponent(script.name)}`;
|
|
25
27
|
if (script.parameter)
|
|
26
28
|
url += '?script.param=' + encodeURIComponent(script.parameter);
|
|
27
|
-
const res = await this.database.
|
|
29
|
+
const res = await this.database.fetchJSON(url, {
|
|
30
|
+
type: ApiScriptResult,
|
|
28
31
|
method: 'GET'
|
|
29
32
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
throw new FMError(res.messages[0].code, res.httpStatus, res);
|
|
39
|
-
}
|
|
33
|
+
const error = parseInt(res.scriptError);
|
|
34
|
+
return {
|
|
35
|
+
scriptError: error ? new FMError(error, 200, res) : undefined,
|
|
36
|
+
scriptResult: res.scriptResult
|
|
37
|
+
};
|
|
40
38
|
}
|
|
41
39
|
/**
|
|
42
40
|
* Retrieves the layout metadata
|
|
@@ -48,10 +46,8 @@ export class Layout {
|
|
|
48
46
|
if (this.metadata) {
|
|
49
47
|
return this.metadata;
|
|
50
48
|
}
|
|
51
|
-
const res = await this.database.
|
|
52
|
-
|
|
53
|
-
throw new FMError(res.messages[0].code, res.httpStatus, res);
|
|
54
|
-
this.metadata = res.response;
|
|
49
|
+
const res = await this.database.fetchJSON(this.endpoint, { type: ApiLayoutMetadata });
|
|
50
|
+
this.metadata = res;
|
|
55
51
|
return this.metadata;
|
|
56
52
|
}
|
|
57
53
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type Script, type ScriptResult } from '../types.js';
|
|
2
2
|
import { type DatabaseBase } from '../connection/databaseBase.js';
|
|
3
3
|
import { type ApiLayoutMetadata } from '../models/apiResults.js';
|
|
4
|
+
import { type z } from 'zod';
|
|
4
5
|
export interface LayoutBase {
|
|
5
6
|
readonly name: string;
|
|
6
|
-
metadata: ApiLayoutMetadata | null;
|
|
7
|
+
metadata: z.infer<typeof ApiLayoutMetadata> | null;
|
|
7
8
|
endpoint: string;
|
|
8
9
|
runScript: (script: Script) => Promise<ScriptResult>;
|
|
9
|
-
getLayoutMeta: () => Promise<ApiLayoutMetadata
|
|
10
|
+
getLayoutMeta: () => Promise<z.infer<typeof ApiLayoutMetadata>>;
|
|
10
11
|
database: DatabaseBase;
|
|
11
12
|
}
|
|
@@ -3,33 +3,24 @@ import { type LayoutInterface } from './layoutInterface.js';
|
|
|
3
3
|
import { type LayoutBase } from './layoutBase.js';
|
|
4
4
|
import { type GetOperationOptions, RecordGetOperation } from '../records/getOperations/recordGetOperation.js';
|
|
5
5
|
import { type PickPortals, type RecordFetchOptions } from '../types.js';
|
|
6
|
-
/**
|
|
7
|
-
* Manager class for handling layout records.
|
|
8
|
-
*/
|
|
6
|
+
/** Provides the `layout.records.*` methods for a Layout. */
|
|
9
7
|
export declare class LayoutRecordManager<T extends LayoutInterface> {
|
|
10
8
|
readonly layout: LayoutBase;
|
|
11
9
|
constructor(layout: LayoutBase);
|
|
12
10
|
/**
|
|
13
|
-
* Creates a new
|
|
11
|
+
* Creates a new unsaved record for this layout.
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
* @return {Promise<LayoutRecord<PickPortals<T, OPTIONS['portals'][number]>>>}
|
|
17
|
-
* The newly created layout record.
|
|
13
|
+
* Set field values, then call `commit()` to save it.
|
|
18
14
|
*/
|
|
19
15
|
create<OPTIONS extends RecordFetchOptions>(options: OPTIONS): Promise<LayoutRecord<PickPortals<T, OPTIONS['portals'][number]>>>;
|
|
20
16
|
/**
|
|
21
|
-
*
|
|
17
|
+
* Returns one record by FileMaker `recordId`.
|
|
22
18
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @returns {Promise<LayoutRecord<PickPortals<T, never>>>} - A Promise that resolves with the retrieved layout record.
|
|
19
|
+
* Prefer a normal find when you have a business field you can search by.
|
|
26
20
|
*/
|
|
27
21
|
get(recordId: number): Promise<LayoutRecord<PickPortals<T, never>>>;
|
|
28
22
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* @param {Array} options - An array of options for the operation.
|
|
32
|
-
* @return {RecordGetOperation} - A new instance of RecordGetOperation.
|
|
23
|
+
* Starts a list or find request for this layout.
|
|
33
24
|
*/
|
|
34
25
|
list<OPTIONS extends GetOperationOptions<T>>(options: OPTIONS): RecordGetOperation<T, OPTIONS>;
|
|
35
26
|
}
|
|
@@ -3,20 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { LayoutRecord } from '../records/layoutRecord.js';
|
|
5
5
|
import { RecordGetOperation } from '../records/getOperations/recordGetOperation.js';
|
|
6
|
-
/**
|
|
7
|
-
* Manager class for handling layout records.
|
|
8
|
-
*/
|
|
6
|
+
/** Provides the `layout.records.*` methods for a Layout. */
|
|
9
7
|
export class LayoutRecordManager {
|
|
10
8
|
layout;
|
|
11
9
|
constructor(layout) {
|
|
12
10
|
this.layout = layout;
|
|
13
11
|
}
|
|
14
12
|
/**
|
|
15
|
-
* Creates a new
|
|
13
|
+
* Creates a new unsaved record for this layout.
|
|
16
14
|
*
|
|
17
|
-
*
|
|
18
|
-
* @return {Promise<LayoutRecord<PickPortals<T, OPTIONS['portals'][number]>>>}
|
|
19
|
-
* The newly created layout record.
|
|
15
|
+
* Set field values, then call `commit()` to save it.
|
|
20
16
|
*/
|
|
21
17
|
async create(options) {
|
|
22
18
|
const metadata = await this.layout.getLayoutMeta();
|
|
@@ -30,11 +26,9 @@ export class LayoutRecordManager {
|
|
|
30
26
|
return new LayoutRecord(this.layout, -1, 0, fields, portals);
|
|
31
27
|
}
|
|
32
28
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* @param {number} recordId - The identifier of the record to retrieve.
|
|
29
|
+
* Returns one record by FileMaker `recordId`.
|
|
36
30
|
*
|
|
37
|
-
*
|
|
31
|
+
* Prefer a normal find when you have a business field you can search by.
|
|
38
32
|
*/
|
|
39
33
|
async get(recordId) {
|
|
40
34
|
await this.layout.getLayoutMeta();
|
|
@@ -43,10 +37,7 @@ export class LayoutRecordManager {
|
|
|
43
37
|
return record;
|
|
44
38
|
}
|
|
45
39
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* @param {Array} options - An array of options for the operation.
|
|
49
|
-
* @return {RecordGetOperation} - A new instance of RecordGetOperation.
|
|
40
|
+
* Starts a list or find request for this layout.
|
|
50
41
|
*/
|
|
51
42
|
list(options) {
|
|
52
43
|
return new RecordGetOperation(this.layout, options);
|