@jd-data-limited/easy-fm 3.1.6 → 3.2.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.
- package/README.md +16 -2
- package/package.json +1 -1
- package/src/connection/database.d.ts +3 -3
- package/src/connection/database.js +11 -23
- package/src/connection/database.ts +28 -34
- package/src/connection/databaseBase.d.ts +2 -0
- package/src/connection/databaseBase.ts +2 -0
- package/src/records/field.d.ts +3 -4
- package/src/records/field.js +21 -26
- package/src/records/field.ts +21 -31
- package/src/types.d.ts +2 -5
- package/src/types.ts +3 -4
package/README.md
CHANGED
|
@@ -7,7 +7,21 @@ a [FileMaker database](https://www.claris.com/filemaker/) stored on
|
|
|
7
7
|
a [FileMaker server](https://www.claris.com/filemaker/server/)
|
|
8
8
|
or [FileMaker Cloud](https://store.claris.com/filemaker-cloud). This module interacts with your server using the
|
|
9
9
|
[FileMaker Data API](https://help.claris.com/en/data-api-guide/content/index.html).
|
|
10
|
-
|
|
10
|
+
<!-- TOC -->
|
|
11
|
+
* [easy-fm](#easy-fm)
|
|
12
|
+
* [FileMaker setup instructions](#filemaker-setup-instructions)
|
|
13
|
+
* [Before you begin](#before-you-begin)
|
|
14
|
+
* [Connecting to a database](#connecting-to-a-database)
|
|
15
|
+
* [Getting records](#getting-records)
|
|
16
|
+
* [Fetch a range of records](#fetch-a-range-of-records)
|
|
17
|
+
* [Searching for records](#searching-for-records)
|
|
18
|
+
* [Fetch a record using its record ID (NOT RECOMMENDED)](#fetch-a-record-using-its-record-id-not-recommended)
|
|
19
|
+
* [Create a record](#create-a-record)
|
|
20
|
+
* [Modify a record](#modify-a-record)
|
|
21
|
+
* [Field names](#field-names)
|
|
22
|
+
* [Portal names](#portal-names)
|
|
23
|
+
* [Typescript Implementation](#typescript-implementation)
|
|
24
|
+
<!-- TOC -->
|
|
11
25
|
## FileMaker setup instructions
|
|
12
26
|
|
|
13
27
|
1. Enable the FileMaker Data API from the server's admin console. This setting is located
|
|
@@ -20,7 +34,7 @@ or [FileMaker Cloud](https://store.claris.com/filemaker-cloud). This module inte
|
|
|
20
34
|
## Before you begin
|
|
21
35
|
|
|
22
36
|
- You need to know what your server's UTC time offset (in minutes) is.
|
|
23
|
-
- Running `0 - (new Date()).getTimezoneOffset()` in javascript will the UTC time offset for your current timezone.
|
|
37
|
+
- Running `0 - (new Date()).getTimezoneOffset()` in javascript will give you the UTC time offset for your current timezone.
|
|
24
38
|
|
|
25
39
|
## Connecting to a database
|
|
26
40
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
2
|
import { EventEmitter } from "events";
|
|
4
3
|
import { Request } from "node-fetch";
|
|
4
|
+
import { LayoutInterface } from "../layouts/layoutInterface.js";
|
|
5
5
|
import { Layout } from "../layouts/layout.js";
|
|
6
|
-
import * as http from "http";
|
|
7
6
|
import { databaseOptionsWithExternalSources, DatabaseStructure, Script } from "../types.js";
|
|
8
7
|
import { HostBase } from "./HostBase.js";
|
|
9
8
|
import { DatabaseBase } from "./databaseBase";
|
|
@@ -21,8 +20,9 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
|
|
|
21
20
|
get endpoint(): string;
|
|
22
21
|
apiRequest<T = any>(url: string | Request, options?: any, autoRelogin?: boolean): Promise<T>;
|
|
23
22
|
getLayout<R extends string>(name: R): Layout<T["layouts"][R]>;
|
|
23
|
+
getLayout<R extends LayoutInterface>(name: string): Layout<R>;
|
|
24
24
|
setGlobals(globalFields: any): Promise<void>;
|
|
25
25
|
script(name: any, parameter?: string): Script;
|
|
26
26
|
_tokenExpired(): void;
|
|
27
|
-
streamContainer(
|
|
27
|
+
streamContainer(url: any): Promise<import("node-fetch").Response>;
|
|
28
28
|
}
|
|
@@ -15,8 +15,6 @@ import fetch from "node-fetch";
|
|
|
15
15
|
import { generateAuthorizationHeaders } from "./generateAuthorizationHeaders.js";
|
|
16
16
|
import { FMError } from "../FMError.js";
|
|
17
17
|
import { Layout } from "../layouts/layout.js";
|
|
18
|
-
import * as http from "http";
|
|
19
|
-
import * as https from "https";
|
|
20
18
|
export class Database extends EventEmitter {
|
|
21
19
|
constructor(host, conn) {
|
|
22
20
|
super();
|
|
@@ -158,16 +156,8 @@ export class Database extends EventEmitter {
|
|
|
158
156
|
_tokenExpired() {
|
|
159
157
|
this.emit("token_expired");
|
|
160
158
|
}
|
|
161
|
-
streamContainer(
|
|
162
|
-
return
|
|
163
|
-
if (field.metadata.result !== "container") {
|
|
164
|
-
reject("Cannot stream the field " + field.id + " as it is not a container");
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (!url || typeof url !== "string") {
|
|
168
|
-
reject("Container is empty, or has invalid value");
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
159
|
+
streamContainer(url) {
|
|
160
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
171
161
|
let headers = {};
|
|
172
162
|
if (Object.keys(this.cookies).length !== 0) {
|
|
173
163
|
headers["Cookie"] = Object.keys(this.cookies)
|
|
@@ -177,19 +167,17 @@ export class Database extends EventEmitter {
|
|
|
177
167
|
.join("; ");
|
|
178
168
|
}
|
|
179
169
|
// Automatically switch between the http and https modules, based on which is needed
|
|
180
|
-
|
|
170
|
+
let res = yield fetch(url, {
|
|
181
171
|
headers
|
|
182
|
-
}, (res) => {
|
|
183
|
-
// Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
|
|
184
|
-
if (res.headers['set-cookie']) {
|
|
185
|
-
for (let cookie of res.headers['set-cookie']) {
|
|
186
|
-
let cookie_split = cookie.split("=");
|
|
187
|
-
this.cookies[cookie_split[0]] = cookie_split[1];
|
|
188
|
-
}
|
|
189
|
-
res.headers['set-cookie'] = null;
|
|
190
|
-
}
|
|
191
|
-
resolve(res);
|
|
192
172
|
});
|
|
173
|
+
if (res.headers['set-cookie']) {
|
|
174
|
+
for (let cookie of res.headers['set-cookie']) {
|
|
175
|
+
let cookie_split = cookie.split("=");
|
|
176
|
+
this.cookies[cookie_split[0]] = cookie_split[1];
|
|
177
|
+
}
|
|
178
|
+
res.headers['set-cookie'] = null;
|
|
179
|
+
}
|
|
180
|
+
return res;
|
|
193
181
|
});
|
|
194
182
|
}
|
|
195
183
|
}
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
import {HostBase} from "./HostBase.js"
|
|
22
22
|
import {DatabaseBase} from "./databaseBase";
|
|
23
23
|
|
|
24
|
+
type GetLayoutReturnType<T extends DatabaseStructure, R extends LayoutInterface | string> = R extends string ? T["layouts"][R] : R
|
|
25
|
+
|
|
24
26
|
export class Database<T extends DatabaseStructure> extends EventEmitter implements DatabaseBase {
|
|
25
27
|
private _token: any;
|
|
26
28
|
readonly host: HostBase;
|
|
@@ -143,8 +145,10 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
143
145
|
return (data as any)
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
getLayout<R extends string>(name: R): Layout<T["layouts"][R]>
|
|
147
|
-
|
|
148
|
+
getLayout<R extends string>(name: R): Layout<T["layouts"][R]>
|
|
149
|
+
getLayout<R extends LayoutInterface>(name: string): Layout<R>
|
|
150
|
+
getLayout(name: string): Layout<LayoutInterface> {
|
|
151
|
+
return new Layout<LayoutInterface>(this, name)
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
setGlobals(globalFields): Promise<void> {
|
|
@@ -177,41 +181,31 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
177
181
|
this.emit("token_expired")
|
|
178
182
|
}
|
|
179
183
|
|
|
180
|
-
streamContainer(
|
|
181
|
-
return new Promise((resolve, reject) => {
|
|
182
|
-
if (field.metadata.result !== "container") {
|
|
183
|
-
reject("Cannot stream the field " + field.id + " as it is not a container")
|
|
184
|
-
return
|
|
185
|
-
}
|
|
186
|
-
if (!url || typeof url !== "string") {
|
|
187
|
-
reject("Container is empty, or has invalid value")
|
|
188
|
-
return
|
|
189
|
-
}
|
|
184
|
+
async streamContainer(url) {
|
|
190
185
|
|
|
191
|
-
let headers = {}
|
|
192
|
-
if (Object.keys(this.cookies).length !== 0) {
|
|
193
|
-
headers["Cookie"] = Object.keys(this.cookies)
|
|
194
|
-
.map(key => {
|
|
195
|
-
return key + "=" + this.cookies[key]
|
|
196
|
-
})
|
|
197
|
-
.join("; ")
|
|
198
|
-
}
|
|
199
186
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.cookies[cookie_split[0]] = cookie_split[1]
|
|
209
|
-
}
|
|
210
|
-
res.headers['set-cookie'] = null
|
|
211
|
-
}
|
|
187
|
+
let headers = {}
|
|
188
|
+
if (Object.keys(this.cookies).length !== 0) {
|
|
189
|
+
headers["Cookie"] = Object.keys(this.cookies)
|
|
190
|
+
.map(key => {
|
|
191
|
+
return key + "=" + this.cookies[key]
|
|
192
|
+
})
|
|
193
|
+
.join("; ")
|
|
194
|
+
}
|
|
212
195
|
|
|
213
|
-
|
|
214
|
-
|
|
196
|
+
// Automatically switch between the http and https modules, based on which is needed
|
|
197
|
+
let res = await fetch(url, {
|
|
198
|
+
headers
|
|
215
199
|
})
|
|
200
|
+
|
|
201
|
+
if (res.headers['set-cookie']) {
|
|
202
|
+
for (let cookie of res.headers['set-cookie']) {
|
|
203
|
+
let cookie_split = cookie.split("=")
|
|
204
|
+
this.cookies[cookie_split[0]] = cookie_split[1]
|
|
205
|
+
}
|
|
206
|
+
res.headers['set-cookie'] = null
|
|
207
|
+
}
|
|
208
|
+
return res
|
|
216
209
|
}
|
|
210
|
+
|
|
217
211
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { HostBase } from "./HostBase.js";
|
|
2
2
|
import { Request } from "node-fetch";
|
|
3
|
+
import { Response } from "node-fetch";
|
|
3
4
|
export interface DatabaseBase {
|
|
4
5
|
host: HostBase;
|
|
5
6
|
readonly name: string;
|
|
6
7
|
endpoint: string;
|
|
7
8
|
apiRequest(url: string | Request, options?: any, autoRelogin?: boolean): Promise<any>;
|
|
9
|
+
streamContainer(url: string): Promise<Response>;
|
|
8
10
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import {HostBase} from "./HostBase.js"
|
|
6
6
|
import {Request} from "node-fetch";
|
|
7
7
|
import {DatabaseStructure} from "../types";
|
|
8
|
+
import {Response} from "node-fetch";
|
|
8
9
|
|
|
9
10
|
export interface DatabaseBase {
|
|
10
11
|
host: HostBase,
|
|
@@ -14,4 +15,5 @@ export interface DatabaseBase {
|
|
|
14
15
|
// layouts: DatabaseStructure["layouts"]
|
|
15
16
|
|
|
16
17
|
apiRequest(url: string | Request, options?: any, autoRelogin?: boolean): Promise<any>
|
|
18
|
+
streamContainer(url: string): Promise<Response>
|
|
17
19
|
}
|
package/src/records/field.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { RecordBase } from "./recordBase.js";
|
|
4
|
-
import
|
|
5
|
-
import { ContainerBufferResult, DOWNLOAD_MODES, FieldMetaData } from "../types.js";
|
|
4
|
+
import { ContainerDataStream, DOWNLOAD_MODES, FieldMetaData } from "../types.js";
|
|
6
5
|
export type FieldValue = string | number | Date | Container;
|
|
7
6
|
export type Container = null;
|
|
8
7
|
export declare class Field<T extends FieldValue> {
|
|
@@ -19,6 +18,6 @@ export declare class Field<T extends FieldValue> {
|
|
|
19
18
|
get date(): Date;
|
|
20
19
|
get number(): number;
|
|
21
20
|
upload(buffer: Buffer, filename: string, mime: string): Promise<void>;
|
|
22
|
-
download(mode: DOWNLOAD_MODES.Stream): Promise<
|
|
23
|
-
download(mode: DOWNLOAD_MODES.Buffer): Promise<
|
|
21
|
+
download(mode: DOWNLOAD_MODES.Stream): Promise<ContainerDataStream<NodeJS.ReadableStream>>;
|
|
22
|
+
download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerDataStream<ArrayBuffer>>;
|
|
24
23
|
}
|
package/src/records/field.js
CHANGED
|
@@ -149,32 +149,27 @@ export class Field {
|
|
|
149
149
|
}));
|
|
150
150
|
}
|
|
151
151
|
download(mode = DOWNLOAD_MODES.Stream) {
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
});
|
|
174
|
-
})
|
|
175
|
-
.catch(e => {
|
|
176
|
-
reject(e);
|
|
177
|
-
});
|
|
152
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
if (this.metadata.result !== "container") {
|
|
154
|
+
throw new Error("Cannot stream the field " + this.id + " as it is not a container");
|
|
155
|
+
}
|
|
156
|
+
if (!this.value || typeof this.value !== "string") {
|
|
157
|
+
throw new Error("Container is empty, or has invalid value");
|
|
158
|
+
}
|
|
159
|
+
let res = yield this.record.layout.database.streamContainer(this.string);
|
|
160
|
+
let data;
|
|
161
|
+
switch (mode) {
|
|
162
|
+
case DOWNLOAD_MODES.Buffer:
|
|
163
|
+
data = yield res.arrayBuffer();
|
|
164
|
+
break;
|
|
165
|
+
case DOWNLOAD_MODES.Stream:
|
|
166
|
+
data = res.body;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
data,
|
|
171
|
+
mime: res.headers["content-type"]
|
|
172
|
+
};
|
|
178
173
|
});
|
|
179
174
|
}
|
|
180
175
|
}
|
package/src/records/field.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import {RecordBase, RecordTypes} from "./recordBase.js";
|
|
6
6
|
import fetch, {File, FormData} from "node-fetch";
|
|
7
7
|
import * as http from "http";
|
|
8
|
-
import {
|
|
8
|
+
import {ContainerDataStream, DOWNLOAD_MODES, FieldMetaData} from "../types.js";
|
|
9
9
|
import {FMError} from "../FMError.js";
|
|
10
10
|
|
|
11
11
|
export type FieldValue = string | number | Date | Container
|
|
@@ -158,36 +158,26 @@ export class Field<T extends FieldValue> {
|
|
|
158
158
|
})
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
download(mode: DOWNLOAD_MODES.Stream): Promise<
|
|
162
|
-
download(mode: DOWNLOAD_MODES.Buffer): Promise<
|
|
163
|
-
download(mode: DOWNLOAD_MODES = DOWNLOAD_MODES.Stream): Promise<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return
|
|
171
|
-
}
|
|
161
|
+
download(mode: DOWNLOAD_MODES.Stream): Promise<ContainerDataStream<NodeJS.ReadableStream>>
|
|
162
|
+
download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerDataStream<ArrayBuffer>>
|
|
163
|
+
async download(mode: DOWNLOAD_MODES = DOWNLOAD_MODES.Stream): Promise<ContainerDataStream> {
|
|
164
|
+
if (this.metadata.result !== "container") {
|
|
165
|
+
throw new Error("Cannot stream the field " + this.id + " as it is not a container")
|
|
166
|
+
}
|
|
167
|
+
if (!this.value || typeof this.value !== "string") {
|
|
168
|
+
throw new Error("Container is empty, or has invalid value")
|
|
169
|
+
}
|
|
172
170
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
request: stream
|
|
185
|
-
} as ContainerBufferResult)
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
.catch(e => {
|
|
189
|
-
reject(e)
|
|
190
|
-
})
|
|
191
|
-
})
|
|
171
|
+
let res = await this.record.layout.database.streamContainer(this.string)
|
|
172
|
+
let data: NodeJS.ReadableStream | ArrayBuffer
|
|
173
|
+
switch (mode) {
|
|
174
|
+
case DOWNLOAD_MODES.Buffer: data = await res.arrayBuffer(); break
|
|
175
|
+
case DOWNLOAD_MODES.Stream: data = res.body; break
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
data,
|
|
180
|
+
mime: res.headers["content-type"]
|
|
181
|
+
}
|
|
192
182
|
}
|
|
193
183
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
/// <reference types="node" />
|
|
3
|
-
import * as http from "http";
|
|
4
2
|
import { FMError } from "./FMError.js";
|
|
5
3
|
import { LayoutInterface } from "./layouts/layoutInterface";
|
|
6
4
|
export interface DatabaseStructure {
|
|
@@ -108,10 +106,9 @@ export interface ScriptResult {
|
|
|
108
106
|
scriptError?: FMError;
|
|
109
107
|
scriptResult?: string;
|
|
110
108
|
}
|
|
111
|
-
export interface
|
|
112
|
-
|
|
109
|
+
export interface ContainerDataStream<T = NodeJS.ReadableStream | ArrayBuffer> {
|
|
110
|
+
data: T;
|
|
113
111
|
mime: string;
|
|
114
|
-
request: http.IncomingMessage;
|
|
115
112
|
}
|
|
116
113
|
export interface AuthorizationHeaders {
|
|
117
114
|
"Content-Type": "application/json";
|
package/src/types.ts
CHANGED
|
@@ -132,10 +132,9 @@ export interface ScriptResult {
|
|
|
132
132
|
scriptResult?: string
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
export interface
|
|
136
|
-
|
|
137
|
-
mime: string
|
|
138
|
-
request: http.IncomingMessage
|
|
135
|
+
export interface ContainerDataStream<T = NodeJS.ReadableStream | ArrayBuffer> {
|
|
136
|
+
data: T,
|
|
137
|
+
mime: string
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
//
|