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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +93 -91
  3. package/dist/bin/cli.js +34 -5
  4. package/dist/bin/generateClient.d.ts +1 -0
  5. package/dist/bin/generateClient.js +48 -0
  6. package/dist/bin/generateTypes.js +19 -18
  7. package/dist/bin/init.d.ts +6 -0
  8. package/dist/bin/init.js +46 -0
  9. package/dist/bin/stressSearch.js +12 -12
  10. package/dist/connection/DataApiProvider.d.ts +15 -0
  11. package/dist/connection/DataApiProvider.js +246 -0
  12. package/dist/connection/dataApiDatabaseSession.d.ts +11 -0
  13. package/dist/connection/dataApiDatabaseSession.js +139 -0
  14. package/dist/connection/database.d.ts +22 -56
  15. package/dist/connection/database.js +175 -111
  16. package/dist/connection/databaseBase.d.ts +9 -22
  17. package/dist/connection/databaseBase.js +0 -3
  18. package/dist/connection/databaseProvider.d.ts +196 -0
  19. package/dist/connection/databaseProvider.js +6 -0
  20. package/dist/connection/databaseSessionPool.d.ts +8 -7
  21. package/dist/connection/databaseSessionPool.js +110 -106
  22. package/dist/index.d.ts +4 -3
  23. package/dist/index.js +3 -3
  24. package/dist/layouts/layout.d.ts +0 -2
  25. package/dist/layouts/layout.js +6 -16
  26. package/dist/layouts/layoutBase.d.ts +0 -1
  27. package/dist/layouts/layoutInterface.d.ts +4 -2
  28. package/dist/records/fields/baseField.d.ts +4 -1
  29. package/dist/records/fields/containerField.d.ts +6 -23
  30. package/dist/records/fields/containerField.js +34 -68
  31. package/dist/records/fields/valueField.js +6 -6
  32. package/dist/records/getOperations/recordGetOperation.js +19 -18
  33. package/dist/records/layoutRecord.d.ts +8 -2
  34. package/dist/records/layoutRecord.js +45 -74
  35. package/dist/records/portalRecord.d.ts +4 -1
  36. package/dist/records/portalRecord.js +12 -2
  37. package/dist/records/recordBase.d.ts +6 -3
  38. package/dist/records/recordBase.js +6 -5
  39. package/dist/schema.d.ts +54 -0
  40. package/dist/schema.js +87 -0
  41. package/dist/typegen/dataApiSchemaSource.d.ts +3 -0
  42. package/dist/typegen/dataApiSchemaSource.js +32 -0
  43. package/dist/typegen/dependencies.d.ts +11 -0
  44. package/dist/typegen/dependencies.js +67 -0
  45. package/dist/typegen/generate.d.ts +5 -0
  46. package/dist/typegen/generate.js +85 -0
  47. package/dist/typegen/index.d.ts +6 -0
  48. package/dist/typegen/index.js +6 -0
  49. package/dist/typegen/init.d.ts +13 -0
  50. package/dist/typegen/init.js +113 -0
  51. package/dist/typegen/types.d.ts +40 -0
  52. package/dist/typegen/types.js +1 -0
  53. package/dist/typegen/zod.d.ts +3 -0
  54. package/dist/typegen/zod.js +32 -0
  55. package/package.json +26 -4
  56. package/dist/layouts/recordFieldsMap.d.ts +0 -2
  57. package/dist/layouts/recordFieldsMap.js +0 -4
  58. package/dist/records/field.d.ts +0 -77
  59. package/dist/records/field.js +0 -192
@@ -1,140 +1,204 @@
1
- /*
2
- * Copyright (c) 2023-2024. See LICENSE file for more information
3
- */
4
- import { FMError } from '../FMError.js';
5
1
  import { Layout } from '../layouts/layout.js';
