oneentry 1.0.162 → 1.0.163

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +18 -0
  2. package/changelog.md +59 -0
  3. package/dist/admins/adminsSchemas.d.ts +6 -22
  4. package/dist/admins/adminsSchemas.js +2 -9
  5. package/dist/attribute-sets/attributeSetsInterfaces.d.ts +6 -6
  6. package/dist/auth-provider/authProvidersInterfaces.d.ts +4 -4
  7. package/dist/base/attributes.d.ts +105 -0
  8. package/dist/base/attributes.js +172 -0
  9. package/dist/base/utils.d.ts +95 -2
  10. package/dist/base/validation.d.ts +29 -0
  11. package/dist/base/validation.js +93 -1
  12. package/dist/blocks/blocksInterfaces.d.ts +1 -1
  13. package/dist/blocks/blocksSchemas.d.ts +6 -6
  14. package/dist/blocks/blocksSchemas.js +2 -1
  15. package/dist/discounts/discountsInterfaces.d.ts +1 -1
  16. package/dist/forms-data/formsDataApi.d.ts +16 -6
  17. package/dist/forms-data/formsDataApi.js +14 -4
  18. package/dist/forms-data/formsDataInterfaces.d.ts +48 -15
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +9 -1
  21. package/dist/integration-collections/integrationCollectionsInterfaces.d.ts +6 -6
  22. package/dist/menus/menusInterfaces.d.ts +1 -1
  23. package/dist/menus/menusSchemas.js +2 -1
  24. package/dist/orders/ordersInterfaces.d.ts +1 -1
  25. package/dist/pages/pagesSchemas.d.ts +3 -3
  26. package/dist/pages/pagesSchemas.js +2 -1
  27. package/dist/product-statuses/productStatusesInterfaces.d.ts +2 -2
  28. package/dist/products/productsSchemas.d.ts +5 -5
  29. package/dist/products/productsSchemas.js +3 -2
  30. package/dist/templates/templatesInterfaces.d.ts +1 -1
  31. package/dist/templates/templatesSchemas.d.ts +3 -3
  32. package/dist/templates/templatesSchemas.js +2 -1
  33. package/dist/templates-preview/templatesPreviewInterfaces.d.ts +2 -2
  34. package/esm/admins/adminsSchemas.d.ts +6 -22
  35. package/esm/admins/adminsSchemas.js +2 -9
  36. package/esm/attribute-sets/attributeSetsInterfaces.d.ts +6 -6
  37. package/esm/auth-provider/authProvidersInterfaces.d.ts +4 -4
  38. package/esm/base/attributes.d.ts +105 -0
  39. package/esm/base/attributes.js +163 -0
  40. package/esm/base/utils.d.ts +95 -2
  41. package/esm/base/validation.d.ts +29 -0
  42. package/esm/base/validation.js +92 -0
  43. package/esm/blocks/blocksInterfaces.d.ts +1 -1
  44. package/esm/blocks/blocksSchemas.d.ts +6 -6
  45. package/esm/blocks/blocksSchemas.js +2 -1
  46. package/esm/discounts/discountsInterfaces.d.ts +1 -1
  47. package/esm/forms-data/formsDataApi.d.ts +16 -6
  48. package/esm/forms-data/formsDataApi.js +14 -4
  49. package/esm/forms-data/formsDataInterfaces.d.ts +48 -15
  50. package/esm/index.d.ts +1 -0
  51. package/esm/index.js +1 -0
  52. package/esm/integration-collections/integrationCollectionsInterfaces.d.ts +6 -6
  53. package/esm/menus/menusInterfaces.d.ts +1 -1
  54. package/esm/menus/menusSchemas.js +2 -1
  55. package/esm/orders/ordersInterfaces.d.ts +1 -1
  56. package/esm/pages/pagesSchemas.d.ts +3 -3
  57. package/esm/pages/pagesSchemas.js +2 -1
  58. package/esm/product-statuses/productStatusesInterfaces.d.ts +2 -2
  59. package/esm/products/productsSchemas.d.ts +5 -5
  60. package/esm/products/productsSchemas.js +3 -2
  61. package/esm/templates/templatesInterfaces.d.ts +1 -1
  62. package/esm/templates/templatesSchemas.d.ts +3 -3
  63. package/esm/templates/templatesSchemas.js +2 -1
  64. package/esm/templates-preview/templatesPreviewInterfaces.d.ts +2 -2
  65. package/package.json +2 -2
package/README.md CHANGED
@@ -202,6 +202,24 @@ Zod and the response schemas are loaded on demand, the first time a response act
202
202
 
203
203
  Together that means a project calling a single SDK method loads about **43 kB minified (9.7 kB gzip)** instead of 536 kB, with Zod, the schemas and Socket.io landing in chunks that are never requested. The SDK is published as both CommonJS and ESM (`sideEffects: false`), so bundlers can tree-shake the rest.
204
204
 
205
+ ### Attribute Values
206
+
207
+ `IAttributeValue.value` is typed `unknown` — its shape is decided by `type` at runtime. The trap is files: an `image`/`file` attribute holding **one** file has its value unwrapped to the file object itself, while several files and every `groupOfImages` stay an array. Reading the wrong shape is not a compile error, it is a silently missing image. Read files through the helpers instead:
208
+
209
+ ```js
210
+ import { defineOneEntry, getAttributeFile, getAttributeFiles } from 'oneentry'
211
+
212
+ const { Pages } = defineOneEntry('your-url', { token: 'your-app-token' })
213
+ const page = await Pages.getPageByUrl('catalog')
214
+
215
+ // Same call whether the attribute holds one file or a gallery.
216
+ const images = getAttributeFiles(page.attributeValues.gallery) // IAttributeFile[]
217
+ const cover = getAttributeFile(page.attributeValues.cover) // IAttributeFile | null
218
+ const blurDataURL = cover?.previewLink?.default?.[0]
219
+ ```
220
+
221
+ Also exported: `getAdditionalFields(attr)` for an attribute's nested fields as a marker map, the type guards `isFileAttribute`, `isStringAttribute`, `isNumberAttribute`, `isListAttribute`, the file type `IAttributeFile`, and `ITypedAttributeValue` — `IAttributeValue` as a discriminated union over `type`, to annotate with where you want the compiler to enforce the shape.
222
+
205
223
  ### Time Intervals
