@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 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,6 +1,6 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "3.1.6",
3
+ "version": "3.2.0",
4
4
  "dependencies": {
5
5
  "@types/btoa": "^1.2.3",
6
6
  "@types/events": "^3.0.0",
@@ -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(field: any, url: any): Promise<http.IncomingMessage>;
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(field, url) {
162
- return new Promise((resolve, reject) => {
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
- (url.startsWith("https") ? https : http).get(url, {
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
- return new Layout<T["layouts"][R]>(this, name)
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(field, url): Promise<http.IncomingMessage> {
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
- // Automatically switch between the http and https modules, based on which is needed
201
- (url.startsWith("https") ? https : http).get(url, {
202
- headers
203
- }, (res) => {
204
- // Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
205
- if (res.headers['set-cookie']) {
206
- for (let cookie of res.headers['set-cookie']) {
207
- let cookie_split = cookie.split("=")
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
- resolve(res)
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
  }
@@ -1,8 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  import { RecordBase } from "./recordBase.js";
4
- import * as http from "http";
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<http.IncomingMessage>;
23
- download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerBufferResult>;
21
+ download(mode: DOWNLOAD_MODES.Stream): Promise<ContainerDataStream<NodeJS.ReadableStream>>;
22
+ download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerDataStream<ArrayBuffer>>;
24
23
  }
@@ -149,32 +149,27 @@ export class Field {
149
149
  }));
150
150
  }
151
151
  download(mode = DOWNLOAD_MODES.Stream) {
152
- return new Promise((resolve, reject) => {
153
- // @ts-ignore
154
- this.record.layout.database.streamContainer(this, this._value)
155
- .then(stream => {
156
- if (mode === DOWNLOAD_MODES.Stream) {
157
- resolve(stream);
158
- return;
159
- }
160
- let body = [];
161
- stream.on("data", chunk => {
162
- body.push(chunk);
163
- });
164
- stream.on("error", (e) => {
165
- reject(e);
166
- });
167
- stream.on("end", () => {
168
- resolve({
169
- buffer: Buffer.concat(body),
170
- mime: stream.headers["content-type"],
171
- request: stream
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
  }
@@ -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 {ContainerBufferResult, DOWNLOAD_MODES, FieldMetaData} from "../types.js";
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<http.IncomingMessage>
162
- download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerBufferResult>
163
- download(mode: DOWNLOAD_MODES = DOWNLOAD_MODES.Stream): Promise<http.IncomingMessage | ContainerBufferResult> {
164
- return new Promise((resolve, reject) => {
165
- // @ts-ignore
166
- this.record.layout.database.streamContainer(this, this._value)
167
- .then(stream => {
168
- if (mode === DOWNLOAD_MODES.Stream) {
169
- resolve(stream)
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
- let body = []
174
- stream.on("data", chunk => {
175
- body.push(chunk)
176
- })
177
- stream.on("error", (e) => {
178
- reject(e)
179
- })
180
- stream.on("end", () => {
181
- resolve({
182
- buffer: Buffer.concat(body),
183
- mime: stream.headers["content-type"],
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 ContainerBufferResult {
112
- buffer: Buffer;
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 ContainerBufferResult {
136
- buffer: Buffer,
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
  //