oneentry 1.0.163 → 1.0.165

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/changelog.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # SDK Change Log
2
2
 
3
+ ## v.1.0.165
4
+
5
+ ### What's New
6
+
7
+ - **`AttributeType` now includes `json`.** The platform has a `json` attribute type, but the union listed every other type and not that one. Because `IAttributesSetsEntity.type` and `IAttributeSchemaItem.type` (and, through them, `IFormAttribute.type`) are typed by that union, a set containing a `json` field made `switch (field.type)` miss the branch and `field.type === 'json'` fail to compile with "This comparison appears to be unintentional" — the type the API actually sends was the one value the compiler refused to accept. Types only; the data has always come through.
8
+
9
+ ### What's Fixed
10
+
11
+ - **FormData > `getFormsDataByMarker` no longer fails on an empty date filter.** `IFormsDataFilter` documents an empty string as "no bound" on `dateFrom`/`dateTo`, and that is how the endpoint used to read it — it now validates the pair as dates first and answers `400 dateFrom must be a valid date in format "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS"`. So a filter built the documented way (`{ status: ['approved'], dateFrom: '', dateTo: '' }`) took down the whole request: no records, and an error about a bound the caller deliberately did not set. Every other field still treats `""` as "no filter", which made the failure look like anything but the date. The SDK now strips empty `dateFrom`/`dateTo` from the body before sending, restoring the documented meaning; a non-empty value the API cannot parse is still rejected, as it should be.
12
+
13
+ ### What's Deleted
14
+
15
+ - **`textEditor` is gone from the text-attribute narrowing.** No such attribute type exists on the platform — rich text is `text` — but `IStringAttributeValue.type` declared `'string' | 'text' | 'textEditor'` and `isStringAttribute` checked for it. A value that never arrives cost nothing at runtime, yet it read as documentation: the union told everyone the CMS had a separate rich-text type, so `case 'textEditor'` branches were written and silently never taken, and someone looking for that type in the panel could not find it. `IStringAttributeValue.type` is now `'string' | 'text'`, the guard drops the third comparison, and the test case for it is removed.
16
+
17
+ ## v.1.0.164
18
+
19
+ ### What's Fixed
20
+
21
+ - **Validation no longer cries wolf over an image attribute with no file.** The API sends an unset file attribute as `value: ""`, and `isEmptyValue` recognised only `null`, `undefined` and `{}` as empty — so the empty string went on to be checked against the file schema and every product response with such an attribute logged `Attribute of type "image" carries an unexpected file shape: expected object, received string`. Nothing was broken (`validateResponseSafe` only reports), but a validator that fires on healthy data is worse than none: real reports drown in it. The empty string now counts as empty, with a test alongside the `null` / `{}` cases in `src/base/tests/attributes.spec.ts`.
22
+
23
+ - **Forms > `IFormAttribute` now declares `multiselect`.** The API has always returned it on `list` attributes (`multiselect: true` means several options from `listTitles` may be picked, `false` — one), and the SDK passed it through untouched, but the interface never mentioned it. Because `IFormAttribute` carries an index signature `[key: string]: unknown`, `attribute.multiselect` compiled and came back as `unknown`: no autocomplete, no check, and `attr.multiselect ? 'checkbox' : 'radio'` either failed to build under `strict` or had to be written through a cast. Whoever had not read the platform ticket simply never learned the flag existed and left a single-choice control on a "check all that apply" question. The field is now `multiselect?: boolean`, documented exactly as on `IAttributeSchemaItem.multiselect` in attribute-sets. Types only — the data was already there.
24
+
3
25
  ## v.1.0.163
4
26
 
5
27
  ### What's Fixed
