@takeshape/schema 8.144.4 → 8.146.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.
Files changed (125) hide show
  1. package/dist/builtin-schema.d.ts.map +1 -1
  2. package/dist/builtin-schema.js +17 -0
  3. package/dist/get-is-leaf.d.ts +1 -1
  4. package/dist/get-is-leaf.d.ts.map +1 -1
  5. package/dist/get-is-leaf.js +3 -1
  6. package/dist/migration/index.d.ts +1 -0
  7. package/dist/migration/index.d.ts.map +1 -1
  8. package/dist/migration/index.js +4 -1
  9. package/dist/migration/to/v3.0.0.d.ts.map +1 -1
  10. package/dist/migration/to/v3.18.0.d.ts +5 -0
  11. package/dist/migration/to/v3.18.0.d.ts.map +1 -0
  12. package/dist/migration/to/v3.18.0.js +85 -0
  13. package/dist/mocks.d.ts.map +1 -1
  14. package/dist/mocks.js +4 -2
  15. package/dist/project-schema/index.d.ts +4 -1
  16. package/dist/project-schema/index.d.ts.map +1 -1
  17. package/dist/project-schema/index.js +20 -3
  18. package/dist/project-schema/latest.d.ts +4 -139
  19. package/dist/project-schema/latest.d.ts.map +1 -1
  20. package/dist/project-schema/migrate.d.ts.map +1 -1
  21. package/dist/project-schema/migrate.js +4 -0
  22. package/dist/project-schema/v3.18.0.d.ts +1334 -0
  23. package/dist/project-schema/v3.18.0.d.ts.map +1 -0
  24. package/dist/project-schema/v3.18.0.js +5 -0
  25. package/dist/refs.d.ts +14 -2
  26. package/dist/refs.d.ts.map +1 -1
  27. package/dist/refs.js +26 -0
  28. package/dist/relationships.d.ts +28 -2
  29. package/dist/relationships.d.ts.map +1 -1
  30. package/dist/relationships.js +234 -19
  31. package/dist/rewrite.d.ts.map +1 -1
  32. package/dist/rewrite.js +10 -2
  33. package/dist/schema-util.d.ts +1 -18
  34. package/dist/schema-util.d.ts.map +1 -1
  35. package/dist/schema-util.js +36 -120
  36. package/dist/schemas/index.d.ts +2 -2
  37. package/dist/schemas/index.d.ts.map +1 -1
  38. package/dist/schemas/index.js +6 -4
  39. package/dist/schemas/index.ts +4 -2
  40. package/dist/schemas/project-schema/latest.json +2175 -2195
  41. package/dist/schemas/project-schema/v3.18.0.json +2350 -0
  42. package/dist/schemas/project-schema.json +3 -0
  43. package/dist/template-shapes/templates.d.ts +5 -0
  44. package/dist/template-shapes/templates.d.ts.map +1 -1
  45. package/dist/template-shapes/templates.js +59 -28
  46. package/dist/template-shapes/where.d.ts +3 -4
  47. package/dist/template-shapes/where.d.ts.map +1 -1
  48. package/dist/template-shapes/where.js +32 -26
  49. package/dist/types/types.d.ts +42 -2
  50. package/dist/types/types.d.ts.map +1 -1
  51. package/dist/types/utils.d.ts +2 -0
  52. package/dist/types/utils.d.ts.map +1 -1
  53. package/dist/types/utils.js +9 -1
  54. package/dist/unions.d.ts.map +1 -1
  55. package/dist/unions.js +3 -1
  56. package/dist/util/detect-cycles.d.ts +3 -1
  57. package/dist/util/detect-cycles.d.ts.map +1 -1
  58. package/dist/util/detect-cycles.js +37 -28
  59. package/dist/util/form-config.d.ts +9 -0
  60. package/dist/util/form-config.d.ts.map +1 -0
  61. package/dist/util/form-config.js +97 -0
  62. package/dist/util/index.d.ts +1 -0
  63. package/dist/util/index.d.ts.map +1 -1
  64. package/dist/util/index.js +13 -0
  65. package/dist/validate.d.ts.map +1 -1
  66. package/dist/validate.js +75 -45
  67. package/es/builtin-schema.js +17 -0
  68. package/es/get-is-leaf.js +2 -1
  69. package/es/migration/index.js +3 -1
  70. package/es/migration/to/v3.18.0.js +72 -0
  71. package/es/mocks.js +4 -2
  72. package/es/project-schema/index.js +3 -1
  73. package/es/project-schema/migrate.js +5 -1
  74. package/es/project-schema/v3.18.0.js +1 -0
  75. package/es/refs.js +22 -1
  76. package/es/relationships.js +215 -19
  77. package/es/rewrite.js +10 -2
  78. package/es/schema-util.js +38 -119
  79. package/es/schemas/index.js +4 -3
  80. package/es/schemas/index.ts +4 -2
  81. package/es/schemas/project-schema/latest.json +2175 -2195
  82. package/es/schemas/project-schema/v3.18.0.json +2350 -0
  83. package/es/schemas/project-schema.json +3 -0
  84. package/es/template-shapes/templates.js +55 -29
  85. package/es/template-shapes/where.js +29 -23
  86. package/es/types/utils.js +8 -2
  87. package/es/unions.js +2 -1
  88. package/es/util/detect-cycles.js +36 -28
  89. package/es/util/form-config.js +85 -0
  90. package/es/util/index.js +2 -1
  91. package/es/validate.js +74 -46
  92. package/examples/latest/betzino.json +12383 -6066
  93. package/examples/latest/blog-schema.json +46 -25
  94. package/examples/latest/brewery-schema.json +14 -9
  95. package/examples/latest/complex-project-schema.json +442 -244
  96. package/examples/latest/complex-schema.json +17205 -0
  97. package/examples/latest/fabric-ecommerce.json +2 -2
  98. package/examples/latest/frank-and-fred-schema.json +5141 -2391
  99. package/examples/latest/klirr-schema.json +35445 -0
  100. package/examples/latest/massive-schema.json +1205 -640
  101. package/examples/latest/mill-components-schema.json +241 -113
  102. package/examples/latest/one-earth.json +14429 -0
  103. package/examples/latest/pet-oneof-array.json +2 -2
  104. package/examples/latest/post-schema.json +18 -11
  105. package/examples/latest/pruned-shopify-product-schema.json +2 -2
  106. package/examples/latest/real-world-schema.json +81 -41
  107. package/examples/latest/recursive-repeater-schema.json +14 -9
  108. package/examples/latest/recursive-schema.json +14 -9
  109. package/examples/latest/rick-and-morty-ast.json +138 -80
  110. package/examples/latest/rick-and-morty-graphql.json +78 -45
  111. package/examples/latest/rick-and-morty-rest.json +2 -2
  112. package/examples/latest/schema-with-repeater-draftjs.json +38 -23
  113. package/examples/latest/shape-books-v3_2_0.json +138 -80
  114. package/examples/latest/shape-books.json +138 -80
  115. package/examples/latest/shopify-lookbook.json +30 -16
  116. package/examples/latest/shopify-namespace-schema.json +364 -0
  117. package/examples/latest/shopify-store-with-widget.json +2 -2
  118. package/examples/latest/stripe-starter-resolved.json +14 -8
  119. package/examples/latest/user-schema-no-required.json +2 -2
  120. package/examples/latest/user-schema-with-defaults.json +2 -2
  121. package/examples/source/complex-schema.json +12760 -0
  122. package/examples/source/klirr-schema.json +27716 -0
  123. package/examples/source/one-earth.json +11897 -0
  124. package/examples/source/post-schema.json +0 -1
  125. package/package.json +5 -4
