@jd-data-limited/easy-fm 5.0.0-beta.1 → 5.0.0-beta.2
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 +3 -2
- package/dist/bin/generateTypes.js +2 -2
- package/dist/bin/stressSearch.js +1 -1
- package/dist/connection/FMHost.d.ts +1 -3
- package/dist/connection/FMHost.js +1 -3
- package/dist/connection/HostBase.d.ts +0 -2
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/layouts/layoutInterface.d.ts +2 -1
- package/dist/models/apiResults.d.ts +7 -6
- package/dist/models/apiResults.js +1 -1
- package/dist/records/fields/baseField.d.ts +57 -0
- package/dist/records/fields/baseField.js +59 -0
- package/dist/records/fields/containerField.d.ts +35 -0
- package/dist/records/fields/containerField.js +78 -0
- package/dist/records/fields/field.d.ts +3 -0
- package/dist/records/fields/field.js +1 -0
- package/dist/records/fields/valueField.d.ts +41 -0
- package/dist/records/fields/valueField.js +108 -0
- package/dist/records/getOperations/recordGetOperation.js +8 -9
- package/dist/records/layoutRecord.d.ts +8 -7
- package/dist/records/layoutRecord.js +16 -24
- package/dist/records/portal.d.ts +1 -1
- package/dist/records/portalBase.d.ts +1 -2
- package/dist/records/portalRecord.d.ts +7 -4
- package/dist/records/portalRecord.js +12 -0
- package/dist/records/recordBase.d.ts +5 -4
- package/dist/records/recordBase.js +32 -33
- package/dist/types.d.ts +1 -1
- package/dist/utils/query.d.ts +21 -12
- package/dist/utils/query.js +58 -23
- package/dist/utils/temporal.d.ts +9 -0
- package/dist/utils/temporal.js +110 -0
- package/package.json +24 -23
|
@@ -4,14 +4,14 @@ import { PortalRecord } from './portalRecord.js';
|
|
|
4
4
|
import { Portal } from './portal.js';
|
|
5
5
|
import { type LayoutInterface } from '../layouts/layoutInterface.js';
|
|
6
6
|
import { type LayoutRecordBase } from './layoutRecordBase.js';
|
|
7
|
-
import { type ApiPortalData, type ApiRowDataDef } from '../models/apiResults.js';
|
|
7
|
+
import { type ApiFieldData, ApiFieldMetadata, type ApiPortalData, type ApiRowDataDef } from '../models/apiResults.js';
|
|
8
8
|
import { type LayoutBase } from '../layouts/layoutBase.js';
|
|
9
|
-
import { type Field, type FieldValue } from './field.js';
|
|
10
9
|
import z from 'zod';
|
|
10
|
+
import { Field } from "./fields/field.js";
|
|
11
11
|
export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAYOUT['fields']> implements LayoutRecordBase {
|
|
12
12
|
portals: LAYOUT['portals'];
|
|
13
13
|
private readonly portalsToInclude;
|
|
14
|
-
constructor(layout: LayoutBase, recordId: number | string, modId?: string | number, fieldData?:
|
|
14
|
+
constructor(layout: LayoutBase, recordId: number | string, modId?: string | number, fieldData?: z.infer<typeof ApiFieldData>, portalData?: z.infer<typeof ApiPortalData> | null, portalsToInclude?: Array<keyof LAYOUT['portals']>);
|
|
15
15
|
/** Returns all loaded portals on this record as an array. */
|
|
16
16
|
get portalsArray(): Array<Portal<any>>;
|
|
17
17
|
/**
|
|
@@ -21,6 +21,7 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
21
21
|
* @throws {FMError} - If an error occurs during the commit process.
|
|
22
22
|
*/
|
|
23
23
|
commit(extraBody?: extraBodyOptions): Promise<this>;
|
|
24
|
+
getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
|
|
24
25
|
protected processPortalData(portalData: z.infer<typeof ApiPortalData>): void;
|
|
25
26
|
/**
|
|
26
27
|
* Reloads this record from FileMaker.
|
|
@@ -36,11 +37,11 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
36
37
|
/** Deletes this record from FileMaker. */
|
|
37
38
|
delete(): Promise<void>;
|
|
38
39
|
/** Returns this record's edited fields as a FileMaker API payload. */
|
|
39
|
-
fieldsToObject(filter?: (a: Field
|
|
40
|
+
fieldsToObject(filter?: (a: Field) => boolean): Omit<z.infer<typeof ApiRowDataDef>, 'portalData'>;
|
|
40
41
|
/** Returns this record and its portal edits as a FileMaker API payload. */
|
|
41
|
-
toObject(filter?: (a: Field
|
|
42
|
-
fieldData: Record<string, string | number>;
|
|
43
|
-
portalData: Record<string, Record<string, string | number>[]>;
|
|
42
|
+
toObject(filter?: (a: Field) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field) => any): {
|
|
43
|
+
fieldData: Record<string, string | number | null>;
|
|
44
|
+
portalData: Record<string, Record<string, string | number | null>[]>;
|
|
44
45
|
modId: string;
|
|
45
46
|
recordId: string;
|
|
46
47
|
portalDataInfo?: {
|
|
@@ -6,8 +6,8 @@ import { PortalRecord } from './portalRecord.js';
|
|
|
6
6
|
import { Portal } from './portal.js';
|
|
7
7
|
import { FMError } from '../FMError.js';
|
|
8
8
|
import { ApiRecordResponseObj } from '../models/apiResults.js';
|
|
9
|
-
import moment from 'moment';
|
|
10
9
|
import z from 'zod';
|
|
10
|
+
import { ValueFieldBase } from "./fields/valueField.js";
|
|
11
11
|
export class LayoutRecord extends RecordBase {
|
|
12
12
|
portals = {};
|
|
13
13
|
portalsToInclude;
|
|
@@ -67,6 +67,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
67
67
|
}
|
|
68
68
|
this.recordId = parseInt(res.recordId);
|
|
69
69
|
this.modId = parseInt(res.modId);
|
|
70
|
+
this._onSave();
|
|
70
71
|
return this;
|
|
71
72
|
}
|
|
72
73
|
// for (let item of Object.keys(data)) extraBody[item] = data[item]
|
|
@@ -90,6 +91,15 @@ export class LayoutRecord extends RecordBase {
|
|
|
90
91
|
this._onSave();
|
|
91
92
|
return this;
|
|
92
93
|
}
|
|
94
|
+
getFieldMetadata(fieldId) {
|
|
95
|
+
if (!this.layout.metadata) {
|
|
96
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
97
|
+
}
|
|
98
|
+
let result = this.layout.metadata.fieldMetaData.find(i => i.name === fieldId);
|
|
99
|
+
if (!result)
|
|
100
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
93
103
|
processPortalData(portalData) {
|
|
94
104
|
for (const portalName of Object.keys(portalData)) {
|
|
95
105
|
const _portal = new Portal(this, portalName);
|
|
@@ -161,37 +171,19 @@ export class LayoutRecord extends RecordBase {
|
|
|
161
171
|
this.emit('deleted');
|
|
162
172
|
}
|
|
163
173
|
/** Returns this record's edited fields as a FileMaker API payload. */
|
|
164
|
-
fieldsToObject(filter = (a) => a.edited) {
|
|
174
|
+
fieldsToObject(filter = (a) => a instanceof ValueFieldBase && a.edited) {
|
|
165
175
|
const fieldsProcessed = {};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let value = field.value;
|
|
169
|
-
if (value instanceof Date) {
|
|
170
|
-
let _value = moment(value);
|
|
171
|
-
_value = _value
|
|
172
|
-
.utcOffset(this.layout.database.host.timezoneOffsetFunc(_value));
|
|
173
|
-
switch (field.metadata.result) {
|
|
174
|
-
case 'time':
|
|
175
|
-
value = _value.format(this.layout.database.host.timeFormat);
|
|
176
|
-
break;
|
|
177
|
-
case 'date':
|
|
178
|
-
value = _value.format(this.layout.database.host.dateFormat);
|
|
179
|
-
break;
|
|
180
|
-
default:
|
|
181
|
-
value = _value.format(this.layout.database.host.timeStampFormat);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
fieldsProcessed[field.id] = value;
|
|
176
|
+
for (const field of this.fieldsArray.filter(field => filter(field))) {
|
|
177
|
+
fieldsProcessed[field.id] = field.serializeRawValue();
|
|
185
178
|
}
|
|
186
|
-
|
|
179
|
+
return {
|
|
187
180
|
recordId: this.recordId.toString(),
|
|
188
181
|
modId: this.modId.toString(),
|
|
189
182
|
fieldData: fieldsProcessed
|
|
190
183
|
};
|
|
191
|
-
return obj;
|
|
192
184
|
}
|
|
193
185
|
/** Returns this record and its portal edits as a FileMaker API payload. */
|
|
194
|
-
toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
|
|
186
|
+
toObject(filter = (a) => a instanceof ValueFieldBase && a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a instanceof ValueFieldBase && a.edited) {
|
|
195
187
|
const obj = {
|
|
196
188
|
...this.fieldsToObject(filter),
|
|
197
189
|
portalData: {}
|
package/dist/records/portal.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PortalRecord } from './portalRecord.js';
|
|
2
|
-
import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
3
2
|
import { type PortalBase } from './portalBase.js';
|
|
4
3
|
import { type LayoutRecord } from './layoutRecord.js';
|
|
4
|
+
import { RecordFieldsMap } from "../layouts/layoutInterface.js";
|
|
5
5
|
/**
|
|
6
6
|
* Represents a portal.
|
|
7
7
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
2
1
|
import { type RecordBase } from './recordBase.js';
|
|
3
2
|
import { type LayoutRecord } from './layoutRecord.js';
|
|
4
|
-
import { type LayoutInterface } from '../layouts/layoutInterface.js';
|
|
3
|
+
import { type LayoutInterface, RecordFieldsMap } from '../layouts/layoutInterface.js';
|
|
5
4
|
export interface PortalBase<T extends RecordFieldsMap> {
|
|
6
5
|
readonly record: LayoutRecord<LayoutInterface>;
|
|
7
6
|
readonly name: string;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { RecordBase } from './recordBase.js';
|
|
2
2
|
import { type extraBodyOptions, RecordTypes } from '../types.js';
|
|
3
|
-
import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
4
3
|
import { type PortalBase } from './portalBase.js';
|
|
5
|
-
import
|
|
4
|
+
import z from "zod";
|
|
5
|
+
import { ApiFieldMetadata } from "../models/apiResults.js";
|
|
6
|
+
import { RecordFieldsMap } from "../layouts/layoutInterface.js";
|
|
7
|
+
import { Field } from "./fields/field.js";
|
|
6
8
|
/**
|
|
7
9
|
* Represents a PortalRecord, which is a record in a portal within a parent record.
|
|
8
10
|
* @template T - The type of the record's field map.
|
|
@@ -18,8 +20,9 @@ export declare class PortalRecord<T extends RecordFieldsMap> extends RecordBase<
|
|
|
18
20
|
* @param {extraBodyOptions} [extraBody={}] - The optional extra body options.
|
|
19
21
|
* @returns {Promise} - A promise that resolves when the record is committed.
|
|
20
22
|
*/
|
|
21
|
-
commit(extraBody?: extraBodyOptions): Promise<import("./layoutRecord.js").LayoutRecord<import("../
|
|
22
|
-
|
|
23
|
+
commit(extraBody?: extraBodyOptions): Promise<import("./layoutRecord.js").LayoutRecord<import("../layouts/layoutInterface.js").LayoutInterface>>;
|
|
24
|
+
getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
|
|
25
|
+
toObject(fieldFilter: (a: Field) => boolean): {
|
|
23
26
|
modId?: string;
|
|
24
27
|
recordId?: string;
|
|
25
28
|
} & Record<string, string>;
|
|
@@ -27,6 +27,18 @@ export class PortalRecord extends RecordBase {
|
|
|
27
27
|
async commit(extraBody = {}) {
|
|
28
28
|
return await this.portal.record.commit(extraBody);
|
|
29
29
|
}
|
|
30
|
+
getFieldMetadata(fieldId) {
|
|
31
|
+
if (!this.layout.metadata) {
|
|
32
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
33
|
+
}
|
|
34
|
+
const portalMetadata = this.portal
|
|
35
|
+
? this.layout.metadata.portalMetaData[this.portal.name] ?? []
|
|
36
|
+
: Object.values(this.layout.metadata.portalMetaData).flat();
|
|
37
|
+
const result = portalMetadata.find(i => i.name === fieldId);
|
|
38
|
+
if (!result)
|
|
39
|
+
throw new Error("Field metadata not found. Ensure you run layout.getLayoutMeta() first.");
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
30
42
|
toObject(fieldFilter) {
|
|
31
43
|
const res = {
|
|
32
44
|
recordId: this.recordId === -1 ? undefined : this.recordId.toString(),
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { RecordTypes } from '../types.js';
|
|
3
|
-
import { type RecordFieldsMap } from '../layouts/recordFieldsMap.js';
|
|
4
3
|
import { type LayoutBase } from '../layouts/layoutBase.js';
|
|
5
|
-
import { type ApiFieldData } from '../models/apiResults.js';
|
|
6
|
-
import { Field, type FieldValue } from './field.js';
|
|
4
|
+
import { type ApiFieldData, type ApiFieldMetadata } from '../models/apiResults.js';
|
|
7
5
|
import { type z } from 'zod';
|
|
6
|
+
import { RecordFieldsMap } from "../layouts/layoutInterface.js";
|
|
7
|
+
import { Field } from "./fields/field.js";
|
|
8
8
|
export declare abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter {
|
|
9
9
|
readonly layout: LayoutBase;
|
|
10
10
|
readonly type: RecordTypes;
|
|
@@ -25,7 +25,8 @@ export declare abstract class RecordBase<T extends RecordFieldsMap> extends Even
|
|
|
25
25
|
* @returns {boolean} A boolean value indicating whether any of the fields have been edited.
|
|
26
26
|
*/
|
|
27
27
|
get edited(): boolean;
|
|
28
|
-
get fieldsArray(): Array<Field
|
|
28
|
+
get fieldsArray(): Array<Field>;
|
|
29
|
+
abstract getFieldMetadata(fieldId: string): z.infer<typeof ApiFieldMetadata>;
|
|
29
30
|
protected processFieldData(fieldData: z.infer<typeof ApiFieldData>): T;
|
|
30
31
|
_onSave(): void;
|
|
31
32
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
|
-
import * as moment from 'moment';
|
|
6
5
|
import { RecordTypes } from '../types.js';
|
|
7
|
-
import {
|
|
6
|
+
import { DateField, NumberField, TextField, TimeField, TimeStampField, ValueFieldBase } from "./fields/valueField.js";
|
|
7
|
+
import { ContainerField } from "./fields/containerField.js";
|
|
8
8
|
export class RecordBase extends EventEmitter {
|
|
9
9
|
layout;
|
|
10
10
|
type = RecordTypes.UNKNOWN;
|
|
@@ -33,49 +33,48 @@ export class RecordBase extends EventEmitter {
|
|
|
33
33
|
* @returns {boolean} A boolean value indicating whether any of the fields have been edited.
|
|
34
34
|
*/
|
|
35
35
|
get edited() {
|
|
36
|
-
return !!this.fieldsArray.find(i => i.edited);
|
|
36
|
+
return !!this.fieldsArray.find(i => i instanceof ValueFieldBase && i.edited);
|
|
37
37
|
}
|
|
38
38
|
get fieldsArray() {
|
|
39
39
|
return Object.values(this.fields);
|
|
40
40
|
}
|
|
41
41
|
processFieldData(fieldData) {
|
|
42
42
|
const fields = {};
|
|
43
|
-
for (const key of Object.
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
_field.set(date);
|
|
68
|
-
_field.updateOriginalContents();
|
|
69
|
-
}
|
|
43
|
+
for (const [key, value] of Object.entries(fieldData)) {
|
|
44
|
+
const fieldMeta = this.getFieldMetadata(key);
|
|
45
|
+
let field;
|
|
46
|
+
switch (fieldMeta.result) {
|
|
47
|
+
case "text":
|
|
48
|
+
field = new TextField(this, key, value);
|
|
49
|
+
break;
|
|
50
|
+
case "number":
|
|
51
|
+
field = new NumberField(this, key, value);
|
|
52
|
+
break;
|
|
53
|
+
case "container":
|
|
54
|
+
field = new ContainerField(this, key, value);
|
|
55
|
+
break;
|
|
56
|
+
case "timeStamp":
|
|
57
|
+
field = new TimeStampField(this, key, value);
|
|
58
|
+
break;
|
|
59
|
+
case "time":
|
|
60
|
+
field = new TimeField(this, key, value);
|
|
61
|
+
break;
|
|
62
|
+
case "date":
|
|
63
|
+
field = new DateField(this, key, value);
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`Attempted to parse unknown field type: ${fieldMeta.result}`);
|
|
70
67
|
}
|
|
71
|
-
fields[key] =
|
|
68
|
+
fields[key] = field;
|
|
72
69
|
}
|
|
73
70
|
this.fields = fields;
|
|
74
71
|
return fields;
|
|
75
72
|
}
|
|
76
73
|
_onSave() {
|
|
77
74
|
this.emit('saved');
|
|
78
|
-
for (const field of this.fieldsArray)
|
|
79
|
-
field
|
|
75
|
+
for (const field of this.fieldsArray) {
|
|
76
|
+
if (field instanceof ValueFieldBase)
|
|
77
|
+
field.updateOriginalContents();
|
|
78
|
+
}
|
|
80
79
|
}
|
|
81
80
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type * as http from 'http';
|
|
|
2
2
|
import { type FMError } from './FMError.js';
|
|
3
3
|
import { type LayoutInterface } from './layouts/layoutInterface.js';
|
|
4
4
|
import z from "zod";
|
|
5
|
-
export type PickPortals<LAYOUT extends LayoutInterface, PORTALS extends
|
|
5
|
+
export type PickPortals<LAYOUT extends LayoutInterface, PORTALS extends keyof LAYOUT['portals'] = never> = Omit<LAYOUT, 'portals'> & {
|
|
6
6
|
portals: Pick<LAYOUT['portals'], PORTALS>;
|
|
7
7
|
};
|
|
8
8
|
/** Shared database connection options. */
|
package/dist/utils/query.d.ts
CHANGED
|
@@ -1,23 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Temporal } from 'temporal-polyfill';
|
|
2
2
|
export declare const FindRequestSymbol: unique symbol;
|
|
3
|
-
|
|
4
|
-
export interface TimestampType {
|
|
5
|
-
type: 'date' | 'time' | 'timestamp';
|
|
6
|
-
moment: Moment;
|
|
7
|
-
}
|
|
3
|
+
export type TimestampType = Temporal.PlainDate | Temporal.PlainTime | Temporal.PlainDateTime;
|
|
8
4
|
type QueryParameter = string | number | TimestampType;
|
|
9
5
|
/** Represents a FileMaker find value produced by `query(...)`. */
|
|
10
6
|
export interface Query {
|
|
11
7
|
[FindRequestSymbol]: Array<string | TimestampType>;
|
|
12
8
|
}
|
|
9
|
+
type LegacyDateLike = {
|
|
10
|
+
toDate: () => Date;
|
|
11
|
+
};
|
|
12
|
+
type DateInput = Date | LegacyDateLike;
|
|
13
|
+
/**
|
|
14
|
+
* Converts a JavaScript Date or legacy Moment-like value to a Temporal date.
|
|
15
|
+
* @deprecated Construct a `Temporal.PlainDate` directly instead.
|
|
16
|
+
*/
|
|
17
|
+
export declare function asDate(value: DateInput | Temporal.PlainDate | Temporal.PlainDateTime): Temporal.PlainDate;
|
|
18
|
+
/**
|
|
19
|
+
* Converts a JavaScript Date or legacy Moment-like value to a Temporal time.
|
|
20
|
+
* @deprecated Construct a `Temporal.PlainTime` directly instead.
|
|
21
|
+
*/
|
|
22
|
+
export declare function asTime(value: DateInput | Temporal.PlainTime | Temporal.PlainDateTime): Temporal.PlainTime;
|
|
23
|
+
/**
|
|
24
|
+
* Converts a JavaScript Date or legacy Moment-like value to a Temporal timestamp.
|
|
25
|
+
* @deprecated Construct a `Temporal.PlainDateTime` directly instead.
|
|
26
|
+
*/
|
|
27
|
+
export declare function asTimestamp(value: DateInput | Temporal.PlainDateTime): Temporal.PlainDateTime;
|
|
13
28
|
/** Escapes special characters in a raw FileMaker find string. */
|
|
14
29
|
export declare function queryEscape(str: string): string;
|
|
15
30
|
/** Builds a FileMaker find value using template-string syntax. */
|
|
16
31
|
export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]): Query;
|
|
17
|
-
/** Marks a value as a FileMaker date for use inside `query(...)`. */
|
|
18
|
-
export declare function asDate(date: Date | Moment): TimestampType;
|
|
19
|
-
/** Marks a value as a FileMaker time for use inside `query(...)`. */
|
|
20
|
-
export declare function asTime(date: Date | Moment): TimestampType;
|
|
21
|
-
/** Marks a value as a FileMaker timestamp for use inside `query(...)`. */
|
|
22
|
-
export declare function asTimestamp(date: Date | Moment): TimestampType;
|
|
23
32
|
export {};
|
package/dist/utils/query.js
CHANGED
|
@@ -1,9 +1,66 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
4
|
+
import { Temporal } from 'temporal-polyfill';
|
|
5
5
|
export const FindRequestSymbol = Symbol('easyfm-findrequest');
|
|
6
6
|
const SPECIAL_CHARACTERS = ['\\', '=', '<', '≤', '≥', '>', '…', '...', '//', '@', '#', '*', '"', '~'];
|
|
7
|
+
function toDate(value) {
|
|
8
|
+
const date = value instanceof Date ? value : value.toDate();
|
|
9
|
+
if (Number.isNaN(date.getTime()))
|
|
10
|
+
throw new RangeError('Invalid date value');
|
|
11
|
+
return date;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Converts a JavaScript Date or legacy Moment-like value to a Temporal date.
|
|
15
|
+
* @deprecated Construct a `Temporal.PlainDate` directly instead.
|
|
16
|
+
*/
|
|
17
|
+
export function asDate(value) {
|
|
18
|
+
if (value instanceof Temporal.PlainDate)
|
|
19
|
+
return value;
|
|
20
|
+
if (value instanceof Temporal.PlainDateTime)
|
|
21
|
+
return value.toPlainDate();
|
|
22
|
+
const date = toDate(value);
|
|
23
|
+
return Temporal.PlainDate.from({
|
|
24
|
+
year: date.getFullYear(),
|
|
25
|
+
month: date.getMonth() + 1,
|
|
26
|
+
day: date.getDate()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Converts a JavaScript Date or legacy Moment-like value to a Temporal time.
|
|
31
|
+
* @deprecated Construct a `Temporal.PlainTime` directly instead.
|
|
32
|
+
*/
|
|
33
|
+
export function asTime(value) {
|
|
34
|
+
if (value instanceof Temporal.PlainTime)
|
|
35
|
+
return value;
|
|
36
|
+
if (value instanceof Temporal.PlainDateTime)
|
|
37
|
+
return value.toPlainTime();
|
|
38
|
+
const date = toDate(value);
|
|
39
|
+
return Temporal.PlainTime.from({
|
|
40
|
+
hour: date.getHours(),
|
|
41
|
+
minute: date.getMinutes(),
|
|
42
|
+
second: date.getSeconds(),
|
|
43
|
+
millisecond: date.getMilliseconds()
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Converts a JavaScript Date or legacy Moment-like value to a Temporal timestamp.
|
|
48
|
+
* @deprecated Construct a `Temporal.PlainDateTime` directly instead.
|
|
49
|
+
*/
|
|
50
|
+
export function asTimestamp(value) {
|
|
51
|
+
if (value instanceof Temporal.PlainDateTime)
|
|
52
|
+
return value;
|
|
53
|
+
const date = toDate(value);
|
|
54
|
+
return Temporal.PlainDateTime.from({
|
|
55
|
+
year: date.getFullYear(),
|
|
56
|
+
month: date.getMonth() + 1,
|
|
57
|
+
day: date.getDate(),
|
|
58
|
+
hour: date.getHours(),
|
|
59
|
+
minute: date.getMinutes(),
|
|
60
|
+
second: date.getSeconds(),
|
|
61
|
+
millisecond: date.getMilliseconds()
|
|
62
|
+
});
|
|
63
|
+
}
|
|
7
64
|
/** Escapes special characters in a raw FileMaker find string. */
|
|
8
65
|
export function queryEscape(str) {
|
|
9
66
|
for (const char of SPECIAL_CHARACTERS) {
|
|
@@ -28,25 +85,3 @@ export function query(strings, ...args) {
|
|
|
28
85
|
}).flat(1);
|
|
29
86
|
return { [FindRequestSymbol]: query };
|
|
30
87
|
}
|
|
31
|
-
/** Marks a value as a FileMaker date for use inside `query(...)`. */
|
|
32
|
-
export function asDate(date) {
|
|
33
|
-
return {
|
|
34
|
-
type: 'date',
|
|
35
|
-
moment: moment(date)
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
/** Marks a value as a FileMaker time for use inside `query(...)`. */
|
|
39
|
-
export function asTime(date) {
|
|
40
|
-
return {
|
|
41
|
-
type: 'time',
|
|
42
|
-
moment: moment(date)
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/** Marks a value as a FileMaker timestamp for use inside `query(...)`. */
|
|
46
|
-
export function asTimestamp(date) {
|
|
47
|
-
return {
|
|
48
|
-
type: 'timestamp',
|
|
49
|
-
moment: moment(date)
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
query;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Temporal } from 'temporal-polyfill';
|
|
2
|
+
export type TemporalValue = Temporal.PlainDate | Temporal.PlainTime | Temporal.PlainDateTime;
|
|
3
|
+
export type TemporalValueType = 'date' | 'time' | 'timestamp';
|
|
4
|
+
/** Formats a Temporal value using the supplied token-based format. */
|
|
5
|
+
export declare function temporalToString(value: TemporalValue, format: string): string;
|
|
6
|
+
export declare function stringToTemporal(value: string, type: 'date', format: string): Temporal.PlainDate;
|
|
7
|
+
export declare function stringToTemporal(value: string, type: 'time', format: string): Temporal.PlainTime;
|
|
8
|
+
export declare function stringToTemporal(value: string, type: 'timestamp', format: string): Temporal.PlainDateTime;
|
|
9
|
+
export declare function stringToTemporal(value: string, type: TemporalValueType, format: string): TemporalValue;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
import { Temporal } from 'temporal-polyfill';
|
|
5
|
+
const FORMAT_TOKEN_PATTERN = /yyyy|MM|dd|HH|mm|ss/g;
|
|
6
|
+
const TOKEN_PATTERNS = {
|
|
7
|
+
yyyy: '\\d{4}',
|
|
8
|
+
MM: '\\d{2}',
|
|
9
|
+
dd: '\\d{2}',
|
|
10
|
+
HH: '\\d{2}',
|
|
11
|
+
mm: '\\d{2}',
|
|
12
|
+
ss: '\\d{2}'
|
|
13
|
+
};
|
|
14
|
+
function normalizeFormat(format) {
|
|
15
|
+
return format.replace(/YYYY/g, 'yyyy').replace(/DD/g, 'dd');
|
|
16
|
+
}
|
|
17
|
+
function pad(value, length = 2) {
|
|
18
|
+
return value.toString().padStart(length, '0');
|
|
19
|
+
}
|
|
20
|
+
function invalidTemporalString(value, format) {
|
|
21
|
+
throw new RangeError(`Invalid Temporal value ${JSON.stringify(value)}; expected ${format}`);
|
|
22
|
+
}
|
|
23
|
+
function escapeRegExp(value) {
|
|
24
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
25
|
+
}
|
|
26
|
+
function tokensIn(format) {
|
|
27
|
+
const tokens = format.match(FORMAT_TOKEN_PATTERN);
|
|
28
|
+
if (!tokens)
|
|
29
|
+
throw new RangeError(`Format ${JSON.stringify(format)} contains no supported tokens`);
|
|
30
|
+
if (new Set(tokens).size !== tokens.length) {
|
|
31
|
+
throw new RangeError(`Format ${JSON.stringify(format)} contains duplicate tokens`);
|
|
32
|
+
}
|
|
33
|
+
return tokens;
|
|
34
|
+
}
|
|
35
|
+
function parserFor(format) {
|
|
36
|
+
const tokens = tokensIn(format);
|
|
37
|
+
let source = '^';
|
|
38
|
+
let lastIndex = 0;
|
|
39
|
+
for (const match of format.matchAll(FORMAT_TOKEN_PATTERN)) {
|
|
40
|
+
source += escapeRegExp(format.slice(lastIndex, match.index));
|
|
41
|
+
source += `(${TOKEN_PATTERNS[match[0]]})`;
|
|
42
|
+
lastIndex = match.index + match[0].length;
|
|
43
|
+
}
|
|
44
|
+
source += `${escapeRegExp(format.slice(lastIndex))}$`;
|
|
45
|
+
return { pattern: new RegExp(source), tokens };
|
|
46
|
+
}
|
|
47
|
+
/** Formats a Temporal value using the supplied token-based format. */
|
|
48
|
+
export function temporalToString(value, format) {
|
|
49
|
+
format = normalizeFormat(format);
|
|
50
|
+
const values = {};
|
|
51
|
+
if (value instanceof Temporal.PlainDateTime) {
|
|
52
|
+
Object.assign(values, {
|
|
53
|
+
yyyy: pad(value.year, 4), MM: pad(value.month), dd: pad(value.day),
|
|
54
|
+
HH: pad(value.hour), mm: pad(value.minute), ss: pad(value.second)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else if (value instanceof Temporal.PlainDate) {
|
|
58
|
+
Object.assign(values, { yyyy: pad(value.year, 4), MM: pad(value.month), dd: pad(value.day) });
|
|
59
|
+
}
|
|
60
|
+
else if (value instanceof Temporal.PlainTime) {
|
|
61
|
+
Object.assign(values, { HH: pad(value.hour), mm: pad(value.minute), ss: pad(value.second) });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new TypeError('Expected a Temporal.PlainDate, Temporal.PlainTime, or Temporal.PlainDateTime');
|
|
65
|
+
}
|
|
66
|
+
tokensIn(format).forEach(token => {
|
|
67
|
+
if (values[token] === undefined)
|
|
68
|
+
throw new RangeError(`Token ${token} is not available for this Temporal type`);
|
|
69
|
+
});
|
|
70
|
+
return format.replace(FORMAT_TOKEN_PATTERN, token => values[token]);
|
|
71
|
+
}
|
|
72
|
+
/** Parses a Temporal value using the supplied token-based format. */
|
|
73
|
+
export function stringToTemporal(value, type, format) {
|
|
74
|
+
format = normalizeFormat(format);
|
|
75
|
+
const { pattern, tokens } = parserFor(format);
|
|
76
|
+
const match = pattern.exec(value);
|
|
77
|
+
if (!match)
|
|
78
|
+
return invalidTemporalString(value, format);
|
|
79
|
+
const parts = Object.fromEntries(tokens.map((token, index) => [token, Number(match[index + 1])]));
|
|
80
|
+
const required = type === 'date'
|
|
81
|
+
? ['yyyy', 'MM', 'dd']
|
|
82
|
+
: type === 'time'
|
|
83
|
+
? ['HH', 'mm', 'ss']
|
|
84
|
+
: ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss'];
|
|
85
|
+
if (tokens.length !== required.length || required.some(token => parts[token] === undefined)) {
|
|
86
|
+
throw new RangeError(`Format ${JSON.stringify(format)} does not match Temporal type ${type}`);
|
|
87
|
+
}
|
|
88
|
+
if (type === 'date') {
|
|
89
|
+
return Temporal.PlainDate.from({
|
|
90
|
+
year: parts.yyyy,
|
|
91
|
+
month: parts.MM,
|
|
92
|
+
day: parts.dd
|
|
93
|
+
}, { overflow: 'reject' });
|
|
94
|
+
}
|
|
95
|
+
else if (type === 'time') {
|
|
96
|
+
return Temporal.PlainTime.from({
|
|
97
|
+
hour: parts.HH,
|
|
98
|
+
minute: parts.mm,
|
|
99
|
+
second: parts.ss
|
|
100
|
+
}, { overflow: 'reject' });
|
|
101
|
+
}
|
|
102
|
+
return Temporal.PlainDateTime.from({
|
|
103
|
+
year: parts.yyyy,
|
|
104
|
+
month: parts.MM,
|
|
105
|
+
day: parts.dd,
|
|
106
|
+
hour: parts.HH,
|
|
107
|
+
minute: parts.mm,
|
|
108
|
+
second: parts.ss
|
|
109
|
+
}, { overflow: 'reject' });
|
|
110
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jd-data-limited/easy-fm",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"commander": "^
|
|
6
|
-
"
|
|
7
|
-
"zod": "^4.
|
|
5
|
+
"commander": "^15.0.0",
|
|
6
|
+
"temporal-polyfill": "^1.0.5",
|
|
7
|
+
"zod": "^4.6.5"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
@@ -40,30 +40,30 @@
|
|
|
40
40
|
"node": ">=22.22.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@babel/cli": "^
|
|
44
|
-
"@babel/core": "^
|
|
45
|
-
"@babel/preset-env": "^
|
|
46
|
-
"@babel/preset-typescript": "^
|
|
43
|
+
"@babel/cli": "^8.0.6",
|
|
44
|
+
"@babel/core": "^8.0.6",
|
|
45
|
+
"@babel/preset-env": "^8.0.6",
|
|
46
|
+
"@babel/preset-typescript": "^8.0.1",
|
|
47
47
|
"@types/btoa": "^1.2.5",
|
|
48
48
|
"@types/events": "^3.0.3",
|
|
49
49
|
"@types/inquirer": "^9.0.10",
|
|
50
|
-
"@types/jest": "^
|
|
51
|
-
"@types/node": "^
|
|
52
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
53
|
-
"babel-jest": "^
|
|
54
|
-
"dotenv": "^
|
|
55
|
-
"eslint": "^
|
|
50
|
+
"@types/jest": "^30.0.0",
|
|
51
|
+
"@types/node": "^26.6.2",
|
|
52
|
+
"@typescript-eslint/eslint-plugin": "^8.70.1",
|
|
53
|
+
"babel-jest": "^30.5.2",
|
|
54
|
+
"dotenv": "^18.0.2",
|
|
55
|
+
"eslint": "^10.11.0",
|
|
56
56
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
57
57
|
"eslint-plugin-import": "^2.32.0",
|
|
58
|
-
"eslint-plugin-n": "^
|
|
59
|
-
"eslint-plugin-promise": "^
|
|
60
|
-
"inquirer": "^
|
|
61
|
-
"jest": "^
|
|
62
|
-
"ts-jest": "^29.4.
|
|
63
|
-
"ts-morph": "^
|
|
64
|
-
"tsx": "^4.
|
|
65
|
-
"typedoc": "^0.28.
|
|
66
|
-
"typescript": "^
|
|
58
|
+
"eslint-plugin-n": "^18.3.0",
|
|
59
|
+
"eslint-plugin-promise": "^7.3.0",
|
|
60
|
+
"inquirer": "^14.2.2",
|
|
61
|
+
"jest": "^30.5.2",
|
|
62
|
+
"ts-jest": "^29.4.12",
|
|
63
|
+
"ts-morph": "^28.0.0",
|
|
64
|
+
"tsx": "^4.23.15",
|
|
65
|
+
"typedoc": "^0.28.20",
|
|
66
|
+
"typescript": "^6.0.3"
|
|
67
67
|
},
|
|
68
68
|
"bin": {
|
|
69
69
|
"easyfm": "dist/bin/cli.js"
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsc",
|
|
73
73
|
"test": "npm run build && jest",
|
|
74
|
+
"test:types": "tsc --project tsconfig.test.json",
|
|
74
75
|
"test:coverage": "jest --coverage --testPathIgnorePatterns='/__tests__/database.test.ts$'",
|
|
75
76
|
"test:integration": "npm run build && jest __tests__/database.test.ts --runInBand",
|
|
76
77
|
"test:unit": "jest __tests__/query.test.ts __tests__/CookieJar.test.ts --runInBand",
|