@makehq/forman-schema 1.5.0 → 1.7.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/README.md CHANGED
@@ -185,6 +185,7 @@ const result = await validateFormanWithDomains(
185
185
  - email → string
186
186
  - file → string
187
187
  - filename → string
188
+ - filter → array
188
189
  - folder → string
189
190
  - hidden → string
190
191
  - hook → number
package/dist/index.cjs CHANGED
@@ -152,6 +152,67 @@ function buildRestoreStructure(items) {
152
152
  }
153
153
  return result;
154
154
  }
155
+ var IML_FILTER_ENTRY_TYPES = ["null", "boolean", "number", "string"];
156
+ var IML_UNARY_FILTER_OPERATORS = ["exist", "notexist"];
157
+ var IML_BINARY_FILTER_OPERATORS = [
158
+ "text:equal",
159
+ "text:equal:ci",
160
+ "text:notequal",
161
+ "text:notequal:ci",
162
+ "text:contain",
163
+ "text:contain:ci",
164
+ "text:notcontain",
165
+ "text:notcontain:ci",
166
+ "text:startwith",
167
+ "text:startwith:ci",
168
+ "text:notstartwith",
169
+ "text:notstartwith:ci",
170
+ "text:endwith",
171
+ "text:endwith:ci",
172
+ "text:notendwith",
173
+ "text:notendwith:ci",
174
+ "text:pattern",
175
+ "text:pattern:ci",
176
+ "text:notpattern",
177
+ "text:notpattern:ci",
178
+ "number:equal",
179
+ "number:notequal",
180
+ "number:greater",
181
+ "number:less",
182
+ "number:greaterorequal",
183
+ "number:lessorequal",
184
+ "date:equal",
185
+ "date:notequal",
186
+ "date:greater",
187
+ "date:less",
188
+ "date:greaterorequal",
189
+ "date:lessorequal",
190
+ "time:equal",
191
+ "time:notequal",
192
+ "time:greater",
193
+ "time:less",
194
+ "time:greaterorequal",
195
+ "time:lessorequal",
196
+ "semver:equal",
197
+ "semver:notequal",
198
+ "semver:greater",
199
+ "semver:less",
200
+ "semver:greaterorequal",
201
+ "semver:lessorequal",
202
+ "array:contain",
203
+ "array:contain:ci",
204
+ "array:notcontain",
205
+ "array:notcontain:ci",
206
+ "array:equal",
207
+ "array:notequal",
208
+ "array:greater",
209
+ "array:less",
210
+ "array:greaterorequal",
211
+ "array:lessorequal",
212
+ "boolean:equal",
213
+ "boolean:notequal"
214
+ ];
215
+ var IML_FILTER_OPERATORS = [...IML_UNARY_FILTER_OPERATORS, ...IML_BINARY_FILTER_OPERATORS];
155
216
 
156
217
  // src/forman.ts