@@ -77,7 +77,7 @@ interface IListTitleExtended {
77
77
  * @type {AttributeType}
78
78
  * @description This type defines the possible values for attribute types used in the system.
79
79
  */
80
- type AttributeType = 'string' | 'text' | 'textWithHeader' | 'integer' | 'real' | 'float' | 'dateTime' | 'date' | 'time' | 'file' | 'image' | 'groupOfImages' | 'radioButton' | 'list' | 'button' | 'spam' | 'entity' | 'timeInterval';
80
+ type AttributeType = 'string' | 'text' | 'textWithHeader' | 'integer' | 'real' | 'float' | 'dateTime' | 'date' | 'time' | 'file' | 'image' | 'groupOfImages' | 'radioButton' | 'list' | 'button' | 'spam' | 'entity' | 'timeInterval' | 'json';
81
81
  /**
82
82
  * Represents an attribute set entity.
83
83
  * @interface IAttributesSetsEntity
@@ -61,7 +61,7 @@ export declare function getAttributeFile(attr: IAttributeValue | undefined): IAt
61
61
  /**
62
62
  * Narrows an attribute value to a text attribute.
63
63
  * @param {IAttributeValue | undefined} attr - The attribute value to test.
64
- * @returns {boolean} True when the attribute is a `string`, `text` or `textEditor` carrying a string (or nothing).
64
+ * @returns {boolean} True when the attribute is a `string` or `text` carrying a string (or nothing).
65
65
  */
66
66
  export declare function isStringAttribute(attr: IAttributeValue | undefined): attr is IStringAttributeValue;
67
67
  /**
@@ -101,13 +101,11 @@ function getAttributeFile(attr) {
101
101
  /**
102
102
  * Narrows an attribute value to a text attribute.
103
103
  * @param {IAttributeValue | undefined} attr - The attribute value to test.
104
- * @returns {boolean} True when the attribute is a `string`, `text` or `textEditor` carrying a string (or nothing).
104
+ * @returns {boolean} True when the attribute is a `string` or `text` carrying a string (or nothing).
105
105
  */
106
106
  function isStringAttribute(attr) {
107
107
  return (!!attr &&
108
- (attr.type === 'string' ||
109
- attr.type === 'text' ||
110
- attr.type === 'textEditor') &&
108
+ (attr.type === 'string' || attr.type === 'text') &&
111
109
  (attr.value === null || typeof attr.value === 'string'));
112
110
  }
113
111
  /**
@@ -325,12 +325,12 @@ interface IFileAttributeValue extends IAttributeValue {
325
325
  }
326
326
  /**
327
327
  * @interface IStringAttributeValue
328
- * @property {'string' | 'text' | 'textEditor'} type - Discriminant identifying the attribute as text-bearing.
328
+ * @property {'string' | 'text'} type - Discriminant identifying the attribute as text-bearing.
329
329
  * @property {string | null} value - The text; `null` when no value is set.
330
330
  * @description {@link IAttributeValue} narrowed to text attributes. Narrow to it with `isStringAttribute`.
331
331
  */
332
332
  interface IStringAttributeValue extends IAttributeValue {
333
- type: 'string' | 'text' | 'textEditor';
333
+ type: 'string' | 'text';
334
334
  value: string | null;
335
335
  }
336
336
  /**
@@ -59,14 +59,17 @@ exports.AttributeFileSchema = zod_1.z.looseObject({
59
59
  */
60
60
  const FILE_ATTRIBUTE_TYPES = ['image', 'file', 'groupOfImages'];
61
61
  /**
62
- * Reports whether a value is the empty localization map the API sends for an
63
- * attribute with no value set.
62
+ * Reports whether a value is one of the empty forms the API sends for an
63
+ * attribute with no value set: `null`, the empty localization map, or the
64
+ * empty string a file attribute carries when no file is attached.
64
65
  * @param {unknown} value - The value to test.
65
66
  * @returns {boolean} True when the value carries nothing.
66
67
  */
67
68
  function isEmptyValue(value) {
68
69
  if (value === null || value === undefined)
69
70
  return true;
71
+ if (value === '')
72
+ return true;
70
73
  return (typeof value === 'object' &&
71
74
  !Array.isArray(value) &&
72
75
  Object.keys(value).length === 0);
@@ -119,6 +119,7 @@ interface IFormLocalizeInfo extends Omit<ILocalizeInfo, 'title'> {
119
119
  * @property {IAttributeLocalizeInfo} localizeInfos - Localized labels for the field. For `timeInterval` attributes, also carries the `intervals` schedule payload.
120
120
  * @property {unknown} initialValue - Default value applied when the field is not filled.
121
121
  * @property {IListTitle[]} listTitles - Predefined options for `list`/`radioButton` fields; empty array for other types.
122
+ * @property {boolean} [multiselect] - For `list` fields — whether several options from `listTitles` may be selected. Optional. Example: false.
122
123
  * @property {IAttributeValidators} validators - Validation rules; empty object when no validators are configured.
123
124
  * @property {Record<string, unknown>} settings - Field-specific configuration; empty object by default.
124
125
  * @property {Record<string, IFormAttributeAdditionalField>} additionalFields - Nested sub-fields keyed by marker; empty object when none.
@@ -139,6 +140,7 @@ interface IFormAttribute {
139
140
  localizeInfos: IAttributeLocalizeInfo;
140
141
  initialValue: unknown;
141
142
  listTitles: IListTitle[];
143
+ multiselect?: boolean;
142
144
  validators: IAttributeValidators;
143
145
  settings: Record<string, unknown>;
144
146
  additionalFields: Record<string, IFormAttributeAdditionalField>;
@@ -60,6 +60,21 @@ export default class FormsDataApi extends AsyncModules implements IFormsData {
60
60
  * @see {@link https://js-sdk.oneentry.cloud/docs/forms-data/postFormsData postFormsData} documentation.
61
61
  */
62
62
  postFormsData(body: IBodyPostFormData, langCode?: string): Promise<IPostFormResponse | IError>;
63
+ /**
64
+ * Drops the date bounds the endpoint refuses to parse.
65
+ *
66
+ * An empty string means "no filter" on every field of {@link IFormsDataFilter},
67
+ * and that is still how `entityIdentifier`, `userIdentifier` and an empty
68
+ * `status` array behave. The date pair is the exception: the endpoint now runs
69
+ * `dateFrom` / `dateTo` through a date validator before it builds the filter,
70
+ * so `dateFrom: ''` no longer means "no bound" — it fails the whole request
71
+ * with `400 dateFrom must be a valid date in format "YYYY-MM-DD"…` and takes
72
+ * the records down with it. An empty bound is therefore removed from the body
73
+ * rather than sent, which the API reads as "no bound", the documented meaning.
74
+ * @param {IFormsDataFilter} body - Filter body as passed by the caller.
75
+ * @returns {IFormsDataFilter} The same filter without empty date bounds.
76
+ */
77
+ private _stripEmptyDates;
63
78
  /**
64
79
  * Get one object of form data by marker.
65
80
  * @handleName getFormsDataByMarker
@@ -79,8 +94,8 @@ export default class FormsDataApi extends AsyncModules implements IFormsData {
79
94
  * @param {number} [body.parentId] - Identifier of the parent record, to fetch replies to one submission. Example: 10.
80
95
  * @param {string} [body.userIdentifier] - Text identifier of the sender. Example: "admin".
81
96
  * @param {FormDataStatus[]} [body.status] - Moderation statuses to keep: "sent", "moderation", "approved", "banned", "deleted". Must be an array; anything outside the set is rejected with `400 each value in status must be a valid enum value`. Example: ["approved"].
82
- * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An unparsable value fails the request with a `500`. Example: "2025-01-01".
83
- * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. Example: "2025-12-31".
97
+ * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound" — the SDK removes it from the body, because the endpoint answers `400 dateFrom must be a valid date in format "YYYY-MM-DD"` to one. An unparsable non-empty value still fails the request. Example: "2025-01-01".
98
+ * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound", handled like `dateFrom`. Example: "2025-12-31".
84
99
  * @param {number} [isExtended] - Flag for getting additional fields. Example: 1.
85
100
  * @param {string} [langCode] - Language code. Default: "en_US".
86
101
  * @param {number} [offset] - Parameter for pagination. Default: 0.
@@ -90,6 +105,8 @@ export default class FormsDataApi extends AsyncModules implements IFormsData {
90
105
  * @description Each record's `formData` comes back **already unwrapped from its locale**. The API sends `formData: { "en_US": [ … ] }`; the SDK normalizes the response and hands over the array for `langCode`, so read `record.formData` — `record.formData[langCode]` is `undefined` and yields an empty list with no error. This holds for both values of `isExtended`.
91
106
  *
92
107
  * The filter body is typed for a reason: the API silently ignores a field it does not know, so a misspelled `statuses` returns every record — unmoderated ones included — and the code looks like it works.
108
+ *
109
+ * Empty `dateFrom` / `dateTo` are stripped from the body before it is sent: the endpoint validates the pair as dates and rejects an empty string with a `400`, while every other field still treats "" as "no filter".
93
110
  * @see {@link https://js-sdk.oneentry.cloud/docs/forms-data/getFormsDataByMarker getFormsDataByMarker} documentation.
94
111
  */
95
112
  getFormsDataByMarker(marker: string, formModuleConfigId: number, body?: IFormsDataFilter, isExtended?: number, langCode?: string, offset?: number, limit?: number): Promise<IFormsByMarkerDataEntity | IError>;
@@ -160,6 +160,28 @@ class FormsDataApi extends asyncModules_1.default {
160
160
  const validated = await this._validateResponse(result, schema('PostFormResponseSchema'));
161
161
  return this._normalizeData(validated);
162
162
  }
163
+ /**
164
+ * Drops the date bounds the endpoint refuses to parse.
165
+ *
166
+ * An empty string means "no filter" on every field of {@link IFormsDataFilter},
167
+ * and that is still how `entityIdentifier`, `userIdentifier` and an empty
168
+ * `status` array behave. The date pair is the exception: the endpoint now runs
169
+ * `dateFrom` / `dateTo` through a date validator before it builds the filter,
170
+ * so `dateFrom: ''` no longer means "no bound" — it fails the whole request
171
+ * with `400 dateFrom must be a valid date in format "YYYY-MM-DD"…` and takes
172
+ * the records down with it. An empty bound is therefore removed from the body
173
+ * rather than sent, which the API reads as "no bound", the documented meaning.
174
+ * @param {IFormsDataFilter} body - Filter body as passed by the caller.
175
+ * @returns {IFormsDataFilter} The same filter without empty date bounds.
176
+ */
177
+ _stripEmptyDates(body) {
178
+ const { dateFrom, dateTo, ...rest } = body;
179
+ return {
180
+ ...rest,
181
+ ...(dateFrom ? { dateFrom } : {}),
182
+ ...(dateTo ? { dateTo } : {}),
183
+ };
184
+ }
163
185
  /**
164
186
  * Get one object of form data by marker.
165
187
  * @handleName getFormsDataByMarker
@@ -179,8 +201,8 @@ class FormsDataApi extends asyncModules_1.default {
179
201
  * @param {number} [body.parentId] - Identifier of the parent record, to fetch replies to one submission. Example: 10.
180
202
  * @param {string} [body.userIdentifier] - Text identifier of the sender. Example: "admin".
181
203
  * @param {FormDataStatus[]} [body.status] - Moderation statuses to keep: "sent", "moderation", "approved", "banned", "deleted". Must be an array; anything outside the set is rejected with `400 each value in status must be a valid enum value`. Example: ["approved"].
182
- * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An unparsable value fails the request with a `500`. Example: "2025-01-01".
183
- * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. Example: "2025-12-31".
204
+ * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound" — the SDK removes it from the body, because the endpoint answers `400 dateFrom must be a valid date in format "YYYY-MM-DD"` to one. An unparsable non-empty value still fails the request. Example: "2025-01-01".
205
+ * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound", handled like `dateFrom`. Example: "2025-12-31".
184
206
  * @param {number} [isExtended] - Flag for getting additional fields. Example: 1.
185
207
  * @param {string} [langCode] - Language code. Default: "en_US".
186
208
  * @param {number} [offset] - Parameter for pagination. Default: 0.
@@ -190,10 +212,12 @@ class FormsDataApi extends asyncModules_1.default {
190
212
  * @description Each record's `formData` comes back **already unwrapped from its locale**. The API sends `formData: { "en_US": [ … ] }`; the SDK normalizes the response and hands over the array for `langCode`, so read `record.formData` — `record.formData[langCode]` is `undefined` and yields an empty list with no error. This holds for both values of `isExtended`.
191
213
  *
192
214
  * The filter body is typed for a reason: the API silently ignores a field it does not know, so a misspelled `statuses` returns every record — unmoderated ones included — and the code looks like it works.
215
+ *
216
+ * Empty `dateFrom` / `dateTo` are stripped from the body before it is sent: the endpoint validates the pair as dates and rejects an empty string with a `400`, while every other field still treats "" as "no filter".
193
217
  * @see {@link https://js-sdk.oneentry.cloud/docs/forms-data/getFormsDataByMarker getFormsDataByMarker} documentation.
194
218
  */
195
219
  async getFormsDataByMarker(marker, formModuleConfigId, body = {}, isExtended = 0, langCode = this.state.lang, offset = 0, limit = 30) {
196
- const result = await this._fetchPost(`/marker/${marker}?formModuleConfigId=${formModuleConfigId}&isExtended=${isExtended}&langCode=${langCode}&offset=${offset}&limit=${limit}`, body);
220
+ const result = await this._fetchPost(`/marker/${marker}?formModuleConfigId=${formModuleConfigId}&isExtended=${isExtended}&langCode=${langCode}&offset=${offset}&limit=${limit}`, this._stripEmptyDates(body));
197
221
  // Validate response if validation is enabled
198
222
  const validated = await this._validateResponse(result, schema('FormsByMarkerDataResponseSchema'));
199
223
  return this._normalizeData(validated, langCode);
@@ -52,7 +52,7 @@ interface IFormsData {
52
52
  * @handleName getFormsDataByMarker
53
53
  * @param {string} marker - The marker identifying the form data. Example: "contact_form_data".
54
54
  * @param {number} formModuleConfigId - The form module configuration ID. Example: 4.
55
- * @param {IFormsDataFilter} [body] - Filter for the records to return. Default: {}. Every field is optional; an omitted or empty one is not applied. Valid `status` values: "sent", "moderation", "approved", "banned", "deleted".
55
+ * @param {IFormsDataFilter} [body] - Filter for the records to return. Default: {}. Every field is optional; an omitted or empty one is not applied — an empty `dateFrom`/`dateTo` is stripped from the body, because the endpoint rejects an empty date with a `400`. Valid `status` values: "sent", "moderation", "approved", "banned", "deleted".
56
56
  * @example
57
57
  {
58
58
  "entityIdentifier": "blog",
@@ -113,8 +113,8 @@ type FormDataStatus = 'sent' | 'moderation' | 'approved' | 'banned' | 'deleted';
113
113
  * @property {number} [parentId] - Identifier of the parent record — the way to fetch replies to one comment. Example: 10.
114
114
  * @property {string} [userIdentifier] - Text identifier of the sender; an empty string means "no filter". Example: "admin".
115
115
  * @property {FormDataStatus[]} [status] - Moderation statuses to keep. Must be an array — a bare string is rejected with `400`. An empty array means "no filter". Example: ["approved"].
116
- * @property {string} [dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound". A value the API cannot parse fails the request with a `500`. Example: "2025-01-01".
117
- * @property {string} [dateTo] - Upper bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound". Example: "2025-12-31".
116
+ * @property {string} [dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound" — `getFormsDataByMarker` strips it from the body, since the endpoint rejects an empty date with a `400`. A non-empty value the API cannot parse still fails the request. Example: "2025-01-01".
117
+ * @property {string} [dateTo] - Upper bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound", handled like `dateFrom`. Example: "2025-12-31".
118
118
  * @description Filter for reading form submissions. Every field is optional and an omitted one is simply not applied.
119
119
  *
120
120
  * Typed rather than left as `object` on purpose: the API **ignores** a field it
@@ -77,7 +77,7 @@ interface IListTitleExtended {
77
77
  * @type {AttributeType}
78
78
  * @description This type defines the possible values for attribute types used in the system.
79
79
  */
80
- type AttributeType = 'string' | 'text' | 'textWithHeader' | 'integer' | 'real' | 'float' | 'dateTime' | 'date' | 'time' | 'file' | 'image' | 'groupOfImages' | 'radioButton' | 'list' | 'button' | 'spam' | 'entity' | 'timeInterval';
80
+ type AttributeType = 'string' | 'text' | 'textWithHeader' | 'integer' | 'real' | 'float' | 'dateTime' | 'date' | 'time' | 'file' | 'image' | 'groupOfImages' | 'radioButton' | 'list' | 'button' | 'spam' | 'entity' | 'timeInterval' | 'json';
81
81
  /**
82
82
  * Represents an attribute set entity.
83
83
  * @interface IAttributesSetsEntity
@@ -61,7 +61,7 @@ export declare function getAttributeFile(attr: IAttributeValue | undefined): IAt
61
61
  /**
62
62
  * Narrows an attribute value to a text attribute.
63
63
  * @param {IAttributeValue | undefined} attr - The attribute value to test.
64
- * @returns {boolean} True when the attribute is a `string`, `text` or `textEditor` carrying a string (or nothing).
64
+ * @returns {boolean} True when the attribute is a `string` or `text` carrying a string (or nothing).
65
65
  */
66
66
  export declare function isStringAttribute(attr: IAttributeValue | undefined): attr is IStringAttributeValue;
67
67
  /**
@@ -92,13 +92,11 @@ export function getAttributeFile(attr) {
92
92
  /**
93
93
  * Narrows an attribute value to a text attribute.
94
94
  * @param {IAttributeValue | undefined} attr - The attribute value to test.
95
- * @returns {boolean} True when the attribute is a `string`, `text` or `textEditor` carrying a string (or nothing).
95
+ * @returns {boolean} True when the attribute is a `string` or `text` carrying a string (or nothing).
96
96
  */
97
97
  export function isStringAttribute(attr) {
98
98
  return (!!attr &&
99
- (attr.type === 'string' ||
100
- attr.type === 'text' ||
101
- attr.type === 'textEditor') &&
99
+ (attr.type === 'string' || attr.type === 'text') &&
102
100
  (attr.value === null || typeof attr.value === 'string'));
103
101
  }
104
102
  /**
@@ -325,12 +325,12 @@ interface IFileAttributeValue extends IAttributeValue {
325
325
  }
326
326
  /**
327
327
  * @interface IStringAttributeValue
328
- * @property {'string' | 'text' | 'textEditor'} type - Discriminant identifying the attribute as text-bearing.
328
+ * @property {'string' | 'text'} type - Discriminant identifying the attribute as text-bearing.
329
329
  * @property {string | null} value - The text; `null` when no value is set.
330
330
  * @description {@link IAttributeValue} narrowed to text attributes. Narrow to it with `isStringAttribute`.
331
331
  */
332
332
  interface IStringAttributeValue extends IAttributeValue {
333
- type: 'string' | 'text' | 'textEditor';
333
+ type: 'string' | 'text';
334
334
  value: string | null;
335
335
  }
336
336
  /**
@@ -53,14 +53,17 @@ export const AttributeFileSchema = z.looseObject({
53
53
  */
54
54
  const FILE_ATTRIBUTE_TYPES = ['image', 'file', 'groupOfImages'];
55
55
  /**
56
- * Reports whether a value is the empty localization map the API sends for an
57
- * attribute with no value set.
56
+ * Reports whether a value is one of the empty forms the API sends for an
57
+ * attribute with no value set: `null`, the empty localization map, or the
58
+ * empty string a file attribute carries when no file is attached.
58
59
  * @param {unknown} value - The value to test.
59
60
  * @returns {boolean} True when the value carries nothing.
60
61
  */
61
62
  function isEmptyValue(value) {
62
63
  if (value === null || value === undefined)
63
64
  return true;
65
+ if (value === '')
66
+ return true;
64
67
  return (typeof value === 'object' &&
65
68
  !Array.isArray(value) &&
66
69
  Object.keys(value).length === 0);
@@ -119,6 +119,7 @@ interface IFormLocalizeInfo extends Omit<ILocalizeInfo, 'title'> {
119
119
  * @property {IAttributeLocalizeInfo} localizeInfos - Localized labels for the field. For `timeInterval` attributes, also carries the `intervals` schedule payload.
120
120
  * @property {unknown} initialValue - Default value applied when the field is not filled.
121
121
  * @property {IListTitle[]} listTitles - Predefined options for `list`/`radioButton` fields; empty array for other types.
122
+ * @property {boolean} [multiselect] - For `list` fields — whether several options from `listTitles` may be selected. Optional. Example: false.
122
123
  * @property {IAttributeValidators} validators - Validation rules; empty object when no validators are configured.
123
124
  * @property {Record<string, unknown>} settings - Field-specific configuration; empty object by default.
124
125
  * @property {Record<string, IFormAttributeAdditionalField>} additionalFields - Nested sub-fields keyed by marker; empty object when none.
@@ -139,6 +140,7 @@ interface IFormAttribute {
139
140
  localizeInfos: IAttributeLocalizeInfo;
140
141
  initialValue: unknown;
141
142
  listTitles: IListTitle[];
143
+ multiselect?: boolean;
142
144
  validators: IAttributeValidators;
143
145
  settings: Record<string, unknown>;
144
146
  additionalFields: Record<string, IFormAttributeAdditionalField>;
@@ -60,6 +60,21 @@ export default class FormsDataApi extends AsyncModules implements IFormsData {
60
60
  * @see {@link https://js-sdk.oneentry.cloud/docs/forms-data/postFormsData postFormsData} documentation.
61
61
  */
62
62
  postFormsData(body: IBodyPostFormData, langCode?: string): Promise<IPostFormResponse | IError>;
63
+ /**
64
+ * Drops the date bounds the endpoint refuses to parse.
65
+ *
66
+ * An empty string means "no filter" on every field of {@link IFormsDataFilter},
67
+ * and that is still how `entityIdentifier`, `userIdentifier` and an empty
68
+ * `status` array behave. The date pair is the exception: the endpoint now runs
69
+ * `dateFrom` / `dateTo` through a date validator before it builds the filter,
70
+ * so `dateFrom: ''` no longer means "no bound" — it fails the whole request
71
+ * with `400 dateFrom must be a valid date in format "YYYY-MM-DD"…` and takes
72
+ * the records down with it. An empty bound is therefore removed from the body
73
+ * rather than sent, which the API reads as "no bound", the documented meaning.
74
+ * @param {IFormsDataFilter} body - Filter body as passed by the caller.
75
+ * @returns {IFormsDataFilter} The same filter without empty date bounds.
76
+ */
77
+ private _stripEmptyDates;
63
78
  /**
64
79
  * Get one object of form data by marker.
65
80
  * @handleName getFormsDataByMarker
@@ -79,8 +94,8 @@ export default class FormsDataApi extends AsyncModules implements IFormsData {
79
94
  * @param {number} [body.parentId] - Identifier of the parent record, to fetch replies to one submission. Example: 10.
80
95
  * @param {string} [body.userIdentifier] - Text identifier of the sender. Example: "admin".
81
96
  * @param {FormDataStatus[]} [body.status] - Moderation statuses to keep: "sent", "moderation", "approved", "banned", "deleted". Must be an array; anything outside the set is rejected with `400 each value in status must be a valid enum value`. Example: ["approved"].
82
- * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An unparsable value fails the request with a `500`. Example: "2025-01-01".
83
- * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. Example: "2025-12-31".
97
+ * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound" — the SDK removes it from the body, because the endpoint answers `400 dateFrom must be a valid date in format "YYYY-MM-DD"` to one. An unparsable non-empty value still fails the request. Example: "2025-01-01".
98
+ * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound", handled like `dateFrom`. Example: "2025-12-31".
84
99
  * @param {number} [isExtended] - Flag for getting additional fields. Example: 1.
85
100
  * @param {string} [langCode] - Language code. Default: "en_US".
86
101
  * @param {number} [offset] - Parameter for pagination. Default: 0.
@@ -90,6 +105,8 @@ export default class FormsDataApi extends AsyncModules implements IFormsData {
90
105
  * @description Each record's `formData` comes back **already unwrapped from its locale**. The API sends `formData: { "en_US": [ … ] }`; the SDK normalizes the response and hands over the array for `langCode`, so read `record.formData` — `record.formData[langCode]` is `undefined` and yields an empty list with no error. This holds for both values of `isExtended`.
91
106
  *
92
107
  * The filter body is typed for a reason: the API silently ignores a field it does not know, so a misspelled `statuses` returns every record — unmoderated ones included — and the code looks like it works.
108
+ *
109
+ * Empty `dateFrom` / `dateTo` are stripped from the body before it is sent: the endpoint validates the pair as dates and rejects an empty string with a `400`, while every other field still treats "" as "no filter".
93
110
  * @see {@link https://js-sdk.oneentry.cloud/docs/forms-data/getFormsDataByMarker getFormsDataByMarker} documentation.
94
111
  */
95
112
  getFormsDataByMarker(marker: string, formModuleConfigId: number, body?: IFormsDataFilter, isExtended?: number, langCode?: string, offset?: number, limit?: number): Promise<IFormsByMarkerDataEntity | IError>;
@@ -122,6 +122,28 @@ export default class FormsDataApi extends AsyncModules {
122
122
  const validated = await this._validateResponse(result, schema('PostFormResponseSchema'));
123
123
  return this._normalizeData(validated);
124
124
  }
125
+ /**
126
+ * Drops the date bounds the endpoint refuses to parse.
127
+ *
128
+ * An empty string means "no filter" on every field of {@link IFormsDataFilter},
129
+ * and that is still how `entityIdentifier`, `userIdentifier` and an empty
130
+ * `status` array behave. The date pair is the exception: the endpoint now runs
131
+ * `dateFrom` / `dateTo` through a date validator before it builds the filter,
132
+ * so `dateFrom: ''` no longer means "no bound" — it fails the whole request
133
+ * with `400 dateFrom must be a valid date in format "YYYY-MM-DD"…` and takes
134
+ * the records down with it. An empty bound is therefore removed from the body
135
+ * rather than sent, which the API reads as "no bound", the documented meaning.
136
+ * @param {IFormsDataFilter} body - Filter body as passed by the caller.
137
+ * @returns {IFormsDataFilter} The same filter without empty date bounds.
138
+ */
139
+ _stripEmptyDates(body) {
140
+ const { dateFrom, dateTo, ...rest } = body;
141
+ return {
142
+ ...rest,
143
+ ...(dateFrom ? { dateFrom } : {}),
144
+ ...(dateTo ? { dateTo } : {}),
145
+ };
146
+ }
125
147
  /**
126
148
  * Get one object of form data by marker.
127
149
  * @handleName getFormsDataByMarker
@@ -141,8 +163,8 @@ export default class FormsDataApi extends AsyncModules {
141
163
  * @param {number} [body.parentId] - Identifier of the parent record, to fetch replies to one submission. Example: 10.
142
164
  * @param {string} [body.userIdentifier] - Text identifier of the sender. Example: "admin".
143
165
  * @param {FormDataStatus[]} [body.status] - Moderation statuses to keep: "sent", "moderation", "approved", "banned", "deleted". Must be an array; anything outside the set is rejected with `400 each value in status must be a valid enum value`. Example: ["approved"].
144
- * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An unparsable value fails the request with a `500`. Example: "2025-01-01".
145
- * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. Example: "2025-12-31".
166
+ * @param {string} [body.dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound" — the SDK removes it from the body, because the endpoint answers `400 dateFrom must be a valid date in format "YYYY-MM-DD"` to one. An unparsable non-empty value still fails the request. Example: "2025-01-01".
167
+ * @param {string} [body.dateTo] - Upper bound of the submission date, `YYYY-MM-DD`. An empty string means "no bound", handled like `dateFrom`. Example: "2025-12-31".
146
168
  * @param {number} [isExtended] - Flag for getting additional fields. Example: 1.
147
169
  * @param {string} [langCode] - Language code. Default: "en_US".
148
170
  * @param {number} [offset] - Parameter for pagination. Default: 0.
@@ -152,10 +174,12 @@ export default class FormsDataApi extends AsyncModules {
152
174
  * @description Each record's `formData` comes back **already unwrapped from its locale**. The API sends `formData: { "en_US": [ … ] }`; the SDK normalizes the response and hands over the array for `langCode`, so read `record.formData` — `record.formData[langCode]` is `undefined` and yields an empty list with no error. This holds for both values of `isExtended`.
153
175
  *
154
176
  * The filter body is typed for a reason: the API silently ignores a field it does not know, so a misspelled `statuses` returns every record — unmoderated ones included — and the code looks like it works.
177
+ *
178
+ * Empty `dateFrom` / `dateTo` are stripped from the body before it is sent: the endpoint validates the pair as dates and rejects an empty string with a `400`, while every other field still treats "" as "no filter".
155
179
  * @see {@link https://js-sdk.oneentry.cloud/docs/forms-data/getFormsDataByMarker getFormsDataByMarker} documentation.
156
180
  */
157
181
  async getFormsDataByMarker(marker, formModuleConfigId, body = {}, isExtended = 0, langCode = this.state.lang, offset = 0, limit = 30) {
158
- const result = await this._fetchPost(`/marker/${marker}?formModuleConfigId=${formModuleConfigId}&isExtended=${isExtended}&langCode=${langCode}&offset=${offset}&limit=${limit}`, body);
182
+ const result = await this._fetchPost(`/marker/${marker}?formModuleConfigId=${formModuleConfigId}&isExtended=${isExtended}&langCode=${langCode}&offset=${offset}&limit=${limit}`, this._stripEmptyDates(body));
159
183
  // Validate response if validation is enabled
160
184
  const validated = await this._validateResponse(result, schema('FormsByMarkerDataResponseSchema'));
161
185
  return this._normalizeData(validated, langCode);
@@ -52,7 +52,7 @@ interface IFormsData {
52
52
  * @handleName getFormsDataByMarker
53
53
  * @param {string} marker - The marker identifying the form data. Example: "contact_form_data".
54
54
  * @param {number} formModuleConfigId - The form module configuration ID. Example: 4.
55
- * @param {IFormsDataFilter} [body] - Filter for the records to return. Default: {}. Every field is optional; an omitted or empty one is not applied. Valid `status` values: "sent", "moderation", "approved", "banned", "deleted".
55
+ * @param {IFormsDataFilter} [body] - Filter for the records to return. Default: {}. Every field is optional; an omitted or empty one is not applied — an empty `dateFrom`/`dateTo` is stripped from the body, because the endpoint rejects an empty date with a `400`. Valid `status` values: "sent", "moderation", "approved", "banned", "deleted".
56
56
  * @example
57
57
  {
58
58
  "entityIdentifier": "blog",
@@ -113,8 +113,8 @@ type FormDataStatus = 'sent' | 'moderation' | 'approved' | 'banned' | 'deleted';
113
113
  * @property {number} [parentId] - Identifier of the parent record — the way to fetch replies to one comment. Example: 10.
114
114
  * @property {string} [userIdentifier] - Text identifier of the sender; an empty string means "no filter". Example: "admin".
115
115
  * @property {FormDataStatus[]} [status] - Moderation statuses to keep. Must be an array — a bare string is rejected with `400`. An empty array means "no filter". Example: ["approved"].
116
- * @property {string} [dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound". A value the API cannot parse fails the request with a `500`. Example: "2025-01-01".
117
- * @property {string} [dateTo] - Upper bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound". Example: "2025-12-31".
116
+ * @property {string} [dateFrom] - Lower bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound" — `getFormsDataByMarker` strips it from the body, since the endpoint rejects an empty date with a `400`. A non-empty value the API cannot parse still fails the request. Example: "2025-01-01".
117
+ * @property {string} [dateTo] - Upper bound of the submission date, `YYYY-MM-DD`; an empty string means "no bound", handled like `dateFrom`. Example: "2025-12-31".
118
118
  * @description Filter for reading form submissions. Every field is optional and an omitted one is simply not applied.
119
119
  *
120
120
  * Typed rather than left as `object` on purpose: the API **ignores** a field it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneentry",
3
- "version": "1.0.163",
3
+ "version": "1.0.165",
4
4
  "description": "OneEntry NPM package",
5
5
  "main": "dist/index.js",
6
6
  "module": "esm/index.js",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "scripts": {
50
50
  "prepublishOnly": "node ../scripts/smoke-pack.js --files-only",
51
- "postpublish": "node ../scripts/verify-published.js --named defineOneEntry --expect 1.0.163"
51
+ "postpublish": "node ../scripts/verify-published.js --named defineOneEntry --expect 1.0.165"
52
52
  },
53
53
  "sideEffects": false,
54
54
  "files": [