@@ -75,6 +75,9 @@
75
75
  {
76
76
  "$ref": "https://schema.takeshape.io/project-schema/v3.17.1#"
77
77
  },
78
+ {
79
+ "$ref": "https://schema.takeshape.io/project-schema/v3.18.0#"
80
+ },
78
81
  {
79
82
  "$ref": "https://schema.takeshape.io/project-schema/v4.0.0#"
80
83
  }
@@ -1,12 +1,13 @@
1
1
  import { TemplateVerbs } from './types';
2
2
  import { listTypePrefix } from '../migration';
3
- import { pascalCase } from '@takeshape/util';
3
+ import { deepClone, pascalCase, visit } from '@takeshape/util';
4
4
  import keyBy from 'lodash/keyBy';
5
5
  import { createShape, getShapeDependencies, isIndexedRemoteShape } from '../schema-util';
6
6
  import { getFlattenedTemplateShapeName, getRefShapeName } from '../refs';
7
7
  import { getWhereSearchArg } from './where';
8
8
  import { mergeObjectSchemas, mergeSchemaProperties } from '../util';
9
9
  import { rewriteSchema } from '../rewrite';
10
+ import get from 'lodash/get';
10
11
  export const TSGetArgs = getIDQueryArgs('TSGetArgs');
11
12
  export const TSGetSingletonArgs = getIDQueryArgs('TSGetSingletonArgs');
12
13
  export const TSListArgs = getShapeListQueryArgs('TSListArgs', true);
@@ -188,14 +189,39 @@ export function getMutationInputShapeName(verb) {
188
189
  return pascalCase([verb, shapeName, 'input']);
189
190
  };
190
191
  }
191
-
192
192
  /**
193
- * Create a shape name variant for an input. Updates will typically want a partial input.
193
+ * Create a shape name variant for an input.
194
194
  */