6
- import { ApiLayout, ApiResults } from '../models/apiResults.js';
7
- import { z } from 'zod';
8
- import { addHeaders } from '../utils/addHeaders.js';
9
- import process from 'node:process';
10
- /**
11
- * Represents a database connection.
12
- * @template T - The structure of the database.
13
- */
2
+ import { ContainerSessionAffinityError, ProviderSessionExpiredError } from './databaseProvider.js';
14
3
  export class Database {
15
- host;
16
4
  name;
17
5
  debug;
6
+ #provider;
18
7
  #layoutCache = new Map();
19
- canOpenNewConnections = true;
20
- /**
21
- * Used during events where the application must logout
22
- * @private
23
- */
24
- abortController = new AbortController();
25
- constructor(host, conn) {
26
- this.host = host;
27
- this.name = conn.database;
28
- this.debug = conn.debug ?? false;
29
- process.on('SIGINT', () => { void this.close(); });
30
- process.on('SIGTERM', () => { void this.close(); });
31
- process.on('beforeExit', () => { void this.close(); });
8
+ #abortController = new AbortController();
9
+ #sessions = [];
10
+ #closingSessions = new Set();
11
+ #queue = [];
12
+ #connectionPromise;
13
+ #closed = false;
14
+ #draining = false;
15
+ constructor(options) {
16
+ this.#provider = options.provider;
17
+ this.name = options.name ?? options.provider.name ?? '';
18
+ this.debug = options.debug ?? false;
19
+ }
20
+ static create(options) {
21
+ return new Database(options);
22
+ }
23
+ async #connection() {
24
+ if (this.#closed)
25
+ throw new Error('Database is closed');
26
+ this.#connectionPromise ??= this.#provider.connect({ signal: this.#abortController.signal, debug: this.debug });
27
+ return await this.#connectionPromise;
28
+ }
29
+ async connect() {
30
+ await this.#connection();
31
+ }
32
+ get dateFormat() {
33
+ return this.#formatting('dateFormat', 'MM/DD/YYYY');
34
+ }
35
+ get timeFormat() {
36
+ return this.#formatting('timeFormat', 'HH:mm:ss');
37
+ }
38
+ get timeStampFormat() {
39
+ return this.#formatting('timeStampFormat', 'MM/DD/YYYY HH:mm:ss');
40
+ }
41
+ #formatting(key, fallback) {
42
+ return this.#provider.formatting[key] ?? fallback;
43
+ }
44
+ async #openSession() {
45
+ const connection = await this.#connection();
46
+ const session = await connection.openSession(this.#abortController.signal);
47
+ if (this.#closed) {
48
+ await session.close();
49
+ throw new Error('Database is closed');
50
+ }
51
+ const active = { binding: { id: Symbol('provider-session') }, session, busy: false, valid: true };
52
+ this.#sessions.push(active);
53
+ return active;
54
+ }
55
+ async #schedule(run, preferred) {
56
+ if (this.#closed)
57
+ throw new Error('Database is closed');
58
+ return await new Promise((resolve, reject) => {
59
+ this.#queue.push({ preferred, run, resolve, reject });
60
+ void this.#drain();
61
+ });
62
+ }
63
+ async #drain() {
64
+ if (this.#draining || this.#closed || this.#queue.length === 0)
65
+ return;
66
+ this.#draining = true;
67
+ try {
68
+ const connection = await this.#connection().catch(error => {
69
+ for (const job of this.#queue.splice(0))
70
+ job.reject(error);
71
+ return undefined;
72
+ });
73
+ if (!connection)
74
+ return;
75
+ for (let index = 0; index < this.#queue.length;) {
76
+ const job = this.#queue[index];
77
+ let active = job.preferred
78
+ ? this.#sessions.find(item => item.binding.id === job.preferred?.id && item.valid && !item.busy)
79
+ : this.#sessions.find(item => item.valid && !item.busy);
80
+ if (!active && job.preferred) {
81
+ if (!this.#sessions.some(item => item.binding.id === job.preferred?.id && item.valid)) {
82
+ this.#queue.splice(index, 1);
83
+ job.reject(new ContainerSessionAffinityError());
84
+ continue;
85
+ }
86
+ index++;
87
+ continue;
88
+ }
89
+ if (!active && !job.preferred && this.#sessions.filter(item => item.valid).length < Math.max(1, connection.maxSessions)) {
90
+ try {
91
+ active = await this.#openSession();
92
+ }
93
+ catch (error) {
94
+ this.#queue.splice(index, 1);
95
+ job.reject(error);
96
+ continue;
97
+ }
98
+ }
99
+ if (!active) {
100
+ index++;
101
+ continue;
102
+ }
103
+ this.#queue.splice(index, 1);
104
+ active.busy = true;
105
+ void job.run(active).then(job.resolve, job.reject).finally(() => {
106
+ active.busy = false;
107
+ void this.#drain();
108
+ });
109
+ }
110
+ }
111
+ finally {
112
+ this.#draining = false;
113
+ }
114
+ }
115
+ async execute(operation) {
116
+ const attempt = async () => await this.#schedule(async (active) => {
117
+ try {
118
+ return { value: await active.session.execute(operation), binding: active.binding };
119
+ }
120
+ catch (error) {
121
+ if (error instanceof ProviderSessionExpiredError)
122
+ this.#invalidate(active);
123
+ throw error;
124
+ }
125
+ });
126
+ try {
127
+ return await attempt();
128
+ }
129
+ catch (error) {
130
+ if (error instanceof ProviderSessionExpiredError && isReadOnly(operation.type))
131
+ return await attempt();
132
+ throw error;
133
+ }
134
+ }
135
+ async fetchContainer(reference, binding, signal) {
136
+ return await this.#schedule(async (active) => {
137
+ try {
138
+ return await active.session.fetchContainer(reference, signal);
139
+ }
140
+ catch (error) {
141
+ if (error instanceof ProviderSessionExpiredError) {
142
+ this.#invalidate(active);
143
+ throw new ContainerSessionAffinityError(undefined, { cause: error });
144
+ }
145
+ throw error;
146
+ }
147
+ }, binding);
148
+ }
149
+ #invalidate(active) {
150
+ if (!active.valid)
151
+ return;
152
+ active.valid = false;
153
+ const index = this.#sessions.indexOf(active);
154
+ if (index >= 0)
155
+ this.#sessions.splice(index, 1);
156
+ const closing = active.session.close().catch(() => {
157
+ });
158
+ this.#closingSessions.add(closing);
159
+ void closing.finally(() => this.#closingSessions.delete(closing));
32
160
  }
