@makehq/forman-schema 1.2.1 → 1.2.3

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/dist/index.cjs CHANGED
@@ -32,12 +32,20 @@ function noEmpty(text) {
32
32
  function isObject(value) {
33
33
  return typeof value === "object" && value !== null && !Array.isArray(value);
34
34
  }
35
+ function isOptionGroup(value) {
36
+ return "options" in value && Array.isArray(value.options);
37
+ }
35
38
 
36
39
  // src/forman.ts
37
40
  var SchemaConversionError = class extends Error {
41
+ /** Field that caused the error */
42
+ field;
43
+ /**
44
+ * @param message The error message
45
+ * @param field The field that caused the error
46
+ */
38
47
  constructor(message, field) {
39
48
  super(message);
40
- this.field = field;
41
49
  this.name = "SchemaConversionError";
42
50
  }
43
51
  };
@@ -236,41 +244,52 @@ function handleArrayType(field, result, context) {
236
244
  return result;
237
245
  }
238
246
  function handleSelectType(field, result, context) {
239
- const options = isObject(field.options) ? field.options.store : field.options;
247
+ const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
240
248
  const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
241
249
  const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
242
- if (typeof options === "string") {
250
+ if (typeof optionsOrGroups === "string") {
243
251
  Object.defineProperty(result, "x-fetch", {
244
252
  configurable: true,
245
253
  enumerable: true,
246
254
  writable: true,
247
- value: appendQueryString(options, context.domain, context.tail)
248
- });
249
- } else if (options?.some((option) => option.label || option.nested)) {
250
- result.oneOf = (options || []).map((option) => {
251
- const localNested = (isObject(option.nested) ? option.nested.store : option.nested) || nested;
252
- const localDomain = (isObject(option.nested) && option.nested.domain ? option.nested.domain : domain) || context.domain;
253
- if (localNested) {
254
- context.addConditionalFields(
255
- field.name,
256
- option.value,
257
- typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : toJSONSchemaInternal(
258
- { type: "collection", spec: localNested },
259
- {
260
- ...context,
261
- domain: localDomain,
262
- tail: [...context.tail, field.name]
263
- }
264
- )
265
- );
266
- }
267
- return {
268
- title: noEmpty(option.label),
269
- const: option.value
270
- };
255
+ value: appendQueryString(optionsOrGroups, context.domain, context.tail)
271
256
  });
272
257
  } else {
273
- result.enum = (options || []).map((option) => option.value);
258
+ const options = optionsOrGroups?.some(isOptionGroup) ? optionsOrGroups.flatMap(
259
+ (group) => group.options.map((option) => ({
260
+ ...option,
261
+ label: `${group.label}: ${option.label || option.value}`
262
+ }))
263
+ ) : optionsOrGroups || [];
264
+ if (options?.some((option) => {
265
+ if (isOptionGroup(option)) return option.options.some((option2) => option2.label || option2.nested);
266
+ return option.label || option.nested;
267
+ })) {
268
+ result.oneOf = (options || []).map((option) => {
269
+ const localNested = (isObject(option.nested) ? option.nested.store : option.nested) || nested;
270
+ const localDomain = (isObject(option.nested) && option.nested.domain ? option.nested.domain : domain) || context.domain;
271
+ if (localNested) {
272
+ context.addConditionalFields(
273
+ field.name,
274
+ option.value,
275
+ typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : toJSONSchemaInternal(
276
+ { type: "collection", spec: localNested },
277
+ {
278
+ ...context,
279
+ domain: localDomain,
280
+ tail: [...context.tail, field.name]
281
+ }
282
+ )
283
+ );
284
+ }
285
+ return {
286
+ title: noEmpty(option.label),
287
+ const: option.value
288
+ };
289
+ });
290
+ } else {
291
+ result.enum = (options || []).map((option) => option.value);
292
+ }
274
293
  }
275
294
  if (nested && domain && domain !== context.domain) {
276
295
  if (typeof nested === "string") {
@@ -292,7 +311,7 @@ function handleSelectType(field, result, context) {
292
311
  configurable: true,
293
312
  enumerable: true,
294
313
  writable: true,
295
- value: typeof nested === "string" ? { $ref: nested } : toJSONSchemaInternal(
314
+ value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
296
315
  { type: "collection", spec: nested },
297
316
  {
298
317
  ...context,
package/dist/index.d.cts CHANGED
@@ -34,7 +34,7 @@ type FormanSchemaField = {
34
34
  /** Default value for the field */
35
35
  default?: FormanSchemaValue;
36
36
  /** Available options for select type fields */
37
- options?: FormanSchemaOption[] | FormanSchemaExtendedOptions | string;
37
+ options?: FormanSchemaOption[] | FormanSchemaOptionGroup[] | FormanSchemaExtendedOptions | string;
38
38
  /** Help text or description for the field */
39
39
  help?: string;
40
40
  /** Sub-fields specification for collection or array types */
@@ -47,25 +47,92 @@ type FormanSchemaField = {
47
47
  nested?: FormanSchemaNested;
48
48
  /** Validation rules */
49
49
  validate?: FormanSchemaValidation;
50
+ /** Whether the field is disabled (`false` by default) */
51
+ disabled?: boolean;
52
+ /** Whether the field is mappable */
53
+ mappable?: boolean;
54
+ /** Whether the user will be able to insert new lines in GUI (a textarea will be displayed instead of the text field) */
55
+ multiline?: boolean;
56
+ /** Specifies how to treat HTML tags in the field (text only) */
57
+ tags?: 'strip' | 'stripall' | 'escape';
58
+ /** Allowed extension or array of allowed extensions. (filename only) */
59
+ extension?: string | string[];
60
+ /** Semantic type for the field */
61
+ semantic?: string;
62
+ /** Whether to allow time selection (date only, `true` by default) */
63
+ time?: boolean;
64
+ /** Whether the properties of the object will be in the same order as they are defined in the spec (collection only) */
65
+ sequence?: boolean;
66
+ /** Codepage for the field (buffer only) */
67
+ codepage?: string;
68
+ /** Whether the field is grouped (select only) */
69
+ grouped?: boolean;
70
+ /** Whether a mapped value in the select should be validated against the option values. If true, the value is treated as a dynamic and validation is disabled. The value is set to `true` automatically if select options are generated using RPC. */
71
+ dynamic?: boolean;
72
+ /** Mode for the field (select only) */
73
+ mode?: 'edit' | 'choose';
74
+ /** Sort order for the field (select only) */
75
+ sort?: string;
76
+ /** Adds an extra button to the field which opens an extra form. When the form is submitted, a specified RPC is called and the result is set as a new value of the parameter. */
77
+ rpc?: FormanSchemaRPCButton;
50
78
  } & Record<`x-${string}`, unknown>;
79
+ /**
80
+ * RPC button allows for dynamic value retrieval from an external source
81
+ */
82
+ type FormanSchemaRPCButton = {
83
+ /** RPC button label */
84
+ label: string;
85
+ /** RPC button URL */
86
+ url: string;
87
+ /** RPC button parameters */
88
+ parameters: FormanSchemaField[];
89
+ };
90
+ /**
91
+ * Valid Forman Schema values
92
+ */
51
93
  type FormanSchemaValue = string | number | boolean | null;
94
+ /**
95
+ * Option for a select field
96
+ */
52
97
  type FormanSchemaOption = {
53
98
  /** Option value */
54
99
  value: FormanSchemaValue;
55
100
  /** Option label */
56
101
  label?: string;
102
+ /** Whether the option is the default */
103
+ default?: boolean;
57
104
  /** Nested fields for this option */
58
105
  nested?: FormanSchemaNested;
59
106
  };
107
+ /**
108
+ * Option group for a select field
109
+ */
110
+ type FormanSchemaOptionGroup = {
111
+ /** Group label */
112
+ label: string;
113
+ /** Group options */
114
+ options: FormanSchemaOption[];
115
+ };
116
+ /**
117
+ * Extended options for a select field
118
+ */
60
119
  type FormanSchemaExtendedOptions = {
61
120
  /** Store for the options */
62
- store: FormanSchemaOption[] | string;
121
+ store: FormanSchemaOption[] | FormanSchemaOptionGroup[] | string;
63
122
  /** Nested fields for every option */
64
123
  nested?: FormanSchemaNested;
65
124
  };
125
+ /**
126
+ * Nested fields
127
+ */
66
128
  type FormanSchemaNested = FormanSchemaField[] | string | FormanSchemaExtendedNested;
129
+ /**
130
+ * Extended nested fields
131
+ */
67
132
  type FormanSchemaExtendedNested = {
133
+ /** Store for the nested fields */
68
134
  store: FormanSchemaField[] | string;
135
+ /** Domain for the nested fields */
69
136
  domain?: string;
70
137
  };
71
138
 
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ type FormanSchemaField = {
34
34
  /** Default value for the field */
35
35
  default?: FormanSchemaValue;
36
36
  /** Available options for select type fields */
37
- options?: FormanSchemaOption[] | FormanSchemaExtendedOptions | string;
37
+ options?: FormanSchemaOption[] | FormanSchemaOptionGroup[] | FormanSchemaExtendedOptions | string;
38
38
  /** Help text or description for the field */
39
39
  help?: string;
40
40
  /** Sub-fields specification for collection or array types */
@@ -47,25 +47,92 @@ type FormanSchemaField = {
47
47
  nested?: FormanSchemaNested;
48
48
  /** Validation rules */
49
49
  validate?: FormanSchemaValidation;
50
+ /** Whether the field is disabled (`false` by default) */
51
+ disabled?: boolean;
52
+ /** Whether the field is mappable */
53
+ mappable?: boolean;
54
+ /** Whether the user will be able to insert new lines in GUI (a textarea will be displayed instead of the text field) */
55
+ multiline?: boolean;
56
+ /** Specifies how to treat HTML tags in the field (text only) */
57
+ tags?: 'strip' | 'stripall' | 'escape';
58
+ /** Allowed extension or array of allowed extensions. (filename only) */
59
+ extension?: string | string[];
60
+ /** Semantic type for the field */
61
+ semantic?: string;
62
+ /** Whether to allow time selection (date only, `true` by default) */
63
+ time?: boolean;
64
+ /** Whether the properties of the object will be in the same order as they are defined in the spec (collection only) */
65
+ sequence?: boolean;
66
+ /** Codepage for the field (buffer only) */
67
+ codepage?: string;
68
+ /** Whether the field is grouped (select only) */
69
+ grouped?: boolean;
70
+ /** Whether a mapped value in the select should be validated against the option values. If true, the value is treated as a dynamic and validation is disabled. The value is set to `true` automatically if select options are generated using RPC. */
71
+ dynamic?: boolean;
72
+ /** Mode for the field (select only) */
73
+ mode?: 'edit' | 'choose';
74
+ /** Sort order for the field (select only) */
75
+ sort?: string;
76
+ /** Adds an extra button to the field which opens an extra form. When the form is submitted, a specified RPC is called and the result is set as a new value of the parameter. */
77
+ rpc?: FormanSchemaRPCButton;
50
78
  } & Record<`x-${string}`, unknown>;
79
+ /**
80
+ * RPC button allows for dynamic value retrieval from an external source
81
+ */
82
+ type FormanSchemaRPCButton = {
83
+ /** RPC button label */
84
+ label: string;
85
+ /** RPC button URL */
86
+ url: string;
87
+ /** RPC button parameters */
88
+ parameters: FormanSchemaField[];
89
+ };
90
+ /**
91
+ * Valid Forman Schema values
92
+ */
51
93
  type FormanSchemaValue = string | number | boolean | null;
94
+ /**
95
+ * Option for a select field
96
+ */
52
97
  type FormanSchemaOption = {
53
98
  /** Option value */
54
99
  value: FormanSchemaValue;
55
100
  /** Option label */
56
101
  label?: string;
102
+ /** Whether the option is the default */
103
+ default?: boolean;
57
104
  /** Nested fields for this option */
58
105
  nested?: FormanSchemaNested;
59
106
  };
107
+ /**
108
+ * Option group for a select field
109
+ */
110
+ type FormanSchemaOptionGroup = {
111
+ /** Group label */
112
+ label: string;
113
+ /** Group options */
114
+ options: FormanSchemaOption[];
115
+ };
116
+ /**
117
+ * Extended options for a select field
118
+ */
60
119
  type FormanSchemaExtendedOptions = {
61
120
  /** Store for the options */
62
- store: FormanSchemaOption[] | string;
121
+ store: FormanSchemaOption[] | FormanSchemaOptionGroup[] | string;
63
122
  /** Nested fields for every option */
64
123
  nested?: FormanSchemaNested;
65
124
  };
125
+ /**
126
+ * Nested fields
127
+ */
66
128
  type FormanSchemaNested = FormanSchemaField[] | string | FormanSchemaExtendedNested;
129
+ /**
130
+ * Extended nested fields
131
+ */
67
132
  type FormanSchemaExtendedNested = {
133
+ /** Store for the nested fields */
68
134
  store: FormanSchemaField[] | string;
135
+ /** Domain for the nested fields */
69
136
  domain?: string;
70
137
  };
71
138
 
package/dist/index.js CHANGED
@@ -5,12 +5,20 @@ function noEmpty(text) {
5
5
  function isObject(value) {
6
6
  return typeof value === "object" && value !== null && !Array.isArray(value);
7
7
  }
8
+ function isOptionGroup(value) {
9
+ return "options" in value && Array.isArray(value.options);
10
+ }
8
11
 
9
12
  // src/forman.ts
10
13
  var SchemaConversionError = class extends Error {
14
+ /** Field that caused the error */
15
+ field;
16
+ /**
17
+ * @param message The error message
18
+ * @param field The field that caused the error
19
+ */
11
20
  constructor(message, field) {
12
21
  super(message);
13
- this.field = field;
14
22
  this.name = "SchemaConversionError";
15
23
  }
16
24
  };
@@ -209,41 +217,52 @@ function handleArrayType(field, result, context) {
209
217
  return result;
210
218
  }
211
219
  function handleSelectType(field, result, context) {
212
- const options = isObject(field.options) ? field.options.store : field.options;
220
+ const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
213
221
  const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
214
222
  const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
215
- if (typeof options === "string") {
223
+ if (typeof optionsOrGroups === "string") {
216
224
  Object.defineProperty(result, "x-fetch", {
217
225
  configurable: true,
218
226
  enumerable: true,
219
227
  writable: true,
220
- value: appendQueryString(options, context.domain, context.tail)
221
- });
222
- } else if (options?.some((option) => option.label || option.nested)) {
223
- result.oneOf = (options || []).map((option) => {
224
- const localNested = (isObject(option.nested) ? option.nested.store : option.nested) || nested;
225
- const localDomain = (isObject(option.nested) && option.nested.domain ? option.nested.domain : domain) || context.domain;
226
- if (localNested) {
227
- context.addConditionalFields(
228
- field.name,
229
- option.value,
230
- typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : toJSONSchemaInternal(
231
- { type: "collection", spec: localNested },
232
- {
233
- ...context,
234
- domain: localDomain,
235
- tail: [...context.tail, field.name]
236
- }
237
- )
238
- );
239
- }
240
- return {
241
- title: noEmpty(option.label),
242
- const: option.value
243
- };
228
+ value: appendQueryString(optionsOrGroups, context.domain, context.tail)
244
229
  });
245
230
  } else {
246
- result.enum = (options || []).map((option) => option.value);
231
+ const options = optionsOrGroups?.some(isOptionGroup) ? optionsOrGroups.flatMap(
232
+ (group) => group.options.map((option) => ({
233
+ ...option,
234
+ label: `${group.label}: ${option.label || option.value}`
235
+ }))
236
+ ) : optionsOrGroups || [];
237
+ if (options?.some((option) => {
238
+ if (isOptionGroup(option)) return option.options.some((option2) => option2.label || option2.nested);
239
+ return option.label || option.nested;
240
+ })) {
241
+ result.oneOf = (options || []).map((option) => {
242
+ const localNested = (isObject(option.nested) ? option.nested.store : option.nested) || nested;
243
+ const localDomain = (isObject(option.nested) && option.nested.domain ? option.nested.domain : domain) || context.domain;
244
+ if (localNested) {
245
+ context.addConditionalFields(
246
+ field.name,
247
+ option.value,
248
+ typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : toJSONSchemaInternal(
249
+ { type: "collection", spec: localNested },
250
+ {
251
+ ...context,
252
+ domain: localDomain,
253
+ tail: [...context.tail, field.name]
254
+ }
255
+ )
256
+ );
257
+ }
258
+ return {
259
+ title: noEmpty(option.label),
260
+ const: option.value
261
+ };
262
+ });
263
+ } else {
264
+ result.enum = (options || []).map((option) => option.value);
265
+ }
247
266
  }
248
267
  if (nested && domain && domain !== context.domain) {
249
268
  if (typeof nested === "string") {
@@ -265,7 +284,7 @@ function handleSelectType(field, result, context) {
265
284
  configurable: true,
266
285
  enumerable: true,
267
286
  writable: true,
268
- value: typeof nested === "string" ? { $ref: nested } : toJSONSchemaInternal(
287
+ value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
269
288
  { type: "collection", spec: nested },
270
289
  {
271
290
  ...context,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",