195
- function getInputShapeName(shapeName, {
196
- isPartial
197
- }) {
198
- return isPartial ? `${shapeName}PartialInput` : `${shapeName}Input`;
195
+
196
+ export function getInputShapeName(shapeName) {
197
+ return shapeName === 'TSRelationship' ? shapeName : `${shapeName}Input`;
198
+ }
199
+ export function getOutputShapeName(name) {
200
+ return name.replace(/(?:Input|PartialInput)$/, '');
201
+ }
202
+
203
+ function rewriteRefs(shapeSchema, rewriteShapeName) {
204
+ const newSchema = deepClone(shapeSchema);
205
+ visit(newSchema, ['$ref', '@ref'], (_, path) => {
206
+ const parentPath = path.slice(0, -1);
207
+ const value = get(newSchema, parentPath);
208
+
209
+ if ((value['@ref'] || value.$ref) && !parentPath.includes('@input') && !parentPath.includes('@output')) {
210
+ if (value['@ref']) {
211
+ const ref = value['@ref'];
212
+ const parts = ref.split(':');
213
+ value['@ref'] = `local:${rewriteShapeName(parts[1])}`;
214
+ }
215
+
216
+ if (value.$ref) {
217
+ const ref = value.$ref;
218
+ const parts = ref.split('/');
219
+ parts[2] = rewriteShapeName(parts[2]);
220
+ value.$ref = parts.join('/');
221
+ }
222
+ }
223
+ });
224
+ return newSchema;
199
225
  }
200
226
  /**
201
227
  * Construct shapes for a mutation inputs, given source shape info.
@@ -213,14 +239,19 @@ export function getMutationArgs(templateName, verb) {
213
239
  const inputName = getShapeName(shape.name);
214
240
  const isSingleton = shape.model && shape.model.type === 'single';
215
241
  const shouldRequireId = !isSingleton && (verb === TemplateVerbs.Update || verb === TemplateVerbs.Delete || verb === TemplateVerbs.Duplicate);
216
- const rewrittenSchema = verb !== TemplateVerbs.Delete ? rewriteSchema(shape.schema, isUpdate ? {
217
- removeDefault: true,
218
- removeRequired: true,
219
- replaceInput: true
220
- } : {
221
- replaceInput: true
222
- }) : undefined;
223
- const inputShape = createShape(inputName, mergeObjectSchemas(shouldRequireId ? idSchema : undefined, rewrittenSchema), {
242
+ let rewrittenSchema;
243
+ let inputShapeSchema;
244
+
245
+ if (verb !== TemplateVerbs.Delete) {
246
+ rewrittenSchema = rewriteSchema(shape.schema, {
247
+ removeDefault: isUpdate,
248
+ removeRequired: isUpdate,
249
+ replaceInput: true
250
+ });
251
+ inputShapeSchema = rewriteRefs(rewrittenSchema, getInputShapeName);
252
+ }
253
+
254
+ const inputShape = createShape(inputName, mergeObjectSchemas(shouldRequireId ? idSchema : undefined, inputShapeSchema), {
224
255
  description: `${verb} ${shape.name} input`
225
256
  });
226
257
  const argsShapeName = getFlattenedTemplateShapeName(shape.name, templateName);
@@ -241,30 +272,25 @@ export function getMutationArgs(templateName, verb) {
241
272
  dependencies: {
242
273
  [inputName]: inputShape,
243
274
  [argsShapeName]: argsShape,
244
- ...(verb !== TemplateVerbs.Delete && keyBy(getShapeDependencies(projectSchema, shape, propSchema => {
245
- if (!propSchema['@resolver']) {
275
+ ...(verb !== TemplateVerbs.Delete && keyBy(getShapeDependencies(projectSchema, // get the shape dependencies of the shape with the @input swapped
276
+ createShape(inputShape.name, rewrittenSchema), propSchema => {
277
+ if (propSchema['@resolver']) {
246
278
  return false;
247
279
  }
248
280
 
249
281
  const shapeName = getRefShapeName(projectSchema, propSchema);
250
282
 
251
283
  if (shapeName) {
252
- return shapeName !== 'TSRelationship' && !projectSchema.shapes[getInputShapeName(shapeName, {
253
- isPartial: isUpdate
254
- })];
284
+ return shapeName !== 'TSRelationship' && !projectSchema.shapes[getInputShapeName(shapeName)];
255
285
  }
256
286
 
257
287
  return true;
258
288
  }).map(shapeName => {
259
- const shape = projectSchema.shapes[shapeName];
260
- const name = getInputShapeName(shapeName, {
261
- isPartial: isUpdate
262
- });
263
- return { ...shape,
264
- name,
265
- id: name,
266
- schema: rewrittenSchema
267
- };
289
+ const shape = projectSchema.shapes[shapeName]; // We only allow partial input at the top-level
290
+
291
+ return createShape(getInputShapeName(shapeName), rewriteRefs(rewriteSchema(shape.schema, {
292
+ replaceInput: true
293
+ }), getInputShapeName));
268
294
  }), 'name')),
269
295
  ...(addStructure ? {
270
296
  ContentStructureInput
@@ -1,14 +1,14 @@
1
- import { pascalCase, isDefined } from '@takeshape/util';
2
1
  import unset from 'lodash/unset';
3
2
  import isFunction from 'lodash/isFunction';
4
- import { createSchemaPropertyList, createShape, getAllRefsInShapes, getShapeById, isIndexedRemoteShape } from '../schema-util';
3
+ import { createSchemaPropertyList, createShape, getAllRefsInShapes, isIndexedRemoteShape } from '../schema-util';
4
+ import uniqBy from 'lodash/uniqBy';
5
+ import md5 from 'blueimp-md5';
6
+ import { pascalCase, isDefined } from '@takeshape/util';
5
7
  import { workflowsEnabled } from '../api-version';
6
8
  import { followRef, getRef, getRefOrItemsRef, hasRefProperty, hasResolvableRef, refItemToShape, refItemToShapeName } from '../refs';
7
9
  import { enumerateOneOfKeys, isUnionSchema } from '../unions';
8
- import { isObjectSchema } from '../types';
9
- import uniqBy from 'lodash/uniqBy';
10
- import md5 from 'blueimp-md5';
11
- import { isEqualRelationship } from '../relationships';
10
+ import { isObjectSchema, isPropertySchemaWithRelationship } from '../types/utils';
11
+ import { isEqualRelationship, getRelationship, getRelationshipShapes } from '../relationships';
12
12
  export const MAX_RELATIONSHIP_DEPTH = 1;
13
13
  export const exceededRelationshipDepth = (depth = 0) => depth >= MAX_RELATIONSHIP_DEPTH;
14
14
 
@@ -159,7 +159,7 @@ function isId(name) {
159
159
  }
160
160
 
161
161
  function isNonRelationshipRef(prop) {
162
- return Boolean(hasRefProperty(prop) && !prop['@relationship']);
162
+ return Boolean(hasRefProperty(prop) && !getRelationship(prop));
163
163
  }
164
164
 
165
165
  function getPropType(projectSchema, name, prop) {
@@ -200,20 +200,17 @@ function truncateNames(names) {
200
200
  // For unions of many types hash the names this reduces shape name size while still allowing reuse
201
201
  return names.length > 80 ? md5(names) : names;
202
202
  }
203
+ /**
204
+ * Get a formatted string of related shape names from a property schema with a
205
+ * relationship resolver
206
+ */
203
207
 
204
- function getRelatedShapeNames(projectSchema, prop) {
205
- var _prop$Relationship;
206
208
 
207
- const names = (_prop$Relationship = prop['@relationship']) === null || _prop$Relationship === void 0 ? void 0 : _prop$Relationship.shapeIds.map(id => {
208
- const shape = getShapeById(projectSchema, id);
209
- return pascalCase(shape ? shape.name : '');
209
+ function getRelationshipShapeName(refs) {
210
+ return refs.map(ref => {
211
+ const shapeName = ref.split(':')[1];
212
+ return pascalCase(shapeName);
210
213
  }).sort().join('');
211
-
212
- if (!names) {
213
- throw new Error(`Relationship property ${prop.title} is missing related shapes`);
214
- }
215
-
216
- return names;
217
214
  }
218
215
 
219
216
  function joinShapeNames(shapes) {
@@ -232,8 +229,16 @@ function getTypeName(name, prop, shapeName, context) {
232
229
  return `TSWhereID`;
233
230
  }
234
231
 
235
- if (prop['@relationship']) {
236
- const shapeNames = truncateNames(getRelatedShapeNames(projectSchema, prop));
232
+ const relationship = getRelationship(prop);
233
+
234
+ if (relationship) {
235
+ const relationshipName = getRelationshipShapeName(relationship.refs);
236
+
237
+ if (!relationshipName) {
238
+ throw new Error(`Relationship property "${prop.title ?? ''}" is missing related shapes`);
239
+ }
240
+
241
+ const shapeNames = truncateNames(relationshipName);
237
242
  return `TSWhere${shapeNames}Relationship`;
238
243
  }
239
244
 
@@ -265,7 +270,7 @@ function skipField({
265
270
  exceededRelationshipDepth,
266
271
  isIndexedShape
267
272
  }) {
268
- return Boolean(prop['@sensitive'] || !isIndexedShape && prop['@resolver'] || prop['@relationship'] && exceededRelationshipDepth || (name === '_enabled' || name === '_enabledAt') && workflowsEnabled);
273
+ return Boolean(prop['@sensitive'] || !isIndexedShape && prop['@resolver'] || isPropertySchemaWithRelationship(prop) && exceededRelationshipDepth || (name === '_enabled' || name === '_enabledAt') && workflowsEnabled);
269
274
  }
270
275
 
271
276
  export function getFields(typeName, shapes, context) {
@@ -355,11 +360,12 @@ export function getPropertyComparisonType(name, prop, shapeName, context) {
355
360
 
356
361
  let props;
357
362
  const propOrItems = prop.items ?? prop;
363
+ const relationship = getRelationship(prop);
358
364
 
359
365
  if (isId(name)) {
360
366
  props = getFieldTypeComparison('id');
361
- } else if (prop['@relationship']) {
362
- const shapes = prop['@relationship'].shapeIds.map(id => getShapeById(projectSchema, id)).filter(isDefined);
367
+ } else if (relationship) {
368
+ const shapes = getRelationshipShapes(projectSchema.shapes, relationship.refs);
363
369
  props = getFields(typeName, shapes, { ...context,
364
370
  relationshipDepth: relationshipDepth + 1
365
371
  });
package/es/types/utils.js CHANGED
@@ -7,12 +7,12 @@ import isArray from 'lodash/isArray';
7
7
  import has from 'lodash/has';
8
8
  import { latestSchemaJson } from '../schemas';
9
9
  import satisfies from 'semver/functions/satisfies';
10
+
10
11
  /** Resolver Type Utils **/
11
12
 
12
13
  /**
13
14
  * Only tests that the shape is right, not that the name is correct. That's a job for the validator.
14
15
  */
15
-
16
16
  export function isBasicResolver(resolver) {
17
17
  return isString(resolver === null || resolver === void 0 ? void 0 : resolver.name) && isString(resolver.service);
18
18
  }
@@ -263,4 +263,10 @@ export const getRefType = refSchema => {
263
263
  if (has(refSchema, '$ref')) {
264
264
  return RefType.$ref;
265
265
  }
266
- };
266
+ };
267
+ export function isPropertySchemaWithRelationship(schema) {
268
+ var _schema$Resolver;
269
+
270
+ schema = schema['@output'] ?? schema;
271
+ return ((_schema$Resolver = schema['@resolver']) === null || _schema$Resolver === void 0 ? void 0 : _schema$Resolver.name) === 'takeshape:getRelated';
272
+ }
package/es/unions.js CHANGED
@@ -5,6 +5,7 @@ import { getContentTransform, toName } from './content-schema-transform';
5
5
  import { isRefSchema } from './types/utils';
6
6
  import { BadDataError } from '@takeshape/errors';
7
7
  import { getShapeById } from './schema-util';
8
+ import { getOutputShapeName } from './template-shapes/templates';
8
9
  export function enumerateOneOfKeys(projectSchema, oneOf) {
9
10
  const result = [];
10
11
 
@@ -17,7 +18,7 @@ export function enumerateOneOfKeys(projectSchema, oneOf) {
17
18
  if (projectSchema.shapes[shapeName]) {
18
19
  const shapeId = projectSchema.shapes[shapeName].id;
19
20
  result.push({
20
- propName: camelCase(refItem.typeName),
21
+ propName: getOutputShapeName(camelCase(refItem.typeName)),
21
22
  propSchema: child,
22
23
  shapeName,
23
24
  shapeId
@@ -1,52 +1,60 @@
1
1
  import { getRef, refItemToShape } from '../refs';
2
+ import { value } from '@takeshape/util';
2
3
 
3
4
  function immutableSetAdd(set, str) {
4
5
  const result = new Set(set);
5
6
  result.add(str);
6
7
  return result;
7
8
  }
9
+
8
10
  /**
9
11
  * Use a basic depth-first search to determine whether a schema contains a cycle
10
12
  */
13
+ export function hasCycle(projectSchema, schema, predicate = value(true)) {
14
+ const hasCycleHelper = (schema, shapesSeen = new Set(), shapeName, propName) => {
15
+ if (!predicate(projectSchema, schema, shapeName, propName)) {
16
+ return false;
17
+ }
11
18
 
19
+ const refItem = getRef(projectSchema, schema);
12
20
 
13
- export function hasCycle(projectSchema, schema, shapesSeen = new Set()) {
14
- const refItem = getRef(projectSchema, schema);
21
+ if (refItem) {
22
+ const refShape = refItemToShape(projectSchema, refItem); // If we have a refItem, but no shape, this is a dangling ref and has no cycles
15
23
 
16
- if (refItem) {
17
- const refShape = refItemToShape(projectSchema, refItem); // If we have a refItem, but no shape, this is a dangling ref and has no cycles
24
+ if (!refShape) {
25
+ return false;
26
+ }
18
27
 
19
- if (!refShape) {
20
- return false;
21
- }
28
+ if (shapesSeen.has(refShape.name)) {
29
+ return true;
30
+ }
22
31
 
23
- if (shapesSeen.has(refShape.name)) {
24
- return true;
32
+ return hasCycleHelper(refShape.schema, immutableSetAdd(shapesSeen, refShape.name), refShape.name);
25
33
  }
26
34
 
27
- return hasCycle(projectSchema, refShape.schema, immutableSetAdd(shapesSeen, refShape.name));
28
- }
35
+ const combo = schema.oneOf ?? schema.allOf ?? schema.extends;
29
36
 
30
- const combo = schema.oneOf ?? schema.allOf ?? schema.extends;
37
+ if (combo) {
38
+ return combo.some(childSchema => hasCycleHelper(childSchema, shapesSeen, shapeName, propName));
39
+ }
31
40
 
32
- if (combo) {
33
- return combo.some(childSchema => hasCycle(projectSchema, childSchema, shapesSeen));
34
- }
41
+ if (schema.items) {
42
+ return hasCycleHelper(schema.items, shapesSeen, shapeName, propName);
43
+ }
35
44
 
36
- if (schema.items) {
37
- return hasCycle(projectSchema, schema.items, shapesSeen);
38
- }
45
+ const {
46
+ properties
47
+ } = schema;
39
48
 
40
- const {
41
- properties
42
- } = schema;
49
+ if (properties) {
50
+ return Object.keys(properties).some(name => {
51
+ const propSchema = properties[name];
52
+ return hasCycleHelper(propSchema, shapesSeen, shapeName, name);
53
+ });
54
+ }
43
55
 
44
- if (properties) {
45
- return Object.keys(properties).some(name => {
46
- const propSchema = properties[name];
47
- return hasCycle(projectSchema, propSchema, shapesSeen);
48
- });
49
- }
56
+ return false;
57
+ };
50
58
 
51
- return false;
59
+ return hasCycleHelper(schema);
52
60
  }
@@ -0,0 +1,85 @@
1
+ import { deepClone } from '@takeshape/util';
2
+ import { getRef, refItemToShapeName } from '../refs';
3
+ import { createSchemaPropertyAccessor } from '../schema-util';
4
+ export function normalizeForms(projectSchema) {
5
+ const normalizedForms = {};
6
+ const {
7
+ forms = {}
8
+ } = projectSchema;
9
+
10
+ const storeSourceForm = (shapeName, formConfig, propName) => {
11
+ // Leave original forms intact for backwards compatibility
12
+ if (!forms[shapeName]) {
13
+ if (!normalizedForms[shapeName]) {
14
+ normalizedForms[shapeName] = {
15
+ default: deepClone(formConfig)
16
+ };
17
+ } // Remove prop now that it has been normalized
18
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
19
+
20
+
21
+ delete normalizedForms[shapeName].default.properties[propName];
22
+ }
23
+ };
24
+
25
+ const normalizeNested = (shapeName, formConfig) => {
26
+ const shape = projectSchema.shapes[shapeName];
27
+
28
+ if (!shape || !formConfig.properties) {
29
+ return;
30
+ }
31
+
32
+ const propertyAccessor = createSchemaPropertyAccessor(projectSchema, shape);
33
+
34
+ for (const propName of Object.keys(formConfig.properties)) {
35
+ const propConfig = formConfig.properties[propName];
36
+ const propSchema = propertyAccessor.getValue(propName);
37
+
38
+ if (propSchema) {
39
+ const ref = getRef(projectSchema, propSchema);
40
+
41
+ if (ref && propConfig.properties) {
42
+ const nestedShapeName = refItemToShapeName(ref);
43
+ normalizedForms[nestedShapeName] = {
44
+ default: deepClone(propConfig)
45
+ };
46
+ storeSourceForm(shapeName, formConfig, propName);
47
+ normalizeNested(nestedShapeName, propConfig);
48
+ }
49
+ }
50
+ }
51
+ };
52
+
53
+ if (forms) {
54
+ for (const shapeName of Object.keys(forms)) {
55
+ normalizeNested(shapeName, forms[shapeName].default); // copy forms with no changes
56
+
57
+ if (!normalizedForms[shapeName]) {
58
+ normalizedForms[shapeName] = forms[shapeName];
59
+ }
60
+ }
61
+ }
62
+
63
+ return normalizedForms;
64
+ }
65
+ const cache = new WeakMap();
66
+ /**
67
+ * Find the form config for a given shape name and the form key.
68
+ */
69
+
70
+ export function findShapeFormConfig(projectSchema, shapeName, formName) {
71
+ var _normalizedForms$shap;
72
+
73
+ if (!projectSchema.forms) {
74
+ return;
75
+ }
76
+
77
+ let normalizedForms = cache.get(projectSchema);
78
+
79
+ if (!normalizedForms) {
80
+ normalizedForms = normalizeForms(projectSchema);
81
+ cache.set(projectSchema, normalizedForms);
82
+ }
83
+
84
+ return (_normalizedForms$shap = normalizedForms[shapeName]) === null || _normalizedForms$shap === void 0 ? void 0 : _normalizedForms$shap[formName];
85
+ }
package/es/util/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './api-indexing';
2
2
  export * from './detect-cycles';
3
- export * from './merge';
3
+ export * from './merge';
4
+ export * from './form-config';
package/es/validate.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { projectSchemaImportOptionalProps } from './types/types';
2
+ import { JSONPath } from 'jsonpath-plus';
2
3
  import { createAjv } from '@takeshape/json-schema';
3
4
  import { SchemaValidationError } from '@takeshape/errors';
4
5
  import coerce from 'semver/functions/coerce';
@@ -11,7 +12,7 @@ import { CURRENT_SCHEMA_VERSION, LEGACY_SCHEMA_VERSION, LEGACY_API_VERSION } fro
11
12
  import { defaultWorkflow } from './workflows';
12
13
  import { allProjectSchemas } from './schemas';
13
14
  import authSchemas from './schemas/auth-schemas.json';
14
- import { getAllNamespaceShapes, visitShapeProperties, getAllRefsInShapes } from './schema-util';
15
+ import { getAllNamespaceShapes, getAllRefsInShapes } from './schema-util';
15
16
  import { builtInShapes } from './builtin-schema';
16
17
  import { isValidTemplate } from './template-shapes';
17
18
  import { isBasicResolver, isComposeResolver } from './types/utils';
@@ -22,6 +23,7 @@ import { isEnumLikeSchema } from './enum';
22
23
  import metaSchemaV3_9_0 from './schemas/project-schema/meta-schema-v3.9.0.json';
23
24
  import { isIntegerLike } from '@takeshape/util';
24
25
  import forOwn from 'lodash/forOwn';
26
+ import { getRelationship } from './relationships';
25
27
 
26
28
  function findDuplicates(items) {
27
29
  const seen = {};
@@ -172,6 +174,15 @@ function validateResolver(projectSchema, basePath, resolver) {
172
174
  return [takeshapeResolver, utilResolver, graphqlResolver, restResolver, awsLambdaResolver].flatMap(resolver => resolver.properties.name.enum).includes(resolverName);
173
175
  };
174
176
 
177
+ const isValidShapeName = shapeName => {
178
+ var _indexedShapes;
179
+
180
+ const modelShape = shapeName ? projectSchema.shapes[shapeName] : undefined;
181
+ return Boolean((modelShape === null || modelShape === void 0 ? void 0 : modelShape.model) || shapeName && ((_indexedShapes = projectSchema.indexedShapes) === null || _indexedShapes === void 0 ? void 0 : _indexedShapes[shapeName]));
182
+ };
183
+
184
+ const isLessThanV3_9_0 = lt(coerce(projectSchema.schemaVersion) ?? LEGACY_SCHEMA_VERSION, '3.9.0');
185
+
175
186
  const validateBasicResolver = resolver => {
176
187
  if (isBasicResolver(resolver)) {
177
188
  var _projectSchema$servic;
@@ -184,35 +195,23 @@ function validateResolver(projectSchema, basePath, resolver) {
184
195
  });
185
196
  }
186
197
 
187
- if (lt(coerce(projectSchema.schemaVersion) ?? LEGACY_SCHEMA_VERSION, '3.9.0')) {
188
- if (!isValidResolverNameV3_9_0(resolver.name)) {
189
- errors.push({
190
- type: 'notFound',
191
- path: basePath.concat(['name']),
192
- message: `Invalid resolver name "${resolver.name}"`
193
- });
194
- }
198
+ if (isLessThanV3_9_0 && !isValidResolverNameV3_9_0(resolver.name)) {
199
+ errors.push({
200
+ type: 'notFound',
201
+ path: basePath.concat(['name']),
202
+ message: `Invalid resolver name "${resolver.name}"`
203
+ });
195
204
  }
196
205
 
197
- if (resolver.name.startsWith('takeshape')) {
206
+ if (isLessThanV3_9_0 && resolver.name.startsWith('takeshape')) {
198
207
  var _options;
199
208
 
200
- let shapeName;
201
-
202
209
  if ((_options = resolver.options) !== null && _options !== void 0 && _options.model) {
203
210
  var _options2;
204
211
 
205
- shapeName = (_options2 = resolver.options) === null || _options2 === void 0 ? void 0 : _options2.model;
206
- } else if (resolver.shapeName) {
207
- shapeName = resolver.shapeName;
208
- }
209
-
210
- if (shapeName) {
211
- var _indexedShapes;
212
+ const shapeName = (_options2 = resolver.options) === null || _options2 === void 0 ? void 0 : _options2.model;
212
213
 
213
- const modelShape = projectSchema.shapes[shapeName];
214
-
215
- if (!(modelShape !== null && modelShape !== void 0 && modelShape.model) && !(shapeName && (_indexedShapes = projectSchema.indexedShapes) !== null && _indexedShapes !== void 0 && _indexedShapes[shapeName])) {
214
+ if (!isValidShapeName(shapeName)) {
216
215
  errors.push({
217
216
  type: 'notFound',
218
217
  path: basePath.concat(['options', 'model']),
@@ -221,6 +220,20 @@ function validateResolver(projectSchema, basePath, resolver) {
221
220
  }
222
221
  }
223
222
  }
223
+
224
+ if (resolver.shapeName) {
225
+ const {
226
+ shapeName
227
+ } = resolver;
228
+
229
+ if (!isValidShapeName(shapeName)) {
230
+ errors.push({
231
+ type: 'notFound',
232
+ path: basePath.concat(['shapeName']),
233
+ message: `Invalid Model Shape "${shapeName ?? ''}"`
234
+ });
235
+ }
236
+ }
224
237
  } else {
225
238
  errors.push({
226
239
  type: 'notFound',
@@ -366,29 +379,43 @@ function validateRefs(projectSchema, additionalShapeNames = builtInShapeNames) {
366
379
  return errors;
367
380
  }
368
381
 
382
+ function visitShapePropertiesJson(projectSchema, callback) {
383
+ JSONPath('shapes.*.schema.properties.*', projectSchema, (property, _type, {
384
+ path: propertyPath
385
+ }) => {
386
+ callback(property, propertyPath.slice(3, -2).split(`']['`));
387
+ }, undefined);
388
+ }
389
+
369
390
  function validateDirectives(projectSchema, additionalShapeIds = builtInModelShapeIds) {
370
391
  const errors = [];
371
392
  const projectShapeIds = new Set([...getModelShapeIds(projectSchema.shapes), ...additionalShapeIds]);
372
- visitShapeProperties(projectSchema.shapes, (schema, path) => {
373
- if (schema['@relationship']) {
374
- var _schema$Relationship;
375
-
376
- const shapeIds = (_schema$Relationship = schema['@relationship']) === null || _schema$Relationship === void 0 ? void 0 : _schema$Relationship.shapeIds;
377
- shapeIds.forEach(shapeId => {
378
- if (!projectShapeIds.has(shapeId)) {
379
- const propPath = path.concat('@relationship');
380
- errors.push({
381
- type: 'notFound',
382
- path: propPath,
383
- message: `Invalid shapeId relationship "${shapeId}" for property "${propPath[4]}" in shape "${propPath[1]}"`
384
- });
385
- }
386
- });
393
+ const isLessThanV3_18_0 = lt(coerce(projectSchema.schemaVersion) ?? LEGACY_SCHEMA_VERSION, '3.18.0');
394
+ visitShapePropertiesJson(projectSchema, (property, propertyPath) => {
395
+ if (isLessThanV3_18_0) {
396
+ const propSchema = property;
397
+
398
+ if (propSchema['@relationship']) {
399
+ var _propSchema$Relation;
400
+
401
+ const shapeIds = (_propSchema$Relation = propSchema['@relationship']) === null || _propSchema$Relation === void 0 ? void 0 : _propSchema$Relation.shapeIds;
402
+ shapeIds.forEach(shapeId => {
403
+ if (!projectShapeIds.has(shapeId)) {
404
+ const propPath = propertyPath.concat('@relationship');
405
+ errors.push({
406
+ type: 'notFound',
407
+ path: propPath,
408
+ message: `Invalid shapeId relationship "${shapeId}" for property "${propPath[4]}" in shape "${propPath[1]}"`
409
+ });
410
+ }
411
+ });
412
+ }
387
413
  }
388
414
 
389
- if (schema['@resolver']) {
390
- const resolver = schema['@resolver'];
391
- const propPath = path.concat('@resolver');
415
+ const resolver = property['@resolver'];
416
+
417
+ if (resolver) {
418
+ const propPath = propertyPath.concat('@resolver');
392
419
  const resolverErrors = validateResolver(projectSchema, propPath, resolver);
393
420
 
394
421
  if (resolverErrors) {
@@ -401,11 +428,12 @@ function validateDirectives(projectSchema, additionalShapeIds = builtInModelShap
401
428
 
402
429
  function validateOneOfs(projectSchema) {
403
430
  const errors = [];
404
- visitShapeProperties(projectSchema.shapes, (schema, path) => {
405
- const oneOfPath = schema.items ? ['items', 'oneOf'] : ['oneOf'];
406
- schema = schema.items ? schema.items : schema;
431
+ visitShapePropertiesJson(projectSchema, (property, propertyPath) => {
432
+ const oneOfPath = property.items ? ['items', 'oneOf'] : ['oneOf'];
433
+ const relationship = getRelationship(property);
434
+ const schema = property.items ? property.items : property;
407
435
 
408
- if (schema.oneOf) {
436
+ if (schema.oneOf && !relationship) {
409
437
  if (isUnionSchema(schema)) {
410
438
  const modelsReferenced = enumerateOneOfKeys(projectSchema, schema.oneOf).map(child => child.shapeName).filter(shapeName => projectSchema.shapes[shapeName].model);
411
439
 
@@ -413,14 +441,14 @@ function validateOneOfs(projectSchema) {
413
441
  const shapeNames = modelsReferenced.join(', ');
414
442
  errors.push({
415
443
  type: 'json',
416
- path: path.concat(oneOfPath),
444
+ path: propertyPath.concat(oneOfPath),
417
445
  message: `Invalid refs to ${shapeNames}. oneOf unions cannot reference model shapes. Use @relationship instead`
418
446
  });
419
447
  }
420
448
  } else if (!isEnumLikeSchema(schema)) {
421
449
  errors.push({
422
450
  type: 'json',
423
- path: path.concat(oneOfPath),
451
+ path: propertyPath.concat(oneOfPath),
424
452
  message: `Invalid oneOf must contain only @ref or title + enum/const schemas`
425
453
  });
426
454
  }