206
224
 
207
225
  Attributes of type `timeInterval` return a compact recurrence rule (an anchor date, daily time ranges and repeat flags), not a ready list of slots. The SDK does not expand it eagerly — a single attribute can materialize into megabytes of slots — so resolve it on demand with `expandAttributeTimeIntervals`, passing the window you actually render:
package/changelog.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # SDK Change Log
2
2
 
3
+ ## v.1.0.163
4
+
5
+ ### What's Fixed
6
+
7
+ - **FormData > `getFormsDataByMarker` — the filter body is typed.** It was `body?: object`, so nothing about it was checked or suggested. That is worse than merely inconvenient: the API **ignores** a field it does not recognize, so `statuses: ['approved']` (note the "s") returned every record — unmoderated ones included — while the code looked correct. The new `IFormsDataFilter` declares the fields the endpoint actually applies, each verified against a live project:
8
+
9
+ ```ts
10
+ await FormData.getFormsDataByMarker('review', 12, {
11
+ entityIdentifier: 'blog',
12
+ parentId: 10,
13
+ userIdentifier: '',
14
+ status: ['approved'],
15
+ dateFrom: '2025-01-01',
16
+ dateTo: '',
17
+ })
18
+ ```
19
+
20
+ Note `entityparentIdentifier` from the old JSDoc example is **not** among them: the API ignores it (filtering by it changes nothing), so it is not declared. Use `parentId` to fetch replies to a submission. Also exported: `FormDataStatus`.
21
+
22
+ - **FormData > the JSDoc example used a status that does not exist.** It showed `"status": ["new", "approved"]`; copied as-is it answers `400 each value in status must be a valid enum value`, without naming the valid ones. The set is `sent`, `moderation`, `approved`, `banned`, `deleted` — now spelled out next to `status` in the JSDoc, and enforced by `FormDataStatus`. `status` must also be an array; a bare string is a `400`.
23
+
24
+ - **FormData > the locale unwrapping of `formData` is now documented.** The API returns `formData: { "en_US": [ … ] }`, the SDK normalizes it and hands over the array for the requested `langCode`. Nothing said so, so code written against the raw API response — the one you see first, through curl or the panel — read `formData[langCode]`, got `undefined`, and rendered an empty list with no error anywhere. It is now stated in the method's JSDoc and on `formData` in `IFormByMarkerDataEntity` / `IFormDataEntity`. Behaviour is unchanged.
25
+
26
+ - **FormData > `IBodyTypeImageGroupOfImages.type` had a typo: `'groupOfImage'`**, a value the API rejects (`400 formData's marker '…' type must be one of […]`). Worse than a plain typo — the editor autocompleted it. It is now `'groupOfImages'`, matching the attribute type and the SDK's own upload branch, which has always checked for the plural.
27
+
28
+ - **FormData > `postFormsData` JSDoc now points at `IImageValue` / `IFileValue`** — the shape an attached file must take, which is *not* what `FileUploading.upload` returns (that one carries `contentType`, which form data rejects; the SDK strips it for you when it uploads the file itself). These interfaces were the only place that shape was written down, and nothing linked to them.
29
+
30
+ - **AttributesSets > `getSingleAttributeByMarkerSet` — the interface declared its arguments in the reverse order of the implementation.** `IAttributesSets` (and its JSDoc) said `(attributeMarker, setMarker, langCode)`, while `AttributesSetsApi` — and the request path `/{setMarker}/attributes/{attributeMarker}` — take `(setMarker, attributeMarker, langCode)`. Both parameters are `string`, so `implements` did not catch it and neither did any caller's compiler: a call written from the interface built `/icon/attributes/page_content` and the API answered `404 Attribute not found`, which reads like a missing attribute rather than a wrong call. The interface now matches the implementation; no runtime behaviour changed.
31
+
32
+ - **15 module interfaces declared `langCode` as required** while the implementing method defaults it — Blocks > `getBlockByMarker`, Discounts > `getDiscountByMarker`, IntegrationCollections > `getICollections` / `getICollectionById` / `getICollectionRowsById` / `getICollectionRowByMarkerAndId` / `createICollectionRow` / `updateICollectionRow`, Menus > `getMenusByMarker`, Orders > `getAllStatusesByStorageMarker`, ProductStatuses > `getProductStatuses` / `getProductsByStatusMarker`, Templates > `getTemplateByType`, TemplatePreviews > `getTemplatePreviews` / `getTemplatePreviewByMarker`. All are now optional, as the JSDoc already stated.
33
+
34
+ - **Two parameters were named differently in the interface than in the implementation**: AuthProvider > `signUp` / `auth` took `data` (now `body`), and FormData > `getFormsDataByMarker` took `isNested`, documented as "hierarchical data", while the method sends `isExtended` — a flag for additional fields. Names and docs now follow the implementation.
35
+
36
+ - **A test now guards this whole class of defect.** `src/base/tests/signatures.spec.ts` parses the sources with the TypeScript compiler API and compares every `*Api` class against the interface it `implements` — parameter names, order and optionality. The compiler cannot do this: it matches parameters positionally by type, so a swapped pair of `string`s is a valid implementation to it.
37
+
38
+ ### What's New
39
+
40
+ - **Typed access to attribute values.** `IAttributeValue.value` is `unknown` — its shape is decided by `type` at runtime — and the rule for that shape lived only in a JSDoc comment, so breaking it cost nothing at compile time. The classic case: an `image` attribute holding **one** file has its `value` unwrapped to the file object itself, so a helper written as `Array.isArray(v) ? v[0] : undefined` returns `undefined` for every single-image attribute, builds cleanly, and shows an empty page. The SDK now ships the missing pieces:
41
+
42
+ ```ts
43
+ import { getAttributeFiles, getAttributeFile } from 'oneentry'
44
+
45
+ // Same call for one image and for a gallery — no branching on the shape.
46
+ const images = getAttributeFiles(page.attributeValues.cover)
47
+ const cover = getAttributeFile(product.attributeValues.img)
48
+ const blurDataURL = cover?.previewLink?.default?.[0]
49
+ ```
50
+
51
+ - `getAttributeFiles(attr)` → `IAttributeFile[]` — every file of an `image`, `file` or `groupOfImages` attribute, collapsing the single-file and array shapes to one list; empty for anything else.
52
+ - `getAttributeFile(attr)` → `IAttributeFile | null` — the first file, for covers and previews.
53
+ - `getAdditionalFields(attr)` → `Record<string, IAttributeValue>` — nested fields as a marker map. `additionalFields` is typed `Record<string, IAttributeValue> | unknown[]` because the API returns an empty **array** when there are none, and that union makes every field read a type error; the helper collapses it (and converts the `rawData: true` array by `marker`).
54
+ - Type guards `isFileAttribute`, `isStringAttribute`, `isNumberAttribute`, `isListAttribute` — alongside the existing `isTimeIntervalAttribute`.
55
+
56
+ - **`IAttributeFile`** — the file object of an attribute value is now an exported type: `filename`, `downloadLink`, `size`, `contentType`, plus the optional `previewLink` (previews keyed by template, each a `[base64 placeholder, url]` pair — the placeholder is what a `blurDataURL` needs) and `defaultPreview`. It had to be re-declared by hand in every project until now.
57
+
58
+ - **Attribute values are now validated.** Every schema carrying `attributeValues` used `z.record(z.string(), z.any())` — the whole attribute payload passed validation unconditionally, so a change in the API's file shape reached consumers as an empty gallery, not as an error. The shared `AttributeValuesSchema` (`base/validation.ts`) replaces it in the admins, blocks, menus, pages, products and templates schemas. It stays open where the data is open — what `value` holds is defined by the project's own attribute set — and checks the one part the API fixes and the SDK's helpers depend on: the files of `image` / `file` / `groupOfImages` attributes, nested `additionalFields` included. A renamed or dropped `downloadLink` is reported as a validation issue naming the marker and index; unknown new fields on a file are accepted. Exported alongside it: `AttributeFileSchema`.
59
+
60
+ - **`ITypedAttributeValue`** — `IAttributeValue` as a discriminated union over `type` (`IFileAttributeValue`, `IStringAttributeValue`, `INumberAttributeValue`, `IListAttributeValue`, `ITimeIntervalAttributeValue`), so `switch (attr.type)` narrows `value` for you. The union is closed on purpose: a loose member (`type: string`) would match every case and collapse the narrowing back to `unknown`; types the SDK does not model (`entity`, `date`, custom ones) stay on `IAttributeValue`. Opt-in — `IAttributeValues` keeps the loose `IAttributeValue`, so nothing existing changes.
61
+
3
62
  ## v.1.0.162
