@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.
Files changed (43) hide show
  1. package/README.md +60 -197
  2. package/dist/bin/generateTypes.js +1 -1
  3. package/dist/bin/stressSearch.d.ts +2 -0
  4. package/dist/bin/stressSearch.js +243 -0
  5. package/dist/connection/CookieJar.d.ts +19 -0
  6. package/dist/connection/CookieJar.js +99 -0
  7. package/dist/connection/FMHost.d.ts +24 -8
  8. package/dist/connection/FMHost.js +28 -8
  9. package/dist/connection/HostBase.d.ts +4 -2
  10. package/dist/connection/Session.d.ts +34 -0
  11. package/dist/connection/Session.js +109 -0
  12. package/dist/connection/database.d.ts +34 -31
  13. package/dist/connection/database.js +72 -133
  14. package/dist/connection/databaseBase.d.ts +22 -10
  15. package/dist/connection/databaseConstantSession.d.ts +15 -0
  16. package/dist/connection/databaseConstantSession.js +70 -0
  17. package/dist/connection/databaseSessionPool.d.ts +14 -0
  18. package/dist/connection/databaseSessionPool.js +148 -0
  19. package/dist/index.d.ts +18 -1
  20. package/dist/index.js +12 -0
  21. package/dist/layouts/layout.d.ts +5 -3
  22. package/dist/layouts/layout.js +11 -15
  23. package/dist/layouts/layoutBase.d.ts +3 -2
  24. package/dist/layouts/layoutRecordManager.d.ts +6 -15
  25. package/dist/layouts/layoutRecordManager.js +6 -15
  26. package/dist/models/apiResults.d.ts +208 -93
  27. package/dist/models/apiResults.js +95 -25
  28. package/dist/records/field.d.ts +17 -2
  29. package/dist/records/field.js +161 -237
  30. package/dist/records/getOperations/recordGetOperation.d.ts +7 -4
  31. package/dist/records/getOperations/recordGetOperation.js +15 -18
  32. package/dist/records/layoutRecord.d.ts +24 -7
  33. package/dist/records/layoutRecord.js +68 -73
  34. package/dist/records/portal.d.ts +0 -4
  35. package/dist/records/portal.js +0 -4
  36. package/dist/records/recordBase.d.ts +3 -2
  37. package/dist/types.d.ts +41 -17
  38. package/dist/types.js +12 -0
  39. package/dist/utils/addHeaders.d.ts +5 -0
  40. package/dist/utils/addHeaders.js +24 -0
  41. package/dist/utils/query.d.ts +7 -0
  42. package/dist/utils/query.js +5 -0
  43. package/package.json +33 -26
@@ -1,16 +1,28 @@
1
1
  import { type HostBase } from './HostBase.js';
2
- import { type ApiResults } from '../models/apiResults.js';
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
- token: string;
9
- _apiRequestJSON: <T = unknown>(url: URL | RequestInfo, options?: RequestInit & {
10
- headers?: Record<string, string>;
11
- } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>;
12
- _apiRequestRaw: (url: URL | RequestInfo, options?: RequestInit & {
13
- headers?: Record<string, string>;
14
- useCookieJar?: boolean;
15
- } | undefined) => Promise<Response>;
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 { type TYPES };
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;
@@ -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 { type ApiLayoutMetadata } from '../models/apiResults.js';
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
  }
@@ -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._apiRequestJSON(url, {
29
+ const res = await this.database.fetchJSON(url, {
30
+ type: ApiScriptResult,
28
31
  method: 'GET'
29
32
  });
30
- if (res.response && res.messages[0].code === '0') {
31
- const error = parseInt(res.response.scriptError);
32
- return {
33
- scriptError: error ? new FMError(error, 200, res) : undefined,
34
- scriptResult: res.response.scriptResult
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._apiRequestJSON(this.endpoint);
52
- if (!res.response)
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 layout record with the provided options.
11
+ * Creates a new unsaved record for this layout.
14
12
  *
15
- * @param {OPTIONS} options - The options for creating the layout record.
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
- * Retrieves a layout record based on the given recordId.
17
+ * Returns one record by FileMaker `recordId`.
22
18
  *
23
- * @param {number} recordId - The identifier of the record to retrieve.
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
- * Creates a new instance of RecordGetOperation with the given options.
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 layout record with the provided options.
13
+ * Creates a new unsaved record for this layout.
16
14
  *
17
- * @param {OPTIONS} options - The options for creating the layout record.
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
- * Retrieves a layout record based on the given recordId.
34
- *
35
- * @param {number} recordId - The identifier of the record to retrieve.
29
+ * Returns one record by FileMaker `recordId`.
36
30
  *
37
- * @returns {Promise<LayoutRecord<PickPortals<T, never>>>} - A Promise that resolves with the retrieved layout record.
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
- * Creates a new instance of RecordGetOperation with the given options.
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);