157
218
  var SchemaConversionError = class extends Error {
@@ -177,6 +238,7 @@ var FORMAN_TYPE_MAP = {
177
238
  collection: "object",
178
239
  dynamicCollection: "object",
179
240
  text: "string",
241
+ editor: "string",
180
242
  number: "number",
181
243
  boolean: "boolean",
182
244
  date: "string",
@@ -187,6 +249,7 @@ var FORMAN_TYPE_MAP = {
187
249
  email: "string",
188
250
  filename: "string",
189
251
  file: "string",
252
+ filter: "array",
190
253
  folder: "string",
191
254
  hidden: void 0,
192
255
  integer: "number",
@@ -253,6 +316,8 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
253
316
  case "file":
254
317
  case "folder":
255
318
  return handleSelectType(normalizedField, result, context);
319
+ case "filter":
320
+ return handleFilterType(normalizedField, result, context);
256
321
  default:
257
322
  return handlePrimitiveType(normalizedField, result);
258
323
  }
@@ -263,6 +328,12 @@ function handleCollectionType(field, result, context) {
263
328
  required: []
264
329
  });
265
330
  function addField(subField, tail) {
331
+ if (typeof subField === "string") {
332
+ const value = { $ref: appendQueryString(subField, context.domain, tail || context.tail) };
333
+ result.allOf ||= [];
334
+ result.allOf.push(value);
335
+ return;
336
+ }
266
337
  if (isVisualType(subField.type)) {
267
338
  return;
268
339
  }
@@ -329,6 +400,41 @@ function handleArrayType(field, result, context) {
329
400
  }
330
401
  return result;
331
402
  }
403
+ function handleFilterType(field, result, context) {
404
+ const filterItems = {
405
+ oneOf: [
406
+ {
407
+ type: "object",
408
+ properties: {
409
+ a: { type: IML_FILTER_ENTRY_TYPES },
410
+ o: { enum: IML_UNARY_FILTER_OPERATORS }
411
+ },
412
+ required: ["a", "o"]
413
+ },
414
+ {
415
+ type: "object",
416
+ properties: {
417
+ a: { type: IML_FILTER_ENTRY_TYPES },
418
+ b: { type: IML_FILTER_ENTRY_TYPES },
419
+ o: { enum: IML_BINARY_FILTER_OPERATORS }
420
+ },
421
+ required: ["a", "b", "o"]
422
+ }
423
+ ]
424
+ };
425
+ const logic = field.logic ?? "default";
426
+ result.items = ["and", "or"].includes(logic) ? filterItems : {
427
+ type: "array",
428
+ items: filterItems
429
+ };
430
+ Object.defineProperty(result, "x-filter", {
431
+ configurable: true,
432
+ enumerable: true,
433
+ writable: true,
434
+ value: logic
435
+ });
436
+ return result;
437
+ }
332
438
  function handleSelectType(field, result, context) {
333
439
  const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
334
440
  const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
@@ -400,9 +506,7 @@ function handleSelectType(field, result, context) {
400
506
  }
401
507
  }
402
508
  if (nested && domain && domain !== context.domain) {
403
- if (typeof nested === "string" || nestedContainsStrings) {
404
- throw new SchemaConversionError("Dynamic nested fields with domain change are not supported.");
405
- }
509
+ const normalizedNested = typeof nested === "string" ? [nested] : nested;
406
510
  let root = context.roots[domain];
407
511
  if (!root) {
408
512
  const buffer = [];
@@ -413,7 +517,7 @@ function handleSelectType(field, result, context) {
413
517
  }
414
518
  };
415
519
  }
416
- root.addFields(nested, [...context.tail, field.name]);
520
+ root.addFields(normalizedNested, [...context.tail, field.name]);
417
521
  } else if (nested) {
418
522
  Object.defineProperty(result, "x-nested", {
419
523
  configurable: true,
@@ -485,6 +589,7 @@ var FORMAN_TYPE_MAP2 = {
485
589
  email: "string",
486
590
  filename: "string",
487
591
  file: "string",
592
+ filter: "array",
488
593
  folder: "string",
489
594
  hidden: void 0,
490
595
  integer: "number",
@@ -648,6 +753,8 @@ async function validateFormanValue(value, field, context) {
648
753
  case "file":
649
754
  case "folder":
650
755
  return handleSelectType2(value, normalizedField, context);
756
+ case "filter":
757
+ return handleFilterType2(value, normalizedField, context);
651
758
  default:
652
759
  return handlePrimitiveType2(value, normalizedField, context);
653
760
  }
@@ -774,6 +881,42 @@ async function handleArrayType2(value, field, context) {
774
881
  errors
775
882
  };
776
883
  }
884
+ async function handleFilterType2(value, field, context) {
885
+ const filterEntry = {
886
+ type: "collection",
887
+ spec: [
888
+ {
889
+ name: "a",
890
+ type: "any",
891
+ required: true
892
+ },
893
+ {
894
+ name: "b",
895
+ type: "any"
896
+ },
897
+ {
898
+ name: "o",
899
+ type: "text",
900
+ validate: {
901
+ enum: IML_FILTER_OPERATORS
902
+ }
903
+ }
904
+ ]
905
+ };
906
+ const inlineSchema = ["and", "or"].includes(field.logic ?? "default") ? {
907
+ name: field.name,
908
+ type: "array",
909
+ spec: filterEntry
910
+ } : {
911
+ name: field.name,
912
+ type: "array",
913
+ spec: {
914
+ type: "array",
915
+ spec: filterEntry
916
+ }
917
+ };
918
+ return handleArrayType2(value, inlineSchema, context);
919
+ }
777
920
  async function handleSelectType2(value, field, context) {
778
921
  const errors = [];
779
922
  let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
@@ -1018,6 +1161,15 @@ function toFormanSchema(field) {
1018
1161
  spec
1019
1162
  };
1020
1163
  case "array":
1164
+ const filterLogic = Object.getOwnPropertyDescriptor(field, "x-filter");
1165
+ if (filterLogic) {
1166
+ return {
1167
+ type: "filter",
1168
+ label: noEmpty(field.title),
1169
+ help: noEmpty(field.description),
1170
+ logic: filterLogic.value === "default" ? void 0 : filterLogic.value
1171
+ };
1172
+ }
1021
1173
  const items = field.items && isObject(field.items) ? field.items : void 0;
1022
1174
  const formanSchema = {
1023
1175
  type: "array",
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@ import { JSONSchema7 } from 'json-schema';
3
3
  /**
4
4
  * Valid Forman Schema field types
5
5
  */
6
- type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
6
+ type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'filter' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
7
7
  /**
8
8
  * Validation configuration for Forman Schema fields
9
9
  */
@@ -79,6 +79,8 @@ type FormanSchemaField = {
79
79
  sort?: string;
80
80
  /** 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. */
81
81
  rpc?: FormanSchemaRPCButton;
82
+ /** Definition of boolean logic to apply (filter only) */
83
+ logic?: 'and' | 'or' | 'reverse';
82
84
  } & Record<`x-${string}`, unknown>;
83
85
  /**
84
86
  * RPC button allows for dynamic value retrieval from an external source
@@ -144,7 +146,7 @@ type FormanSchemaNested = (FormanSchemaField | string)[] | string | FormanSchema
144
146
  */
145
147
  type FormanSchemaExtendedNested = {
146
148
  /** Store for the nested fields */
147
- store: FormanSchemaField[] | string;
149
+ store: (FormanSchemaField | string)[] | string;
148
150
  /** Domain for the nested fields */
149
151
  domain?: string;
150
152
  };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { JSONSchema7 } from 'json-schema';
3
3
  /**
4
4
  * Valid Forman Schema field types
5
5
  */
6
- type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
6
+ type FormanSchemaFieldType = 'aiagent' | 'account' | 'hook' | 'device' | 'keychain' | 'datastore' | 'udt' | 'scenario' | 'array' | 'collection' | 'text' | 'number' | 'boolean' | 'date' | 'json' | 'buffer' | 'cert' | 'color' | 'email' | 'filename' | 'file' | 'filter' | 'folder' | 'hidden' | 'integer' | 'uinteger' | 'path' | 'pkey' | 'port' | 'select' | 'time' | 'timestamp' | 'timezone' | 'url' | 'uuid' | `account:${string}` | `hook:${string}` | `keychain:${string}` | 'banner' | 'markdown' | 'html' | 'separator' | string;
7
7
  /**
8
8
  * Validation configuration for Forman Schema fields
9
9
  */
@@ -79,6 +79,8 @@ type FormanSchemaField = {
79
79
  sort?: string;
80
80
  /** 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. */
81
81
  rpc?: FormanSchemaRPCButton;
82
+ /** Definition of boolean logic to apply (filter only) */
83
+ logic?: 'and' | 'or' | 'reverse';
82
84
  } & Record<`x-${string}`, unknown>;
83
85
  /**
84
86
  * RPC button allows for dynamic value retrieval from an external source
@@ -144,7 +146,7 @@ type FormanSchemaNested = (FormanSchemaField | string)[] | string | FormanSchema
144
146
  */
145
147
  type FormanSchemaExtendedNested = {
146
148
  /** Store for the nested fields */
147
- store: FormanSchemaField[] | string;
149
+ store: (FormanSchemaField | string)[] | string;
148
150
  /** Domain for the nested fields */
149
151
  domain?: string;
150
152
  };
package/dist/index.js CHANGED
@@ -123,6 +123,67 @@ function buildRestoreStructure(items) {
123
123
  }
124
124
  return result;
125
125
  }
126
+ var IML_FILTER_ENTRY_TYPES = ["null", "boolean", "number", "string"];
127
+ var IML_UNARY_FILTER_OPERATORS = ["exist", "notexist"];
128
+ var IML_BINARY_FILTER_OPERATORS = [
129
+ "text:equal",
130
+ "text:equal:ci",
131
+ "text:notequal",
132
+ "text:notequal:ci",
133
+ "text:contain",
134
+ "text:contain:ci",
135
+ "text:notcontain",
136
+ "text:notcontain:ci",
137
+ "text:startwith",
138
+ "text:startwith:ci",
139
+ "text:notstartwith",
140
+ "text:notstartwith:ci",
141
+ "text:endwith",
142
+ "text:endwith:ci",
143
+ "text:notendwith",
144
+ "text:notendwith:ci",
145
+ "text:pattern",
146
+ "text:pattern:ci",
147
+ "text:notpattern",
148
+ "text:notpattern:ci",
149
+ "number:equal",
150
+ "number:notequal",
151
+ "number:greater",
152
+ "number:less",
153
+ "number:greaterorequal",
154
+ "number:lessorequal",
155
+ "date:equal",
156
+ "date:notequal",
157
+ "date:greater",
158
+ "date:less",
159
+ "date:greaterorequal",
160
+ "date:lessorequal",
161
+ "time:equal",
162
+ "time:notequal",
163
+ "time:greater",
164
+ "time:less",
165
+ "time:greaterorequal",
166
+ "time:lessorequal",
167
+ "semver:equal",
168
+ "semver:notequal",
169
+ "semver:greater",
170
+ "semver:less",
171
+ "semver:greaterorequal",
172
+ "semver:lessorequal",
173
+ "array:contain",
174
+ "array:contain:ci",
175
+ "array:notcontain",
176
+ "array:notcontain:ci",
177
+ "array:equal",
178
+ "array:notequal",
179
+ "array:greater",
180
+ "array:less",
181
+ "array:greaterorequal",
182
+ "array:lessorequal",
183
+ "boolean:equal",
184
+ "boolean:notequal"
185
+ ];
186
+ var IML_FILTER_OPERATORS = [...IML_UNARY_FILTER_OPERATORS, ...IML_BINARY_FILTER_OPERATORS];
126
187
 
127
188
  // src/forman.ts
128
189
  var SchemaConversionError = class extends Error {
@@ -148,6 +209,7 @@ var FORMAN_TYPE_MAP = {
148
209
  collection: "object",
149
210
  dynamicCollection: "object",
150
211
  text: "string",
212
+ editor: "string",
151
213
  number: "number",
152
214
  boolean: "boolean",
153
215
  date: "string",
@@ -158,6 +220,7 @@ var FORMAN_TYPE_MAP = {
158
220
  email: "string",
159
221
  filename: "string",
160
222
  file: "string",
223
+ filter: "array",
161
224
  folder: "string",
162
225
  hidden: void 0,
163
226
  integer: "number",
@@ -224,6 +287,8 @@ function toJSONSchemaInternal(field, context = createDefaultContext()) {
224
287
  case "file":
225
288
  case "folder":
226
289
  return handleSelectType(normalizedField, result, context);
290
+ case "filter":
291
+ return handleFilterType(normalizedField, result, context);
227
292
  default:
228
293
  return handlePrimitiveType(normalizedField, result);
229
294
  }
@@ -234,6 +299,12 @@ function handleCollectionType(field, result, context) {
234
299
  required: []
235
300
  });
236
301
  function addField(subField, tail) {
302
+ if (typeof subField === "string") {
303
+ const value = { $ref: appendQueryString(subField, context.domain, tail || context.tail) };
304
+ result.allOf ||= [];
305
+ result.allOf.push(value);
306
+ return;
307
+ }
237
308
  if (isVisualType(subField.type)) {
238
309
  return;
239
310
  }
@@ -300,6 +371,41 @@ function handleArrayType(field, result, context) {
300
371
  }
301
372
  return result;
302
373
  }
374
+ function handleFilterType(field, result, context) {
375
+ const filterItems = {
376
+ oneOf: [
377
+ {
378
+ type: "object",
379
+ properties: {
380
+ a: { type: IML_FILTER_ENTRY_TYPES },
381
+ o: { enum: IML_UNARY_FILTER_OPERATORS }
382
+ },
383
+ required: ["a", "o"]
384
+ },
385
+ {
386
+ type: "object",
387
+ properties: {
388
+ a: { type: IML_FILTER_ENTRY_TYPES },
389
+ b: { type: IML_FILTER_ENTRY_TYPES },
390
+ o: { enum: IML_BINARY_FILTER_OPERATORS }
391
+ },
392
+ required: ["a", "b", "o"]
393
+ }
394
+ ]
395
+ };
396
+ const logic = field.logic ?? "default";
397
+ result.items = ["and", "or"].includes(logic) ? filterItems : {
398
+ type: "array",
399
+ items: filterItems
400
+ };
401
+ Object.defineProperty(result, "x-filter", {
402
+ configurable: true,
403
+ enumerable: true,
404
+ writable: true,
405
+ value: logic
406
+ });
407
+ return result;
408
+ }
303
409
  function handleSelectType(field, result, context) {
304
410
  const optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
305
411
  const nested = isObject(field.options) ? isObject(field.options.nested) ? field.options.nested.store : field.options.nested : void 0;
@@ -371,9 +477,7 @@ function handleSelectType(field, result, context) {
371
477
  }
372
478
  }
373
479
  if (nested && domain && domain !== context.domain) {
374
- if (typeof nested === "string" || nestedContainsStrings) {
375
- throw new SchemaConversionError("Dynamic nested fields with domain change are not supported.");
376
- }
480
+ const normalizedNested = typeof nested === "string" ? [nested] : nested;
377
481
  let root = context.roots[domain];
378
482
  if (!root) {
379
483
  const buffer = [];
@@ -384,7 +488,7 @@ function handleSelectType(field, result, context) {
384
488
  }
385
489
  };
386
490
  }
387
- root.addFields(nested, [...context.tail, field.name]);
491
+ root.addFields(normalizedNested, [...context.tail, field.name]);
388
492
  } else if (nested) {
389
493
  Object.defineProperty(result, "x-nested", {
390
494
  configurable: true,
@@ -456,6 +560,7 @@ var FORMAN_TYPE_MAP2 = {
456
560
  email: "string",
457
561
  filename: "string",
458
562
  file: "string",
563
+ filter: "array",
459
564
  folder: "string",
460
565
  hidden: void 0,
461
566
  integer: "number",
@@ -619,6 +724,8 @@ async function validateFormanValue(value, field, context) {
619
724
  case "file":
620
725
  case "folder":
621
726
  return handleSelectType2(value, normalizedField, context);
727
+ case "filter":
728
+ return handleFilterType2(value, normalizedField, context);
622
729
  default:
623
730
  return handlePrimitiveType2(value, normalizedField, context);
624
731
  }
@@ -745,6 +852,42 @@ async function handleArrayType2(value, field, context) {
745
852
  errors
746
853
  };
747
854
  }
855
+ async function handleFilterType2(value, field, context) {
856
+ const filterEntry = {
857
+ type: "collection",
858
+ spec: [
859
+ {
860
+ name: "a",
861
+ type: "any",
862
+ required: true
863
+ },
864
+ {
865
+ name: "b",
866
+ type: "any"
867
+ },
868
+ {
869
+ name: "o",
870
+ type: "text",
871
+ validate: {
872
+ enum: IML_FILTER_OPERATORS
873
+ }
874
+ }
875
+ ]
876
+ };
877
+ const inlineSchema = ["and", "or"].includes(field.logic ?? "default") ? {
878
+ name: field.name,
879
+ type: "array",
880
+ spec: filterEntry
881
+ } : {
882
+ name: field.name,
883
+ type: "array",
884
+ spec: {
885
+ type: "array",
886
+ spec: filterEntry
887
+ }
888
+ };
889
+ return handleArrayType2(value, inlineSchema, context);
890
+ }
748
891
  async function handleSelectType2(value, field, context) {
749
892
  const errors = [];
750
893
  let optionsOrGroups = isObject(field.options) ? field.options.store : field.options;
@@ -989,6 +1132,15 @@ function toFormanSchema(field) {
989
1132
  spec
990
1133
  };
991
1134
  case "array":
1135
+ const filterLogic = Object.getOwnPropertyDescriptor(field, "x-filter");
1136
+ if (filterLogic) {
1137
+ return {
1138
+ type: "filter",
1139
+ label: noEmpty(field.title),
1140
+ help: noEmpty(field.description),
1141
+ logic: filterLogic.value === "default" ? void 0 : filterLogic.value
1142
+ };
1143
+ }
992
1144
  const items = field.items && isObject(field.items) ? field.items : void 0;
993
1145
  const formanSchema = {
994
1146
  type: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makehq/forman-schema",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Forman Schema Tools",
5
5
  "license": "MIT",
6
6
  "author": "Make",