@proofkit/fmdapi 5.0.0-beta.0

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 (49) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +110 -0
  3. package/dist/esm/adapters/core.d.ts +55 -0
  4. package/dist/esm/adapters/fetch-base-types.d.ts +7 -0
  5. package/dist/esm/adapters/fetch-base.d.ts +60 -0
  6. package/dist/esm/adapters/fetch-base.js +256 -0
  7. package/dist/esm/adapters/fetch-base.js.map +1 -0
  8. package/dist/esm/adapters/fetch.d.ts +27 -0
  9. package/dist/esm/adapters/fetch.js +79 -0
  10. package/dist/esm/adapters/fetch.js.map +1 -0
  11. package/dist/esm/adapters/otto.d.ts +26 -0
  12. package/dist/esm/adapters/otto.js +29 -0
  13. package/dist/esm/adapters/otto.js.map +1 -0
  14. package/dist/esm/client-types.d.ts +226 -0
  15. package/dist/esm/client-types.js +41 -0
  16. package/dist/esm/client-types.js.map +1 -0
  17. package/dist/esm/client.d.ts +133 -0
  18. package/dist/esm/client.js +295 -0
  19. package/dist/esm/client.js.map +1 -0
  20. package/dist/esm/index.d.ts +8 -0
  21. package/dist/esm/index.js +16 -0
  22. package/dist/esm/index.js.map +1 -0
  23. package/dist/esm/tokenStore/file.d.ts +3 -0
  24. package/dist/esm/tokenStore/index.d.ts +3 -0
  25. package/dist/esm/tokenStore/memory.d.ts +3 -0
  26. package/dist/esm/tokenStore/memory.js +21 -0
  27. package/dist/esm/tokenStore/memory.js.map +1 -0
  28. package/dist/esm/tokenStore/types.d.ts +8 -0
  29. package/dist/esm/tokenStore/upstash.d.ts +6 -0
  30. package/dist/esm/utils.d.ts +8 -0
  31. package/dist/esm/utils.js +16 -0
  32. package/dist/esm/utils.js.map +1 -0
  33. package/package.json +99 -0
  34. package/src/adapters/core.ts +62 -0
  35. package/src/adapters/fetch-base-types.ts +5 -0
  36. package/src/adapters/fetch-base.ts +339 -0
  37. package/src/adapters/fetch.ts +95 -0
  38. package/src/adapters/otto.ts +59 -0
  39. package/src/client-types.ts +296 -0
  40. package/src/client.ts +534 -0
  41. package/src/index.ts +11 -0
  42. package/src/tokenStore/file.ts +33 -0
  43. package/src/tokenStore/index.ts +3 -0
  44. package/src/tokenStore/memory.ts +20 -0
  45. package/src/tokenStore/types.ts +7 -0
  46. package/src/tokenStore/upstash.ts +31 -0
  47. package/src/utils.ts +29 -0
  48. package/stubs/fmschema.config.stub.js +17 -0
  49. package/stubs/fmschema.config.stub.mjs +17 -0
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Proof+Geist
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ <p align="center" style="display:flex;justify-content:center; align-items:center;">
2
+ <img height="64px" src="logo-fm.png" style="margin-right:10px;" />
3
+ <img height="50px" src="logo-ts.png" />
4
+ </p>
5
+
6
+ # Claris FileMaker Data API Client for TypeScript
7
+
8
+ This package is designed to make working with the FileMaker Data API much easier. Here's just a few key features:
9
+
10
+ - Handles token refresh for you automatically
11
+ - [Otto](https://ottofms.com/) Data API proxy support
12
+ - TypeScript support for easy auto-completion of your fields
13
+ - Automated type generation based on layout metadata
14
+ - Supports both node and edge runtimes with a customizable token store
15
+ - Customizable adapters allow usage even within the [FileMaker WebViewer](https://fm-webviewer-fetch.proofgeist.com/).
16
+
17
+ ## Installation
18
+
19
+ This library requires zod as a peer depenency and Node 18 or later
20
+
21
+ ```sh
22
+ npm install @proofgeist/fmdapi zod
23
+ ```
24
+
25
+ ```sh
26
+ yarn add @proofgeist/fmdapi zod
27
+ ```
28
+
29
+ ## Upgrading to v4
30
+
31
+ Version 4 changes the way the client is created to allow for Custom Adapters, but the methods on the client remain the same. If you are using the codegen CLI tool, simply re-run codegen after upgrading to apply the changes.
32
+
33
+ ## Quick Start
34
+
35
+ > Note: For the best experience, use the [codegen tool](https://github.com/proofgeist/fmdapi/wiki/codegen) to generate layout-specific clients and get autocomplete hints in your IDE with your actual field names. This minimal example just demonstrates the basic setup
36
+
37
+ Add the following envnironment variables to your project's `.env` file:
38
+
39
+ ```sh
40
+ FM_DATABASE=filename.fmp12
41
+ FM_SERVER=https://filemaker.example.com
42
+
43
+ # if you want to use the OttoFMS Data API Proxy
44
+ OTTO_API_KEY=dk_123456...789
45
+ # otherwise
46
+ FM_USERNAME=admin
47
+ FM_PASSWORD=password
48
+ ```
49
+
50
+ Initialize the client with credentials, depending on your adapter
51
+
52
+ ```typescript
53
+ // to use the OttoFMS Data API Proxy
54
+ import { DataApi, OttoAdapter } from "@proofgeist/fmdapi";
55
+ const client = DataApi({
56
+ adapter: new OttoAdapter({
57
+ auth: { apiKey: process.env.OTTO_API_KEY },
58
+ db: process.env.FM_DATABASE,
59
+ server: process.env.FM_SERVER,
60
+ }),
61
+ });
62
+ ```
63
+
64
+ ```typescript
65
+ // to use the raw Data API
66
+ import { DataApi, FetchAdapter } from "@proofgeist/fmdapi";
67
+ const client = DataApi({
68
+ adapter: new FetchAdapter({
69
+ auth: {
70
+ username: process.env.FM_USERNAME,
71
+ password: process.env.FM_PASSWORD,
72
+ },
73
+ db: process.env.FM_DATABASE,
74
+ server: process.env.FM_SERVER,
75
+ }),
76
+ });
77
+ ```
78
+
79
+ Then, use the client to query your FileMaker database. [View all available methods here](https://github.com/proofgeist/fmdapi/wiki/methods).
80
+
81
+ Basic Example:
82
+
83
+ ```typescript
84
+ const result = await client.list({ layout: "Contacts" });
85
+ ```
86
+
87
+ ## TypeScript Support
88
+
89
+ The basic client will return the generic FileMaker response object by default. You can also create a type for your exepcted response and get a fully typed response that includes your own fields.
90
+
91
+ ```typescript
92
+ type TContact = {
93
+ name: string;
94
+ email: string;
95
+ phone: string;
96
+ };
97
+ const result = await client.list<TContact>({ layout: "Contacts" });
98
+ ```
99
+
100
+ 💡 TIP: For a more ergonomic TypeScript experience, use the [included codegen tool](https://github.com/proofgeist/fmdapi/wiki/codegen) to generate these types based on your FileMaker layout metadata.
101
+
102
+ For full docs, see the [wiki](https://github.com/proofgeist/fmdapi/wiki)
103
+
104
+ ## Edge Runtime Support (v3.0+)
105
+
106
+ Since version 3.0 uses the native `fetch` API, it is compatible with edge runtimes, but there are some additional considerations to avoid overwhelming your FileMaker server with too many connections. If you are developing for the edge, be sure to implement one of the following strategies:
107
+
108
+ - ✅ Use a custom token store (see above) with a persistent storage method such as Upstash
109
+ - ✅ Use a proxy such as the [Otto Data API Proxy](https://www.ottofms.com/docs/otto/working-with-otto/proxy-api-keys/data-api) which handles management of the access tokens itself.
110
+ - Providing an API key to the client instead of username/password will automatically use the Otto proxy
@@ -0,0 +1,55 @@
1
+ import { CreateParams, CreateResponse, DeleteParams, DeleteResponse, FieldData, GetParams, GetResponse, ListParamsRaw, LayoutMetadataResponse, Query, UpdateParams, UpdateResponse } from '../client-types.js';
2
+ export type BaseRequest = {
3
+ layout: string;
4
+ fetch?: RequestInit;
5
+ timeout?: number;
6
+ };
7
+ export type ListOptions = BaseRequest & {
8
+ data: ListParamsRaw;
9
+ };
10
+ export type GetOptions = BaseRequest & {
11
+ data: GetParams & {
12
+ recordId: number;
13
+ };
14
+ };
15
+ export type FindOptions = BaseRequest & {
16
+ data: ListParamsRaw & {
17
+ query: Array<Query>;
18
+ };
19
+ };
20
+ export type CreateOptions = BaseRequest & {
21
+ data: CreateParams & {
22
+ fieldData: Partial<FieldData>;
23
+ };
24
+ };
25
+ export type UpdateOptions = BaseRequest & {
26
+ data: UpdateParams & {
27
+ recordId: number;
28
+ fieldData: Partial<FieldData>;
29
+ };
30
+ };
31
+ export type DeleteOptions = BaseRequest & {
32
+ data: DeleteParams & {
33
+ recordId: number;
34
+ };
35
+ };
36
+ export type ContainerUploadOptions = BaseRequest & {
37
+ data: {
38
+ containerFieldName: string;
39
+ repetition?: string | number;
40
+ file: Blob;
41
+ recordId: string | number;
42
+ modId?: number;
43
+ };
44
+ };
45
+ export type LayoutMetadataOptions = BaseRequest;
46
+ export interface Adapter {
47
+ list: (opts: ListOptions) => Promise<GetResponse>;
48
+ get: (opts: GetOptions) => Promise<GetResponse>;
49
+ find: (opts: FindOptions) => Promise<GetResponse>;
50
+ create: (opts: CreateOptions) => Promise<CreateResponse>;
51
+ update: (opts: UpdateOptions) => Promise<UpdateResponse>;
52
+ delete: (opts: DeleteOptions) => Promise<DeleteResponse>;
53
+ containerUpload: (opts: ContainerUploadOptions) => Promise<void>;
54
+ layoutMetadata: (opts: LayoutMetadataOptions) => Promise<LayoutMetadataResponse>;
55
+ }
@@ -0,0 +1,7 @@
1
+ export type BaseFetchAdapterOptions = {
2
+ server: string;
3
+ db: string;
4
+ };
5
+ export type GetTokenArguments = {
6
+ refresh?: boolean;
7
+ };
@@ -0,0 +1,60 @@
1
+ import { AllLayoutsMetadataResponse, CreateResponse, DeleteResponse, GetResponse, LayoutMetadataResponse, PortalRanges, ScriptsMetadataResponse, UpdateResponse } from '../client-types.js';
2
+ import { Adapter, BaseRequest, ContainerUploadOptions, CreateOptions, DeleteOptions, FindOptions, GetOptions, LayoutMetadataOptions, ListOptions, UpdateOptions } from './core.js';
3
+ import { BaseFetchAdapterOptions, GetTokenArguments } from './fetch-base-types.js';
4
+ export type ExecuteScriptOptions = BaseRequest & {
5
+ script: string;
6
+ scriptParam?: string;
7
+ };
8
+ export declare class BaseFetchAdapter implements Adapter {
9
+ protected server: string;
10
+ protected db: string;
11
+ private refreshToken;
12
+ baseUrl: URL;
13
+ constructor(options: BaseFetchAdapterOptions & {
14
+ refreshToken?: boolean;
15
+ });
16
+ protected getToken: (args?: GetTokenArguments) => Promise<string>;
17
+ protected request: (params: {
18
+ url: string;
19
+ body?: object | FormData;
20
+ query?: Record<string, string>;
21
+ method?: string;
22
+ retry?: boolean;
23
+ portalRanges?: PortalRanges;
24
+ timeout?: number;
25
+ fetchOptions?: RequestInit;
26
+ }) => Promise<unknown>;
27
+ list: (opts: ListOptions) => Promise<GetResponse>;
28
+ get: (opts: GetOptions) => Promise<GetResponse>;
29
+ find: (opts: FindOptions) => Promise<GetResponse>;
30
+ create: (opts: CreateOptions) => Promise<CreateResponse>;
31
+ update: (opts: UpdateOptions) => Promise<UpdateResponse>;
32
+ delete: (opts: DeleteOptions) => Promise<DeleteResponse>;
33
+ layoutMetadata: (opts: LayoutMetadataOptions) => Promise<LayoutMetadataResponse>;
34
+ /**
35
+ * Execute a script within the database
36
+ */
37
+ executeScript: (opts: ExecuteScriptOptions) => Promise<{
38
+ scriptResult?: string | undefined;
39
+ scriptError?: string | undefined;
40
+ "scriptResult.prerequest"?: string | undefined;
41
+ "scriptError.prerequest"?: string | undefined;
42
+ "scriptResult.presort"?: string | undefined;
43
+ "scriptError.presort"?: string | undefined;
44
+ }>;
45
+ /**
46
+ * Returns a list of available layouts on the database.
47
+ */
48
+ layouts: (opts?: Omit<BaseRequest, "layout">) => Promise<AllLayoutsMetadataResponse>;
49
+ /**
50
+ * Returns a list of available scripts on the database.
51
+ */
52
+ scripts: (opts?: Omit<BaseRequest, "layout">) => Promise<ScriptsMetadataResponse>;
53
+ containerUpload: (opts: ContainerUploadOptions) => Promise<void>;
54
+ /**
55
+ * Set global fields for the current session
56
+ */
57
+ globals: (opts: Omit<BaseRequest, "layout"> & {
58
+ globalFields: Record<string, string | number>;
59
+ }) => Promise<Record<string, never>>;
60
+ }
@@ -0,0 +1,256 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { FileMakerError } from "../client-types.js";
5
+ class BaseFetchAdapter {
6
+ constructor(options) {
7
+ __publicField(this, "server");
8
+ __publicField(this, "db");
9
+ __publicField(this, "refreshToken");
10
+ __publicField(this, "baseUrl");
11
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
+ __publicField(this, "getToken", async (args) => {
13
+ throw new Error("getToken method not implemented by Fetch Adapter");
14
+ });
15
+ __publicField(this, "request", async (params) => {
16
+ var _a, _b;
17
+ const {
18
+ query,
19
+ body,
20
+ method = "GET",
21
+ retry = false,
22
+ fetchOptions = {}
23
+ } = params;
24
+ const url = new URL(`${this.baseUrl}${params.url}`);
25
+ if (query) {
26
+ const { _sort, ...rest } = query;
27
+ const searchParams = new URLSearchParams(rest);
28
+ if (query.portalRanges && typeof query.portalRanges === "object") {
29
+ for (const [portalName, value] of Object.entries(
30
+ query.portalRanges
31
+ )) {
32
+ if (value) {
33
+ if (value.offset && value.offset > 0) {
34
+ searchParams.set(
35
+ `_offset.${portalName}`,
36
+ value.offset.toString()
37
+ );
38
+ }
39
+ if (value.limit) {
40
+ searchParams.set(`_limit.${portalName}`, value.limit.toString());
41
+ }
42
+ }
43
+ }
44
+ }
45
+ if (_sort) {
46
+ searchParams.set("_sort", JSON.stringify(_sort));
47
+ }
48
+ searchParams.delete("portalRanges");
49
+ url.search = searchParams.toString();
50
+ }
51
+ if (body && "portalRanges" in body) {
52
+ for (const [portalName, value] of Object.entries(
53
+ body.portalRanges
54
+ )) {
55
+ if (value) {
56
+ if (value.offset && value.offset > 0) {
57
+ url.searchParams.set(
58
+ `_offset.${portalName}`,
59
+ value.offset.toString()
60
+ );
61
+ }
62
+ if (value.limit) {
63
+ url.searchParams.set(
64
+ `_limit.${portalName}`,
65
+ value.limit.toString()
66
+ );
67
+ }
68
+ }
69
+ }
70
+ delete body.portalRanges;
71
+ }
72
+ const controller = new AbortController();
73
+ let timeout = null;
74
+ if (params.timeout)
75
+ timeout = setTimeout(() => controller.abort(), params.timeout);
76
+ const token = await this.getToken({ refresh: retry });
77
+ const headers = new Headers(fetchOptions == null ? void 0 : fetchOptions.headers);
78
+ headers.set("Authorization", `Bearer ${token}`);
79
+ if (!(body instanceof FormData)) {
80
+ headers.set("Content-Type", "application/json");
81
+ }
82
+ const res = await fetch(url.toString(), {
83
+ ...fetchOptions,
84
+ method,
85
+ body: body instanceof FormData ? body : body ? JSON.stringify(body) : void 0,
86
+ headers,
87
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
88
+ // @ts-ignore
89
+ signal: controller.signal
90
+ });
91
+ if (timeout) clearTimeout(timeout);
92
+ let respData;
93
+ try {
94
+ respData = await res.json();
95
+ } catch {
96
+ respData = {};
97
+ }
98
+ if (!res.ok) {
99
+ if (((_a = respData == null ? void 0 : respData.messages) == null ? void 0 : _a[0].code) === "952" && !retry && this.refreshToken) {
100
+ return this.request({ ...params, retry: true });
101
+ } else {
102
+ throw new FileMakerError(
103
+ ((_b = respData == null ? void 0 : respData.messages) == null ? void 0 : _b[0].code) ?? "500",
104
+ `Filemaker Data API failed with (${res.status}): ${JSON.stringify(
105
+ respData,
106
+ null,
107
+ 2
108
+ )}`
109
+ );
110
+ }
111
+ }
112
+ return respData.response;
113
+ });
114
+ __publicField(this, "list", async (opts) => {
115
+ const { data, layout } = opts;
116
+ const resp = await this.request({
117
+ url: `/layouts/${layout}/records`,
118
+ query: data,
119
+ fetchOptions: opts.fetch,
120
+ timeout: opts.timeout
121
+ });
122
+ return resp;
123
+ });
124
+ __publicField(this, "get", async (opts) => {
125
+ const { data, layout } = opts;
126
+ const resp = await this.request({
127
+ url: `/layouts/${layout}/records/${data.recordId}`,
128
+ fetchOptions: opts.fetch,
129
+ timeout: opts.timeout
130
+ });
131
+ return resp;
132
+ });
133
+ __publicField(this, "find", async (opts) => {
134
+ const { data, layout } = opts;
135
+ const resp = await this.request({
136
+ url: `/layouts/${layout}/_find`,
137
+ body: data,
138
+ method: "POST",
139
+ fetchOptions: opts.fetch,
140
+ timeout: opts.timeout
141
+ });
142
+ return resp;
143
+ });
144
+ __publicField(this, "create", async (opts) => {
145
+ const { data, layout } = opts;
146
+ const resp = await this.request({
147
+ url: `/layouts/${layout}/records`,
148
+ body: data,
149
+ method: "POST",
150
+ fetchOptions: opts.fetch,
151
+ timeout: opts.timeout
152
+ });
153
+ return resp;
154
+ });
155
+ __publicField(this, "update", async (opts) => {
156
+ const {
157
+ data: { recordId, ...data },
158
+ layout
159
+ } = opts;
160
+ const resp = await this.request({
161
+ url: `/layouts/${layout}/records/${recordId}`,
162
+ body: data,
163
+ method: "PATCH",
164
+ fetchOptions: opts.fetch,
165
+ timeout: opts.timeout
166
+ });
167
+ return resp;
168
+ });
169
+ __publicField(this, "delete", async (opts) => {
170
+ const { data, layout } = opts;
171
+ const resp = await this.request({
172
+ url: `/layouts/${layout}/records/${data.recordId}`,
173
+ method: "DELETE",
174
+ fetchOptions: opts.fetch,
175
+ timeout: opts.timeout
176
+ });
177
+ return resp;
178
+ });
179
+ __publicField(this, "layoutMetadata", async (opts) => {
180
+ return await this.request({
181
+ url: `/layouts/${opts.layout}`,
182
+ fetchOptions: opts.fetch,
183
+ timeout: opts.timeout
184
+ });
185
+ });
186
+ /**
187
+ * Execute a script within the database
188
+ */
189
+ __publicField(this, "executeScript", async (opts) => {
190
+ const { script, scriptParam, layout } = opts;
191
+ const resp = await this.request({
192
+ url: `/layouts/${layout}/script/${script}`,
193
+ query: scriptParam ? { "script.param": scriptParam } : void 0,
194
+ fetchOptions: opts.fetch,
195
+ timeout: opts.timeout
196
+ });
197
+ return resp;
198
+ });
199
+ /**
200
+ * Returns a list of available layouts on the database.
201
+ */
202
+ __publicField(this, "layouts", async (opts) => {
203
+ return await this.request({
204
+ url: "/layouts",
205
+ fetchOptions: opts == null ? void 0 : opts.fetch,
206
+ timeout: opts == null ? void 0 : opts.timeout
207
+ });
208
+ });
209
+ /**
210
+ * Returns a list of available scripts on the database.
211
+ */
212
+ __publicField(this, "scripts", async (opts) => {
213
+ return await this.request({
214
+ url: "/scripts",
215
+ fetchOptions: opts == null ? void 0 : opts.fetch,
216
+ timeout: opts == null ? void 0 : opts.timeout
217
+ });
218
+ });
219
+ __publicField(this, "containerUpload", async (opts) => {
220
+ let url = `/layouts/${opts.layout}/records/${opts.data.recordId}/containers/${opts.data.containerFieldName}`;
221
+ if (opts.data.repetition) url += `/${opts.data.repetition}`;
222
+ const formData = new FormData();
223
+ formData.append("upload", opts.data.file);
224
+ await this.request({
225
+ url,
226
+ method: "POST",
227
+ body: formData,
228
+ timeout: opts.timeout,
229
+ fetchOptions: opts.fetch
230
+ });
231
+ });
232
+ /**
233
+ * Set global fields for the current session
234
+ */
235
+ __publicField(this, "globals", async (opts) => {
236
+ return await this.request({
237
+ url: "/globals",
238
+ method: "PATCH",
239
+ body: { globalFields: opts.globalFields },
240
+ fetchOptions: opts == null ? void 0 : opts.fetch,
241
+ timeout: opts == null ? void 0 : opts.timeout
242
+ });
243
+ });
244
+ this.server = options.server;
245
+ this.db = options.db;
246
+ this.refreshToken = options.refreshToken ?? false;
247
+ this.baseUrl = new URL(
248
+ `${this.server}/fmi/data/vLatest/databases/${this.db}`
249
+ );
250
+ if (this.db === "") throw new Error("Database name is required");
251
+ }
252
+ }
253
+ export {
254
+ BaseFetchAdapter
255
+ };
256
+ //# sourceMappingURL=fetch-base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-base.js","sources":["../../../src/adapters/fetch-base.ts"],"sourcesContent":["import type {\n AllLayoutsMetadataResponse,\n CreateResponse,\n DeleteResponse,\n GetResponse,\n LayoutMetadataResponse,\n PortalRanges,\n RawFMResponse,\n ScriptResponse,\n ScriptsMetadataResponse,\n UpdateResponse,\n} from \"../client-types.js\";\nimport { FileMakerError } from \"../client-types.js\";\nimport type {\n Adapter,\n BaseRequest,\n ContainerUploadOptions,\n CreateOptions,\n DeleteOptions,\n FindOptions,\n GetOptions,\n LayoutMetadataOptions,\n ListOptions,\n UpdateOptions,\n} from \"./core.js\";\nimport type {\n BaseFetchAdapterOptions,\n GetTokenArguments,\n} from \"./fetch-base-types.js\";\n\nexport type ExecuteScriptOptions = BaseRequest & {\n script: string;\n scriptParam?: string;\n};\n\nexport class BaseFetchAdapter implements Adapter {\n protected server: string;\n protected db: string;\n private refreshToken: boolean;\n baseUrl: URL;\n\n constructor(options: BaseFetchAdapterOptions & { refreshToken?: boolean }) {\n this.server = options.server;\n this.db = options.db;\n this.refreshToken = options.refreshToken ?? false;\n this.baseUrl = new URL(\n `${this.server}/fmi/data/vLatest/databases/${this.db}`,\n );\n\n if (this.db === \"\") throw new Error(\"Database name is required\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n protected getToken = async (args?: GetTokenArguments): Promise<string> => {\n // method must be implemented in subclass\n throw new Error(\"getToken method not implemented by Fetch Adapter\");\n };\n\n protected request = async (params: {\n url: string;\n body?: object | FormData;\n query?: Record<string, string>;\n method?: string;\n retry?: boolean;\n portalRanges?: PortalRanges;\n timeout?: number;\n fetchOptions?: RequestInit;\n }): Promise<unknown> => {\n const {\n query,\n body,\n method = \"GET\",\n retry = false,\n fetchOptions = {},\n } = params;\n\n const url = new URL(`${this.baseUrl}${params.url}`);\n\n if (query) {\n const { _sort, ...rest } = query;\n const searchParams = new URLSearchParams(rest);\n if (query.portalRanges && typeof query.portalRanges === \"object\") {\n for (const [portalName, value] of Object.entries(\n query.portalRanges as PortalRanges,\n )) {\n if (value) {\n if (value.offset && value.offset > 0) {\n searchParams.set(\n `_offset.${portalName}`,\n value.offset.toString(),\n );\n }\n if (value.limit) {\n searchParams.set(`_limit.${portalName}`, value.limit.toString());\n }\n }\n }\n }\n if (_sort) {\n searchParams.set(\"_sort\", JSON.stringify(_sort));\n }\n searchParams.delete(\"portalRanges\");\n url.search = searchParams.toString();\n }\n\n if (body && \"portalRanges\" in body) {\n for (const [portalName, value] of Object.entries(\n body.portalRanges as PortalRanges,\n )) {\n if (value) {\n if (value.offset && value.offset > 0) {\n url.searchParams.set(\n `_offset.${portalName}`,\n value.offset.toString(),\n );\n }\n if (value.limit) {\n url.searchParams.set(\n `_limit.${portalName}`,\n value.limit.toString(),\n );\n }\n }\n }\n delete body.portalRanges;\n }\n\n const controller = new AbortController();\n let timeout: NodeJS.Timeout | null = null;\n if (params.timeout)\n timeout = setTimeout(() => controller.abort(), params.timeout);\n\n const token = await this.getToken({ refresh: retry });\n\n const headers = new Headers(fetchOptions?.headers);\n headers.set(\"Authorization\", `Bearer ${token}`);\n\n // Only set Content-Type for JSON bodies\n if (!(body instanceof FormData)) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n const res = await fetch(url.toString(), {\n ...fetchOptions,\n method,\n body:\n body instanceof FormData\n ? body\n : body\n ? JSON.stringify(body)\n : undefined,\n headers,\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n signal: controller.signal,\n });\n\n if (timeout) clearTimeout(timeout);\n\n let respData: RawFMResponse;\n try {\n respData = await res.json();\n } catch {\n respData = {};\n }\n\n if (!res.ok) {\n if (\n respData?.messages?.[0].code === \"952\" &&\n !retry &&\n this.refreshToken\n ) {\n // token expired, get new token and retry once\n return this.request({ ...params, retry: true });\n } else {\n throw new FileMakerError(\n respData?.messages?.[0].code ?? \"500\",\n `Filemaker Data API failed with (${res.status}): ${JSON.stringify(\n respData,\n null,\n 2,\n )}`,\n );\n }\n }\n\n return respData.response;\n };\n\n public list = async (opts: ListOptions): Promise<GetResponse> => {\n const { data, layout } = opts;\n\n const resp = await this.request({\n url: `/layouts/${layout}/records`,\n query: data as Record<string, string>,\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as GetResponse;\n };\n\n public get = async (opts: GetOptions): Promise<GetResponse> => {\n const { data, layout } = opts;\n const resp = await this.request({\n url: `/layouts/${layout}/records/${data.recordId}`,\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as GetResponse;\n };\n\n public find = async (opts: FindOptions): Promise<GetResponse> => {\n const { data, layout } = opts;\n const resp = await this.request({\n url: `/layouts/${layout}/_find`,\n body: data,\n method: \"POST\",\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as GetResponse;\n };\n\n public create = async (opts: CreateOptions): Promise<CreateResponse> => {\n const { data, layout } = opts;\n const resp = await this.request({\n url: `/layouts/${layout}/records`,\n body: data,\n method: \"POST\",\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as CreateResponse;\n };\n\n public update = async (opts: UpdateOptions): Promise<UpdateResponse> => {\n const {\n data: { recordId, ...data },\n layout,\n } = opts;\n const resp = await this.request({\n url: `/layouts/${layout}/records/${recordId}`,\n body: data,\n method: \"PATCH\",\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as UpdateResponse;\n };\n\n public delete = async (opts: DeleteOptions): Promise<DeleteResponse> => {\n const { data, layout } = opts;\n const resp = await this.request({\n url: `/layouts/${layout}/records/${data.recordId}`,\n method: \"DELETE\",\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as DeleteResponse;\n };\n\n public layoutMetadata = async (\n opts: LayoutMetadataOptions,\n ): Promise<LayoutMetadataResponse> => {\n return (await this.request({\n url: `/layouts/${opts.layout}`,\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n })) as LayoutMetadataResponse;\n };\n\n /**\n * Execute a script within the database\n */\n public executeScript = async (opts: ExecuteScriptOptions) => {\n const { script, scriptParam, layout } = opts;\n const resp = await this.request({\n url: `/layouts/${layout}/script/${script}`,\n query: scriptParam ? { \"script.param\": scriptParam } : undefined,\n fetchOptions: opts.fetch,\n timeout: opts.timeout,\n });\n return resp as ScriptResponse;\n };\n\n /**\n * Returns a list of available layouts on the database.\n */\n public layouts = async (opts?: Omit<BaseRequest, \"layout\">) => {\n return (await this.request({\n url: \"/layouts\",\n fetchOptions: opts?.fetch,\n timeout: opts?.timeout,\n })) as AllLayoutsMetadataResponse;\n };\n\n /**\n * Returns a list of available scripts on the database.\n */\n public scripts = async (opts?: Omit<BaseRequest, \"layout\">) => {\n return (await this.request({\n url: \"/scripts\",\n fetchOptions: opts?.fetch,\n timeout: opts?.timeout,\n })) as ScriptsMetadataResponse;\n };\n\n public containerUpload = async (opts: ContainerUploadOptions) => {\n let url = `/layouts/${opts.layout}/records/${opts.data.recordId}/containers/${opts.data.containerFieldName}`;\n if (opts.data.repetition) url += `/${opts.data.repetition}`;\n const formData = new FormData();\n formData.append(\"upload\", opts.data.file);\n\n await this.request({\n url,\n method: \"POST\",\n body: formData,\n timeout: opts.timeout,\n fetchOptions: opts.fetch,\n });\n };\n\n /**\n * Set global fields for the current session\n */\n public globals = async (\n opts: Omit<BaseRequest, \"layout\"> & {\n globalFields: Record<string, string | number>;\n },\n ) => {\n return (await this.request({\n url: \"/globals\",\n method: \"PATCH\",\n body: { globalFields: opts.globalFields },\n fetchOptions: opts?.fetch,\n timeout: opts?.timeout,\n })) as Record<string, never>;\n };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { TokenStoreDefinitions } from '../tokenStore/types.js';
2
+ import { BaseFetchAdapterOptions, GetTokenArguments } from './fetch-base-types.js';
3
+ import { BaseFetchAdapter } from './fetch-base.js';
4
+ export interface FetchAdapterOptions extends BaseFetchAdapterOptions {
5
+ auth: {
6
+ username: string;
7
+ password: string;
8
+ };
9
+ tokenStore?: TokenStoreDefinitions;
10
+ }
11
+ export declare class FetchAdapter extends BaseFetchAdapter {
12
+ private username;
13
+ private password;
14
+ private tokenStore;
15
+ private getTokenKey;
16
+ constructor(args: FetchAdapterOptions);
17
+ /**
18
+ * Gets a FileMaker Data API token for authentication.
19
+ *
20
+ * This token is **NOT** guaranteed to be valid, since it expires 15 minutes after the last use. Pass `refresh=true` to forcibly get a fresh token
21
+ *
22
+ * @param args.refresh - If true, forces getting a new token instead of using cached token
23
+ * @internal This method is intended for internal use, you should not need to use it in most cases.
24
+ */
25
+ getToken: (args?: GetTokenArguments) => Promise<string>;
26
+ disconnect: () => Promise<void>;
27
+ }
@@ -0,0 +1,79 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ import { FileMakerError } from "../client-types.js";
5
+ import { memoryStore } from "../tokenStore/memory.js";
6
+ import { BaseFetchAdapter } from "./fetch-base.js";
7
+ class FetchAdapter extends BaseFetchAdapter {
8
+ constructor(args) {
9
+ var _a;
10
+ super({ ...args, refreshToken: true });
11
+ __publicField(this, "username");
12
+ __publicField(this, "password");
13
+ __publicField(this, "tokenStore");
14
+ __publicField(this, "getTokenKey");
15
+ /**
16
+ * Gets a FileMaker Data API token for authentication.
17
+ *
18
+ * This token is **NOT** guaranteed to be valid, since it expires 15 minutes after the last use. Pass `refresh=true` to forcibly get a fresh token
19
+ *
20
+ * @param args.refresh - If true, forces getting a new token instead of using cached token
21
+ * @internal This method is intended for internal use, you should not need to use it in most cases.
22
+ */
23
+ __publicField(this, "getToken", async (args) => {
24
+ const { refresh = false } = args ?? {};
25
+ let token = null;
26
+ if (!refresh) {
27
+ token = await this.tokenStore.getToken(this.getTokenKey());
28
+ }
29
+ if (!token) {
30
+ const res = await fetch(`${this.baseUrl}/sessions`, {
31
+ method: "POST",
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ Authorization: `Basic ${Buffer.from(
35
+ `${this.username}:${this.password}`
36
+ ).toString("base64")}`
37
+ }
38
+ });
39
+ if (!res.ok) {
40
+ const data = await res.json();
41
+ throw new FileMakerError(
42
+ data.messages[0].code,
43
+ data.messages[0].message
44
+ );
45
+ }
46
+ token = res.headers.get("X-FM-Data-Access-Token");
47
+ if (!token) throw new Error("Could not get token");
48
+ this.tokenStore.setToken(this.getTokenKey(), token);
49
+ }
50
+ return token;
51
+ });
52
+ __publicField(this, "disconnect", async () => {
53
+ const token = await this.tokenStore.getToken(this.getTokenKey());
54
+ if (token) {
55
+ await this.request({
56
+ url: `/sessions/${token}`,
57
+ method: "DELETE",
58
+ fetchOptions: {
59
+ headers: {
60
+ Authorization: `Bearer ${token}`,
61
+ "Content-Type": "application/json"
62
+ }
63
+ }
64
+ });
65
+ this.tokenStore.clearToken(this.getTokenKey());
66
+ }
67
+ });
68
+ this.username = args.auth.username;
69
+ this.password = args.auth.password;
70
+ this.tokenStore = args.tokenStore ?? memoryStore();
71
+ this.getTokenKey = ((_a = args.tokenStore) == null ? void 0 : _a.getKey) ?? (() => `${args.server}/${args.db}`);
72
+ if (this.username === "") throw new Error("Username is required");
73
+ if (this.password === "") throw new Error("Password is required");
74
+ }
75
+ }
76
+ export {
77
+ FetchAdapter
78
+ };
79
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sources":["../../../src/adapters/fetch.ts"],"sourcesContent":["import { FileMakerError } from \"../client-types.js\";\nimport memoryStore from \"../tokenStore/memory.js\";\nimport type { TokenStoreDefinitions } from \"../tokenStore/types.js\";\nimport type {\n BaseFetchAdapterOptions,\n GetTokenArguments,\n} from \"./fetch-base-types.js\";\nimport { BaseFetchAdapter } from \"./fetch-base.js\";\n\nexport interface FetchAdapterOptions extends BaseFetchAdapterOptions {\n auth: {\n username: string;\n password: string;\n };\n tokenStore?: TokenStoreDefinitions;\n}\n\nexport class FetchAdapter extends BaseFetchAdapter {\n private username: string;\n private password: string;\n private tokenStore: Omit<TokenStoreDefinitions, \"getKey\">;\n private getTokenKey: Required<TokenStoreDefinitions>[\"getKey\"];\n\n constructor(args: FetchAdapterOptions) {\n super({ ...args, refreshToken: true });\n this.username = args.auth.username;\n this.password = args.auth.password;\n this.tokenStore = args.tokenStore ?? memoryStore();\n this.getTokenKey =\n args.tokenStore?.getKey ?? (() => `${args.server}/${args.db}`);\n\n if (this.username === \"\") throw new Error(\"Username is required\");\n if (this.password === \"\") throw new Error(\"Password is required\");\n }\n\n /**\n * Gets a FileMaker Data API token for authentication.\n *\n * This token is **NOT** guaranteed to be valid, since it expires 15 minutes after the last use. Pass `refresh=true` to forcibly get a fresh token\n *\n * @param args.refresh - If true, forces getting a new token instead of using cached token\n * @internal This method is intended for internal use, you should not need to use it in most cases.\n */\n public override getToken = async (\n args?: GetTokenArguments,\n ): Promise<string> => {\n const { refresh = false } = args ?? {};\n let token: string | null = null;\n if (!refresh) {\n token = await this.tokenStore.getToken(this.getTokenKey());\n }\n\n if (!token) {\n const res = await fetch(`${this.baseUrl}/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Basic ${Buffer.from(\n `${this.username}:${this.password}`,\n ).toString(\"base64\")}`,\n },\n });\n\n if (!res.ok) {\n const data = await res.json();\n throw new FileMakerError(\n data.messages[0].code,\n data.messages[0].message,\n );\n }\n token = res.headers.get(\"X-FM-Data-Access-Token\");\n if (!token) throw new Error(\"Could not get token\");\n this.tokenStore.setToken(this.getTokenKey(), token);\n }\n\n return token;\n };\n\n public disconnect = async (): Promise<void> => {\n const token = await this.tokenStore.getToken(this.getTokenKey());\n if (token) {\n await this.request({\n url: `/sessions/${token}`,\n method: \"DELETE\",\n fetchOptions: {\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n },\n });\n this.tokenStore.clearToken(this.getTokenKey());\n }\n };\n}\n"],"names":[],"mappings":";;;;;;AAiBO,MAAM,qBAAqB,iBAAiB;AAAA,EAMjD,YAAY,MAA2B;;AACrC,UAAM,EAAE,GAAG,MAAM,cAAc,MAAM;AAN/B;AACA;AACA;AACA;AAsBQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAAW,OACzB,SACoB;AACpB,YAAM,EAAE,UAAU,MAAM,IAAI,QAAQ,CAAC;AACrC,UAAI,QAAuB;AAC3B,UAAI,CAAC,SAAS;AACZ,gBAAQ,MAAM,KAAK,WAAW,SAAS,KAAK,aAAa;AAAA,MAAA;AAG3D,UAAI,CAAC,OAAO;AACV,cAAM,MAAM,MAAM,MAAM,GAAG,KAAK,OAAO,aAAa;AAAA,UAClD,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,eAAe,SAAS,OAAO;AAAA,cAC7B,GAAG,KAAK,QAAQ,IAAI,KAAK,QAAQ;AAAA,YAAA,EACjC,SAAS,QAAQ,CAAC;AAAA,UAAA;AAAA,QACtB,CACD;AAEG,YAAA,CAAC,IAAI,IAAI;AACL,gBAAA,OAAO,MAAM,IAAI,KAAK;AAC5B,gBAAM,IAAI;AAAA,YACR,KAAK,SAAS,CAAC,EAAE;AAAA,YACjB,KAAK,SAAS,CAAC,EAAE;AAAA,UACnB;AAAA,QAAA;AAEM,gBAAA,IAAI,QAAQ,IAAI,wBAAwB;AAChD,YAAI,CAAC,MAAa,OAAA,IAAI,MAAM,qBAAqB;AACjD,aAAK,WAAW,SAAS,KAAK,YAAA,GAAe,KAAK;AAAA,MAAA;AAG7C,aAAA;AAAA,IACT;AAEO,sCAAa,YAA2B;AAC7C,YAAM,QAAQ,MAAM,KAAK,WAAW,SAAS,KAAK,aAAa;AAC/D,UAAI,OAAO;AACT,cAAM,KAAK,QAAQ;AAAA,UACjB,KAAK,aAAa,KAAK;AAAA,UACvB,QAAQ;AAAA,UACR,cAAc;AAAA,YACZ,SAAS;AAAA,cACP,eAAe,UAAU,KAAK;AAAA,cAC9B,gBAAgB;AAAA,YAAA;AAAA,UAClB;AAAA,QACF,CACD;AACD,aAAK,WAAW,WAAW,KAAK,YAAA,CAAa;AAAA,MAAA;AAAA,IAEjD;AApEO,SAAA,WAAW,KAAK,KAAK;AACrB,SAAA,WAAW,KAAK,KAAK;AACrB,SAAA,aAAa,KAAK,cAAc,YAAY;AAC5C,SAAA,gBACH,UAAK,eAAL,mBAAiB,YAAW,MAAM,GAAG,KAAK,MAAM,IAAI,KAAK,EAAE;AAE7D,QAAI,KAAK,aAAa,GAAU,OAAA,IAAI,MAAM,sBAAsB;AAChE,QAAI,KAAK,aAAa,GAAU,OAAA,IAAI,MAAM,sBAAsB;AAAA,EAAA;AA8DpE;"}