@tinacms/schema-tools 0.1.0 → 0.1.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.d.ts CHANGED
@@ -13,3 +13,4 @@ limitations under the License.
13
13
  export * from './schema';
14
14
  export * from './types';
15
15
  export * from './validate';
16
+ export * from './util/namer';
package/dist/index.es.js CHANGED
@@ -7,6 +7,9 @@ function addNamespaceToSchema(maybeNode, namespace = []) {
7
7
  if (typeof maybeNode === "boolean") {
8
8
  return maybeNode;
9
9
  }
10
+ if (typeof maybeNode === "function") {
11
+ return maybeNode;
12
+ }
10
13
  const newNode = { ...maybeNode };
11
14
  const keys = Object.keys(maybeNode);
12
15
  Object.values(maybeNode).map((m, index) => {
@@ -102,12 +105,14 @@ const NAMER = {
102
105
  return generateNamespacedFieldName(namespace, "ConnectionEdges");
103
106
  }
104
107
  };
105
- function hasDuplicates(array) {
106
- if (!array) {
107
- return false;
108
- } else {
109
- return new Set(array).size !== array.length;
110
- }
108
+ function findDuplicates(array = []) {
109
+ const duplicates = [
110
+ ...new Set(array.filter((item, index) => array.indexOf(item) !== index))
111
+ ].map((x) => `"${x}"`);
112
+ if (duplicates.length) {
113
+ return duplicates.join(", ");
114
+ } else
115
+ return void 0;
111
116
  }
112
117
  class TinaSchema {
113
118
  constructor(config) {
@@ -160,7 +165,7 @@ class TinaSchema {
160
165
  };
161
166
  this.getCollectionByFullPath = (filepath) => {
162
167
  const collection = this.getCollections().find((collection2) => {
163
- return filepath.replace("\\", "/").startsWith(collection2.path);
168
+ return filepath.replace(/\\/g, "/").startsWith(collection2.path.replace(/\/?$/, "/"));
164
169
  });
165
170
  if (!collection) {
166
171
  throw new Error(`Unable to find collection for file at ${filepath}`);
@@ -169,12 +174,7 @@ class TinaSchema {
169
174
  };
170
175
  this.getCollectionAndTemplateByFullPath = (filepath, templateName) => {
171
176
  let template;
172
- const collection = this.getCollections().find((collection2) => {
173
- return filepath.replace("\\", "/").startsWith(collection2.path);
174
- });
175
- if (!collection) {
176
- throw new Error(`Unable to find collection for file at ${filepath}`);
177
- }
177
+ const collection = this.getCollectionByFullPath(filepath);
178
178
  const templates = this.getTemplatesForCollectable(collection);
179
179
  if (templates.type === "union") {
180
180
  if (templateName) {
@@ -211,6 +211,72 @@ class TinaSchema {
211
211
  return template;
212
212
  }
213
213
  };
214
+ this.transformPayload = (collectionName, payload) => {
215
+ const collection = this.getCollection(collectionName);
216
+ if (collection.templates) {
217
+ const template = collection.templates.find((template2) => {
218
+ if (typeof template2 === "string") {
219
+ throw new Error("Global templates not supported");
220
+ }
221
+ return payload["_template"] === template2.name;
222
+ });
223
+ if (!template) {
224
+ console.error(payload);
225
+ throw new Error(`Unable to find template for payload`);
226
+ }
227
+ if (typeof template === "string") {
228
+ throw new Error("Global templates not supported");
229
+ }
230
+ return {
231
+ [collectionName]: {
232
+ [template.name]: this.transformCollectablePayload(payload, template)
233
+ }
234
+ };
235
+ } else {
236
+ return {
237
+ [collectionName]: this.transformCollectablePayload(payload, collection)
238
+ };
239
+ }
240
+ };
241
+ this.transformCollectablePayload = (payload, collection) => {
242
+ const accumulator = {};
243
+ Object.entries(payload).forEach(([key, value]) => {
244
+ if (typeof collection.fields === "string") {
245
+ throw new Error("Global templates not supported");
246
+ }
247
+ const field = collection.fields.find((field2) => {
248
+ if (typeof field2 === "string") {
249
+ throw new Error("Global templates not supported");
250
+ }
251
+ return field2.name === key;
252
+ });
253
+ if (field) {
254
+ accumulator[key] = this.transformField(field, value);
255
+ }
256
+ });
257
+ return accumulator;
258
+ };
259
+ this.transformField = (field, value) => {
260
+ if (field.type === "object")
261
+ if (field.templates) {
262
+ if (field.list) {
263
+ assertShape(value, (yup2) => yup2.array(yup2.object({ _template: yup2.string().required() })));
264
+ return value.map((item) => {
265
+ const { _template, ...rest } = item;
266
+ return { [_template]: rest };
267
+ });
268
+ } else {
269
+ assertShape(value, (yup2) => yup2.object({ _template: yup2.string().required() }));
270
+ const { _template, ...rest } = value;
271
+ return { [_template]: rest };
272
+ }
273
+ } else {
274
+ return value;
275
+ }
276
+ else {
277
+ return value;
278
+ }
279
+ };
214
280
  this.isMarkdownCollection = (collectionName) => {
215
281
  const collection = this.getCollection(collectionName);
216
282
  const format = collection.format;
@@ -261,9 +327,9 @@ class TinaSchema {
261
327
  this.schema = config;
262
328
  }
263
329
  }
264
- const resolveField = ({ namespace, ...field }, schema) => {
330
+ const resolveField = (field, schema) => {
265
331
  var _a;
266
- field.parentTypename = NAMER.dataTypeName(namespace.filter((_, i) => i < namespace.length - 1));
332
+ field.parentTypename = NAMER.dataTypeName(field.namespace.filter((_, i) => i < field.namespace.length - 1));
267
333
  const extraFields = field.ui || {};
268
334
  switch (field.type) {
269
335
  case "number":
@@ -324,10 +390,7 @@ const resolveField = ({ namespace, ...field }, schema) => {
324
390
  ...extraFields
325
391
  };
326
392
  case "object":
327
- const templateInfo = schema.getTemplatesForCollectable({
328
- ...field,
329
- namespace
330
- });
393
+ const templateInfo = schema.getTemplatesForCollectable(field);
331
394
  if (templateInfo.type === "object") {
332
395
  return {
333
396
  ...field,
@@ -345,6 +408,7 @@ const resolveField = ({ namespace, ...field }, schema) => {
345
408
  templates2[lastItem(template.namespace)] = {
346
409
  label: template.label || templateName,
347
410
  key: templateName,
411
+ namespace: [...field.namespace, templateName],
348
412
  fields: template.fields.map((field2) => resolveField(field2, schema)),
349
413
  ...extraFields2
350
414
  };
@@ -353,6 +417,7 @@ const resolveField = ({ namespace, ...field }, schema) => {
353
417
  return {
354
418
  ...field,
355
419
  typeMap: typeMap2,
420
+ namespace: field.namespace,
356
421
  component: field.list ? "blocks" : "not-implemented",
357
422
  templates: templates2,
358
423
  ...extraFields
@@ -438,7 +503,9 @@ const parseZodError = ({ zodError }) => {
438
503
  const name = z.string({
439
504
  required_error: "Name is required but not provided",
440
505
  invalid_type_error: "Name must be a string"
441
- });
506
+ }).refine((val) => val.match(/^[a-zA-Z0-9_]*$/) !== null, (val) => ({
507
+ message: `name, "${val}" must be alphanumeric and can only contain underscores`
508
+ }));
442
509
  const TypeName = [
443
510
  "string",
444
511
  "boolean",
@@ -457,7 +524,7 @@ const nameProp = z.string({
457
524
  }).superRefine((val, ctx) => {
458
525
  if (val.includes(" "))
459
526
  ctx.addIssue({
460
- message: "name cannot contain spaces",
527
+ message: `name "${val}" cannot contain spaces`,
461
528
  code: z.ZodIssueCode.custom,
462
529
  fatal: true
463
530
  });
@@ -527,22 +594,37 @@ const TinaFieldZod = z.lazy(() => {
527
594
  start: z.string(),
528
595
  end: z.string()
529
596
  }).optional()
530
- }).refine((val) => {
531
- var _a;
532
- return !hasDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
533
- }, {
534
- message: "Fields must have a unique name"
597
+ }).superRefine((val, ctx) => {
598
+ const dups = findDuplicates(val == null ? void 0 : val.fields.map((x) => x.name));
599
+ if (dups) {
600
+ ctx.addIssue({
601
+ code: z.ZodIssueCode.custom,
602
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
603
+ });
604
+ }
535
605
  });
536
606
  const ObjectField = FieldWithList.extend({
537
607
  type: z.literal("object", {
538
608
  invalid_type_error: typeTypeError,
539
609
  required_error: typeRequiredError
540
610
  }),
541
- fields: z.array(TinaFieldZod).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
542
- message: "Fields must have a unique name"
611
+ fields: z.array(TinaFieldZod).min(1).optional().superRefine((val, ctx) => {
612
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
613
+ if (dups) {
614
+ ctx.addIssue({
615
+ code: z.ZodIssueCode.custom,
616
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
617
+ });
618
+ }
543
619
  }),
544
- templates: z.array(TemplateTemp).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
545
- message: "Templates must have a unique name"
620
+ templates: z.array(TemplateTemp).min(1).optional().superRefine((val, ctx) => {
621
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
622
+ if (dups) {
623
+ ctx.addIssue({
624
+ code: z.ZodIssueCode.custom,
625
+ message: `Templates must have a unique name, duplicate template names: ${dups}`
626
+ });
627
+ }
546
628
  })
547
629
  });
548
630
  const RichTextField = FieldWithList.extend({
@@ -550,8 +632,14 @@ const TinaFieldZod = z.lazy(() => {
550
632
  invalid_type_error: typeTypeError,
551
633
  required_error: typeRequiredError
552
634
  }),
553
- templates: z.array(TemplateTemp).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
554
- message: "Templates must have a unique name"
635
+ templates: z.array(TemplateTemp).optional().superRefine((val, ctx) => {
636
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
637
+ if (dups) {
638
+ ctx.addIssue({
639
+ code: z.ZodIssueCode.custom,
640
+ message: `Templates must have a unique name, duplicate template names: ${dups}`
641
+ });
642
+ }
555
643
  })
556
644
  });
557
645
  return z.discriminatedUnion("type", [
@@ -581,13 +669,17 @@ const TinaFieldZod = z.lazy(() => {
581
669
  if (val.list) {
582
670
  ctx.addIssue({
583
671
  code: z.ZodIssueCode.custom,
584
- message: "You can not have `list: true` when using `isTitle`"
672
+ message: `Can not have \`list: true\` when using \`isTitle\`. Error in value
673
+ ${JSON.stringify(val, null, 2)}
674
+ `
585
675
  });
586
676
  }
587
677
  if (!val.required) {
588
678
  ctx.addIssue({
589
679
  code: z.ZodIssueCode.custom,
590
- message: "You must have { required: true } when using `isTitle`"
680
+ message: `Must have { required: true } when using \`isTitle\` Error in value
681
+ ${JSON.stringify(val, null, 2)}
682
+ `
591
683
  });
592
684
  }
593
685
  }
@@ -638,11 +730,15 @@ const Template = z.object({
638
730
  }),
639
731
  name,
640
732
  fields: z.array(TinaFieldZod)
641
- }).refine((val) => {
733
+ }).superRefine((val, ctx) => {
642
734
  var _a;
643
- return !hasDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
644
- }, {
645
- message: "Fields must have a unique name"
735
+ const dups = findDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
736
+ if (dups) {
737
+ ctx.addIssue({
738
+ code: z.ZodIssueCode.custom,
739
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
740
+ });
741
+ }
646
742
  });
647
743
  const TinaCloudCollectionBase = z.object({
648
744
  label: z.string().optional(),
@@ -650,16 +746,28 @@ const TinaCloudCollectionBase = z.object({
650
746
  format: z.enum(FORMATS).optional()
651
747
  });
652
748
  const TinaCloudCollection = TinaCloudCollectionBase.extend({
653
- fields: z.array(TinaFieldZod).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
654
- message: "Fields must have a unique name"
749
+ fields: z.array(TinaFieldZod).min(1).optional().superRefine((val, ctx) => {
750
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
751
+ if (dups) {
752
+ ctx.addIssue({
753
+ code: z.ZodIssueCode.custom,
754
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
755
+ });
756
+ }
655
757
  }).refine((val) => {
656
758
  const arr = (val == null ? void 0 : val.filter((x) => x.type === "string" && x.isTitle)) || [];
657
759
  return arr.length < 2;
658
760
  }, {
659
761
  message: "Fields can only have one use of `isTitle`"
660
762
  }),
661
- templates: z.array(Template).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
662
- message: "Templates must have a unique name"
763
+ templates: z.array(Template).min(1).optional().superRefine((val, ctx) => {
764
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
765
+ if (dups) {
766
+ ctx.addIssue({
767
+ code: z.ZodIssueCode.custom,
768
+ message: `Templates must have a unique name, duplicate template names: ${dups}`
769
+ });
770
+ }
663
771
  })
664
772
  }).refine((val) => {
665
773
  let isValid = Boolean(val == null ? void 0 : val.templates) || Boolean(val == null ? void 0 : val.fields);
@@ -674,20 +782,21 @@ const TinaCloudSchemaZod = z.object({
674
782
  collections: z.array(TinaCloudCollection),
675
783
  config: tinaConfigZod.optional()
676
784
  }).superRefine((val, ctx) => {
677
- var _a, _b;
678
- if (hasDuplicates(val.collections.map((x) => x.name))) {
785
+ var _a, _b, _c;
786
+ const dups = findDuplicates((_a = val.collections) == null ? void 0 : _a.map((x) => x.name));
787
+ if (dups) {
679
788
  ctx.addIssue({
680
789
  code: z.ZodIssueCode.custom,
681
- message: "can not have two collections with the same name",
790
+ message: `${dups} are duplicate names in your collections. Collection names must be unique.`,
682
791
  fatal: true
683
792
  });
684
793
  }
685
- (_a = val == null ? void 0 : val.collections) == null ? void 0 : _a.map((x) => {
794
+ (_b = val == null ? void 0 : val.collections) == null ? void 0 : _b.map((x) => {
686
795
  if (!x.format) {
687
796
  console.warn(`No format provided for collection ${x.name}, defaulting to .md`);
688
797
  }
689
798
  });
690
- const media = (_b = val == null ? void 0 : val.config) == null ? void 0 : _b.media;
799
+ const media = (_c = val == null ? void 0 : val.config) == null ? void 0 : _c.media;
691
800
  if (media && media.tina && media.loadCustomStore) {
692
801
  ctx.addIssue({
693
802
  code: z.ZodIssueCode.custom,
@@ -717,4 +826,4 @@ const validateSchema = ({
717
826
  }
718
827
  }
719
828
  };
720
- export { TinaSchema, TinaSchemaValidationError, addNamespaceToSchema, resolveField, resolveForm, validateSchema, validateTinaCloudSchemaConfig };
829
+ export { NAMER, TinaSchema, TinaSchemaValidationError, addNamespaceToSchema, resolveField, resolveForm, validateSchema, validateTinaCloudSchemaConfig };
package/dist/index.js CHANGED
@@ -34,6 +34,9 @@
34
34
  if (typeof maybeNode === "boolean") {
35
35
  return maybeNode;
36
36
  }
37
+ if (typeof maybeNode === "function") {
38
+ return maybeNode;
39
+ }
37
40
  const newNode = { ...maybeNode };
38
41
  const keys = Object.keys(maybeNode);
39
42
  Object.values(maybeNode).map((m, index) => {
@@ -129,12 +132,14 @@
129
132
  return generateNamespacedFieldName(namespace, "ConnectionEdges");
130
133
  }
131
134
  };
132
- function hasDuplicates(array) {
133
- if (!array) {
134
- return false;
135
- } else {
136
- return new Set(array).size !== array.length;
137
- }
135
+ function findDuplicates(array = []) {
136
+ const duplicates = [
137
+ ...new Set(array.filter((item, index) => array.indexOf(item) !== index))
138
+ ].map((x) => `"${x}"`);
139
+ if (duplicates.length) {
140
+ return duplicates.join(", ");
141
+ } else
142
+ return void 0;
138
143
  }
139
144
  class TinaSchema {
140
145
  constructor(config) {
@@ -187,7 +192,7 @@
187
192
  };
188
193
  this.getCollectionByFullPath = (filepath) => {
189
194
  const collection = this.getCollections().find((collection2) => {
190
- return filepath.replace("\\", "/").startsWith(collection2.path);
195
+ return filepath.replace(/\\/g, "/").startsWith(collection2.path.replace(/\/?$/, "/"));
191
196
  });
192
197
  if (!collection) {
193
198
  throw new Error(`Unable to find collection for file at ${filepath}`);
@@ -196,12 +201,7 @@
196
201
  };
197
202
  this.getCollectionAndTemplateByFullPath = (filepath, templateName) => {
198
203
  let template;
199
- const collection = this.getCollections().find((collection2) => {
200
- return filepath.replace("\\", "/").startsWith(collection2.path);
201
- });
202
- if (!collection) {
203
- throw new Error(`Unable to find collection for file at ${filepath}`);
204
- }
204
+ const collection = this.getCollectionByFullPath(filepath);
205
205
  const templates = this.getTemplatesForCollectable(collection);
206
206
  if (templates.type === "union") {
207
207
  if (templateName) {
@@ -238,6 +238,72 @@
238
238
  return template;
239
239
  }
240
240
  };
241
+ this.transformPayload = (collectionName, payload) => {
242
+ const collection = this.getCollection(collectionName);
243
+ if (collection.templates) {
244
+ const template = collection.templates.find((template2) => {
245
+ if (typeof template2 === "string") {
246
+ throw new Error("Global templates not supported");
247
+ }
248
+ return payload["_template"] === template2.name;
249
+ });
250
+ if (!template) {
251
+ console.error(payload);
252
+ throw new Error(`Unable to find template for payload`);
253
+ }
254
+ if (typeof template === "string") {
255
+ throw new Error("Global templates not supported");
256
+ }
257
+ return {
258
+ [collectionName]: {
259
+ [template.name]: this.transformCollectablePayload(payload, template)
260
+ }
261
+ };
262
+ } else {
263
+ return {
264
+ [collectionName]: this.transformCollectablePayload(payload, collection)
265
+ };
266
+ }
267
+ };
268
+ this.transformCollectablePayload = (payload, collection) => {
269
+ const accumulator = {};
270
+ Object.entries(payload).forEach(([key, value]) => {
271
+ if (typeof collection.fields === "string") {
272
+ throw new Error("Global templates not supported");
273
+ }
274
+ const field = collection.fields.find((field2) => {
275
+ if (typeof field2 === "string") {
276
+ throw new Error("Global templates not supported");
277
+ }
278
+ return field2.name === key;
279
+ });
280
+ if (field) {
281
+ accumulator[key] = this.transformField(field, value);
282
+ }
283
+ });
284
+ return accumulator;
285
+ };
286
+ this.transformField = (field, value) => {
287
+ if (field.type === "object")
288
+ if (field.templates) {
289
+ if (field.list) {
290
+ assertShape(value, (yup2) => yup2.array(yup2.object({ _template: yup2.string().required() })));
291
+ return value.map((item) => {
292
+ const { _template, ...rest } = item;
293
+ return { [_template]: rest };
294
+ });
295
+ } else {
296
+ assertShape(value, (yup2) => yup2.object({ _template: yup2.string().required() }));
297
+ const { _template, ...rest } = value;
298
+ return { [_template]: rest };
299
+ }
300
+ } else {
301
+ return value;
302
+ }
303
+ else {
304
+ return value;
305
+ }
306
+ };
241
307
  this.isMarkdownCollection = (collectionName) => {
242
308
  const collection = this.getCollection(collectionName);
243
309
  const format = collection.format;
@@ -288,9 +354,9 @@
288
354
  this.schema = config;
289
355
  }
290
356
  }
291
- const resolveField = ({ namespace, ...field }, schema) => {
357
+ const resolveField = (field, schema) => {
292
358
  var _a;
293
- field.parentTypename = NAMER.dataTypeName(namespace.filter((_, i) => i < namespace.length - 1));
359
+ field.parentTypename = NAMER.dataTypeName(field.namespace.filter((_, i) => i < field.namespace.length - 1));
294
360
  const extraFields = field.ui || {};
295
361
  switch (field.type) {
296
362
  case "number":
@@ -351,10 +417,7 @@
351
417
  ...extraFields
352
418
  };
353
419
  case "object":
354
- const templateInfo = schema.getTemplatesForCollectable({
355
- ...field,
356
- namespace
357
- });
420
+ const templateInfo = schema.getTemplatesForCollectable(field);
358
421
  if (templateInfo.type === "object") {
359
422
  return {
360
423
  ...field,
@@ -372,6 +435,7 @@
372
435
  templates2[lastItem(template.namespace)] = {
373
436
  label: template.label || templateName,
374
437
  key: templateName,
438
+ namespace: [...field.namespace, templateName],
375
439
  fields: template.fields.map((field2) => resolveField(field2, schema)),
376
440
  ...extraFields2
377
441
  };
@@ -380,6 +444,7 @@
380
444
  return {
381
445
  ...field,
382
446
  typeMap: typeMap2,
447
+ namespace: field.namespace,
383
448
  component: field.list ? "blocks" : "not-implemented",
384
449
  templates: templates2,
385
450
  ...extraFields
@@ -465,7 +530,9 @@
465
530
  const name = z.z.string({
466
531
  required_error: "Name is required but not provided",
467
532
  invalid_type_error: "Name must be a string"
468
- });
533
+ }).refine((val) => val.match(/^[a-zA-Z0-9_]*$/) !== null, (val) => ({
534
+ message: `name, "${val}" must be alphanumeric and can only contain underscores`
535
+ }));
469
536
  const TypeName = [
470
537
  "string",
471
538
  "boolean",
@@ -484,7 +551,7 @@
484
551
  }).superRefine((val, ctx) => {
485
552
  if (val.includes(" "))
486
553
  ctx.addIssue({
487
- message: "name cannot contain spaces",
554
+ message: `name "${val}" cannot contain spaces`,
488
555
  code: z.z.ZodIssueCode.custom,
489
556
  fatal: true
490
557
  });
@@ -554,22 +621,37 @@
554
621
  start: z.z.string(),
555
622
  end: z.z.string()
556
623
  }).optional()
557
- }).refine((val) => {
558
- var _a;
559
- return !hasDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
560
- }, {
561
- message: "Fields must have a unique name"
624
+ }).superRefine((val, ctx) => {
625
+ const dups = findDuplicates(val == null ? void 0 : val.fields.map((x) => x.name));
626
+ if (dups) {
627
+ ctx.addIssue({
628
+ code: z.z.ZodIssueCode.custom,
629
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
630
+ });
631
+ }
562
632
  });
563
633
  const ObjectField = FieldWithList.extend({
564
634
  type: z.z.literal("object", {
565
635
  invalid_type_error: typeTypeError,
566
636
  required_error: typeRequiredError
567
637
  }),
568
- fields: z.z.array(TinaFieldZod).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
569
- message: "Fields must have a unique name"
638
+ fields: z.z.array(TinaFieldZod).min(1).optional().superRefine((val, ctx) => {
639
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
640
+ if (dups) {
641
+ ctx.addIssue({
642
+ code: z.z.ZodIssueCode.custom,
643
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
644
+ });
645
+ }
570
646
  }),
571
- templates: z.z.array(TemplateTemp).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
572
- message: "Templates must have a unique name"
647
+ templates: z.z.array(TemplateTemp).min(1).optional().superRefine((val, ctx) => {
648
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
649
+ if (dups) {
650
+ ctx.addIssue({
651
+ code: z.z.ZodIssueCode.custom,
652
+ message: `Templates must have a unique name, duplicate template names: ${dups}`
653
+ });
654
+ }
573
655
  })
574
656
  });
575
657
  const RichTextField = FieldWithList.extend({
@@ -577,8 +659,14 @@
577
659
  invalid_type_error: typeTypeError,
578
660
  required_error: typeRequiredError
579
661
  }),
580
- templates: z.z.array(TemplateTemp).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
581
- message: "Templates must have a unique name"
662
+ templates: z.z.array(TemplateTemp).optional().superRefine((val, ctx) => {
663
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
664
+ if (dups) {
665
+ ctx.addIssue({
666
+ code: z.z.ZodIssueCode.custom,
667
+ message: `Templates must have a unique name, duplicate template names: ${dups}`
668
+ });
669
+ }
582
670
  })
583
671
  });
584
672
  return z.z.discriminatedUnion("type", [
@@ -608,13 +696,17 @@
608
696
  if (val.list) {
609
697
  ctx.addIssue({
610
698
  code: z.z.ZodIssueCode.custom,
611
- message: "You can not have `list: true` when using `isTitle`"
699
+ message: `Can not have \`list: true\` when using \`isTitle\`. Error in value
700
+ ${JSON.stringify(val, null, 2)}
701
+ `
612
702
  });
613
703
  }
614
704
  if (!val.required) {
615
705
  ctx.addIssue({
616
706
  code: z.z.ZodIssueCode.custom,
617
- message: "You must have { required: true } when using `isTitle`"
707
+ message: `Must have { required: true } when using \`isTitle\` Error in value
708
+ ${JSON.stringify(val, null, 2)}
709
+ `
618
710
  });
619
711
  }
620
712
  }
@@ -665,11 +757,15 @@
665
757
  }),
666
758
  name,
667
759
  fields: z.z.array(TinaFieldZod)
668
- }).refine((val) => {
760
+ }).superRefine((val, ctx) => {
669
761
  var _a;
670
- return !hasDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
671
- }, {
672
- message: "Fields must have a unique name"
762
+ const dups = findDuplicates((_a = val.fields) == null ? void 0 : _a.map((x) => x.name));
763
+ if (dups) {
764
+ ctx.addIssue({
765
+ code: z.z.ZodIssueCode.custom,
766
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
767
+ });
768
+ }
673
769
  });
674
770
  const TinaCloudCollectionBase = z.z.object({
675
771
  label: z.z.string().optional(),
@@ -677,16 +773,28 @@
677
773
  format: z.z.enum(FORMATS).optional()
678
774
  });
679
775
  const TinaCloudCollection = TinaCloudCollectionBase.extend({
680
- fields: z.z.array(TinaFieldZod).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
681
- message: "Fields must have a unique name"
776
+ fields: z.z.array(TinaFieldZod).min(1).optional().superRefine((val, ctx) => {
777
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
778
+ if (dups) {
779
+ ctx.addIssue({
780
+ code: z.z.ZodIssueCode.custom,
781
+ message: `Fields must have a unique name, duplicate field names: ${dups}`
782
+ });
783
+ }
682
784
  }).refine((val) => {
683
785
  const arr = (val == null ? void 0 : val.filter((x) => x.type === "string" && x.isTitle)) || [];
684
786
  return arr.length < 2;
685
787
  }, {
686
788
  message: "Fields can only have one use of `isTitle`"
687
789
  }),
688
- templates: z.z.array(Template).min(1).optional().refine((val) => !hasDuplicates(val == null ? void 0 : val.map((x) => x.name)), {
689
- message: "Templates must have a unique name"
790
+ templates: z.z.array(Template).min(1).optional().superRefine((val, ctx) => {
791
+ const dups = findDuplicates(val == null ? void 0 : val.map((x) => x.name));
792
+ if (dups) {
793
+ ctx.addIssue({
794
+ code: z.z.ZodIssueCode.custom,
795
+ message: `Templates must have a unique name, duplicate template names: ${dups}`
796
+ });
797
+ }
690
798
  })
691
799
  }).refine((val) => {
692
800
  let isValid = Boolean(val == null ? void 0 : val.templates) || Boolean(val == null ? void 0 : val.fields);
@@ -701,20 +809,21 @@
701
809
  collections: z.z.array(TinaCloudCollection),
702
810
  config: tinaConfigZod.optional()
703
811
  }).superRefine((val, ctx) => {
704
- var _a, _b;
705
- if (hasDuplicates(val.collections.map((x) => x.name))) {
812
+ var _a, _b, _c;
813
+ const dups = findDuplicates((_a = val.collections) == null ? void 0 : _a.map((x) => x.name));
814
+ if (dups) {
706
815
  ctx.addIssue({
707
816
  code: z.z.ZodIssueCode.custom,
708
- message: "can not have two collections with the same name",
817
+ message: `${dups} are duplicate names in your collections. Collection names must be unique.`,
709
818
  fatal: true
710
819
  });
711
820
  }
712
- (_a = val == null ? void 0 : val.collections) == null ? void 0 : _a.map((x) => {
821
+ (_b = val == null ? void 0 : val.collections) == null ? void 0 : _b.map((x) => {
713
822
  if (!x.format) {
714
823
  console.warn(`No format provided for collection ${x.name}, defaulting to .md`);
715
824
  }
716
825
  });
717
- const media = (_b = val == null ? void 0 : val.config) == null ? void 0 : _b.media;
826
+ const media = (_c = val == null ? void 0 : val.config) == null ? void 0 : _c.media;
718
827
  if (media && media.tina && media.loadCustomStore) {
719
828
  ctx.addIssue({
720
829
  code: z.z.ZodIssueCode.custom,
@@ -744,6 +853,7 @@
744
853
  }
745
854
  }
746
855
  };
856
+ exports2.NAMER = NAMER;
747
857
  exports2.TinaSchema = TinaSchema;
748
858
  exports2.TinaSchemaValidationError = TinaSchemaValidationError;
749
859
  exports2.addNamespaceToSchema = addNamespaceToSchema;
@@ -64,6 +64,13 @@ export declare class TinaSchema {
64
64
  data?: unknown;
65
65
  collection: Collectable;
66
66
  }) => Templateable;
67
+ transformPayload: (collectionName: string, payload: object) => {
68
+ [x: string]: {
69
+ [x: string]: {};
70
+ };
71
+ };
72
+ private transformCollectablePayload;
73
+ private transformField;
67
74
  isMarkdownCollection: (collectionName: string) => boolean;
68
75
  /**
69
76
  * Gets the template or templates from the item.
@@ -21,4 +21,9 @@ import { TinaSchema } from './TinaSchema';
21
21
  * @param {TinaSchema} schema the entireT Tina Schema
22
22
  * @returns unknown
23
23
  */
24
- export declare const resolveField: ({ namespace, ...field }: TinaFieldEnriched, schema: TinaSchema) => unknown;
24
+ export declare const resolveField: (field: TinaFieldEnriched, schema: TinaSchema) => {
25
+ [key: string]: unknown;
26
+ name: string;
27
+ component: string;
28
+ type: string;
29
+ };
@@ -18,5 +18,10 @@ export declare const resolveForm: ({ collection, basename, template, schema, }:
18
18
  id: string;
19
19
  label: string;
20
20
  name: string;
21
- fields: unknown[];
21
+ fields: {
22
+ [key: string]: unknown;
23
+ name: string;
24
+ component: string;
25
+ type: string;
26
+ }[];
22
27
  };
@@ -44,11 +44,46 @@ export declare type TinaCloudCollection<WithNamespace extends boolean> = Collect
44
44
  export declare type TinaCloudCollectionBase = TinaCloudCollection<false>;
45
45
  export declare type TinaCloudCollectionEnriched = TinaCloudCollection<true>;
46
46
  declare type FormatType = 'json' | 'md' | 'markdown' | 'mdx';
47
+ declare type Document = {
48
+ _sys: {
49
+ title?: string;
50
+ template: string;
51
+ breadcrumbs: string[];
52
+ path: string;
53
+ basename: string;
54
+ relativePath: string;
55
+ filename: string;
56
+ extension: string;
57
+ };
58
+ };
47
59
  interface BaseCollection {
48
60
  label?: string;
49
61
  name: string;
50
62
  path: string;
51
63
  format?: FormatType;
64
+ ui?: {
65
+ /**
66
+ * Forms for this collection will be editable from the global sidebar rather than the form panel
67
+ */
68
+ global?: boolean | {
69
+ icon?: any;
70
+ layout: 'fullscreen' | 'popup';
71
+ };
72
+ /**
73
+ * Provide the path that your document is viewable on your site
74
+ *
75
+ * eg:
76
+ * ```ts
77
+ * router: ({ document }) => {
78
+ * return `blog-posts/${document._sys.filename}`;
79
+ * }
80
+ * ```
81
+ */
82
+ router?: (args: {
83
+ document: Document;
84
+ collection: TinaCloudCollection<true>;
85
+ }) => string | undefined;
86
+ };
52
87
  match?: string;
53
88
  }
54
89
  declare type CollectionTemplates<WithNamespace extends boolean> = WithNamespace extends true ? CollectionTemplatesWithNamespace<WithNamespace> : CollectionTemplatesInner<WithNamespace>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ Copyright 2021 Forestry.io Holdings, Inc.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ import { TinaCloudSchema } from './SchemaTypes';
14
+ /**
15
+ * Used with `defineStaticConfig`
16
+ *
17
+ * These are mostly similar types as whats in `schemaTypes`
18
+ * but since those have gone through several iterations
19
+ * they're pretty messy. These should act as the happy path
20
+ * for iframe/standalone setups which we hope to eventually
21
+ * make the default/only path for all Tina users.
22
+ */
23
+ export declare type TinaCMSConfig<CMSCallback = undefined, FormifyCallback = undefined, DocumentCreatorCallback = undefined, Store = undefined> = {
24
+ schema: TinaCloudSchema<false>;
25
+ /**
26
+ * The base branch to pull content from. Note that this is ignored for local development
27
+ */
28
+ branch: string | null;
29
+ /**
30
+ * Your clientId from app.tina.io
31
+ */
32
+ clientId: string | null;
33
+ /**
34
+ * Your read only token from app.tina.io
35
+ */
36
+ token: string | null;
37
+ /**
38
+ * Configurations for the autogenerated GraphQL HTTP client
39
+ */
40
+ client?: {
41
+ /**
42
+ * Autogenerated queries will traverse references to a given depth
43
+ * @default 2
44
+ */
45
+ referenceDepth?: number;
46
+ };
47
+ build?: {
48
+ /**
49
+ * The folder where your application stores assets, eg. `"public"`
50
+ */
51
+ publicFolder: string;
52
+ /**
53
+ * TinaCMS is shipped as a single-page app, the value specified here will
54
+ * determine the path when visiting the TinaCMS dashboard.
55
+ *
56
+ * Eg. `"admin"` will be viewable at `[your-development-url]/admin/index.html`
57
+ */
58
+ outputFolder: string;
59
+ };
60
+ media?: {
61
+ /**
62
+ * Load a media store like Cloudinary
63
+ *
64
+ * ```ts
65
+ * loadCustomStore = async () => {
66
+ * const pack = await import("next-tinacms-cloudinary");
67
+ * return pack.TinaCloudCloudinaryMediaStore;
68
+ * }
69
+ * ```
70
+ */
71
+ loadCustomStore: () => Promise<Store>;
72
+ tina?: never;
73
+ } | {
74
+ /**
75
+ * Use Git-backed assets for media, these values will
76
+ * [Learn more](https://tina.io/docs/reference/media/repo-based/)
77
+ */
78
+ tina: {
79
+ /**
80
+ * The folder where your application stores assets, eg. `"public"`
81
+ */
82
+ publicFolder: string;
83
+ /**
84
+ * The root folder for media managed by Tina. For example, `"uploads"`
85
+ * would store content in `"<my-public-folder>/uploads"`
86
+ */
87
+ mediaRoot: string;
88
+ };
89
+ loadCustomStore?: never;
90
+ };
91
+ tinaioConfig?: {
92
+ assetsApiUrlOverride?: string;
93
+ frontendUrlOverride?: string;
94
+ identityApiUrlOverride?: string;
95
+ contentApiUrlOverride?: string;
96
+ };
97
+ cmsCallback?: CMSCallback;
98
+ formifyCallback?: FormifyCallback;
99
+ documentCreatorCallback?: DocumentCreatorCallback;
100
+ };
101
+ export {};
@@ -16,3 +16,4 @@ limitations under the License.
16
16
 
17
17
  */
18
18
  export * from './SchemaTypes';
19
+ export * from './SchemaTypes2';
@@ -0,0 +1,13 @@
1
+ /**
2
+ Copyright 2021 Forestry.io Holdings, Inc.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ export {};
@@ -10,4 +10,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
10
  See the License for the specific language governing permissions and
11
11
  limitations under the License.
12
12
  */
13
- export declare function hasDuplicates<T = any>(array: T[]): boolean;
13
+ export declare function hasDuplicates<T = any>(array?: T[]): boolean;
14
+ /**
15
+ *
16
+ * @param array
17
+ * @returns False if the array is undefined or has no duplicates.
18
+ */
19
+ export declare function findDuplicates<T = any>(array?: T[] | undefined): undefined | string;
@@ -11,4 +11,4 @@ See the License for the specific language governing permissions and
11
11
  limitations under the License.
12
12
  */
13
13
  import { z } from 'zod';
14
- export declare const name: z.ZodString;
14
+ export declare const name: z.ZodEffects<z.ZodString, string, string>;
@@ -14,13 +14,13 @@ import { z } from 'zod';
14
14
  export declare const TinaCloudSchemaZod: z.ZodEffects<z.ZodObject<{
15
15
  collections: z.ZodArray<z.ZodEffects<z.ZodObject<z.extendShape<{
16
16
  label: z.ZodOptional<z.ZodString>;
17
- name: z.ZodString;
17
+ name: z.ZodEffects<z.ZodString, string, string>;
18
18
  format: z.ZodOptional<z.ZodEnum<["json", "md", "markdown", "mdx"]>>;
19
19
  }, {
20
20
  fields: z.ZodEffects<z.ZodEffects<z.ZodOptional<z.ZodArray<z.ZodType<import("..").TinaFieldInner<false>, z.ZodTypeDef, import("..").TinaFieldInner<false>>, "many">>, import("..").TinaFieldInner<false>[], import("..").TinaFieldInner<false>[]>, import("..").TinaFieldInner<false>[], import("..").TinaFieldInner<false>[]>;
21
21
  templates: z.ZodEffects<z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
22
22
  label: z.ZodString;
23
- name: z.ZodString;
23
+ name: z.ZodEffects<z.ZodString, string, string>;
24
24
  fields: z.ZodArray<z.ZodType<import("..").TinaFieldInner<false>, z.ZodTypeDef, import("..").TinaFieldInner<false>>, "many">;
25
25
  }, "strip", z.ZodTypeAny, {
26
26
  name?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinacms/schema-tools",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "exports": {
@@ -23,7 +23,7 @@
23
23
  ]
24
24
  },
25
25
  "devDependencies": {
26
- "@tinacms/scripts": "0.51.0",
26
+ "@tinacms/scripts": "0.51.1",
27
27
  "@types/yup": "^0.29.10",
28
28
  "jest": "^27.0.6",
29
29
  "react": "17.0.2",