33
161
  async login() {
34
- await Promise.resolve();
162
+ await this.connect();
35
163
  }
36
164
  async logout() {
37
165
  await this.close();
38
166
  }
39
167
  async close() {
40
- this.canOpenNewConnections = false;
41
- if (!this.abortController.signal.aborted)
42
- this.abortController.abort('Closing connection');
168
+ if (this.#closed)
169
+ return;
170
+ this.#closed = true;
171
+ this.#abortController.abort('Closing database');
172
+ for (const job of this.#queue.splice(0))
173
+ job.reject(new Error('Database is closed'));
174
+ const connection = await this.#connectionPromise?.catch(() => undefined);
175
+ await Promise.allSettled([...this.#closingSessions, ...this.#sessions.map(active => active.session.close())]);
176
+ this.#sessions.splice(0);
177
+ if (connection?.close)
178
+ await connection.close();
43
179
  }
44
180
  async [Symbol.asyncDispose]() {
45
181
  await this.close();
46
182
  }
47
- /**
48
- * Returns the endpoint URL for the database connection.
49
- *
50
- * @returns {string} The endpoint URL.
51
- */
52
- get endpoint() {
53
- return `${this.host.protocol}//${this.host.hostname}/fmi/data/v2/databases/${this.name}`;
54
- }
55
- /**
56
- * Uses an available session to run a fetch
57
- * @param url
58
- * @param options
59
- */
60
- async fetch(url, options) {
61
- return await this.withSession(async (session) => await session.fetch(url, options));
62
- }
63
- /**
64
- * Uses an available session to run a fetch on a FileMaker Data API JSON endpoint. Also applies JSON/Zod type enforcement on result.
65
- */
66
- async fetchJSON(url, options) {
67
- const _options = options ?? {};
68
- addHeaders(_options, {
69
- 'Content-Type': 'application/json'
70
- });
71
- const res = await this.fetch(url, _options);
72
- const rawData = await res.json();
73
- if (this.debug)
74
- console.log(rawData.response);
75
- if (options.type !== null) {
76
- const data = ApiResults.extend({ response: options.type.optional() })
77
- .parse(rawData);
78
- if (data.messages[0].code !== 0) {
79
- throw new FMError(data.messages[0].code, res.status, data);
80
- }
81
- return data.response
82
- // @ts-expect-error is correct
83
- ? {
84
- ...data.response,
85
- httpStatus: res.status
86
- }
87
- // @ts-expect-error is correct
88
- : { httpStatus: res.status };
89
- }
90
- const data = ApiResults.parse(rawData);
91
- if (data.messages[0].code !== 0) {
92
- throw new FMError(data.messages[0].code, res.status, data);
93
- }
94
- // @ts-expect-error is correct
95
- return {
96
- httpStatus: res.status
97
- };
98
- }
99
- /**
100
- * Retrieves a list of layouts in the current FileMaker database.
101
- *
102
- * @returns {Promise<Layout[]>} A promise that resolves to an array of Layout objects.
103
- * @throws {FMError} If there was an error retrieving the layouts.
104
- */
105
183
  async listLayouts(page = 0) {
106
- const res = await this.fetchJSON(`${this.endpoint}/layouts?page=${encodeURIComponent(page)}`, {
107
- type: z.object({ layouts: z.array(ApiLayout) })
108
- });
109
- const cycleLayoutNames = (layouts) => {
110
- let names = [];
111
- for (const layout of layouts) {
112
- if (layout.folderLayoutNames)
113
- names = names.concat(cycleLayoutNames(layout.folderLayoutNames));
114
- else
115
- names.push(layout.name);
116
- }
117
- return names;
118
- };
119
- return cycleLayoutNames(res.layouts).map(layout => new Layout(this, layout));
184
+ const { value } = await this.execute({ type: 'layout.list', page });
185
+ return value.map(name => new Layout(this, name));
120
186
  }
121
- /**
122
- * Returns a FileMaker Layout object by name.
123
- */
124
187
  layout(name) {
125
188
  let layout = this.#layoutCache.get(name);
126
- if (layout)
127
- return layout;
128
- layout = new Layout(this, name);
129
- this.#layoutCache.set(name, layout);
189
+ if (!layout) {
190
+ layout = new Layout(this, name);
191
+ this.#layoutCache.set(name, layout);
192
+ }
130
193
  return layout;
131
194
  }
132
- /** Clears any Layout objects previously returned by `database.layout(...)`. */
133
195
  clearLayoutCache() {
134
196
  this.#layoutCache.clear();
135
197
  }
136
- /** Creates a script reference you can pass to read and write helpers. */
137
198
  script(name, parameter = '') {
138
199
  return { name, parameter };
139
200
  }
140
201
  }
202
+ function isReadOnly(type) {
203
+ return type === 'layout.list' || type === 'layout.metadata' || type === 'record.list' || type === 'record.get';
204
+ }
@@ -1,28 +1,15 @@
1
- import { type HostBase } from './HostBase.js';
2
- import { type z, type ZodType } from 'zod';
1
+ import type { DatabaseOperation, DatabaseOperationResult, DatabaseOperationType, SessionBinding } from './databaseProvider.js';
3
2
  export interface DatabaseBase {
4
- host: HostBase;
5
3
  readonly name: string;
6
- endpoint: string;
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
- */
4
+ readonly timeFormat: string;
5
+ readonly dateFormat: string;
6
+ readonly timeStampFormat: string;
7
+ connect: () => Promise<void>;
14
8
  close: () => Promise<void>;
15
- /**
16
- * Immediately closes all open sessions and prevents new ones from being created.
17
- * Alias of {@link close}
18
- */
19
9
  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;
10
+ execute: <K extends DatabaseOperationType>(operation: DatabaseOperation<K>) => Promise<{
11
+ value: DatabaseOperationResult<K>;
12
+ binding: SessionBinding;
27
13
  }>;
14
+ fetchContainer: (reference: string, binding: SessionBinding, signal?: AbortSignal) => Promise<Response>;
28
15
  }
@@ -1,4 +1 @@
1
- /*
2
- * Copyright (c) 2023-2024. See LICENSE file for more information
3
- */
4
1
  export {};
@@ -0,0 +1,196 @@
1
+ import type { z } from 'zod';
2
+ import type { ApiFieldData, ApiLayoutMetadata, ApiPortalData } from '../models/apiResults.js';
3
+ export interface DatabaseFormatting {
4
+ readonly dateFormat: string;
5
+ readonly timeFormat: string;
6
+ readonly timeStampFormat: string;
7
+ }
8
+ export interface ProviderContext {
9
+ readonly signal: AbortSignal;
10
+ readonly debug: boolean;
11
+ }
12
+ export interface DatabaseProvider {
13
+ readonly name?: string;
14
+ /** Synchronous formatting defaults, refined in place when connect resolves. */
15
+ readonly formatting: DatabaseFormatting;
16
+ connect: (context: ProviderContext) => Promise<ProviderConnection>;
17
+ }
18
+ export interface ProviderConnection {
19
+ readonly formatting: DatabaseFormatting;
20
+ readonly maxSessions: number;
21
+ openSession: (signal: AbortSignal) => Promise<ProviderSession>;
22
+ close?: () => Promise<void>;
23
+ }
24
+ export interface ProviderSession {
25
+ execute: <K extends DatabaseOperationType>(operation: DatabaseOperation<K>) => Promise<DatabaseOperationResult<K>>;
26
+ /** Use this exact session. Non-HTTP transports may synthesize a Web Response. */
27
+ fetchContainer: (reference: string, signal?: AbortSignal) => Promise<Response>;
28
+ close: () => Promise<void>;
29
+ }
30
+ export interface ProviderRecord {
31
+ recordId: string;
32
+ modId: string;
33
+ fieldData: z.infer<typeof ApiFieldData>;
34
+ portalData?: z.infer<typeof ApiPortalData>;
35
+ }
36
+ export interface RecordQueryClause {
37
+ omit: boolean;
38
+ fields: Record<string, string>;
39
+ }
40
+ export interface RecordOperationOptions {
41
+ scripts: {
42
+ prerequest?: {
43
+ name: string;
44
+ parameter: string;
45
+ };
46
+ presort?: {
47
+ name: string;
48
+ parameter: string;
49
+ };
50
+ after?: {
51
+ name: string;
52
+ parameter: string;
53
+ };
54
+ };
55
+ portals: Record<string, {
56
+ limit: number;
57
+ offset: number;
58
+ }>;
59
+ }
60
+ export interface RecordQueryOptions extends RecordOperationOptions {
61
+ limit: number;
62
+ offset: number;
63
+ sort: Array<{
64
+ fieldName: string;
65
+ sortOrder: 'ascend' | 'descend';
66
+ }>;
67
+ }
68
+ export interface RecordWriteBody {
69
+ fieldData: Record<string, unknown>;
70
+ portalData?: Record<string, unknown>;
71
+ options?: Record<string, unknown>;
72
+ deleteRelatedRecords?: Array<{
73
+ table: string;
74
+ recordId: number;
75
+ }>;
76
+ }
77
+ export interface DatabaseOperationMap {
78
+ 'layout.list': {
79
+ operation: {
80
+ page: number;
81
+ };
82
+ result: string[];
83
+ };
84
+ 'layout.metadata': {
85
+ operation: {
86
+ layout: string;
87
+ };
88
+ result: z.infer<typeof ApiLayoutMetadata>;
89
+ };
90
+ 'script.run': {
91
+ operation: {
92
+ layout: string;
93
+ script: string;
94
+ parameter?: string;
95
+ };
96
+ result: {
97
+ scriptError?: number;
98
+ scriptResult?: string;
99
+ status?: number;
100
+ };
101
+ };
102
+ 'record.list': {
103
+ operation: {
104
+ layout: string;
105
+ query: RecordQueryClause[];
106
+ options: RecordQueryOptions;
107
+ };
108
+ result: ProviderRecord[];
109
+ };
110
+ 'record.get': {
111
+ operation: {
112
+ layout: string;
113
+ recordId: number;
114
+ options: RecordOperationOptions;
115
+ };
116
+ result: ProviderRecord;
117
+ };
118
+ 'record.create': {
119
+ operation: {
120
+ layout: string;
121
+ body: RecordWriteBody;
122
+ options: RecordOperationOptions;
123
+ };
124
+ result: {
125
+ recordId: string;
126
+ modId: string;
127
+ scriptError?: number;
128
+ status?: number;
129
+ };
130
+ };
131
+ 'record.update': {
132
+ operation: {
133
+ layout: string;
134
+ recordId: number;
135
+ body: RecordWriteBody;
136
+ options: RecordOperationOptions;
137
+ };
138
+ result: {
139
+ modId: string;
140
+ scriptError?: number;
141
+ status?: number;
142
+ };
143
+ };
144
+ 'record.duplicate': {
145
+ operation: {
146
+ layout: string;
147
+ recordId: number;
148
+ options: RecordOperationOptions;
149
+ };
150
+ result: {
151
+ recordId: string;
152
+ modId: string;
153
+ scriptError?: number;
154
+ status?: number;
155
+ };
156
+ };
157
+ 'record.delete': {
158
+ operation: {
159
+ layout: string;
160
+ recordId: number;
161
+ options: RecordOperationOptions;
162
+ };
163
+ result: {
164
+ scriptError?: number;
165
+ status?: number;
166
+ };
167
+ };
168
+ 'container.upload': {
169
+ operation: {
170
+ layout: string;
171
+ recordId: number;
172
+ field: string;
173
+ file: File;
174
+ };
175
+ result: undefined;
176
+ };
177
+ }
178
+ export type DatabaseOperationType = keyof DatabaseOperationMap;
179
+ export type DatabaseOperation<K extends DatabaseOperationType = DatabaseOperationType> = K extends DatabaseOperationType ? {
180
+ type: K;
181
+ } & DatabaseOperationMap[K]['operation'] : never;
182
+ export type DatabaseOperationResult<K extends DatabaseOperationType> = DatabaseOperationMap[K]['result'];
183
+ export declare class ProviderSessionExpiredError extends Error {
184
+ constructor(message?: string, options?: ErrorOptions);
185
+ }
186
+ export declare class ContainerSessionAffinityError extends Error {
187
+ constructor(message?: string, options?: ErrorOptions);
188
+ }
189
+ export interface ContainerDownloadOptions {
190
+ signal?: AbortSignal;
191
+ refreshOnSessionLoss?: boolean;
192
+ }
193
+ /** @internal */
194
+ export interface SessionBinding {
195
+ readonly id: symbol;
196
+ }
@@ -0,0 +1,6 @@
1
+ export class ProviderSessionExpiredError extends Error {
2
+ constructor(message = 'Provider session expired', options) { super(message, options); this.name = 'ProviderSessionExpiredError'; }
3
+ }
4
+ export class ContainerSessionAffinityError extends Error {
5
+ constructor(message = 'The session associated with this container is no longer available', options) { super(message, options); this.name = 'ContainerSessionAffinityError'; }
6
+ }
@@ -1,14 +1,15 @@
1
- import { Database } from './database.js';
2
1
  import type { DatabaseStructure } from '../databaseStructure.js';
3
- import { type Session } from './Session.js';
4
2
  import { type databaseOptionsWithExternalSources, type loginOptionsFileMaker } from '../types.js';
5
3
  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> {
4
+ import { type DatabaseEndpoint } from './Session.js';
5
+ import { type DatabaseProvider, type DatabaseExecutor } from './databaseProvider.js';
6
+ /** Reserves independent FileMaker Data API sessions from a bounded pool. */
7
+ export declare class DatabaseSessionPool<T extends DatabaseStructure = DatabaseStructure> implements DatabaseProvider {
10
8
  #private;
9
+ readonly host: FMHost;
11
10
  constructor(host: FMHost, connectionDetails: databaseOptionsWithExternalSources<loginOptionsFileMaker>);
11
+ get formatting(): FMHost;
12
+ get endpoint(): DatabaseEndpoint;
13
+ withSession<R>(callback: (executor: DatabaseExecutor) => Promise<R>): Promise<R>;
12
14
  close(): Promise<void>;
13
- withSession<T>(callback: (session: Session) => Promise<T>, retry?: boolean): Promise<T>;
14
15
  }