@makehq/forman-schema 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -90,6 +90,7 @@ var FORMAN_TYPE_MAP = {
90
90
  udt: "number",
91
91
  array: "array",
92
92
  collection: "object",
93
+ dynamicCollection: "object",
93
94
  text: "string",
94
95
  number: "number",
95
96
  boolean: "boolean",
@@ -154,6 +155,7 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
154
155
  };
155
156
  switch (normalizedField.type) {
156
157
  case "collection":
158
+ case "dynamicCollection":
157
159
  return handleCollectionType(normalizedField, result, context);
158
160
  case "array":
159
161
  return handleArrayType(normalizedField, result, context);
@@ -245,6 +247,7 @@ function handleArrayType(field, result, context) {
245
247
  function handleSelectType(field, result, context) {
246
248
  const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
247
249
  const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
250
+ const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
248
251
  const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
249
252
  if (typeof optionsOrGroups === "string") {
250
253
  Object.defineProperty(result, "x-fetch", {
@@ -266,12 +269,33 @@ function handleSelectType(field, result, context) {
266
269
  })) {
267
270
  result.oneOf = (options || []).map((option) => {
268
271
  const localNested = (isObject(option.nested) ? option.nested.store : option.nested) || nested;
272
+ const localNestedContainsStrings = Array.isArray(localNested) && localNested.some((item) => typeof item === "string");
269
273
  const localDomain = (isObject(option.nested) && option.nested.domain ? option.nested.domain : domain) || context.domain;
270
274
  if (localNested) {
271
275
  context.addConditionalFields(
272
276
  field.name,
273
277
  option.value,
274
- typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : toJSONSchemaInternal(
278
+ typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : localNestedContainsStrings ? {
279
+ type: "object",
280
+ allOf: localNested.map(
281
+ (item) => typeof item === "string" ? {
282
+ $ref: appendQueryString(item, localDomain, [
283
+ ...context.tail,
284
+ field.name
285
+ ])
286
+ } : toJSONSchemaInternal(
287
+ {
288
+ type: "collection",
289
+ spec: [item]
290
+ },
291
+ {
292
+ ...context,
293
+ domain: localDomain,
294
+ tail: [...context.tail, field.name]
295
+ }
296
+ )
297
+ )
298
+ } : toJSONSchemaInternal(
275
299
  { type: "collection", spec: localNested },
276
300
  {
277
301
  ...context,
@@ -291,7 +315,7 @@ function handleSelectType(field, result, context) {
291
315
  }
292
316
  }
293
317
  if (nested && domain && domain !== context.domain) {
294
- if (typeof nested === "string") {
318
+ if (typeof nested === "string" || nestedContainsStrings) {
295
319
  throw new SchemaConversionError("Dynamic nested fields with domain change are not supported.");
296
320
  }
297
321
  let root = context.roots[domain];
@@ -310,7 +334,19 @@ function handleSelectType(field, result, context) {
310
334
  configurable: true,
311
335
  enumerable: true,
312
336
  writable: true,
313
- value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
337
+ value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : nestedContainsStrings ? {
338
+ type: "object",
339
+ allOf: nested.map(
340
+ (item) => typeof item === "string" ? { $ref: appendQueryString(item, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
341
+ { type: "collection", spec: [item] },
342
+ {
343
+ ...context,
344
+ domain: domain || context.domain,
345
+ tail: [...context.tail, field.name]
346
+ }
347
+ )
348
+ )
349
+ } : toJSONSchemaInternal(
314
350
  { type: "collection", spec: nested },
315
351
  {
316
352
  ...context,
@@ -727,21 +763,32 @@ async function handleNestedFields(nested, value, field, context) {
727
763
  tail: field.name ? [...context.tail, { name: field.name, value }] : context.tail
728
764
  };
729
765
  if (typeof store === "string") {
730
- try {
731
- store = await context.resolveRemote(store, context);
732
- } catch (error) {
733
- return {
734
- valid: false,
735
- errors: [
736
- ...errors,
737
- {
738
- domain: context.domain,
739
- path: context.path.join("."),
740
- message: `Failed to resolve remote resource ${store}: ${error}`
741
- }
742
- ]
743
- };
766
+ store = [store];
767
+ }
768
+ if (Array.isArray(store) && store.some((item) => typeof item === "string")) {
769
+ const resolvedStore = [];
770
+ for (const item of store) {
771
+ if (typeof item === "string") {
772
+ try {
773
+ resolvedStore.push(...await context.resolveRemote(item, context));
774
+ } catch (error) {
775
+ return {
776
+ valid: false,
777
+ errors: [
778
+ ...errors,
779
+ {
780
+ domain: context.domain,
781
+ path: context.path.join("."),
782
+ message: `Failed to resolve remote resource ${item}: ${error}`
783
+ }
784
+ ]
785
+ };
786
+ }
787
+ } else {
788
+ resolvedStore.push(item);
789
+ }
744
790
  }
791
+ store = resolvedStore;
745
792
  }
746
793
  if (store && domain && domain !== context.domain) {
747
794
  if (!context.roots[domain]) {
@@ -845,12 +892,19 @@ var JSON_PRIMITIVE_TYPE_MAP = {
845
892
  function toFormanSchema(field) {
846
893
  switch (field.type) {
847
894
  case "object":
848
- const spec = field.properties ? Object.entries(field.properties).filter(([name, property]) => !!property).map(([name, property]) => {
895
+ if (!field.properties || !Object.entries(field.properties).length) {
896
+ return {
897
+ type: "dynamicCollection",
898
+ label: noEmpty(field.title),
899
+ help: noEmpty(field.description)
900
+ };
901
+ }
902
+ const spec = Object.entries(field.properties).filter(([name, property]) => !!property).map(([name, property]) => {
849
903
  const subField = toFormanSchema(property);
850
904
  subField.name = name;
851
905
  subField.required = field.required?.includes(name) || false;
852
906
  return subField;
853
- }) : [];
907
+ });
854
908
  return {
855
909
  type: "collection",
856
910
  label: noEmpty(field.title),
package/dist/index.d.cts CHANGED
@@ -138,7 +138,7 @@ type FormanSchemaExtendedOptions = {
138
138
  /**
139
139
  * Nested fields
140
140
  */
141
- type FormanSchemaNested = FormanSchemaField[] | string | FormanSchemaExtendedNested;
141
+ type FormanSchemaNested = (FormanSchemaField | string)[] | string | FormanSchemaExtendedNested;
142
142
  /**
143
143
  * Extended nested fields
144
144
  */
package/dist/index.d.ts CHANGED
@@ -138,7 +138,7 @@ type FormanSchemaExtendedOptions = {
138
138
  /**
139
139
  * Nested fields
140
140
  */
141
- type FormanSchemaNested = FormanSchemaField[] | string | FormanSchemaExtendedNested;
141
+ type FormanSchemaNested = (FormanSchemaField | string)[] | string | FormanSchemaExtendedNested;
142
142
  /**
143
143
  * Extended nested fields
144
144
  */
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ var FORMAN_TYPE_MAP = {
61
61
  udt: "number",
62
62
  array: "array",
63
63
  collection: "object",
64
+ dynamicCollection: "object",
64
65
  text: "string",
65
66
  number: "number",
66
67
  boolean: "boolean",
@@ -125,6 +126,7 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
125
126
  };
126
127
  switch (normalizedField.type) {
127
128
  case "collection":
129
+ case "dynamicCollection":
128
130
  return handleCollectionType(normalizedField, result, context);
129
131
  case "array":
130
132
  return handleArrayType(normalizedField, result, context);
@@ -216,6 +218,7 @@ function handleArrayType(field, result, context) {
216
218
  function handleSelectType(field, result, context) {
217
219
  const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
218
220
  const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
221
+ const nestedContainsStrings = Array.isArray(nested) && nested.some((item) => typeof item === "string");
219
222
  const domain = isObject(field.options) ? isObject(field.options.nested) && field.options.nested.domain ? field.options.nested.domain : void 0 : void 0;
220
223
  if (typeof optionsOrGroups === "string") {
221
224
  Object.defineProperty(result, "x-fetch", {
@@ -237,12 +240,33 @@ function handleSelectType(field, result, context) {
237
240
  })) {
238
241
  result.oneOf = (options || []).map((option) => {
239
242
  const localNested = (isObject(option.nested) ? option.nested.store : option.nested) || nested;
243
+ const localNestedContainsStrings = Array.isArray(localNested) && localNested.some((item) => typeof item === "string");
240
244
  const localDomain = (isObject(option.nested) && option.nested.domain ? option.nested.domain : domain) || context.domain;
241
245
  if (localNested) {
242
246
  context.addConditionalFields(
243
247
  field.name,
244
248
  option.value,
245
- typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : toJSONSchemaInternal(
249
+ typeof localNested === "string" ? appendQueryString(localNested, localDomain, [...context.tail, field.name]) : localNestedContainsStrings ? {
250
+ type: "object",
251
+ allOf: localNested.map(
252
+ (item) => typeof item === "string" ? {
253
+ $ref: appendQueryString(item, localDomain, [
254
+ ...context.tail,
255
+ field.name
256
+ ])
257
+ } : toJSONSchemaInternal(
258
+ {
259
+ type: "collection",
260
+ spec: [item]
261
+ },
262
+ {
263
+ ...context,
264
+ domain: localDomain,
265
+ tail: [...context.tail, field.name]
266
+ }
267
+ )
268
+ )
269
+ } : toJSONSchemaInternal(
246
270
  { type: "collection", spec: localNested },
247
271
  {
248
272
  ...context,
@@ -262,7 +286,7 @@ function handleSelectType(field, result, context) {
262
286
  }
263
287
  }
264
288
  if (nested && domain && domain !== context.domain) {
265
- if (typeof nested === "string") {
289
+ if (typeof nested === "string" || nestedContainsStrings) {
266
290
  throw new SchemaConversionError("Dynamic nested fields with domain change are not supported.");
267
291
  }
268
292
  let root = context.roots[domain];
@@ -281,7 +305,19 @@ function handleSelectType(field, result, context) {
281
305
  configurable: true,
282
306
  enumerable: true,
283
307
  writable: true,
284
- value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
308
+ value: typeof nested === "string" ? { $ref: appendQueryString(nested, context.domain, [...context.tail, field.name]) } : nestedContainsStrings ? {
309
+ type: "object",
310
+ allOf: nested.map(
311
+ (item) => typeof item === "string" ? { $ref: appendQueryString(item, context.domain, [...context.tail, field.name]) } : toJSONSchemaInternal(
312
+ { type: "collection", spec: [item] },
313
+ {
314
+ ...context,
315
+ domain: domain || context.domain,
316
+ tail: [...context.tail, field.name]
317
+ }
318
+ )
319
+ )
320
+ } : toJSONSchemaInternal(
285
321
  { type: "collection", spec: nested },
286
322
  {
287
323
  ...context,
@@ -698,21 +734,32 @@ async function handleNestedFields(nested, value, field, context) {
698
734
  tail: field.name ? [...context.tail, { name: field.name, value }] : context.tail
699
735
  };
700
736
  if (typeof store === "string") {
701
- try {
702
- store = await context.resolveRemote(store, context);
703
- } catch (error) {
704
- return {
705
- valid: false,
706
- errors: [
707
- ...errors,
708
- {
709
- domain: context.domain,
710
- path: context.path.join("."),
711
- message: `Failed to resolve remote resource ${store}: ${error}`
712
- }
713
- ]
714
- };
737
+ store = [store];
738
+ }
739
+ if (Array.isArray(store) && store.some((item) => typeof item === "string")) {
740
+ const resolvedStore = [];
741
+ for (const item of store) {
742
+ if (typeof item === "string") {
743
+ try {
744
+ resolvedStore.push(...await context.resolveRemote(item, context));
745
+ } catch (error) {
746
+ return {
747
+ valid: false,
748
+ errors: [
749
+ ...errors,
750
+ {
751
+ domain: context.domain,
752
+ path: context.path.join("."),
753
+ message: `Failed to resolve remote resource ${item}: ${error}`
754
+ }
755
+ ]
756
+ };
757
+ }
758
+ } else {
759
+ resolvedStore.push(item);
760
+ }
715
761
  }
762
+ store = resolvedStore;
716
763
  }
717
764
  if (store && domain && domain !== context.domain) {
718
765
  if (!context.roots[domain]) {
@@ -816,12 +863,19 @@ var JSON_PRIMITIVE_TYPE_MAP = {
816
863
  function toFormanSchema(field) {
817
864
  switch (field.type) {
818
865
  case "object":
819
- const spec = field.properties ? Object.entries(field.properties).filter(([name, property]) => !!property).map(([name, property]) => {
866
+ if (!field.properties || !Object.entries(field.properties).length) {
867
+ return {
868
+ type: "dynamicCollection",
869
+ label: noEmpty(field.title),
870
+ help: noEmpty(field.description)
871
+ };
872
+ }
873
+ const spec = Object.entries(field.properties).filter(([name, property]) => !!property).map(([name, property]) => {
820
874
  const subField = toFormanSchema(property);
821
875
  subField.name = name;
822
876
  subField.required = field.required?.includes(name) || false;
823
877
  return subField;
824
- }) : [];
878
+ });
825
879
  return {
826
880
  type: "collection",
827
881
  label: noEmpty(field.title),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",