4
63
 
5
64
  ### What's New
@@ -3,6 +3,10 @@
3
3
  * @description Zod schemas for validating admins-related API responses
4
4
  */
5
5
  import { z } from 'zod';
6
+ /**
7
+ * Admin entity schema
8
+ * Includes all fields returned by the API
9
+ */
6
10
  export declare const AdminEntitySchema: z.ZodObject<{
7
11
  id: z.ZodNumber;
8
12
  attributeSetId: z.ZodNullable<z.ZodNumber>;
@@ -10,17 +14,7 @@ export declare const AdminEntitySchema: z.ZodObject<{
10
14
  attributeSetIdentifier: z.ZodNullable<z.ZodString>;
11
15
  position: z.ZodNullable<z.ZodNumber>;
12
16
  isSync: z.ZodBoolean;
13
- attributeValues: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<{
14
- type: string;
15
- value: unknown;
16
- position?: number;
17
- additionalFields?: Record<string, unknown> | unknown[];
18
- }, unknown, z.core.$ZodTypeInternals<{
19
- type: string;
20
- value: unknown;
21
- position?: number;
22
- additionalFields?: Record<string, unknown> | unknown[];
23
- }, unknown>>>>;
17
+ attributeValues: z.ZodRecord<z.ZodString, z.ZodUnknown>;
24
18
  moduleFormConfigs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
25
19
  distance: z.ZodOptional<z.ZodNumber>;
26
20
  }, z.core.$strip>;
@@ -34,17 +28,7 @@ export declare const AdminsResponseSchema: z.ZodArray<z.ZodObject<{
34
28
  attributeSetIdentifier: z.ZodNullable<z.ZodString>;
35
29
  position: z.ZodNullable<z.ZodNumber>;
36
30
  isSync: z.ZodBoolean;
37
- attributeValues: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<{
38
- type: string;
39
- value: unknown;
40
- position?: number;
41
- additionalFields?: Record<string, unknown> | unknown[];
42
- }, unknown, z.core.$ZodTypeInternals<{
43
- type: string;
44
- value: unknown;
45
- position?: number;
46
- additionalFields?: Record<string, unknown> | unknown[];
47
- }, unknown>>>>;
31
+ attributeValues: z.ZodRecord<z.ZodString, z.ZodUnknown>;
48
32
  moduleFormConfigs: z.ZodOptional<z.ZodArray<z.ZodAny>>;
49
33
  distance: z.ZodOptional<z.ZodNumber>;
50
34
  }, z.core.$strip>>;
@@ -6,18 +6,11 @@ exports.AdminsResponseSchema = exports.AdminEntitySchema = void 0;
6
6
  * @description Zod schemas for validating admins-related API responses
7
7
  */
8
8
  const zod_1 = require("zod");
9
+ const validation_1 = require("../base/validation");
9
10
  /**
10
11
  * Admin entity schema
11
12
  * Includes all fields returned by the API
12
13
  */
13
- const AttributeValueSchema = zod_1.z.object({
14
- type: zod_1.z.string(),
15
- value: zod_1.z.unknown(),
16
- position: zod_1.z.number().optional(),
17
- additionalFields: zod_1.z
18
- .union([zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()), zod_1.z.array(zod_1.z.unknown())])
19
- .optional(),
20
- });
21
14
  exports.AdminEntitySchema = zod_1.z.object({
22
15
  id: zod_1.z.number(),
23
16
  attributeSetId: zod_1.z.number().nullable(),
@@ -25,7 +18,7 @@ exports.AdminEntitySchema = zod_1.z.object({
25
18
  attributeSetIdentifier: zod_1.z.string().nullable(),
26
19
  position: zod_1.z.number().nullable(),
27
20
  isSync: zod_1.z.boolean(),
28
- attributeValues: zod_1.z.record(zod_1.z.string(), zod_1.z.record(zod_1.z.string(), AttributeValueSchema)),
21
+ attributeValues: validation_1.AttributeValuesSchema,
29
22
  moduleFormConfigs: zod_1.z.array(zod_1.z.any()).optional(),
30
23
  distance: zod_1.z.number().optional(),
31
24
  });
@@ -14,18 +14,18 @@ interface IAttributesSets {
14
14
  * @throws {IError} - If there is an error during the fetch operation, it will return an error object.
15
15
  * @description This method fetches attributes by a specific marker.
16
16
  */
17
- getAttributesByMarker(marker: string, langCode: string): Promise<IAttributesSetsEntity[] | IError>;
17
+ getAttributesByMarker(marker: string, langCode?: string): Promise<IAttributesSetsEntity[] | IError>;
18
18
  /**
19
- * Fetches a single attribute by its marker and the set marker.
19
+ * Fetches a single attribute by the set marker and the attribute marker.
20
20
  * @handleName getSingleAttributeByMarkerSet
21
- * @param {string} attributeMarker - The marker identifying the attribute. Example: "color".
22
- * @param {string} setMarker - The marker identifying the attribute set. Example: "productAttributes".
21
+ * @param {string} setMarker - The marker identifying the attribute set — the first path segment. Example: "productAttributes".
22
+ * @param {string} attributeMarker - The marker identifying the attribute inside that set. Example: "color".
23
23
  * @param {string} [langCode] - The language code for localization purposes. Default: "en_US".
24
24
  * @returns {IAttributesSetsEntity} A promise that resolves to an attribute set entity or an error.
25
25
  * @throws {IError} - If there is an error during the fetch operation, it will return an error object.
26
- * @description This method fetches a single attribute by its marker and the set marker.
26
+ * @description This method fetches a single attribute of an attribute set. The order matches the request path `/{setMarker}/attributes/{attributeMarker}`: the set comes first. Both parameters are strings, so swapping them type-checks and answers `404 Attribute not found`.
27
27
  */
28
- getSingleAttributeByMarkerSet(attributeMarker: string, setMarker: string, langCode: string): Promise<IAttributesSetsEntity | IError>;
28
+ getSingleAttributeByMarkerSet(setMarker: string, attributeMarker: string, langCode?: string): Promise<IAttributesSetsEntity | IError>;
29
29
  }
30
30
  /**
31
31
  * Represents the structure of a list item.
@@ -8,7 +8,7 @@ interface IAuthProvider {
8
8
  * Registers a new user.
9
9
  * @handleName signUp
10
10
  * @param {string} marker - The marker identifying the auth provider. Example: "email".
11
- * @param {ISignUpData} data - The data required for user registration.
11
+ * @param {ISignUpData} body - The data required for user registration.
12
12
  * @example
13
13
  {
14
14
  "formIdentifier": "reg",
@@ -40,7 +40,7 @@ interface IAuthProvider {
40
40
  * @throws {IError} - If there is an error during the fetch operation, it will return an error object.
41
41
  * @description This method registers a new user.
42
42
  */
43
- signUp(marker: string, data: ISignUpData, langCode?: string): Promise<ISignUpEntity | IError>;
43
+ signUp(marker: string, body: ISignUpData, langCode?: string): Promise<ISignUpEntity | IError>;
44
44
  /**
45
45
  * Generates an activation code for a user.
46
46
  * @handleName generateCode
@@ -79,12 +79,12 @@ interface IAuthProvider {
79
79
  * Authorizes a user.
80
80
  * @handleName auth
81
81
  * @param {string} marker - The marker identifying the auth provider. Example: "email".
82
- * @param {IAuthPostBody} data - The data required for user authorization. Example: .
82
+ * @param {IAuthPostBody} body - The data required for user authorization. Example: .
83
83
  * @returns {IAuthEntity} A promise that resolves to an auth entity or an error.
84
84
  * @throws {IError} - If there is an error during the fetch operation, it will return an error object.
85
85
  * @description This method authorizes a user.
86
86
  */
87
- auth(marker: string, data: IAuthPostBody): Promise<IAuthEntity | IError>;
87
+ auth(marker: string, body: IAuthPostBody): Promise<IAuthEntity | IError>;
88
88
  /**
89
89
  * Refreshes a user's access token.
90
90
  * @handleName refresh
@@ -0,0 +1,105 @@
1
+ import type { IAttributeFile, IAttributeValue, IFileAttributeValue, IListAttributeValue, INumberAttributeValue, IStringAttributeValue } from './utils';
2
+ /**
3
+ * Narrows an attribute value to a file-bearing attribute.
4
+ *
5
+ * `IAttributeValue.value` is `unknown` — its shape depends on `type` — so this
6
+ * guard is what lets you reach the files without a cast. It accepts both shapes
7
+ * the SDK produces: a single file object for an `image`/`file` attribute holding
8
+ * exactly one file, an array for several and for every `groupOfImages`.
9
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
10
+ * @returns {boolean} True when the attribute is an `image`, `file` or `groupOfImages` carrying files (or nothing).
11
+ * @example
12
+ * ```ts
13
+ * const attr = page.attributeValues.cover;
14
+ * if (isFileAttribute(attr)) {
15
+ * // attr.value is IAttributeFile | IAttributeFile[] | null — no cast
16
+ * }
17
+ * ```
18
+ */
19
+ export declare function isFileAttribute(attr: IAttributeValue | undefined): attr is IFileAttributeValue;
20
+ /**
21
+ * Reads every file of an attribute as an array.
22
+ *
23
+ * The one-call path for the common case. An `image`/`file` attribute holding a
24
+ * single file has it unwrapped to the object itself, while several files and
25
+ * every `groupOfImages` stay an array — a rule that is easy to get wrong in a
26
+ * way the compiler cannot catch, since `value` is `unknown`: reading `value[0]`
27
+ * off a single file yields `undefined` and nothing anywhere reports an error.
28
+ * This helper collapses both shapes to a list.
29
+ *
30
+ * Anything that is not a file-bearing attribute yields an empty array, so it is
31
+ * safe to call on an arbitrary attribute without checking `type` first.
32
+ * @param {IAttributeValue | undefined} attr - An attribute value, e.g. `page.attributeValues.cover`.
33
+ * @returns {IAttributeFile[]} The files, in the order the API returned them; empty when the attribute holds none or is not file-bearing.
34
+ * @example
35
+ * ```ts
36
+ * import { getAttributeFiles } from 'oneentry';
37
+ *
38
+ * // Works the same for one image and for a gallery.
39
+ * const images = getAttributeFiles(page.attributeValues.cover);
40
+ * images.map((file) => file.downloadLink);
41
+ * ```
42
+ */
43
+ export declare function getAttributeFiles(attr: IAttributeValue | undefined): IAttributeFile[];
44
+ /**
45
+ * Reads the first file of an attribute.
46
+ *
47
+ * The single-image counterpart of {@link getAttributeFiles} — same shape
48
+ * handling, but returns the file itself instead of a list. For a gallery it
49
+ * returns the first entry, which is what a preview or a cover usually needs.
50
+ * @param {IAttributeValue | undefined} attr - An attribute value, e.g. `product.attributeValues.img`.
51
+ * @returns {IAttributeFile | null} The first file, or null when the attribute holds none.
52
+ * @example
53
+ * ```ts
54
+ * import { getAttributeFile } from 'oneentry';
55
+ *
56
+ * const cover = getAttributeFile(product.attributeValues.img);
57
+ * const blurDataURL = cover?.previewLink?.default?.[0];
58
+ * ```
59
+ */
60
+ export declare function getAttributeFile(attr: IAttributeValue | undefined): IAttributeFile | null;
61
+ /**
62
+ * Narrows an attribute value to a text attribute.
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).
65
+ */
66
+ export declare function isStringAttribute(attr: IAttributeValue | undefined): attr is IStringAttributeValue;
67
+ /**
68
+ * Narrows an attribute value to a numeric attribute.
69
+ *
70
+ * The SDK casts `integer`, `float` and `real` values to a JS number, so the
71
+ * guard checks for one — a value that failed to cast is `null`.
72
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
73
+ * @returns {boolean} True when the attribute is an `integer`, `float` or `real` carrying a number (or nothing).
74
+ */
75
+ export declare function isNumberAttribute(attr: IAttributeValue | undefined): attr is INumberAttributeValue;
76
+ /**
77
+ * Narrows an attribute value to a `list` attribute.
78
+ *
79
+ * The option shape is defined by the attribute set, so the entries stay
80
+ * `unknown` — the guard only promises the array container.
81
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
82
+ * @returns {boolean} True when the attribute is a `list` carrying an array.
83
+ */
84
+ export declare function isListAttribute(attr: IAttributeValue | undefined): attr is IListAttributeValue;
85
+ /**
86
+ * Reads the nested fields of an attribute as a marker map.
87
+ *
88
+ * `additionalFields` is typed `Record<string, IAttributeValue> | unknown[]`
89
+ * because the API returns an empty **array** when an attribute has no nested
90
+ * fields — the union makes every read of a field a type error. This helper
91
+ * collapses that case to an empty map, so the result is always keyed by marker.
92
+ *
93
+ * With `rawData: true` in the config the SDK keeps the API's original array of
94
+ * fields; it is converted here by `marker` as well, so the helper returns the
95
+ * same map in both modes.
96
+ * @param {IAttributeValue | undefined} attr - An attribute value whose nested fields to read.
97
+ * @returns {Record<string, IAttributeValue>} Nested attribute values keyed by marker; empty when there are none.
98
+ * @example
99
+ * ```ts
100
+ * import { getAdditionalFields } from 'oneentry';
101
+ *
102
+ * const alt = getAdditionalFields(page.attributeValues.cover).alt?.value;
103
+ * ```
104
+ */
105
+ export declare function getAdditionalFields(attr: IAttributeValue | undefined): Record<string, IAttributeValue>;
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isFileAttribute = isFileAttribute;
4
+ exports.getAttributeFiles = getAttributeFiles;
5
+ exports.getAttributeFile = getAttributeFile;
6
+ exports.isStringAttribute = isStringAttribute;
7
+ exports.isNumberAttribute = isNumberAttribute;
8
+ exports.isListAttribute = isListAttribute;
9
+ exports.getAdditionalFields = getAdditionalFields;
10
+ const _FILE_TYPES = ['image', 'file', 'groupOfImages'];
11
+ /**
12
+ * Tests whether a value looks like a file object of an attribute.
13
+ *
14
+ * A file is identified by `downloadLink`: it is the one field every file of
15
+ * every attribute carries, while previews are only generated for images.
16
+ * @param {unknown} value - The value to test.
17
+ * @returns {boolean} True when the value is a file object.
18
+ */
19
+ function _isFile(value) {
20
+ return (!!value &&
21
+ typeof value === 'object' &&
22
+ typeof value.downloadLink === 'string');
23
+ }
24
+ /**
25
+ * Narrows an attribute value to a file-bearing attribute.
26
+ *
27
+ * `IAttributeValue.value` is `unknown` — its shape depends on `type` — so this
28
+ * guard is what lets you reach the files without a cast. It accepts both shapes
29
+ * the SDK produces: a single file object for an `image`/`file` attribute holding
30
+ * exactly one file, an array for several and for every `groupOfImages`.
31
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
32
+ * @returns {boolean} True when the attribute is an `image`, `file` or `groupOfImages` carrying files (or nothing).
33
+ * @example
34
+ * ```ts
35
+ * const attr = page.attributeValues.cover;
36
+ * if (isFileAttribute(attr)) {
37
+ * // attr.value is IAttributeFile | IAttributeFile[] | null — no cast
38
+ * }
39
+ * ```
40
+ */
41
+ function isFileAttribute(attr) {
42
+ if (!attr || !_FILE_TYPES.includes(attr.type))
43
+ return false;
44
+ if (attr.value === null || attr.value === undefined)
45
+ return true;
46
+ return Array.isArray(attr.value)
47
+ ? attr.value.every((item) => _isFile(item))
48
+ : _isFile(attr.value);
49
+ }
50
+ /**
51
+ * Reads every file of an attribute as an array.
52
+ *
53
+ * The one-call path for the common case. An `image`/`file` attribute holding a
54
+ * single file has it unwrapped to the object itself, while several files and
55
+ * every `groupOfImages` stay an array — a rule that is easy to get wrong in a
56
+ * way the compiler cannot catch, since `value` is `unknown`: reading `value[0]`
57
+ * off a single file yields `undefined` and nothing anywhere reports an error.
58
+ * This helper collapses both shapes to a list.
59
+ *
60
+ * Anything that is not a file-bearing attribute yields an empty array, so it is
61
+ * safe to call on an arbitrary attribute without checking `type` first.
62
+ * @param {IAttributeValue | undefined} attr - An attribute value, e.g. `page.attributeValues.cover`.
63
+ * @returns {IAttributeFile[]} The files, in the order the API returned them; empty when the attribute holds none or is not file-bearing.
64
+ * @example
65
+ * ```ts
66
+ * import { getAttributeFiles } from 'oneentry';
67
+ *
68
+ * // Works the same for one image and for a gallery.
69
+ * const images = getAttributeFiles(page.attributeValues.cover);
70
+ * images.map((file) => file.downloadLink);
71
+ * ```
72
+ */
73
+ function getAttributeFiles(attr) {
74
+ if (!attr)
75
+ return [];
76
+ const value = attr.value;
77
+ if (Array.isArray(value))
78
+ return value.filter((item) => _isFile(item));
79
+ return _isFile(value) ? [value] : [];
80
+ }
81
+ /**
82
+ * Reads the first file of an attribute.
83
+ *
84
+ * The single-image counterpart of {@link getAttributeFiles} — same shape
85
+ * handling, but returns the file itself instead of a list. For a gallery it
86
+ * returns the first entry, which is what a preview or a cover usually needs.
87
+ * @param {IAttributeValue | undefined} attr - An attribute value, e.g. `product.attributeValues.img`.
88
+ * @returns {IAttributeFile | null} The first file, or null when the attribute holds none.
89
+ * @example
90
+ * ```ts
91
+ * import { getAttributeFile } from 'oneentry';
92
+ *
93
+ * const cover = getAttributeFile(product.attributeValues.img);
94
+ * const blurDataURL = cover?.previewLink?.default?.[0];
95
+ * ```
96
+ */
97
+ function getAttributeFile(attr) {
98
+ var _a;
99
+ return (_a = getAttributeFiles(attr)[0]) !== null && _a !== void 0 ? _a : null;
100
+ }
101
+ /**
102
+ * Narrows an attribute value to a text attribute.
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).
105
+ */
106
+ function isStringAttribute(attr) {
107
+ return (!!attr &&
108
+ (attr.type === 'string' ||
109
+ attr.type === 'text' ||
110
+ attr.type === 'textEditor') &&
111
+ (attr.value === null || typeof attr.value === 'string'));
112
+ }
113
+ /**
114
+ * Narrows an attribute value to a numeric attribute.
115
+ *
116
+ * The SDK casts `integer`, `float` and `real` values to a JS number, so the
117
+ * guard checks for one — a value that failed to cast is `null`.
118
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
119
+ * @returns {boolean} True when the attribute is an `integer`, `float` or `real` carrying a number (or nothing).
120
+ */
121
+ function isNumberAttribute(attr) {
122
+ return (!!attr &&
123
+ (attr.type === 'integer' ||
124
+ attr.type === 'float' ||
125
+ attr.type === 'real') &&
126
+ (attr.value === null || typeof attr.value === 'number'));
127
+ }
128
+ /**
129
+ * Narrows an attribute value to a `list` attribute.
130
+ *
131
+ * The option shape is defined by the attribute set, so the entries stay
132
+ * `unknown` — the guard only promises the array container.
133
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
134
+ * @returns {boolean} True when the attribute is a `list` carrying an array.
135
+ */
136
+ function isListAttribute(attr) {
137
+ return !!attr && attr.type === 'list' && Array.isArray(attr.value);
138
+ }
139
+ /**
140
+ * Reads the nested fields of an attribute as a marker map.
141
+ *
142
+ * `additionalFields` is typed `Record<string, IAttributeValue> | unknown[]`
143
+ * because the API returns an empty **array** when an attribute has no nested
144
+ * fields — the union makes every read of a field a type error. This helper
145
+ * collapses that case to an empty map, so the result is always keyed by marker.
146
+ *
147
+ * With `rawData: true` in the config the SDK keeps the API's original array of
148
+ * fields; it is converted here by `marker` as well, so the helper returns the
149
+ * same map in both modes.
150
+ * @param {IAttributeValue | undefined} attr - An attribute value whose nested fields to read.
151
+ * @returns {Record<string, IAttributeValue>} Nested attribute values keyed by marker; empty when there are none.
152
+ * @example
153
+ * ```ts
154
+ * import { getAdditionalFields } from 'oneentry';
155
+ *
156
+ * const alt = getAdditionalFields(page.attributeValues.cover).alt?.value;
157
+ * ```
158
+ */
159
+ function getAdditionalFields(attr) {
160
+ const fields = attr === null || attr === void 0 ? void 0 : attr.additionalFields;
161
+ if (!fields || typeof fields !== 'object')
162
+ return {};
163
+ if (Array.isArray(fields)) {
164
+ // rawData mode: the API's own array of fields, each carrying its marker.
165
+ return Object.fromEntries(fields
166
+ .filter((field) => !!field &&
167
+ typeof field === 'object' &&
168
+ typeof field.marker === 'string')
169
+ .map((field) => [field.marker, field]));
170
+ }
171
+ return fields;
172
+ }
@@ -266,10 +266,19 @@ interface ITimeIntervalRange {
266
266
  * @property {string} type - Attribute data type (e.g. "string", "integer", "list", "file", "image").
267
267
  * @property {unknown} value - Attribute value — actual TS type depends on `type`, and the SDK normalizes it to the same shape in every module: `string` for "string"/"text"; `number | null` for "integer"/"float"/"real"; the file object itself for a single-file "image"/"file" and an array of them for several; an array for "list" and "groupOfImages". An attribute with no value is always `null`. Example: "Admins text".
268
268
  * @property {number} [position] - Sort position of the value inside its set; the containing collection is returned sorted by it. Example: 0.
269
- * @property {Record<string, IAttributeValue> | unknown[]} [additionalFields] - Nested attribute values keyed by marker; the API may also return an empty array when none are configured. Optional.
269
+ * @property {Record<string, IAttributeValue> | unknown[]} [additionalFields] - Nested attribute values keyed by marker; the API may also return an empty array when none are configured. Read it through `getAdditionalFields`, which collapses that array to an empty map. Optional.
270
270
  * @property {boolean} [isIcon] - Block/preview attribute flag — whether the field is treated as an icon. Optional.
271
271
  * @property {boolean} [isProductPreview] - Block/preview attribute flag — whether the field is shown in product preview. Optional.
272
272
  * @description A single attribute value attached to an entity (admin, user, page, product, …).
273
+ *
274
+ * `value` is `unknown` because its shape is decided by `type` at runtime, which
275
+ * a single interface cannot express. Do not read it by assumption — the type
276
+ * checker cannot catch a wrong guess (an array read on a single file yields
277
+ * `undefined`, silently, with no error anywhere). Reach it through the guards
278
+ * and helpers instead: `isFileAttribute` / `getAttributeFiles`,
279
+ * `isStringAttribute`, `isNumberAttribute`, `isListAttribute`,
280
+ * `isTimeIntervalAttribute`; or annotate with {@link ITypedAttributeValue} to
281
+ * have the compiler enforce the rule.
273
282
  */
274
283
  interface IAttributeValue {
275
284
  type: string;
@@ -286,6 +295,90 @@ interface IAttributeValue {
286
295
  interface IAttributeValues {
287
296
  [key: string]: IAttributeValue;
288
297
  }
298
+ /**
299
+ * @interface IAttributeFile
300
+ * @property {string} filename - Storage-relative path of the file. Example: "files/project/product/2954/image/48853cbf-4462-42c6-8b99-d14f721c21a2.jpeg".
301
+ * @property {string} downloadLink - Public URL of the original file. Example: "https://my-project.oneentry.cloud/cloud-static/files/project/product/2954/image/48853cbf.jpeg".
302
+ * @property {number} size - File size in bytes. Example: 742690.
303
+ * @property {string} contentType - MIME type of the file. Example: "image/jpeg".
304
+ * @property {Record<string, string[]>} [previewLink] - Generated previews keyed by preview template. Each entry is a `[placeholder, url]` pair: the first item is an inline base64 thumbnail (usable as a `blurDataURL`), the second the preview URL. Optional — previews are generated for images; a non-image `file` may arrive without them. Example: { "default": ["data:image/webp;base64,UklGRmwA…", "https://…48853cbf.preview.default.jpeg"] }.
305
+ * @property {string} [defaultPreview] - Key of `previewLink` to use by default. Optional — present alongside `previewLink`. Example: "default".
306
+ * @description One file of an `image`, `file` or `groupOfImages` attribute value. Reach it through {@link IFileAttributeValue} — or, without caring whether the attribute holds one file or many, through `getAttributeFiles`.
307
+ */
308
+ interface IAttributeFile {
309
+ filename: string;
310
+ downloadLink: string;
311
+ size: number;
312
+ contentType: string;
313
+ previewLink?: Record<string, string[]>;
314
+ defaultPreview?: string;
315
+ }
316
+ /**
317
+ * @interface IFileAttributeValue
318
+ * @property {'image' | 'file' | 'groupOfImages'} type - Discriminant identifying the attribute as file-bearing.
319
+ * @property {IAttributeFile | IAttributeFile[] | null} value - The file itself when an `image`/`file` attribute holds exactly one, an array when it holds several, always an array for `groupOfImages`, `null` when nothing is set.
320
+ * @description {@link IAttributeValue} narrowed to attributes that carry files, whose generic `value: unknown` resolves to {@link IAttributeFile}. The single/array split is what the SDK's normalization produces, so it has to be handled: narrow with `isFileAttribute`, or skip the branch entirely with `getAttributeFiles` / `getAttributeFile`.
321
+ */
322
+ interface IFileAttributeValue extends IAttributeValue {
323
+ type: 'image' | 'file' | 'groupOfImages';
324
+ value: IAttributeFile | IAttributeFile[] | null;
325
+ }
326
+ /**
327
+ * @interface IStringAttributeValue
328
+ * @property {'string' | 'text' | 'textEditor'} type - Discriminant identifying the attribute as text-bearing.
329
+ * @property {string | null} value - The text; `null` when no value is set.
330
+ * @description {@link IAttributeValue} narrowed to text attributes. Narrow to it with `isStringAttribute`.
331
+ */
332
+ interface IStringAttributeValue extends IAttributeValue {
333
+ type: 'string' | 'text' | 'textEditor';
334
+ value: string | null;
335
+ }
336
+ /**
337
+ * @interface INumberAttributeValue
338
+ * @property {'integer' | 'float' | 'real'} type - Discriminant identifying the attribute as numeric.
339
+ * @property {number | null} value - The number, cast by the SDK from the API's string form; `null` when no value is set.
340
+ * @description {@link IAttributeValue} narrowed to numeric attributes. Narrow to it with `isNumberAttribute`.
341
+ */
342
+ interface INumberAttributeValue extends IAttributeValue {
343
+ type: 'integer' | 'float' | 'real';
344
+ value: number | null;
345
+ }
346
+ /**
347
+ * @interface IListAttributeValue
348
+ * @property {'list'} type - Discriminant identifying the attribute as a list.
349
+ * @property {unknown[]} value - The selected options. The option shape is defined by the attribute set, so the entries stay `unknown` — only the array container is guaranteed.
350
+ * @description {@link IAttributeValue} narrowed to `list` attributes. Narrow to it with `isListAttribute`.
351
+ */
352
+ interface IListAttributeValue extends IAttributeValue {
353
+ type: 'list';
354
+ value: unknown[];
355
+ }
356
+ /**
357
+ * ITypedAttributeValue — {@link IAttributeValue} as a discriminated union over `type`.
358
+ *
359
+ * Opt-in, and deliberately closed: it lists only the types whose `value` shape
360
+ * the SDK guarantees, so `switch (attr.type)` narrows `value` for you. A loose
361
+ * member (`type: string`) would be assignable to every case and collapse the
362
+ * narrowing back to `unknown`, which is exactly what this type exists to avoid.
363
+ *
364
+ * `IAttributeValues` keeps the loose `IAttributeValue`, so nothing existing
365
+ * changes: narrow into this union with the guards (`isFileAttribute` and the
366
+ * rest), or annotate a value you control. Attribute types the SDK does not
367
+ * model (`entity`, `date`, custom ones) stay on `IAttributeValue`.
368
+ * @example
369
+ * ```ts
370
+ * function render(attr: ITypedAttributeValue) {
371
+ * switch (attr.type) {
372
+ * case 'image':
373
+ * case 'file':
374
+ * return attr.value; // IAttributeFile | IAttributeFile[] | null
375
+ * case 'integer':
376
+ * return attr.value; // number | null
377
+ * }
378
+ * }
379
+ * ```
380
+ */
381
+ type ITypedAttributeValue = IFileAttributeValue | IStringAttributeValue | INumberAttributeValue | IListAttributeValue | ITimeIntervalAttributeValue;
289
382
  /**
290
383
  * @interface IRating
291
384
  * @property {number} [value] - Average rating value. Example: 4.5.
@@ -351,4 +444,4 @@ type LangType = string | Array<string>;
351
444
  * LocalizeType
352
445
  */
353
446
  type LocalizeType = ILocalizeInfo;
354
- export type { IAttributeLocalizeInfo, IAttributes, IAttributeValue, IAttributeValues, IConfig, IError, IHttpHeaders, IHttpOptions, ILocalizeInfo, IRating, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalGroup, ITimeIntervalPoint, ITimeIntervalRange, ITimeIntervalSchedule, ITimeIntervalWindow, LangType, LocalizeType, TimeIntervalPair, };
447
+ export type { IAttributeFile, IAttributeLocalizeInfo, IAttributes, IAttributeValue, IAttributeValues, IConfig, IError, IFileAttributeValue, IHttpHeaders, IHttpOptions, IListAttributeValue, ILocalizeInfo, INumberAttributeValue, IRating, IStringAttributeValue, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalGroup, ITimeIntervalPoint, ITimeIntervalRange, ITimeIntervalSchedule, ITimeIntervalWindow, ITypedAttributeValue, LangType, LocalizeType, TimeIntervalPair, };